diff options
| author | TG-Techie <39284876+TG-Techie@users.noreply.github.com> | 2020-03-06 13:57:31 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-03-06 13:57:31 -0500 |
| commit | 7198056bc31ebae22337af119c07c7dca03e85d5 (patch) | |
| tree | 421b1c044250320ae15500f8578f6fec529937fa /shared-module | |
| parent | 6d4ffc69fdfe19366b15861694f97cbf77452937 (diff) | |
| parent | ba0c14f9ddf01f6cf98d278cbc4769a476342db0 (diff) | |
Merge pull request #2 from adafruit/5.0.x
5.0.x
Diffstat (limited to 'shared-module')
26 files changed, 1350 insertions, 430 deletions
diff --git a/shared-module/_eve/__init__.c b/shared-module/_eve/__init__.c new file mode 100644 index 000000000..2c93eb69f --- /dev/null +++ b/shared-module/_eve/__init__.c @@ -0,0 +1,317 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 James Bowman for Excamera Labs + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <stddef.h> +#include <stdint.h> +#include "py/runtime.h" +#include "shared-module/_eve/__init__.h" + +STATIC void write(common_hal__eve_t *eve, size_t len, void *buf) { + eve->dest[2] = mp_obj_new_bytearray_by_ref(len, buf); + mp_call_method_n_kw(1, 0, eve->dest); +} + +void common_hal__eve_flush(common_hal__eve_t *eve) { + if (eve->n != 0) { + write(eve, eve->n, eve->buf); + eve->n = 0; + } +} + +static void *append(common_hal__eve_t *eve, size_t m) { + if ((eve->n + m) > sizeof(eve->buf)) + common_hal__eve_flush(eve); + uint8_t *r = eve->buf + eve->n; + eve->n += m; + return (void*)r; +} + +void common_hal__eve_add(common_hal__eve_t *eve, size_t len, void *buf) { + if (len <= sizeof(eve->buf)) { + uint8_t *p = (uint8_t*)append(eve, len); + // memcpy(p, buffer_info.buf, buffer_info.len); + uint8_t *s = buf; for (size_t i = 0; i < len; i++) *p++ = *s++; + } else { + common_hal__eve_flush(eve); + write(eve, len, buf); + } +} + +#define C4(eve, u) (*(uint32_t*)append((eve), sizeof(uint32_t)) = (u)) + +void common_hal__eve_Vertex2f(common_hal__eve_t *eve, mp_float_t x, mp_float_t y) { + int16_t ix = (int)(eve->vscale * x); + int16_t iy = (int)(eve->vscale * y); + C4(eve, (1 << 30) | ((ix & 32767) << 15) | (iy & 32767)); +} + +void common_hal__eve_VertexFormat(common_hal__eve_t *eve, uint32_t frac) +{ + C4(eve, ((27 << 24) | ((frac & 7)))); + eve->vscale = 1 << eve->vscale; +} + + + +void common_hal__eve_AlphaFunc(common_hal__eve_t *eve, uint32_t func, uint32_t ref) { + C4(eve, ((9 << 24) | ((func & 7) << 8) | ((ref & 255)))); +} + + +void common_hal__eve_Begin(common_hal__eve_t *eve, uint32_t prim) { + C4(eve, ((31 << 24) | ((prim & 15)))); +} + + +void common_hal__eve_BitmapExtFormat(common_hal__eve_t *eve, uint32_t fmt) { + C4(eve, ((46 << 24) | (fmt & 65535))); +} + + +void common_hal__eve_BitmapHandle(common_hal__eve_t *eve, uint32_t handle) { + C4(eve, ((5 << 24) | ((handle & 31)))); +} + + +void common_hal__eve_BitmapLayoutH(common_hal__eve_t *eve, uint32_t linestride, uint32_t height) { + C4(eve, ((40 << 24) | (((linestride) & 3) << 2) | (((height) & 3)))); +} + + +void common_hal__eve_BitmapLayout(common_hal__eve_t *eve, uint32_t format, uint32_t linestride, uint32_t height) { + C4(eve, ((7 << 24) | ((format & 31) << 19) | ((linestride & 1023) << 9) | ((height & 511)))); +} + + +void common_hal__eve_BitmapSizeH(common_hal__eve_t *eve, uint32_t width, uint32_t height) { + C4(eve, ((41 << 24) | (((width) & 3) << 2) | (((height) & 3)))); +} + + +void common_hal__eve_BitmapSize(common_hal__eve_t *eve, uint32_t filter, uint32_t wrapx, uint32_t wrapy, uint32_t width, uint32_t height) { + C4(eve, ((8 << 24) | ((filter & 1) << 20) | ((wrapx & 1) << 19) | ((wrapy & 1) << 18) | ((width & 511) << 9) | ((height & 511)))); +} + + +void common_hal__eve_BitmapSource(common_hal__eve_t *eve, uint32_t addr) { + C4(eve, ((1 << 24) | ((addr & 0xffffff)))); +} + + +void common_hal__eve_BitmapSwizzle(common_hal__eve_t *eve, uint32_t r, uint32_t g, uint32_t b, uint32_t a) { + C4(eve, ((47 << 24) | ((r & 7) << 9) | ((g & 7) << 6) | ((b & 7) << 3) | ((a & 7)))); +} + + +void common_hal__eve_BitmapTransformA(common_hal__eve_t *eve, uint32_t p, uint32_t v) { + C4(eve, ((21 << 24) | ((p & 1) << 17) | ((v & 131071)))); +} + + +void common_hal__eve_BitmapTransformB(common_hal__eve_t *eve, uint32_t p, uint32_t v) { + C4(eve, ((22 << 24) | ((p & 1) << 17) | ((v & 131071)))); +} + + +void common_hal__eve_BitmapTransformC(common_hal__eve_t *eve, uint32_t v) { + C4(eve, ((23 << 24) | ((v & 16777215)))); +} + + +void common_hal__eve_BitmapTransformD(common_hal__eve_t *eve, uint32_t p, uint32_t v) { + C4(eve, ((24 << 24) | ((p & 1) << 17) | ((v & 131071)))); +} + + +void common_hal__eve_BitmapTransformE(common_hal__eve_t *eve, uint32_t p, uint32_t v) { + C4(eve, ((25 << 24) | ((p & 1) << 17) | ((v & 131071)))); +} + + +void common_hal__eve_BitmapTransformF(common_hal__eve_t *eve, uint32_t v) { + C4(eve, ((26 << 24) | ((v & 16777215)))); +} + + +void common_hal__eve_BlendFunc(common_hal__eve_t *eve, uint32_t src, uint32_t dst) { + C4(eve, ((11 << 24) | ((src & 7) << 3) | ((dst & 7)))); +} + + +void common_hal__eve_Call(common_hal__eve_t *eve, uint32_t dest) { + C4(eve, ((29 << 24) | ((dest & 65535)))); +} + + +void common_hal__eve_Cell(common_hal__eve_t *eve, uint32_t cell) { + C4(eve, ((6 << 24) | ((cell & 127)))); +} + + +void common_hal__eve_ClearColorA(common_hal__eve_t *eve, uint32_t alpha) { + C4(eve, ((15 << 24) | ((alpha & 255)))); +} + + +void common_hal__eve_ClearColorRGB(common_hal__eve_t *eve, uint32_t red, uint32_t green, uint32_t blue) { + C4(eve, ((2 << 24) | ((red & 255) << 16) | ((green & 255) << 8) | ((blue & 255)))); +} + + +void common_hal__eve_Clear(common_hal__eve_t *eve, uint32_t c, uint32_t s, uint32_t t) { + C4(eve, ((38 << 24) | ((c & 1) << 2) | ((s & 1) << 1) | ((t & 1)))); +} + + +void common_hal__eve_ClearStencil(common_hal__eve_t *eve, uint32_t s) { + C4(eve, ((17 << 24) | ((s & 255)))); +} + + +void common_hal__eve_ClearTag(common_hal__eve_t *eve, uint32_t s) { + C4(eve, ((18 << 24) | ((s & 255)))); +} + + +void common_hal__eve_ColorA(common_hal__eve_t *eve, uint32_t alpha) { + C4(eve, ((16 << 24) | ((alpha & 255)))); +} + + +void common_hal__eve_ColorMask(common_hal__eve_t *eve, uint32_t r, uint32_t g, uint32_t b, uint32_t a) { + C4(eve, ((32 << 24) | ((r & 1) << 3) | ((g & 1) << 2) | ((b & 1) << 1) | ((a & 1)))); +} + + +void common_hal__eve_ColorRGB(common_hal__eve_t *eve, uint32_t red, uint32_t green, uint32_t blue) { + C4(eve, ((4 << 24) | ((red & 255) << 16) | ((green & 255) << 8) | ((blue & 255)))); +} + + +void common_hal__eve_Display(common_hal__eve_t *eve) { + C4(eve, ((0 << 24))); +} + + +void common_hal__eve_End(common_hal__eve_t *eve) { + C4(eve, ((33 << 24))); +} + + +void common_hal__eve_Jump(common_hal__eve_t *eve, uint32_t dest) { + C4(eve, ((30 << 24) | ((dest & 65535)))); +} + + +void common_hal__eve_LineWidth(common_hal__eve_t *eve, uint32_t width) { + C4(eve, ((14 << 24) | ((width & 4095)))); +} + + +void common_hal__eve_Macro(common_hal__eve_t *eve, uint32_t m) { + C4(eve, ((37 << 24) | ((m & 1)))); +} + + +void common_hal__eve_Nop(common_hal__eve_t *eve) { + C4(eve, ((45 << 24))); +} + + +void common_hal__eve_PaletteSource(common_hal__eve_t *eve, uint32_t addr) { + C4(eve, ((42 << 24) | (((addr) & 4194303)))); +} + + +void common_hal__eve_PointSize(common_hal__eve_t *eve, uint32_t size) { + C4(eve, ((13 << 24) | ((size & 8191)))); +} + + +void common_hal__eve_RestoreContext(common_hal__eve_t *eve) { + C4(eve, ((35 << 24))); +} + + +void common_hal__eve_Return(common_hal__eve_t *eve) { + C4(eve, ((36 << 24))); +} + + +void common_hal__eve_SaveContext(common_hal__eve_t *eve) { + C4(eve, ((34 << 24))); +} + + +void common_hal__eve_ScissorSize(common_hal__eve_t *eve, uint32_t width, uint32_t height) { + C4(eve, ((28 << 24) | ((width & 4095) << 12) | ((height & 4095)))); +} + + +void common_hal__eve_ScissorXY(common_hal__eve_t *eve, uint32_t x, uint32_t y) { + C4(eve, ((27 << 24) | ((x & 2047) << 11) | ((y & 2047)))); +} + + +void common_hal__eve_StencilFunc(common_hal__eve_t *eve, uint32_t func, uint32_t ref, uint32_t mask) { + C4(eve, ((10 << 24) | ((func & 7) << 16) | ((ref & 255) << 8) | ((mask & 255)))); +} + + +void common_hal__eve_StencilMask(common_hal__eve_t *eve, uint32_t mask) { + C4(eve, ((19 << 24) | ((mask & 255)))); +} + + +void common_hal__eve_StencilOp(common_hal__eve_t *eve, uint32_t sfail, uint32_t spass) { + C4(eve, ((12 << 24) | ((sfail & 7) << 3) | ((spass & 7)))); +} + + +void common_hal__eve_TagMask(common_hal__eve_t *eve, uint32_t mask) { + C4(eve, ((20 << 24) | ((mask & 1)))); +} + + +void common_hal__eve_Tag(common_hal__eve_t *eve, uint32_t s) { + C4(eve, ((3 << 24) | ((s & 255)))); +} + + +void common_hal__eve_VertexTranslateX(common_hal__eve_t *eve, uint32_t x) { + C4(eve, ((43 << 24) | (((x) & 131071)))); +} + + +void common_hal__eve_VertexTranslateY(common_hal__eve_t *eve, uint32_t y) { + C4(eve, ((44 << 24) | (((y) & 131071)))); +} + + +void common_hal__eve_Vertex2ii(common_hal__eve_t *eve, uint32_t x, uint32_t y, uint32_t handle, uint32_t cell) { + C4(eve, ((2 << 30) | (((x) & 511) << 21) | (((y) & 511) << 12) | (((handle) & 31) << 7) | (((cell) & 127) << 0))); +} + diff --git a/shared-module/_eve/__init__.h b/shared-module/_eve/__init__.h new file mode 100644 index 000000000..5217d860a --- /dev/null +++ b/shared-module/_eve/__init__.h @@ -0,0 +1,37 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 James Bowman for Excamera Labs + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_SHARED_MODULE__EVE___INIT___H +#define MICROPY_INCLUDED_SHARED_MODULE__EVE___INIT___H + +typedef struct _common_hal__eve_t { + mp_obj_t dest[3]; // Own 'write' method, plus argument + int vscale; // fixed-point scaling used for Vertex2f + size_t n; // Current size of command buffer + uint8_t buf[512]; // Command buffer +} common_hal__eve_t; + +#endif // MICROPY_INCLUDED_SHARED_MODULE__EVE___INIT___H diff --git a/shared-module/_pixelbuf/PixelBuf.c b/shared-module/_pixelbuf/PixelBuf.c index d32697239..bad8539ea 100644 --- a/shared-module/_pixelbuf/PixelBuf.c +++ b/shared-module/_pixelbuf/PixelBuf.c @@ -26,95 +26,263 @@ #include "py/obj.h" -#include "py/objarray.h" +#include "py/objstr.h" +#include "py/objtype.h" #include "py/runtime.h" -#include "PixelBuf.h" +#include "shared-bindings/_pixelbuf/PixelBuf.h" #include <string.h> -void pixelbuf_set_pixel_int(uint8_t *buf, mp_int_t value, pixelbuf_byteorder_obj_t *byteorder) { - buf[byteorder->byteorder.r] = value >> 16 & 0xff; - buf[byteorder->byteorder.g] = (value >> 8) & 0xff; - buf[byteorder->byteorder.b] = value & 0xff; - if (byteorder->bpp == 4 && byteorder->has_white && - (buf[byteorder->byteorder.r] == buf[byteorder->byteorder.g] && - buf[byteorder->byteorder.r] == buf[byteorder->byteorder.b])) { - buf[byteorder->byteorder.w] = buf[byteorder->byteorder.r]; - buf[byteorder->byteorder.r] = buf[byteorder->byteorder.g] = buf[byteorder->byteorder.b] = 0; - } -} - -void pixelbuf_set_pixel(uint8_t *buf, uint8_t *rawbuf, float brightness, mp_obj_t *item, pixelbuf_byteorder_obj_t *byteorder, bool dotstar) { - if (MP_OBJ_IS_INT(item)) { - uint8_t *target = rawbuf ? rawbuf : buf; - pixelbuf_set_pixel_int(target, mp_obj_get_int_truncated(item), byteorder); - if (dotstar) { - buf[0] = DOTSTAR_LED_START_FULL_BRIGHT; - if (rawbuf) - rawbuf[0] = DOTSTAR_LED_START_FULL_BRIGHT; - } - if (rawbuf) { - buf[byteorder->byteorder.r] = rawbuf[byteorder->byteorder.r] * brightness; - buf[byteorder->byteorder.g] = rawbuf[byteorder->byteorder.g] * brightness; - buf[byteorder->byteorder.b] = rawbuf[byteorder->byteorder.b] * brightness; - } else { - buf[byteorder->byteorder.r] *= brightness; - buf[byteorder->byteorder.g] *= brightness; - buf[byteorder->byteorder.b] *= brightness; +// Helper to ensure we have the native super class instead of a subclass. +static pixelbuf_pixelbuf_obj_t* native_pixelbuf(mp_obj_t pixelbuf_obj) { + mp_obj_t native_pixelbuf = mp_instance_cast_to_native_base(pixelbuf_obj, &pixelbuf_pixelbuf_type); + mp_obj_assert_native_inited(native_pixelbuf); + return MP_OBJ_TO_PTR(native_pixelbuf); +} + +void common_hal__pixelbuf_pixelbuf_construct(pixelbuf_pixelbuf_obj_t *self, size_t n, + pixelbuf_byteorder_details_t* byteorder, mp_float_t brightness, bool auto_write, + uint8_t* header, size_t header_len, uint8_t* trailer, size_t trailer_len) { + + self->pixel_count = n; + self->byteorder = *byteorder; // Copied because we modify for dotstar + self->bytes_per_pixel = byteorder->is_dotstar ? 4 : byteorder->bpp; + self->auto_write = false; + + size_t pixel_len = self->pixel_count * self->bytes_per_pixel; + self->transmit_buffer_obj = mp_obj_new_bytes_of_zeros(header_len + pixel_len + trailer_len); + mp_obj_str_t *o = MP_OBJ_TO_PTR(self->transmit_buffer_obj); + + // Abuse the bytes object a bit by mutating it's data by dropping the const. If the user's + // Python code holds onto it, they'll find out that it changes. At least this way it isn't + // mutable by the code itself. + uint8_t* transmit_buffer = (uint8_t*) o->data; + memcpy(transmit_buffer, header, header_len); + memcpy(transmit_buffer + header_len + pixel_len, trailer, trailer_len); + self->post_brightness_buffer = transmit_buffer + header_len; + + if (self->byteorder.is_dotstar) { + // Initialize the buffer with the dotstar start bytes. + // Note: Header and end must be setup by caller + for (uint i = 0; i < self->pixel_count * 4; i += 4) { + self->post_brightness_buffer[i] = DOTSTAR_LED_START_FULL_BRIGHT; + } + } + // Call set_brightness so that it can allocate a second buffer if needed. + self->brightness = 1.0; + common_hal__pixelbuf_pixelbuf_set_brightness(MP_OBJ_FROM_PTR(self), brightness); + + // Turn on auto_write. We don't want to do it with the above brightness call. + self->auto_write = auto_write; +} + +size_t common_hal__pixelbuf_pixelbuf_get_len(mp_obj_t self_in) { + pixelbuf_pixelbuf_obj_t* self = native_pixelbuf(self_in); + return self->pixel_count; +} + +uint8_t common_hal__pixelbuf_pixelbuf_get_bpp(mp_obj_t self_in) { + pixelbuf_pixelbuf_obj_t* self = native_pixelbuf(self_in); + return self->byteorder.bpp; +} + +mp_obj_t common_hal__pixelbuf_pixelbuf_get_byteorder_string(mp_obj_t self_in) { + pixelbuf_pixelbuf_obj_t* self = native_pixelbuf(self_in); + return self->byteorder.order_string; +} + +bool common_hal__pixelbuf_pixelbuf_get_auto_write(mp_obj_t self_in) { + pixelbuf_pixelbuf_obj_t* self = native_pixelbuf(self_in); + return self->auto_write; +} + +void common_hal__pixelbuf_pixelbuf_set_auto_write(mp_obj_t self_in, bool auto_write) { + pixelbuf_pixelbuf_obj_t* self = native_pixelbuf(self_in); + self->auto_write = auto_write; +} + +mp_float_t common_hal__pixelbuf_pixelbuf_get_brightness(mp_obj_t self_in) { + pixelbuf_pixelbuf_obj_t* self = native_pixelbuf(self_in); + return self->brightness; +} + +void common_hal__pixelbuf_pixelbuf_set_brightness(mp_obj_t self_in, mp_float_t brightness) { + pixelbuf_pixelbuf_obj_t* self = native_pixelbuf(self_in); + // Skip out if the brightness is already set. The default of self->brightness is 1.0. So, this + // also prevents the pre_brightness_buffer allocation when brightness is set to 1.0 again. + mp_float_t change = brightness - self->brightness; + if (-0.001 < change && change < 0.001) { + return; + } + self->brightness = brightness; + size_t pixel_len = self->pixel_count * self->bytes_per_pixel; + if (self->pre_brightness_buffer == NULL) { + self->pre_brightness_buffer = m_malloc(pixel_len, false); + memcpy(self->pre_brightness_buffer, self->post_brightness_buffer, pixel_len); + } + for (size_t i = 0; i < pixel_len; i++) { + // Don't adjust per-pixel luminance bytes in dotstar mode + if (self->byteorder.is_dotstar && i % 4 == 0) { + continue; + } + self->post_brightness_buffer[i] = self->pre_brightness_buffer[i] * self->brightness; + } + + if (self->auto_write) { + common_hal__pixelbuf_pixelbuf_show(self_in); + } +} + +void _pixelbuf_parse_color(pixelbuf_pixelbuf_obj_t* self, mp_obj_t color, uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* w) { + pixelbuf_byteorder_details_t *byteorder = &self->byteorder; + // w is shared between white in NeoPixels and brightness in dotstars (so that DotStars can have + // per-pixel brightness). Set the defaults here in case it isn't set below. + if (byteorder->is_dotstar) { + *w = 255; + } else { + *w = 0; + } + + if (MP_OBJ_IS_INT(color)) { + mp_int_t value = mp_obj_get_int_truncated(color); + *r = value >> 16 & 0xff; + *g = (value >> 8) & 0xff; + *b = value & 0xff; + // Int colors can't set white directly so convert to white when all components are equal. + if (!byteorder->is_dotstar && byteorder->bpp == 4 && byteorder->has_white && *r == *g && *r == *b) { + *w = *r; + *r = 0; + *g = 0; + *b = 0; } } else { mp_obj_t *items; size_t len; - mp_obj_get_array(item, &len, &items); - if (len != byteorder->bpp && !dotstar) + mp_obj_get_array(color, &len, &items); + if (len != byteorder->bpp && !byteorder->is_dotstar) { mp_raise_ValueError_varg(translate("Expected tuple of length %d, got %d"), byteorder->bpp, len); - - buf[byteorder->byteorder.r] = mp_obj_get_int_truncated(items[PIXEL_R]) * brightness; - buf[byteorder->byteorder.g] = mp_obj_get_int_truncated(items[PIXEL_G]) * brightness; - buf[byteorder->byteorder.b] = mp_obj_get_int_truncated(items[PIXEL_B]) * brightness; - if (rawbuf) { - rawbuf[byteorder->byteorder.r] = mp_obj_get_int_truncated(items[PIXEL_R]); - rawbuf[byteorder->byteorder.g] = mp_obj_get_int_truncated(items[PIXEL_G]); - rawbuf[byteorder->byteorder.b] = mp_obj_get_int_truncated(items[PIXEL_B]); } + + *r = mp_obj_get_int_truncated(items[PIXEL_R]); + *g = mp_obj_get_int_truncated(items[PIXEL_G]); + *b = mp_obj_get_int_truncated(items[PIXEL_B]); if (len > 3) { - if (dotstar) { - buf[byteorder->byteorder.w] = DOTSTAR_LED_START | DOTSTAR_BRIGHTNESS(mp_obj_get_float(items[PIXEL_W])); - if (rawbuf) - rawbuf[byteorder->byteorder.w] = buf[byteorder->byteorder.w]; + if (mp_obj_is_float(items[PIXEL_W])) { + *w = 255 * mp_obj_get_float(items[PIXEL_W]); } else { - buf[byteorder->byteorder.w] = mp_obj_get_int_truncated(items[PIXEL_W]) * brightness; - if (rawbuf) - rawbuf[byteorder->byteorder.w] = mp_obj_get_int_truncated(items[PIXEL_W]); + *w = mp_obj_get_int_truncated(items[PIXEL_W]); } - } else if (dotstar) { - buf[byteorder->byteorder.w] = DOTSTAR_LED_START_FULL_BRIGHT; - if (rawbuf) - rawbuf[byteorder->byteorder.w] = DOTSTAR_LED_START_FULL_BRIGHT; } } } -mp_obj_t *pixelbuf_get_pixel_array(uint8_t *buf, uint len, pixelbuf_byteorder_obj_t *byteorder, uint8_t step, bool dotstar) { - mp_obj_t elems[len]; - for (uint i = 0; i < len; i++) { - elems[i] = pixelbuf_get_pixel(buf + (i * step), byteorder, dotstar); +void _pixelbuf_set_pixel_color(pixelbuf_pixelbuf_obj_t* self, size_t index, uint8_t r, uint8_t g, uint8_t b, uint8_t w) { + // DotStars don't have white, instead they have 5 bit brightness so pack it into w. Shift right + // by three to leave the top five bits. + if (self->bytes_per_pixel == 4 && self->byteorder.is_dotstar) { + w = DOTSTAR_LED_START | w >> 3; + } + pixelbuf_rgbw_t *rgbw_order = &self->byteorder.byteorder; + size_t offset = index * self->bytes_per_pixel; + if (self->pre_brightness_buffer != NULL) { + uint8_t* pre_brightness_buffer = self->pre_brightness_buffer + offset; + if (self->bytes_per_pixel == 4) { + pre_brightness_buffer[rgbw_order->w] = w; + } + + pre_brightness_buffer[rgbw_order->r] = r; + pre_brightness_buffer[rgbw_order->g] = g; + pre_brightness_buffer[rgbw_order->b] = b; + } + + uint8_t* post_brightness_buffer = self->post_brightness_buffer + offset; + if (self->bytes_per_pixel == 4) { + // Only apply brightness if w is actually white (aka not DotStar.) + if (!self->byteorder.is_dotstar) { + w *= self->brightness; + } + post_brightness_buffer[rgbw_order->w] = w; + } + post_brightness_buffer[rgbw_order->r] = r * self->brightness; + post_brightness_buffer[rgbw_order->g] = g * self->brightness; + post_brightness_buffer[rgbw_order->b] = b * self->brightness; +} + +void _pixelbuf_set_pixel(pixelbuf_pixelbuf_obj_t* self, size_t index, mp_obj_t value) { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t w; + _pixelbuf_parse_color(self, value, &r, &g, &b, &w); + _pixelbuf_set_pixel_color(self, index, r, g, b, w); +} + +void common_hal__pixelbuf_pixelbuf_set_pixels(mp_obj_t self_in, size_t start, size_t stop, size_t step, mp_obj_t* values) { + pixelbuf_pixelbuf_obj_t* self = native_pixelbuf(self_in); + size_t source_i = 0; + for (size_t target_i = start; target_i < stop; target_i += step) { + _pixelbuf_set_pixel(self, target_i, values[source_i]); + source_i++; + } + if (self->auto_write) { + common_hal__pixelbuf_pixelbuf_show(self_in); } - return mp_obj_new_tuple(len, elems); } -mp_obj_t *pixelbuf_get_pixel(uint8_t *buf, pixelbuf_byteorder_obj_t *byteorder, bool dotstar) { - mp_obj_t elems[byteorder->bpp]; - - elems[0] = mp_obj_new_int(buf[byteorder->byteorder.r]); - elems[1] = mp_obj_new_int(buf[byteorder->byteorder.g]); - elems[2] = mp_obj_new_int(buf[byteorder->byteorder.b]); - if (byteorder->bpp > 3) - { - if (dotstar) - elems[3] = mp_obj_new_float(DOTSTAR_GET_BRIGHTNESS(buf[byteorder->byteorder.w])); - else - elems[3] = mp_obj_new_int(buf[byteorder->byteorder.w]); +void common_hal__pixelbuf_pixelbuf_set_pixel(mp_obj_t self_in, size_t index, mp_obj_t value) { + pixelbuf_pixelbuf_obj_t* self = native_pixelbuf(self_in); + _pixelbuf_set_pixel(self, index, value); + if (self->auto_write) { + common_hal__pixelbuf_pixelbuf_show(self_in); } +} - return mp_obj_new_tuple(byteorder->bpp, elems); +mp_obj_t common_hal__pixelbuf_pixelbuf_get_pixel(mp_obj_t self_in, size_t index) { + pixelbuf_pixelbuf_obj_t* self = native_pixelbuf(self_in); + mp_obj_t elems[self->byteorder.bpp]; + uint8_t* pixel_buffer = self->post_brightness_buffer; + if (self->pre_brightness_buffer != NULL) { + pixel_buffer = self->pre_brightness_buffer; + } + + pixelbuf_rgbw_t *rgbw_order = &self->byteorder.byteorder; + elems[0] = MP_OBJ_NEW_SMALL_INT(pixel_buffer[rgbw_order->r]); + elems[1] = MP_OBJ_NEW_SMALL_INT(pixel_buffer[rgbw_order->g]); + elems[2] = MP_OBJ_NEW_SMALL_INT(pixel_buffer[rgbw_order->b]); + if (self->byteorder.bpp > 3) { + uint8_t w = pixel_buffer[rgbw_order->w]; + if (self->byteorder.is_dotstar) { + elems[3] = mp_obj_new_float((w & 0b00011111) / 31.0); + } else { + elems[3] = MP_OBJ_NEW_SMALL_INT(w); + } + } + + return mp_obj_new_tuple(self->byteorder.bpp, elems); +} + +void common_hal__pixelbuf_pixelbuf_show(mp_obj_t self_in) { + pixelbuf_pixelbuf_obj_t* self = native_pixelbuf(self_in); + mp_obj_t dest[2 + 1]; + mp_load_method(self_in, MP_QSTR__transmit, dest); + + dest[2] = self->transmit_buffer_obj; + + mp_call_method_n_kw(1, 0, dest); +} + +void common_hal__pixelbuf_pixelbuf_fill(mp_obj_t self_in, mp_obj_t fill_color) { + pixelbuf_pixelbuf_obj_t* self = native_pixelbuf(self_in); + + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t w; + _pixelbuf_parse_color(self, fill_color, &r, &g, &b, &w); + + for (size_t i = 0; i < self->pixel_count; i++) { + _pixelbuf_set_pixel_color(self, i, r, g, b, w); + } + if (self->auto_write) { + common_hal__pixelbuf_pixelbuf_show(self_in); + } } diff --git a/shared-module/_pixelbuf/PixelBuf.h b/shared-module/_pixelbuf/PixelBuf.h index 9e115fe0c..a9fbed366 100644 --- a/shared-module/_pixelbuf/PixelBuf.h +++ b/shared-module/_pixelbuf/PixelBuf.h @@ -27,24 +27,45 @@ #include "py/obj.h" #include "py/objarray.h" -#include "../../shared-bindings/_pixelbuf/types.h" #ifndef PIXELBUF_SHARED_MODULE_H #define PIXELBUF_SHARED_MODULE_H +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t w; +} pixelbuf_rgbw_t; + +typedef struct { + uint8_t bpp; + pixelbuf_rgbw_t byteorder; + bool has_white; + bool is_dotstar; + mp_obj_t order_string; +} pixelbuf_byteorder_details_t; + +typedef struct { + mp_obj_base_t base; + size_t pixel_count; + size_t bytes_per_pixel; + pixelbuf_byteorder_details_t byteorder; + mp_float_t brightness; + mp_obj_t transmit_buffer_obj; + // The post_brightness_buffer is offset into the buffer allocated in transmit_buffer_obj to + // account for any header. + uint8_t *post_brightness_buffer; + uint8_t *pre_brightness_buffer; + bool auto_write; +} pixelbuf_pixelbuf_obj_t; + #define PIXEL_R 0 #define PIXEL_G 1 #define PIXEL_B 2 #define PIXEL_W 3 #define DOTSTAR_LED_START 0b11100000 -#define DOTSTAR_BRIGHTNESS(brightness) ((32 - (uint8_t)(32 - brightness * 31)) & 0b00011111) -#define DOTSTAR_GET_BRIGHTNESS(value) ((value & 0b00011111) / 31.0) #define DOTSTAR_LED_START_FULL_BRIGHT 0xFF -void pixelbuf_set_pixel(uint8_t *buf, uint8_t *rawbuf, float brightness, mp_obj_t *item, pixelbuf_byteorder_obj_t *byteorder, bool dotstar); -mp_obj_t *pixelbuf_get_pixel(uint8_t *buf, pixelbuf_byteorder_obj_t *byteorder, bool dotstar); -mp_obj_t *pixelbuf_get_pixel_array(uint8_t *buf, uint len, pixelbuf_byteorder_obj_t *byteorder, uint8_t step, bool dotstar); -void pixelbuf_set_pixel_int(uint8_t *buf, mp_int_t value, pixelbuf_byteorder_obj_t *byteorder); - #endif diff --git a/shared-module/_stage/__init__.c b/shared-module/_stage/__init__.c index d5da74272..0323c32cb 100644 --- a/shared-module/_stage/__init__.c +++ b/shared-module/_stage/__init__.c @@ -34,7 +34,8 @@ void render_stage(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, mp_obj_t *layers, size_t layers_size, uint16_t *buffer, size_t buffer_size, - displayio_display_obj_t *display, uint8_t scale) { + displayio_display_obj_t *display, + uint8_t scale, uint16_t background) { displayio_area_t area; @@ -68,6 +69,9 @@ void render_stage(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, break; } } + if (c == TRANSPARENT) { + c = background; + } for (uint8_t xscale = 0; xscale < scale; ++xscale) { buffer[index] = c; index += 1; diff --git a/shared-module/_stage/__init__.h b/shared-module/_stage/__init__.h index 5af2124ae..7a1826200 100644 --- a/shared-module/_stage/__init__.h +++ b/shared-module/_stage/__init__.h @@ -37,6 +37,7 @@ void render_stage(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, mp_obj_t *layers, size_t layers_size, uint16_t *buffer, size_t buffer_size, - displayio_display_obj_t *display, uint8_t scale); + displayio_display_obj_t *display, + uint8_t scale, uint16_t background); #endif // MICROPY_INCLUDED_SHARED_MODULE__STAGE diff --git a/shared-module/audiocore/RawSample.c b/shared-module/audiocore/RawSample.c index d6bf3a567..a69ddeb65 100644 --- a/shared-module/audiocore/RawSample.c +++ b/shared-module/audiocore/RawSample.c @@ -60,6 +60,12 @@ void common_hal_audioio_rawsample_set_sample_rate(audioio_rawsample_obj_t* self, uint32_t sample_rate) { self->sample_rate = sample_rate; } +uint8_t common_hal_audioio_rawsample_get_bits_per_sample(audioio_rawsample_obj_t* self) { + return self->bits_per_sample; +} +uint8_t common_hal_audioio_rawsample_get_channel_count(audioio_rawsample_obj_t* self) { + return self->channel_count; +} void audioio_rawsample_reset_buffer(audioio_rawsample_obj_t* self, bool single_channel, diff --git a/shared-module/audiocore/__init__.c b/shared-module/audiocore/__init__.c index f9762676f..ac9d41b60 100644 --- a/shared-module/audiocore/__init__.c +++ b/shared-module/audiocore/__init__.c @@ -36,103 +36,37 @@ #include "shared-module/audiomixer/Mixer.h" uint32_t audiosample_sample_rate(mp_obj_t sample_obj) { - if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) { - audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj); - return sample->sample_rate; - } else if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) { - audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj); - return file->sample_rate; - #if CIRCUITPY_AUDIOMIXER - } else if (MP_OBJ_IS_TYPE(sample_obj, &audiomixer_mixer_type)) { - audiomixer_mixer_obj_t* mixer = MP_OBJ_TO_PTR(sample_obj); - return mixer->sample_rate; - #endif - } - return 16000; + const audiosample_p_t *proto = mp_proto_get_or_throw(MP_QSTR_protocol_audiosample, sample_obj); + return proto->sample_rate(MP_OBJ_TO_PTR(sample_obj)); } uint8_t audiosample_bits_per_sample(mp_obj_t sample_obj) { - if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) { - audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj); - return sample->bits_per_sample; - } else if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) { - audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj); - return file->bits_per_sample; - #if CIRCUITPY_AUDIOMIXER - } else if (MP_OBJ_IS_TYPE(sample_obj, &audiomixer_mixer_type)) { - audiomixer_mixer_obj_t* mixer = MP_OBJ_TO_PTR(sample_obj); - return mixer->bits_per_sample; - #endif - } - return 8; + const audiosample_p_t *proto = mp_proto_get_or_throw(MP_QSTR_protocol_audiosample, sample_obj); + return proto->bits_per_sample(MP_OBJ_TO_PTR(sample_obj)); } uint8_t audiosample_channel_count(mp_obj_t sample_obj) { - if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) { - audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj); - return sample->channel_count; - } else if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) { - audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj); - return file->channel_count; - #if CIRCUITPY_AUDIOMIXER - } else if (MP_OBJ_IS_TYPE(sample_obj, &audiomixer_mixer_type)) { - audiomixer_mixer_obj_t* mixer = MP_OBJ_TO_PTR(sample_obj); - return mixer->channel_count; - #endif - } - return 1; + const audiosample_p_t *proto = mp_proto_get_or_throw(MP_QSTR_protocol_audiosample, sample_obj); + return proto->channel_count(MP_OBJ_TO_PTR(sample_obj)); } void audiosample_reset_buffer(mp_obj_t sample_obj, bool single_channel, uint8_t audio_channel) { - if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) { - audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj); - audioio_rawsample_reset_buffer(sample, single_channel, audio_channel); - } else if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) { - audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj); - audioio_wavefile_reset_buffer(file, single_channel, audio_channel); - #if CIRCUITPY_AUDIOMIXER - } else if (MP_OBJ_IS_TYPE(sample_obj, &audiomixer_mixer_type)) { - audiomixer_mixer_obj_t* file = MP_OBJ_TO_PTR(sample_obj); - audiomixer_mixer_reset_buffer(file, single_channel, audio_channel); - #endif - } + const audiosample_p_t *proto = mp_proto_get_or_throw(MP_QSTR_protocol_audiosample, sample_obj); + proto->reset_buffer(MP_OBJ_TO_PTR(sample_obj), single_channel, audio_channel); } audioio_get_buffer_result_t audiosample_get_buffer(mp_obj_t sample_obj, bool single_channel, uint8_t channel, uint8_t** buffer, uint32_t* buffer_length) { - if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) { - audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj); - return audioio_rawsample_get_buffer(sample, single_channel, channel, buffer, buffer_length); - } else if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) { - audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj); - return audioio_wavefile_get_buffer(file, single_channel, channel, buffer, buffer_length); - #if CIRCUITPY_AUDIOMIXER - } else if (MP_OBJ_IS_TYPE(sample_obj, &audiomixer_mixer_type)) { - audiomixer_mixer_obj_t* file = MP_OBJ_TO_PTR(sample_obj); - return audiomixer_mixer_get_buffer(file, single_channel, channel, buffer, buffer_length); - #endif - } - return GET_BUFFER_DONE; + const audiosample_p_t *proto = mp_proto_get_or_throw(MP_QSTR_protocol_audiosample, sample_obj); + return proto->get_buffer(MP_OBJ_TO_PTR(sample_obj), single_channel, channel, buffer, buffer_length); } void audiosample_get_buffer_structure(mp_obj_t sample_obj, bool single_channel, bool* single_buffer, bool* samples_signed, uint32_t* max_buffer_length, uint8_t* spacing) { - if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) { - audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj); - audioio_rawsample_get_buffer_structure(sample, single_channel, single_buffer, - samples_signed, max_buffer_length, spacing); - } else if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) { - audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj); - audioio_wavefile_get_buffer_structure(file, single_channel, single_buffer, samples_signed, - max_buffer_length, spacing); - #if CIRCUITPY_AUDIOMIXER - } else if (MP_OBJ_IS_TYPE(sample_obj, &audiomixer_mixer_type)) { - audiomixer_mixer_obj_t* file = MP_OBJ_TO_PTR(sample_obj); - audiomixer_mixer_get_buffer_structure(file, single_channel, single_buffer, samples_signed, - max_buffer_length, spacing); - #endif - } + const audiosample_p_t *proto = mp_proto_get_or_throw(MP_QSTR_protocol_audiosample, sample_obj); + proto->get_buffer_structure(MP_OBJ_TO_PTR(sample_obj), single_channel, single_buffer, + samples_signed, max_buffer_length, spacing); } diff --git a/shared-module/audiocore/__init__.h b/shared-module/audiocore/__init__.h index 7aa0c1824..7a56454b8 100644 --- a/shared-module/audiocore/__init__.h +++ b/shared-module/audiocore/__init__.h @@ -31,6 +31,7 @@ #include <stdint.h> #include "py/obj.h" +#include "py/proto.h" typedef enum { GET_BUFFER_DONE, // No more data to read @@ -38,6 +39,29 @@ typedef enum { GET_BUFFER_ERROR, // Error while reading data. } audioio_get_buffer_result_t; +typedef uint32_t (*audiosample_sample_rate_fun)(mp_obj_t); +typedef uint8_t (*audiosample_bits_per_sample_fun)(mp_obj_t); +typedef uint8_t (*audiosample_channel_count_fun)(mp_obj_t); +typedef void (*audiosample_reset_buffer_fun)(mp_obj_t, + bool single_channel, uint8_t audio_channel); +typedef audioio_get_buffer_result_t (*audiosample_get_buffer_fun)(mp_obj_t, + bool single_channel, uint8_t channel, uint8_t** buffer, + uint32_t* buffer_length); +typedef void (*audiosample_get_buffer_structure_fun)(mp_obj_t, + bool single_channel, bool* single_buffer, + bool* samples_signed, uint32_t *max_buffer_length, + uint8_t* spacing); + +typedef struct _audiosample_p_t { + MP_PROTOCOL_HEAD // MP_QSTR_protocol_audiosample + audiosample_sample_rate_fun sample_rate; + audiosample_bits_per_sample_fun bits_per_sample; + audiosample_channel_count_fun channel_count; + audiosample_reset_buffer_fun reset_buffer; + audiosample_get_buffer_fun get_buffer; + audiosample_get_buffer_structure_fun get_buffer_structure; +} audiosample_p_t; + uint32_t audiosample_sample_rate(mp_obj_t sample_obj); uint8_t audiosample_bits_per_sample(mp_obj_t sample_obj); uint8_t audiosample_channel_count(mp_obj_t sample_obj); diff --git a/shared-module/audiomixer/Mixer.c b/shared-module/audiomixer/Mixer.c index a52489f85..4a72dab28 100644 --- a/shared-module/audiomixer/Mixer.c +++ b/shared-module/audiomixer/Mixer.c @@ -76,6 +76,14 @@ uint32_t common_hal_audiomixer_mixer_get_sample_rate(audiomixer_mixer_obj_t* sel return self->sample_rate; } +uint8_t common_hal_audiomixer_mixer_get_channel_count(audiomixer_mixer_obj_t* self) { + return self->channel_count; +} + +uint8_t common_hal_audiomixer_mixer_get_bits_per_sample(audiomixer_mixer_obj_t* self) { + return self->bits_per_sample; +} + bool common_hal_audiomixer_mixer_get_playing(audiomixer_mixer_obj_t* self) { for (uint8_t v = 0; v < self->voice_count; v++) { if (common_hal_audiomixer_mixervoice_get_playing(MP_OBJ_TO_PTR(self->voice[v]))) { @@ -93,198 +101,141 @@ void audiomixer_mixer_reset_buffer(audiomixer_mixer_obj_t* self, } } -uint32_t add8signed(uint32_t a, uint32_t b) { - #if (defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) //Cortex-M4 w/FPU - return __SHADD8(a, b); - #else - uint32_t result = 0; - for (int8_t i = 0; i < 4; i++) { - int8_t ai = a >> (sizeof(int8_t) * 8 * i); - int8_t bi = b >> (sizeof(int8_t) * 8 * i); - int32_t intermediate = (int32_t) ai + bi / 2; - if (intermediate > CHAR_MAX) { - intermediate = CHAR_MAX; - } else if (intermediate < CHAR_MIN) { - intermediate = CHAR_MIN; - } - result |= ((uint32_t) intermediate & 0xff) << (sizeof(int8_t) * 8 * i); - } - return result; - #endif +__attribute__((always_inline)) +static inline uint32_t add16signed(uint32_t a, uint32_t b) { + return __QADD16(a, b); } -uint32_t add8unsigned(uint32_t a, uint32_t b) { - #if (defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) //Cortex-M4 w/FPU - return __UHADD8(a, b); - #else - uint32_t result = 0; - for (int8_t i = 0; i < 4; i++) { - uint8_t ai = (a >> (sizeof(uint8_t) * 8 * i)); - uint8_t bi = (b >> (sizeof(uint8_t) * 8 * i)); - int32_t intermediate = (int32_t) (ai + bi) / 2; - if (intermediate > UCHAR_MAX) { - intermediate = UCHAR_MAX; - } - result |= ((uint32_t) intermediate & 0xff) << (sizeof(uint8_t) * 8 * i); - } - return result; - #endif +__attribute__((always_inline)) +static inline uint32_t mult16signed(uint32_t val, int32_t mul) { + mul <<= 16; + int32_t hi, lo; + enum { bits = 16 }; // saturate to 16 bits + enum { shift = 15 }; // shift is done automatically + asm volatile("smulwb %0, %1, %2" : "=r" (lo) : "r" (mul), "r" (val)); + asm volatile("smulwt %0, %1, %2" : "=r" (hi) : "r" (mul), "r" (val)); + asm volatile("ssat %0, %1, %2, asr %3" : "=r" (lo) : "I" (bits), "r" (lo), "I" (shift)); + asm volatile("ssat %0, %1, %2, asr %3" : "=r" (hi) : "I" (bits), "r" (hi), "I" (shift)); + asm volatile("pkhbt %0, %1, %2, lsl #16" : "=r" (val) : "r" (lo), "r" (hi)); // pack + return val; } -uint32_t add16signed(uint32_t a, uint32_t b) { - #if (defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) //Cortex-M4 w/FPU - return __SHADD16(a, b); - #else - uint32_t result = 0; - for (int8_t i = 0; i < 2; i++) { - int16_t ai = a >> (sizeof(int16_t) * 8 * i); - int16_t bi = b >> (sizeof(int16_t) * 8 * i); - int32_t intermediate = (int32_t) ai + bi / 2; - if (intermediate > SHRT_MAX) { - intermediate = SHRT_MAX; - } else if (intermediate < SHRT_MIN) { - intermediate = SHRT_MIN; - } - result |= (((uint32_t) intermediate) & 0xffff) << (sizeof(int16_t) * 8 * i); - } - return result; - #endif +static inline uint32_t tounsigned8(uint32_t val) { + return __UADD8(val, 0x80808080); } -uint32_t add16unsigned(uint32_t a, uint32_t b) { - #if (defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) //Cortex-M4 w/FPU - return __UHADD16(a, b); - #else - uint32_t result = 0; - for (int8_t i = 0; i < 2; i++) { - int16_t ai = (a >> (sizeof(uint16_t) * 8 * i)) - 0x8000; - int16_t bi = (b >> (sizeof(uint16_t) * 8 * i)) - 0x8000; - int32_t intermediate = (int32_t) ai + bi / 2; - if (intermediate > USHRT_MAX) { - intermediate = USHRT_MAX; - } - result |= ((uint32_t) intermediate & 0xffff) << (sizeof(int16_t) * 8 * i); - } - return result; - #endif +static inline uint32_t tounsigned16(uint32_t val) { + return __UADD16(val, 0x80008000); } -static inline uint32_t mult8unsigned(uint32_t val, int32_t mul) { - // if mul == 0, no need in wasting cycles - if (mul == 0) { - return 0; - } - /* TODO: workout ARMv7 instructions - #if (defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) //Cortex-M4 w/FPU - return val; - #else*/ - uint32_t result = 0; - float mod_mul = (float) mul / (float) ((1<<15)-1); - for (int8_t i = 0; i < 4; i++) { - uint8_t ai = val >> (sizeof(uint8_t) * 8 * i); - int32_t intermediate = ai * mod_mul; - if (intermediate > SHRT_MAX) { - intermediate = SHRT_MAX; - } - result |= ((uint32_t) intermediate & 0xff) << (sizeof(uint8_t) * 8 * i); - } +static inline uint32_t tosigned16(uint32_t val) { + return __UADD16(val, 0x80008000); +} - return result; - //#endif +static inline uint32_t unpack8(uint16_t val) { + return ((val & 0xff00) << 16) | ((val & 0x00ff) << 8); } -static inline uint32_t mult8signed(uint32_t val, int32_t mul) { - // if mul == 0, no need in wasting cycles - if (mul == 0) { - return 0; - } - /* TODO: workout ARMv7 instructions - #if (defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) //Cortex-M4 w/FPU - return val; - #else - */ - uint32_t result = 0; - float mod_mul = (float)mul / (float)((1<<15)-1); - for (int8_t i = 0; i < 4; i++) { - int16_t ai = val >> (sizeof(int8_t) * 8 * i); - int32_t intermediate = ai * mod_mul; - if (intermediate > CHAR_MAX) { - intermediate = CHAR_MAX; - } else if (intermediate < CHAR_MIN) { - intermediate = CHAR_MIN; - } - result |= (((uint32_t) intermediate) & 0xff) << (sizeof(int16_t) * 8 * i); - } - return result; - //#endif +static inline uint32_t pack8(uint32_t val) { + return ((val & 0xff000000) >> 16) | ((val & 0xff00) >> 8); } -//TODO: -static inline uint32_t mult16unsigned(uint32_t val, int32_t mul) { - // if mul == 0, no need in wasting cycles - if (mul == 0) { - return 0; - } - /* TODO: the below ARMv7m instructions "work", but the amplitude is much higher/louder - #if (defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) //Cortex-M4 w/FPU - // there is no unsigned equivalent to the 'SMULWx' ARMv7 Thumb function, - // so we have to do it by hand. - uint32_t lo = val & 0xffff; - uint32_t hi = val >> 16; - //mp_printf(&mp_plat_print, "pre-asm: (mul: %d)\n\tval: %x\tlo: %x\thi: %x\n", mul, val, lo, hi); - uint32_t val_lo; - asm volatile("mul %0, %1, %2" : "=r" (val_lo) : "r" (mul), "r" (lo)); - asm volatile("mla %0, %1, %2, %3" : "=r" (val) : "r" (mul), "r" (hi), "r" (val_lo)); - //mp_printf(&mp_plat_print, "post-asm:\n\tval: %x\tlo: %x\n\n", val, val_lo); - return val; - #else - */ - uint32_t result = 0; - float mod_mul = (float)mul / (float)((1<<15)-1); - for (int8_t i = 0; i < 2; i++) { - int16_t ai = (val >> (sizeof(uint16_t) * 8 * i)) - 0x8000; - int32_t intermediate = ai * mod_mul; - if (intermediate > SHRT_MAX) { - intermediate = SHRT_MAX; - } else if (intermediate < SHRT_MIN) { - intermediate = SHRT_MIN; +static void mix_down_one_voice(audiomixer_mixer_obj_t* self, + audiomixer_mixervoice_obj_t* voice, bool voices_active, + uint32_t* word_buffer, uint32_t length) { + bool voice_done = voice->sample == NULL; + while (!voice_done && length != 0) { + if (voice->buffer_length == 0) { + if (!voice->more_data) { + if (voice->loop) { + audiosample_reset_buffer(voice->sample, false, 0); + } else { + voice->sample = NULL; + voice_done = true; + break; + } + } + if (!voice_done) { + // Load another buffer + audioio_get_buffer_result_t result = audiosample_get_buffer(voice->sample, false, 0, (uint8_t**) &voice->remaining_buffer, &voice->buffer_length); + // Track length in terms of words. + voice->buffer_length /= sizeof(uint32_t); + voice->more_data = result == GET_BUFFER_MORE_DATA; + } } - result |= (((uint32_t) intermediate) + 0x8000) << (sizeof(int16_t) * 8 * i); - } - return result; - //#endif -} -static inline uint32_t mult16signed(uint32_t val, int32_t mul) { - // if mul == 0, no need in wasting cycles - if (mul == 0) { - return 0; + uint32_t n = MIN(voice->buffer_length, length); + uint32_t *src = voice->remaining_buffer; + uint16_t level = voice->level; + + // First active voice gets copied over verbatim. + if (!voices_active) { + if (MP_LIKELY(self->bits_per_sample == 16)) { + if (MP_LIKELY(self->samples_signed)) { + for (uint32_t i = 0; i<n; i++) { + uint32_t v = src[i]; + word_buffer[i] = mult16signed(v, level); + } + } else { + for (uint32_t i = 0; i<n; i++) { + uint32_t v = src[i]; + v = tosigned16(v); + word_buffer[i] = mult16signed(v, level); + } + } + } else { + uint16_t *hword_buffer = (uint16_t*)word_buffer; + uint16_t *hsrc = (uint16_t*)src; + for (uint32_t i = 0; i<n*2; i++) { + uint32_t word = unpack8(hsrc[i]); + if (MP_LIKELY(!self->samples_signed)) { + word = tosigned16(word); + } + word = mult16signed(word, level); + hword_buffer[i] = pack8(word); + } + } + } else { + if (MP_LIKELY(self->bits_per_sample == 16)) { + if (MP_LIKELY(self->samples_signed)) { + for (uint32_t i = 0; i<n; i++) { + uint32_t word = src[i]; + word_buffer[i] = add16signed(mult16signed(word, level), word_buffer[i]); + } + } else { + for (uint32_t i = 0; i<n; i++) { + uint32_t word = src[i]; + word = tosigned16(word); + word_buffer[i] = add16signed(mult16signed(word, level), word_buffer[i]); + } + } + } else { + uint16_t *hword_buffer = (uint16_t*)word_buffer; + uint16_t *hsrc = (uint16_t*)src; + for (uint32_t i = 0; i<n*2; i++) { + uint32_t word = unpack8(hsrc[i]); + if (MP_LIKELY(!self->samples_signed)) { + word = tosigned16(word); + } + word = mult16signed(word, level); + word = add16signed(word, unpack8(hword_buffer[i])); + hword_buffer[i] = pack8(word); + } + } + } + length -= n; + word_buffer += n; + voice->remaining_buffer += n; + voice->buffer_length -= n; } - #if (defined (__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) //Cortex-M4 w/FPU - int32_t hi, lo; - enum { bits = 16 }; // saturate to 16 bits - enum { shift = 0 }; // shift is done automatically - asm volatile("smulwb %0, %1, %2" : "=r" (lo) : "r" (mul), "r" (val)); - asm volatile("smulwt %0, %1, %2" : "=r" (hi) : "r" (mul), "r" (val)); - asm volatile("ssat %0, %1, %2, asr %3" : "=r" (lo) : "I" (bits), "r" (lo), "I" (shift)); - asm volatile("ssat %0, %1, %2, asr %3" : "=r" (hi) : "I" (bits), "r" (hi), "I" (shift)); - asm volatile("pkhbt %0, %1, %2, lsl #16" : "=r" (val) : "r" (lo), "r" (hi)); // pack - return val; - #else - uint32_t result = 0; - float mod_mul = (float)mul / (float)((1<<15)-1); - for (int8_t i = 0; i < 2; i++) { - int16_t ai = val >> (sizeof(int16_t) * 8 * i); - int32_t intermediate = ai * mod_mul; - if (intermediate > SHRT_MAX) { - intermediate = SHRT_MAX; - } else if (intermediate < SHRT_MIN) { - intermediate = SHRT_MIN; + + if (length && !voices_active) { + uint32_t sample_value = self->bits_per_sample == 8 + ? 0x80808080 : 0x80008000; + for (uint32_t i = 0; i<length; i++) { + word_buffer[i] = sample_value; } - result |= (((uint32_t) intermediate) & 0xffff) << (sizeof(int16_t) * 8 * i); } - return result; - #endif } audioio_get_buffer_result_t audiomixer_mixer_get_buffer(audiomixer_mixer_obj_t* self, @@ -314,87 +265,25 @@ audioio_get_buffer_result_t audiomixer_mixer_get_buffer(audiomixer_mixer_obj_t* } self->use_first_buffer = !self->use_first_buffer; bool voices_active = false; + uint32_t length = self->len / sizeof(uint32_t); + for (int32_t v = 0; v < self->voice_count; v++) { audiomixer_mixervoice_obj_t* voice = MP_OBJ_TO_PTR(self->voice[v]); - uint32_t j = 0; - bool voice_done = voice->sample == NULL; - for (uint32_t i = 0; i < self->len / sizeof(uint32_t); i++) { - if (!voice_done && j >= voice->buffer_length) { - if (!voice->more_data) { - if (voice->loop) { - audiosample_reset_buffer(voice->sample, false, 0); - } else { - voice->sample = NULL; - voice_done = true; - } - } - if (!voice_done) { - // Load another buffer - audioio_get_buffer_result_t result = audiosample_get_buffer(voice->sample, false, 0, (uint8_t**) &voice->remaining_buffer, &voice->buffer_length); - // Track length in terms of words. - voice->buffer_length /= sizeof(uint32_t); - voice->more_data = result == GET_BUFFER_MORE_DATA; - j = 0; - } - } - // First active voice gets copied over verbatim. - uint32_t sample_value; - if (voice_done) { - // Exit early if another voice already set all samples once. - if (voices_active) { - continue; - } - sample_value = 0; - if (!self->samples_signed) { - if (self->bits_per_sample == 8) { - sample_value = 0x7f7f7f7f; - } else { - sample_value = 0x7fff7fff; - } - } - } else { - sample_value = voice->remaining_buffer[j]; - } + mix_down_one_voice(self, voice, voices_active, word_buffer, length); + voices_active = true; + } - // apply the mixer level - if (!self->samples_signed) { - if (self->bits_per_sample == 8) { - sample_value = mult8unsigned(sample_value, voice->level); - } else { - sample_value = mult16unsigned(sample_value, voice->level); - } - } else { - if (self->bits_per_sample == 8) { - sample_value = mult8signed(sample_value, voice->level); - } else { - sample_value = mult16signed(sample_value, voice->level); - } + if (!self->samples_signed) { + if (self->bits_per_sample == 16) { + for (uint32_t i = 0; i < length; i++) { + word_buffer[i] = tounsigned16(word_buffer[i]); } - - if (!voices_active) { - word_buffer[i] = sample_value; - } else { - if (self->bits_per_sample == 8) { - if (self->samples_signed) { - word_buffer[i] = add8signed(word_buffer[i], sample_value); - } else { - word_buffer[i] = add8unsigned(word_buffer[i], sample_value); - } - } else { - if (self->samples_signed) { - word_buffer[i] = add16signed(word_buffer[i], sample_value); - } else { - word_buffer[i] = add16unsigned(word_buffer[i], sample_value); - } - } + } else { + for (uint32_t i = 0; i < length; i++) { + word_buffer[i] = tounsigned8(word_buffer[i]); } - j++; } - voice->buffer_length -= j; - voice->remaining_buffer += j; - - voices_active = true; } self->read_count += 1; diff --git a/shared-module/audiomixer/MixerVoice.c b/shared-module/audiomixer/MixerVoice.c index ff05dc93e..9be104afc 100644 --- a/shared-module/audiomixer/MixerVoice.c +++ b/shared-module/audiomixer/MixerVoice.c @@ -34,7 +34,7 @@ void common_hal_audiomixer_mixervoice_construct(audiomixer_mixervoice_obj_t *self) { self->sample = NULL; - self->level = ((1 << 15) - 1); + self->level = 1 << 15; } void common_hal_audiomixer_mixervoice_set_parent(audiomixer_mixervoice_obj_t* self, audiomixer_mixer_obj_t *parent) { @@ -42,11 +42,11 @@ void common_hal_audiomixer_mixervoice_set_parent(audiomixer_mixervoice_obj_t* se } float common_hal_audiomixer_mixervoice_get_level(audiomixer_mixervoice_obj_t* self) { - return ((float) self->level / ((1 << 15) - 1)); + return ((float) self->level / (1 << 15)); } void common_hal_audiomixer_mixervoice_set_level(audiomixer_mixervoice_obj_t* self, float level) { - self->level = level * ((1 << 15)-1); + self->level = level * (1 << 15); } void common_hal_audiomixer_mixervoice_play(audiomixer_mixervoice_obj_t* self, mp_obj_t sample, bool loop) { diff --git a/shared-module/audiomixer/MixerVoice.h b/shared-module/audiomixer/MixerVoice.h index efac19156..a85316e3d 100644 --- a/shared-module/audiomixer/MixerVoice.h +++ b/shared-module/audiomixer/MixerVoice.h @@ -39,7 +39,7 @@ typedef struct { bool more_data; uint32_t* remaining_buffer; uint32_t buffer_length; - int16_t level; + uint16_t level; } audiomixer_mixervoice_obj_t; diff --git a/shared-module/audiomp3/MP3Decoder.c b/shared-module/audiomp3/MP3Decoder.c new file mode 100644 index 000000000..30357c616 --- /dev/null +++ b/shared-module/audiomp3/MP3Decoder.c @@ -0,0 +1,352 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * Copyright (c) 2019 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "shared-bindings/audiomp3/MP3Decoder.h" + +#include <stdint.h> +#include <string.h> +#include <math.h> + +#include "py/mperrno.h" +#include "py/runtime.h" + +#include "shared-module/audiomp3/MP3Decoder.h" +#include "supervisor/shared/translate.h" +#include "lib/mp3/src/mp3common.h" + +#define MAX_BUFFER_LEN (MAX_NSAMP * MAX_NGRAN * MAX_NCHAN * sizeof(int16_t)) + +/** Fill the input buffer if it is less than half full. + * + * Returns true if the input buffer contains any useful data, + * false otherwise. (The input buffer will be padded to the end with + * 0 bytes, which do not interfere with MP3 decoding) + * + * Raises OSError if f_read fails. + * + * Sets self->eof if any read of the file returns 0 bytes + */ +STATIC bool mp3file_update_inbuf(audiomp3_mp3file_obj_t* self) { + // If buffer is over half full, do nothing + if (self->inbuf_offset < self->inbuf_length/2) return true; + + // If we didn't previously reach the end of file, we can try reading now + if (!self->eof) { + + // Move the unconsumed portion of the buffer to the start + uint8_t *end_of_buffer = self->inbuf + self->inbuf_length; + uint8_t *new_end_of_data = self->inbuf + self->inbuf_length - self->inbuf_offset; + memmove(self->inbuf, self->inbuf + self->inbuf_offset, + self->inbuf_length - self->inbuf_offset); + self->inbuf_offset = 0; + + UINT to_read = end_of_buffer - new_end_of_data; + UINT bytes_read = 0; + memset(new_end_of_data, 0, to_read); + if (f_read(&self->file->fp, new_end_of_data, to_read, &bytes_read) != FR_OK) { + self->eof = true; + mp_raise_OSError(MP_EIO); + } + + if (bytes_read == 0) { + self->eof = true; + } + + if (to_read != bytes_read) { + new_end_of_data += bytes_read; + memset(new_end_of_data, 0, end_of_buffer - new_end_of_data); + } + + } + + // Return true iff there are at least some useful bytes in the buffer + return self->inbuf_offset < self->inbuf_length; +} + +#define READ_PTR(self) (self->inbuf + self->inbuf_offset) +#define BYTES_LEFT(self) (self->inbuf_length - self->inbuf_offset) +#define CONSUME(self, n) (self->inbuf_offset += n) + +// http://id3.org/d3v2.3.0 +// http://id3.org/id3v2.3.0 +STATIC void mp3file_skip_id3v2(audiomp3_mp3file_obj_t* self) { + mp3file_update_inbuf(self); + if (BYTES_LEFT(self) < 10) { + return; + } + uint8_t *data = READ_PTR(self); + if (!( + data[0] == 'I' && + data[1] == 'D' && + data[2] == '3' && + data[3] != 0xff && + data[4] != 0xff && + (data[5] & 0x1f) == 0 && + (data[6] & 0x80) == 0 && + (data[7] & 0x80) == 0 && + (data[8] & 0x80) == 0 && + (data[9] & 0x80) == 0)) { + return; + } + uint32_t size = (data[6] << 21) | (data[7] << 14) | (data[8] << 7) | (data[9]); + size += 10; // size excludes the "header" (but not the "extended header") + // First, deduct from size whatever is left in buffer + uint32_t to_consume = MIN(size, BYTES_LEFT(self)); + CONSUME(self, to_consume); + size -= to_consume; + + // Next, seek in the file after the header + f_lseek(&self->file->fp, f_tell(&self->file->fp) + size); + return; +} + +/* If a sync word can be found, advance to it and return true. Otherwise, + * return false. + */ +STATIC bool mp3file_find_sync_word(audiomp3_mp3file_obj_t* self) { + do { + mp3file_update_inbuf(self); + int offset = MP3FindSyncWord(READ_PTR(self), BYTES_LEFT(self)); + if (offset >= 0) { + CONSUME(self, offset); + mp3file_update_inbuf(self); + return true; + } + CONSUME(self, MAX(0, BYTES_LEFT(self) - 16)); + } while (!self->eof); + return false; +} + +STATIC bool mp3file_get_next_frame_info(audiomp3_mp3file_obj_t* self, MP3FrameInfo* fi) { + int err; + do { + err = MP3GetNextFrameInfo(self->decoder, fi, READ_PTR(self)); + if (err == ERR_MP3_NONE) { + break; + } + CONSUME(self, 1); + mp3file_find_sync_word(self); + } while (!self->eof); + return err == ERR_MP3_NONE; +} + +void common_hal_audiomp3_mp3file_construct(audiomp3_mp3file_obj_t* self, + pyb_file_obj_t* file, + uint8_t *buffer, + size_t buffer_size) { + // XXX Adafruit_MP3 uses a 2kB input buffer and two 4kB output buffers. + // for a whopping total of 10kB buffers (+mp3 decoder state and frame buffer) + // At 44kHz, that's 23ms of output audio data. + // + // We will choose a slightly different allocation strategy for the output: + // Make sure the buffers are sized exactly to match (a multiple of) the + // frame size; this is typically 2304 * 2 bytes, so a little bit bigger + // than the two 4kB output buffers, except that the alignment allows to + // never allocate that extra frame buffer. + + self->inbuf_length = 2048; + self->inbuf_offset = self->inbuf_length; + self->inbuf = m_malloc(self->inbuf_length, false); + if (self->inbuf == NULL) { + common_hal_audiomp3_mp3file_deinit(self); + mp_raise_msg(&mp_type_MemoryError, + translate("Couldn't allocate input buffer")); + } + self->decoder = MP3InitDecoder(); + if (self->decoder == NULL) { + common_hal_audiomp3_mp3file_deinit(self); + mp_raise_msg(&mp_type_MemoryError, + translate("Couldn't allocate decoder")); + } + + if ((intptr_t)buffer & 1) { + buffer += 1; buffer_size -= 1; + } + if (buffer_size >= 2 * MAX_BUFFER_LEN) { + self->buffers[0] = (int16_t*)(void*)buffer; + self->buffers[1] = (int16_t*)(void*)(buffer + MAX_BUFFER_LEN); + } else { + self->buffers[0] = m_malloc(MAX_BUFFER_LEN, false); + if (self->buffers[0] == NULL) { + common_hal_audiomp3_mp3file_deinit(self); + mp_raise_msg(&mp_type_MemoryError, + translate("Couldn't allocate first buffer")); + } + + self->buffers[1] = m_malloc(MAX_BUFFER_LEN, false); + if (self->buffers[1] == NULL) { + common_hal_audiomp3_mp3file_deinit(self); + mp_raise_msg(&mp_type_MemoryError, + translate("Couldn't allocate second buffer")); + } + } + + common_hal_audiomp3_mp3file_set_file(self, file); +} + +void common_hal_audiomp3_mp3file_set_file(audiomp3_mp3file_obj_t* self, pyb_file_obj_t* file) { + self->file = file; + f_lseek(&self->file->fp, 0); + self->inbuf_offset = self->inbuf_length; + self->eof = 0; + self->other_channel = -1; + mp3file_update_inbuf(self); + mp3file_find_sync_word(self); + // It **SHOULD** not be necessary to do this; the buffer should be filled + // with fresh content before it is returned by get_buffer(). The fact that + // this is necessary to avoid a glitch at the start of playback of a second + // track using the same decoder object means there's still a bug in + // get_buffer() that I didn't understand. + memset(self->buffers[0], 0, MAX_BUFFER_LEN); + memset(self->buffers[1], 0, MAX_BUFFER_LEN); + MP3FrameInfo fi; + if(!mp3file_get_next_frame_info(self, &fi)) { + mp_raise_msg(&mp_type_RuntimeError, + translate("Failed to parse MP3 file")); + } + + self->sample_rate = fi.samprate; + self->channel_count = fi.nChans; + self->frame_buffer_size = fi.outputSamps*sizeof(int16_t); + self->len = 2 * self->frame_buffer_size; +} + +void common_hal_audiomp3_mp3file_deinit(audiomp3_mp3file_obj_t* self) { + MP3FreeDecoder(self->decoder); + self->decoder = NULL; + self->inbuf = NULL; + self->buffers[0] = NULL; + self->buffers[1] = NULL; + self->file = NULL; +} + +bool common_hal_audiomp3_mp3file_deinited(audiomp3_mp3file_obj_t* self) { + return self->buffers[0] == NULL; +} + +uint32_t common_hal_audiomp3_mp3file_get_sample_rate(audiomp3_mp3file_obj_t* self) { + return self->sample_rate; +} + +void common_hal_audiomp3_mp3file_set_sample_rate(audiomp3_mp3file_obj_t* self, + uint32_t sample_rate) { + self->sample_rate = sample_rate; +} + +uint8_t common_hal_audiomp3_mp3file_get_bits_per_sample(audiomp3_mp3file_obj_t* self) { + return 16; +} + +uint8_t common_hal_audiomp3_mp3file_get_channel_count(audiomp3_mp3file_obj_t* self) { + return self->channel_count; +} + +bool audiomp3_mp3file_samples_signed(audiomp3_mp3file_obj_t* self) { + return true; +} + +void audiomp3_mp3file_reset_buffer(audiomp3_mp3file_obj_t* self, + bool single_channel, + uint8_t channel) { + if (single_channel && channel == 1) { + return; + } + // We don't reset the buffer index in case we're looping and we have an odd number of buffer + // loads + f_lseek(&self->file->fp, 0); + self->inbuf_offset = self->inbuf_length; + self->eof = 0; + self->other_channel = -1; + mp3file_update_inbuf(self); + mp3file_skip_id3v2(self); + mp3file_find_sync_word(self); +} + +audioio_get_buffer_result_t audiomp3_mp3file_get_buffer(audiomp3_mp3file_obj_t* self, + bool single_channel, + uint8_t channel, + uint8_t** bufptr, + uint32_t* buffer_length) { + if (!self->inbuf) { + return GET_BUFFER_ERROR; + } + if (!single_channel) { + channel = 0; + } + + *buffer_length = self->frame_buffer_size; + + if (channel == self->other_channel) { + *bufptr = (uint8_t*)(self->buffers[self->other_buffer_index] + channel); + self->other_channel = -1; + return GET_BUFFER_MORE_DATA; + } + + + self->buffer_index = !self->buffer_index; + self->other_channel = 1-channel; + self->other_buffer_index = self->buffer_index; + int16_t *buffer = (int16_t *)(void *)self->buffers[self->buffer_index]; + *bufptr = (uint8_t*)buffer; + + mp3file_skip_id3v2(self); + if (!mp3file_find_sync_word(self)) { + return self->eof ? GET_BUFFER_DONE : GET_BUFFER_ERROR; + } + int bytes_left = BYTES_LEFT(self); + uint8_t *inbuf = READ_PTR(self); + int err = MP3Decode(self->decoder, &inbuf, &bytes_left, buffer, 0); + CONSUME(self, BYTES_LEFT(self) - bytes_left); + if (err) { + return GET_BUFFER_DONE; + } + + return GET_BUFFER_MORE_DATA; +} + +void audiomp3_mp3file_get_buffer_structure(audiomp3_mp3file_obj_t* self, bool single_channel, + bool* single_buffer, bool* samples_signed, + uint32_t* max_buffer_length, uint8_t* spacing) { + *single_buffer = false; + *samples_signed = true; + *max_buffer_length = self->frame_buffer_size; + if (single_channel) { + *spacing = self->channel_count; + } else { + *spacing = 1; + } +} + +float common_hal_audiomp3_mp3file_get_rms_level(audiomp3_mp3file_obj_t* self) { + float sumsq = 0.f; + // Assumes no DC component to the audio. Is that a safe assumption? + int16_t *buffer = (int16_t *)(void *)self->buffers[self->buffer_index]; + for(size_t i=0; i<self->frame_buffer_size / sizeof(int16_t); i++) { + sumsq += (float)buffer[i] * buffer[i]; + } + return sqrtf(sumsq) / (self->frame_buffer_size / sizeof(int16_t)); +} diff --git a/shared-module/audiomp3/MP3Decoder.h b/shared-module/audiomp3/MP3Decoder.h new file mode 100644 index 000000000..9ee1d0949 --- /dev/null +++ b/shared-module/audiomp3/MP3Decoder.h @@ -0,0 +1,72 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft + * Copyright (c) 2019 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_MP3FILE_H +#define MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_MP3FILE_H + +#include "extmod/vfs_fat.h" +#include "py/obj.h" + +#include "shared-module/audiocore/__init__.h" + +typedef struct { + mp_obj_base_t base; + struct _MP3DecInfo *decoder; + uint8_t* inbuf; + uint32_t inbuf_length; + uint32_t inbuf_offset; + int16_t* buffers[2]; + uint32_t len; + uint32_t frame_buffer_size; + + uint32_t sample_rate; + pyb_file_obj_t* file; + + uint8_t buffer_index; + uint8_t channel_count; + bool eof; + + int8_t other_channel; + int8_t other_buffer_index; +} audiomp3_mp3file_obj_t; + +// These are not available from Python because it may be called in an interrupt. +void audiomp3_mp3file_reset_buffer(audiomp3_mp3file_obj_t* self, + bool single_channel, + uint8_t channel); +audioio_get_buffer_result_t audiomp3_mp3file_get_buffer(audiomp3_mp3file_obj_t* self, + bool single_channel, + uint8_t channel, + uint8_t** buffer, + uint32_t* buffer_length); // length in bytes +void audiomp3_mp3file_get_buffer_structure(audiomp3_mp3file_obj_t* self, bool single_channel, + bool* single_buffer, bool* samples_signed, + uint32_t* max_buffer_length, uint8_t* spacing); + +float audiomp3_mp3file_get_rms_level(audiomp3_mp3file_obj_t* self); + +#endif // MICROPY_INCLUDED_SHARED_MODULE_AUDIOIO_MP3FILE_H diff --git a/shared-module/audiomp3/__init__.c b/shared-module/audiomp3/__init__.c new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/shared-module/audiomp3/__init__.c diff --git a/shared-module/audiomp3/__init__.h b/shared-module/audiomp3/__init__.h new file mode 100644 index 000000000..e7b1f3aab --- /dev/null +++ b/shared-module/audiomp3/__init__.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_SHARED_MODULE_AUDIOMP3__INIT__H +#define MICROPY_INCLUDED_SHARED_MODULE_AUDIOMP3__INIT__H + +#endif // MICROPY_INCLUDED_SHARED_MODULE_AUDIOMP3__INIT__H diff --git a/shared-module/board/__init__.c b/shared-module/board/__init__.c index dd07d7190..bbf177384 100644 --- a/shared-module/board/__init__.c +++ b/shared-module/board/__init__.c @@ -100,8 +100,30 @@ mp_obj_t common_hal_board_create_uart(void) { const mcu_pin_obj_t* rx = MP_OBJ_TO_PTR(DEFAULT_UART_BUS_RX); const mcu_pin_obj_t* tx = MP_OBJ_TO_PTR(DEFAULT_UART_BUS_TX); +#ifdef DEFAULT_UART_BUS_RTS + const mcu_pin_obj_t* rts = MP_OBJ_TO_PTR(DEFAULT_UART_BUS_RTS); +#else + const mcu_pin_obj_t* rts = mp_const_none; +#endif +#ifdef DEFAULT_UART_BUS_CTS + const mcu_pin_obj_t* cts = MP_OBJ_TO_PTR(DEFAULT_UART_BUS_CTS); +#else + const mcu_pin_obj_t* cts = mp_const_none; +#endif +#ifdef DEFAULT_UART_IS_RS485 + const mcu_pin_obj_t* rs485_dir = MP_OBJ_TO_PTR(DEFAULT_UART_BUS_RS485DIR); +#ifdef DEFAULT_UART_RS485_INVERT + const bool rs485_invert = true; +#else + const bool rs485_invert = false; +#endif +#else + const mcu_pin_obj_t* rs485_dir = mp_const_none; + const bool rs485_invert = false; +#endif - common_hal_busio_uart_construct(self, tx, rx, 9600, 8, PARITY_NONE, 1, 1000, 64); + common_hal_busio_uart_construct(self, tx, rx, rts, cts, rs485_dir, rs485_invert, + 9600, 8, PARITY_NONE, 1, 1.0f, 64); MP_STATE_VM(shared_uart_bus) = MP_OBJ_FROM_PTR(self); return MP_STATE_VM(shared_uart_bus); } diff --git a/shared-module/displayio/Display.c b/shared-module/displayio/Display.c index 862d2cf59..299c8ad35 100644 --- a/shared-module/displayio/Display.c +++ b/shared-module/displayio/Display.c @@ -35,6 +35,7 @@ #include "shared-module/displayio/__init__.h" #include "shared-module/displayio/display_core.h" #include "supervisor/shared/display.h" +#include "supervisor/shared/tick.h" #include "supervisor/usb.h" #include <stdint.h> @@ -307,13 +308,30 @@ STATIC void _refresh_display(displayio_display_obj_t* self) { displayio_display_core_finish_refresh(&self->core); } +void common_hal_displayio_display_set_rotation(displayio_display_obj_t* self, int rotation){ + bool transposed = (self->core.rotation == 90 || self->core.rotation == 270); + bool will_transposed = (rotation == 90 || rotation == 270); + if(transposed != will_transposed) { + int tmp = self->core.width; + self->core.width = self->core.height; + self->core.height = tmp; + } + displayio_display_core_set_rotation(&self->core, rotation); + supervisor_stop_terminal(); + supervisor_start_terminal(self->core.width, self->core.height); + if (self->core.current_group != NULL) { + displayio_group_update_transform(self->core.current_group, &self->core.transform); + } +} + uint16_t common_hal_displayio_display_get_rotation(displayio_display_obj_t* self){ return self->core.rotation; } + bool common_hal_displayio_display_refresh(displayio_display_obj_t* self, uint32_t target_ms_per_frame, uint32_t maximum_ms_per_real_frame) { if (!self->auto_refresh && !self->first_manual_refresh) { - uint64_t current_time = ticks_ms; + uint64_t current_time = supervisor_ticks_ms64(); uint32_t current_ms_since_real_refresh = current_time - self->core.last_refresh; // Test to see if the real frame time is below our minimum. if (current_ms_since_real_refresh > maximum_ms_per_real_frame) { @@ -327,7 +345,7 @@ bool common_hal_displayio_display_refresh(displayio_display_obj_t* self, uint32_ } uint32_t remaining_time = target_ms_per_frame - (current_ms_since_real_refresh % target_ms_per_frame); // We're ahead of the game so wait until we align with the frame rate. - while (ticks_ms - self->last_refresh_call < remaining_time) { + while (supervisor_ticks_ms64() - self->last_refresh_call < remaining_time) { RUN_BACKGROUND_TASKS; } } @@ -350,20 +368,20 @@ STATIC void _update_backlight(displayio_display_obj_t* self) { if (!self->auto_brightness || self->updating_backlight) { return; } - if (ticks_ms - self->last_backlight_refresh < 100) { + if (supervisor_ticks_ms64() - self->last_backlight_refresh < 100) { return; } // TODO(tannewt): Fade the backlight based on it's existing value and a target value. The target // should account for ambient light when possible. common_hal_displayio_display_set_brightness(self, 1.0); - self->last_backlight_refresh = ticks_ms; + self->last_backlight_refresh = supervisor_ticks_ms64(); } void displayio_display_background(displayio_display_obj_t* self) { _update_backlight(self); - if (self->auto_refresh && (ticks_ms - self->core.last_refresh) > self->native_ms_per_frame) { + if (self->auto_refresh && (supervisor_ticks_ms64() - self->core.last_refresh) > self->native_ms_per_frame) { _refresh_display(self); } } diff --git a/shared-module/displayio/EPaperDisplay.c b/shared-module/displayio/EPaperDisplay.c index df1d5162b..91d4bb7f9 100644 --- a/shared-module/displayio/EPaperDisplay.c +++ b/shared-module/displayio/EPaperDisplay.c @@ -26,6 +26,7 @@ #include "shared-bindings/displayio/EPaperDisplay.h" +#include "py/gc.h" #include "py/runtime.h" #include "shared-bindings/displayio/ColorConverter.h" #include "shared-bindings/displayio/FourWire.h" @@ -35,6 +36,7 @@ #include "shared-bindings/time/__init__.h" #include "shared-module/displayio/__init__.h" #include "supervisor/shared/display.h" +#include "supervisor/shared/tick.h" #include "supervisor/usb.h" #include <stdint.h> @@ -175,7 +177,7 @@ uint32_t common_hal_displayio_epaperdisplay_get_time_to_refresh(displayio_epaper return 0; } // Refresh at seconds per frame rate. - uint32_t elapsed_time = ticks_ms - self->core.last_refresh; + uint32_t elapsed_time = supervisor_ticks_ms64() - self->core.last_refresh; if (elapsed_time > self->milliseconds_per_frame) { return 0; } @@ -298,7 +300,7 @@ bool displayio_epaperdisplay_refresh_area(displayio_epaperdisplay_obj_t* self, c } bool common_hal_displayio_epaperdisplay_refresh(displayio_epaperdisplay_obj_t* self) { - + if (self->refreshing && self->busy.base.type == &digitalio_digitalinout_type) { if (common_hal_digitalio_digitalinout_get_value(&self->busy) != self->busy_state) { self->refreshing = false; @@ -339,7 +341,7 @@ void displayio_epaperdisplay_background(displayio_epaperdisplay_obj_t* self) { bool busy = common_hal_digitalio_digitalinout_get_value(&self->busy); refresh_done = busy != self->busy_state; } else { - refresh_done = ticks_ms - self->core.last_refresh > self->refresh_time; + refresh_done = supervisor_ticks_ms64() - self->core.last_refresh > self->refresh_time; } if (refresh_done) { self->refreshing = false; @@ -365,6 +367,8 @@ void release_epaperdisplay(displayio_epaperdisplay_obj_t* self) { void displayio_epaperdisplay_collect_ptrs(displayio_epaperdisplay_obj_t* self) { displayio_display_core_collect_ptrs(&self->core); + gc_collect_ptr(self->start_sequence); + gc_collect_ptr(self->stop_sequence); } bool maybe_refresh_epaperdisplay(void) { diff --git a/shared-module/displayio/FourWire.c b/shared-module/displayio/FourWire.c index 92d4507dd..256c29642 100644 --- a/shared-module/displayio/FourWire.c +++ b/shared-module/displayio/FourWire.c @@ -49,8 +49,8 @@ void common_hal_displayio_fourwire_construct(displayio_fourwire_obj_t* self, gc_never_free(self->bus); self->frequency = baudrate; - self->polarity = common_hal_busio_spi_get_polarity(spi); - self->phase = common_hal_busio_spi_get_phase(spi); + self->polarity = 0; + self->phase = 0; common_hal_digitalio_digitalinout_construct(&self->command, command); common_hal_digitalio_digitalinout_switch_to_output(&self->command, true, DRIVE_MODE_PUSH_PULL); diff --git a/shared-module/displayio/__init__.c b/shared-module/displayio/__init__.c index af22cb0df..efa61265e 100644 --- a/shared-module/displayio/__init__.c +++ b/shared-module/displayio/__init__.c @@ -16,6 +16,9 @@ #include "supervisor/shared/display.h" #include "supervisor/memory.h" +#include "supervisor/spi_flash_api.h" +#include "py/mpconfig.h" + primary_display_t displays[CIRCUITPY_DISPLAY_LIMIT]; // Check for recursive calls to displayio_background. @@ -100,6 +103,11 @@ void reset_displays(void) { continue; } #endif + #ifdef BOARD_USE_INTERNAL_SPI + if (original_spi == (mp_obj_t)(&supervisor_flash_spi_bus)) { + continue; + } + #endif memcpy(&fourwire->inline_bus, original_spi, sizeof(busio_spi_obj_t)); fourwire->bus = &fourwire->inline_bus; // Check for other displays that use the same spi bus and swap them too. diff --git a/shared-module/displayio/display_core.c b/shared-module/displayio/display_core.c index a73ea81d1..120b70f76 100644 --- a/shared-module/displayio/display_core.c +++ b/shared-module/displayio/display_core.c @@ -35,6 +35,7 @@ #include "shared-bindings/time/__init__.h" #include "shared-module/displayio/__init__.h" #include "supervisor/shared/display.h" +#include "supervisor/shared/tick.h" #include <stdint.h> #include <string.h> @@ -85,6 +86,15 @@ void displayio_display_core_construct(displayio_display_core_t* self, self->height = height; self->ram_width = ram_width; self->ram_height = ram_height; + + displayio_display_core_set_rotation(self, rotation); +} + +void displayio_display_core_set_rotation( displayio_display_core_t* self, + int rotation) { + int height = self->height; + int width = self->width; + rotation = rotation % 360; self->rotation = rotation; self->transform.x = 0; @@ -281,7 +291,7 @@ void displayio_display_core_set_region_to_update(displayio_display_core_t* self, } void displayio_display_core_start_refresh(displayio_display_core_t* self) { - self->last_refresh = ticks_ms; + self->last_refresh = supervisor_ticks_ms64(); } void displayio_display_core_finish_refresh(displayio_display_core_t* self) { @@ -289,7 +299,7 @@ void displayio_display_core_finish_refresh(displayio_display_core_t* self) { displayio_group_finish_refresh(self->current_group); } self->full_refresh = false; - self->last_refresh = ticks_ms; + self->last_refresh = supervisor_ticks_ms64(); } void release_display_core(displayio_display_core_t* self) { diff --git a/shared-module/displayio/display_core.h b/shared-module/displayio/display_core.h index 3a69cd40a..1c3d0f09c 100644 --- a/shared-module/displayio/display_core.h +++ b/shared-module/displayio/display_core.h @@ -68,6 +68,8 @@ uint16_t displayio_display_core_get_height(displayio_display_core_t* self); void displayio_display_core_set_dither(displayio_display_core_t* self, bool dither); bool displayio_display_core_get_dither(displayio_display_core_t* self); +void displayio_display_core_set_rotation(displayio_display_core_t* self, int rotation); + bool displayio_display_core_bus_free(displayio_display_core_t *self); bool displayio_display_core_begin_transaction(displayio_display_core_t* self); void displayio_display_core_end_transaction(displayio_display_core_t* self); diff --git a/shared-module/network/__init__.c b/shared-module/network/__init__.c index 96648b260..925e9a2a3 100644 --- a/shared-module/network/__init__.c +++ b/shared-module/network/__init__.c @@ -31,6 +31,8 @@ #include "py/mphal.h" #include "py/mperrno.h" +#include "supervisor/shared/tick.h" + #include "shared-bindings/random/__init__.h" #include "shared-module/network/__init__.h" @@ -53,7 +55,7 @@ void network_module_deinit(void) { void network_module_background(void) { static uint32_t next_tick = 0; - uint32_t this_tick = ticks_ms; + uint32_t this_tick = supervisor_ticks_ms32(); if (this_tick < next_tick) return; next_tick = this_tick + 1000; diff --git a/shared-module/storage/__init__.c b/shared-module/storage/__init__.c index 215e1356a..c3d4b50c8 100644 --- a/shared-module/storage/__init__.c +++ b/shared-module/storage/__init__.c @@ -149,9 +149,7 @@ void common_hal_storage_remount(const char *mount_path, bool readonly, bool disa } #ifdef USB_AVAILABLE - // TODO(dhalbert): is this is a good enough check? It checks for - // CDC enabled. There is no "MSC enabled" check. - if (usb_enabled()) { + if (!usb_msc_ejected()) { mp_raise_RuntimeError(translate("Cannot remount '/' when USB is active.")); } #endif diff --git a/shared-module/usb_hid/Device.c b/shared-module/usb_hid/Device.c index bed7d163f..8744f2ed3 100644 --- a/shared-module/usb_hid/Device.c +++ b/shared-module/usb_hid/Device.c @@ -30,6 +30,7 @@ #include "shared-bindings/usb_hid/Device.h" #include "shared-module/usb_hid/Device.h" #include "supervisor/shared/translate.h" +#include "supervisor/shared/tick.h" #include "tusb.h" uint8_t common_hal_usb_hid_device_get_usage_page(usb_hid_device_obj_t *self) { @@ -46,8 +47,8 @@ void common_hal_usb_hid_device_send_report(usb_hid_device_obj_t *self, uint8_t* } // Wait until interface is ready, timeout = 2 seconds - uint64_t end_ticks = ticks_ms + 2000; - while ( (ticks_ms < end_ticks) && !tud_hid_ready() ) { + uint64_t end_ticks = supervisor_ticks_ms64() + 2000; + while ( (supervisor_ticks_ms64() < end_ticks) && !tud_hid_ready() ) { RUN_BACKGROUND_TASKS; } |
