Merge branch 'adafruit:main' into m5timer

This commit is contained in:
Bill Sideris 2023-05-18 15:13:24 +03:00 committed by GitHub
commit d426ba79f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
108 changed files with 3207 additions and 1102 deletions

View File

@ -209,7 +209,7 @@ jobs:
TWINE_USERNAME: ${{ secrets.pypi_username }} TWINE_USERNAME: ${{ secrets.pypi_username }}
TWINE_PASSWORD: ${{ secrets.pypi_password }} TWINE_PASSWORD: ${{ secrets.pypi_password }}
run: | run: |
# setup.py sdist was run by 'make stubs' # python -m build was run by 'make stubs'
[ -z "$TWINE_USERNAME" ] || echo "Uploading dev release to PyPi" [ -z "$TWINE_USERNAME" ] || echo "Uploading dev release to PyPi"
[ -z "$TWINE_USERNAME" ] || twine upload circuitpython-stubs/dist/* [ -z "$TWINE_USERNAME" ] || twine upload circuitpython-stubs/dist/*

3
.gitmodules vendored
View File

@ -338,3 +338,6 @@
[submodule "frozen/circuitpython-pcf85063a"] [submodule "frozen/circuitpython-pcf85063a"]
path = frozen/circuitpython-pcf85063a path = frozen/circuitpython-pcf85063a
url = https://github.com/bablokb/circuitpython-pcf85063a url = https://github.com/bablokb/circuitpython-pcf85063a
[submodule "frozen/Adafruit_CircuitPython_Wave"]
path = frozen/Adafruit_CircuitPython_Wave
url = http://github.com/adafruit/Adafruit_CircuitPython_Wave.git

View File

@ -266,7 +266,7 @@ stubs:
@cp setup.py-stubs circuitpython-stubs/setup.py @cp setup.py-stubs circuitpython-stubs/setup.py
@cp README.rst-stubs circuitpython-stubs/README.rst @cp README.rst-stubs circuitpython-stubs/README.rst
@cp MANIFEST.in-stubs circuitpython-stubs/MANIFEST.in @cp MANIFEST.in-stubs circuitpython-stubs/MANIFEST.in
@(cd circuitpython-stubs && $(PYTHON) setup.py -q sdist) @$(PYTHON) -m build circuitpython-stubs
.PHONY: check-stubs .PHONY: check-stubs
check-stubs: stubs check-stubs: stubs

View File

@ -60,6 +60,12 @@ CIRCUITPY_BLE_NAME
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
Default BLE name the board advertises as, including for the BLE workflow. Default BLE name the board advertises as, including for the BLE workflow.
CIRCUITPY_PYSTACK_SIZE
~~~~~~~~~~~~~~~~~~~~~~
Sets the size of the python stack. Must be a multiple of 4. The default value is currently 1536.
Increasing the stack reduces the size of the heap available to python code.
Used to avoid "Pystack exhausted" errors when the code can't be reworked to avoid it.
CIRCUITPY_RESERVED_PSRAM CIRCUITPY_RESERVED_PSRAM
~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~
On boards with Espressif microcontrollers with PSRAM (also called SPIRAM), permanently reserve a portion of PSRAM for use by esp-idf. On boards with Espressif microcontrollers with PSRAM (also called SPIRAM), permanently reserve a portion of PSRAM for use by esp-idf.

@ -0,0 +1 @@
Subproject commit 02b748f2e6826dc442c842885e58b07ad10d9287

View File

@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-01-04 12:55-0600\n" "POT-Creation-Date: 2021-01-04 12:55-0600\n"
"PO-Revision-Date: 2023-04-22 15:51+0000\n" "PO-Revision-Date: 2023-05-16 19:50+0000\n"
"Last-Translator: Jose David M <jquintana202020@gmail.com>\n" "Last-Translator: Luis Ruiz San Segundo <luisan00@hotmail.com>\n"
"Language-Team: \n" "Language-Team: \n"
"Language: es\n" "Language: es\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -206,7 +206,7 @@ msgstr "%q debe ser un bytearray o array de tipo 'H' o 'B'"
#: shared-bindings/audiocore/RawSample.c #: shared-bindings/audiocore/RawSample.c
msgid "%q must be a bytearray or array of type 'h', 'H', 'b', or 'B'" msgid "%q must be a bytearray or array of type 'h', 'H', 'b', or 'B'"
msgstr "%q debe ser un byte-matriz o matriz de tipo 'h', 'H', 'b', o 'B'" msgstr "%q debe ser un bytearray o array de tipo 'h', 'H', 'b', o 'B'"
#: ports/espressif/common-hal/analogbufio/BufferedIn.c #: ports/espressif/common-hal/analogbufio/BufferedIn.c
msgid "%q must be array of type 'H'" msgid "%q must be array of type 'H'"
@ -601,8 +601,7 @@ msgstr "Por debajo de la tasa mínima de refrescamiento"
#: ports/raspberrypi/common-hal/audiobusio/I2SOut.c #: ports/raspberrypi/common-hal/audiobusio/I2SOut.c
msgid "Bit clock and word select must be sequential pins" msgid "Bit clock and word select must be sequential pins"
msgstr "" msgstr "Los pines \"clock\" y \"word-select\" deben ser consecutivos"
"Los pines de reloj de bit y de selector de palabra deben ser secuenciales"
#: ports/atmel-samd/common-hal/audiobusio/I2SOut.c #: ports/atmel-samd/common-hal/audiobusio/I2SOut.c
msgid "Bit clock and word select must share a clock unit" msgid "Bit clock and word select must share a clock unit"

4
main.c
View File

@ -666,8 +666,10 @@ STATIC bool run_code_py(safe_mode_t safe_mode, bool *simulate_reset) {
#endif #endif
{ {
// Refresh the ePaper display if we have one. That way it'll show an error message. // Refresh the ePaper display if we have one. That way it'll show an error message.
// Skip if we're about to autoreload. Otherwise we may delay when user code can update
// the display.
#if CIRCUITPY_DISPLAYIO #if CIRCUITPY_DISPLAYIO
if (time_to_epaper_refresh > 0) { if (time_to_epaper_refresh > 0 && !autoreload_pending()) {
time_to_epaper_refresh = maybe_refresh_epaperdisplay(); time_to_epaper_refresh = maybe_refresh_epaperdisplay();
} }

View File

@ -12,5 +12,6 @@ LONGINT_IMPL = MPZ
CIRCUITPY__EVE = 1 CIRCUITPY__EVE = 1
CIRCUITPY_CANIO = 1 CIRCUITPY_CANIO = 1
CIRCUITPY_SYNTHIO = 0
CIRCUITPY_LTO_PARTITION = one CIRCUITPY_LTO_PARTITION = one

View File

@ -11,3 +11,4 @@ EXTERNAL_FLASH_DEVICES = GD25Q16C
LONGINT_IMPL = MPZ LONGINT_IMPL = MPZ
CIRCUITPY__EVE = 1 CIRCUITPY__EVE = 1
CIRCUITPY_SYNTHIO = 0

View File

@ -11,3 +11,4 @@ EXTERNAL_FLASH_DEVICES = "S25FL116K, S25FL216K, GD25Q16C"
LONGINT_IMPL = MPZ LONGINT_IMPL = MPZ
CIRCUITPY__EVE = 1 CIRCUITPY__EVE = 1
CIRCUITPY_SYNTHIO = 0

View File

@ -11,3 +11,4 @@ EXTERNAL_FLASH_DEVICES = "S25FL116K, S25FL216K, GD25Q16C"
LONGINT_IMPL = MPZ LONGINT_IMPL = MPZ
CIRCUITPY__EVE = 1 CIRCUITPY__EVE = 1
CIRCUITPY_SYNTHIO = 0

View File

@ -120,19 +120,25 @@ CFLAGS += -DSTACK_CANARY_VALUE=0xa5a5a5a5
#Debugging/Optimization #Debugging/Optimization
ifeq ($(DEBUG), 1) ifeq ($(DEBUG), 1)
CFLAGS += -DDEBUG -ggdb CFLAGS += -ggdb
OPTIMIZATION_FLAGS ?= -Og ifeq ($(IDF_TARGET_ARCH),riscv)
# You may want to enable these flags to make setting breakpoints easier. OPTIMIZATION_FLAGS ?= -Os
# CFLAGS += -fno-inline -fno-ipa-sra CFLAGS += -DNDEBUG
else
OPTIMIZATION_FLAGS ?= -Og
CFLAGS += -DDEBUG
endif
# You may want to enable these flags to make setting breakpoints easier.
# CFLAGS += -fno-inline -fno-ipa-sra
else else
CFLAGS += -DNDEBUG -ggdb3 CFLAGS += -DNDEBUG -ggdb3
# RISC-V is larger than xtensa # RISC-V is larger than xtensa
# Use -Os for RISC-V when it overflows # Use -Os for RISC-V when it overflows
ifeq ($(IDF_TARGET_ARCH),riscv) ifeq ($(IDF_TARGET_ARCH),riscv)
OPTIMIZATION_FLAGS ?= -Os OPTIMIZATION_FLAGS ?= -Os
else else
OPTIMIZATION_FLAGS ?= -O2 OPTIMIZATION_FLAGS ?= -O2
endif endif
endif endif
# option to override compiler optimization level, set in boards/$(BOARD)/mpconfigboard.mk # option to override compiler optimization level, set in boards/$(BOARD)/mpconfigboard.mk

View File

@ -56,8 +56,10 @@ void spi_reset(void) {
static void set_spi_config(busio_spi_obj_t *self, static void set_spi_config(busio_spi_obj_t *self,
uint32_t baudrate, uint8_t polarity, uint8_t phase, uint8_t bits) { uint32_t baudrate, uint8_t polarity, uint8_t phase, uint8_t bits) {
// 128 is a 50% duty cycle.
const int closest_clock = spi_get_actual_clock(APB_CLK_FREQ, baudrate, 128);
const spi_device_interface_config_t device_config = { const spi_device_interface_config_t device_config = {
.clock_speed_hz = baudrate, .clock_speed_hz = closest_clock,
.mode = phase | (polarity << 1), .mode = phase | (polarity << 1),
.spics_io_num = -1, // No CS pin .spics_io_num = -1, // No CS pin
.queue_size = MAX_SPI_TRANSACTIONS, .queue_size = MAX_SPI_TRANSACTIONS,
@ -67,7 +69,7 @@ static void set_spi_config(busio_spi_obj_t *self,
if (result != ESP_OK) { if (result != ESP_OK) {
mp_raise_RuntimeError(translate("SPI configuration failed")); mp_raise_RuntimeError(translate("SPI configuration failed"));
} }
self->baudrate = baudrate; self->baudrate = closest_clock;
self->polarity = polarity; self->polarity = polarity;
self->phase = phase; self->phase = phase;
self->bits = bits; self->bits = bits;

View File

@ -62,6 +62,11 @@ static const uint64_t pin_mask_reset_forbidden =
GPIO_SEL_18 | // USB D- GPIO_SEL_18 | // USB D-
GPIO_SEL_19 | // USB D+ GPIO_SEL_19 | // USB D+
#endif #endif
#if defined(CONFIG_ESP_CONSOLE_UART_DEFAULT) && CONFIG_ESP_CONSOLE_UART_DEFAULT && CONFIG_ESP_CONSOLE_UART_NUM == 0
// Never reset debug UART/console pins.
GPIO_SEL_20 |
GPIO_SEL_21 |
#endif
#endif // ESP32C3 #endif // ESP32C3
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)

View File

@ -304,5 +304,11 @@ STATIC const mp_rom_map_elem_t mcu_pin_global_dict_table[] = {
#ifdef GPIO46_EXISTS #ifdef GPIO46_EXISTS
{ MP_ROM_QSTR(MP_QSTR_GPIO46), MP_ROM_PTR(&pin_GPIO46) }, { MP_ROM_QSTR(MP_QSTR_GPIO46), MP_ROM_PTR(&pin_GPIO46) },
#endif #endif
#ifdef GPIO47_EXISTS
{ MP_ROM_QSTR(MP_QSTR_GPIO47), MP_ROM_PTR(&pin_GPIO47) },
#endif
#ifdef GPIO48_EXISTS
{ MP_ROM_QSTR(MP_QSTR_GPIO48), MP_ROM_PTR(&pin_GPIO48) },
#endif
}; };
MP_DEFINE_CONST_DICT(mcu_pin_globals, mcu_pin_global_dict_table); MP_DEFINE_CONST_DICT(mcu_pin_globals, mcu_pin_global_dict_table);

View File

@ -475,8 +475,16 @@ void common_hal_wifi_radio_stop_dhcp_client(wifi_radio_obj_t *self) {
esp_netif_dhcpc_stop(self->netif); esp_netif_dhcpc_stop(self->netif);
} }
void common_hal_wifi_radio_start_dhcp_server(wifi_radio_obj_t *self) {
esp_netif_dhcps_start(self->ap_netif);
}
void common_hal_wifi_radio_stop_dhcp_server(wifi_radio_obj_t *self) {
esp_netif_dhcps_stop(self->ap_netif);
}
void common_hal_wifi_radio_set_ipv4_address(wifi_radio_obj_t *self, mp_obj_t ipv4, mp_obj_t netmask, mp_obj_t gateway, mp_obj_t ipv4_dns) { void common_hal_wifi_radio_set_ipv4_address(wifi_radio_obj_t *self, mp_obj_t ipv4, mp_obj_t netmask, mp_obj_t gateway, mp_obj_t ipv4_dns) {
common_hal_wifi_radio_stop_dhcp_client(self); // Must stop DHCP to set a manual address common_hal_wifi_radio_stop_dhcp_client(self); // Must stop station DHCP to set a manual address
esp_netif_ip_info_t ip_info; esp_netif_ip_info_t ip_info;
ipaddress_ipaddress_to_esp_idf_ip4(ipv4, &ip_info.ip); ipaddress_ipaddress_to_esp_idf_ip4(ipv4, &ip_info.ip);
@ -490,6 +498,19 @@ void common_hal_wifi_radio_set_ipv4_address(wifi_radio_obj_t *self, mp_obj_t ipv
} }
} }
void common_hal_wifi_radio_set_ipv4_address_ap(wifi_radio_obj_t *self, mp_obj_t ipv4, mp_obj_t netmask, mp_obj_t gateway) {
common_hal_wifi_radio_stop_dhcp_server(self); // Must stop access point DHCP to set a manual address
esp_netif_ip_info_t ip_info;
ipaddress_ipaddress_to_esp_idf_ip4(ipv4, &ip_info.ip);
ipaddress_ipaddress_to_esp_idf_ip4(netmask, &ip_info.netmask);
ipaddress_ipaddress_to_esp_idf_ip4(gateway, &ip_info.gw);
esp_netif_set_ip_info(self->ap_netif, &ip_info);
common_hal_wifi_radio_start_dhcp_server(self); // restart access point DHCP
}
mp_int_t common_hal_wifi_radio_ping(wifi_radio_obj_t *self, mp_obj_t ip_address, mp_float_t timeout) { mp_int_t common_hal_wifi_radio_ping(wifi_radio_obj_t *self, mp_obj_t ip_address, mp_float_t timeout) {
esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG(); esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG();
ipaddress_ipaddress_to_esp_idf(ip_address, &ping_config.target_addr); ipaddress_ipaddress_to_esp_idf(ip_address, &ping_config.target_addr);

View File

@ -101,21 +101,28 @@ bool usb_serial_jtag_connected(void) {
} }
char usb_serial_jtag_read_char(void) { char usb_serial_jtag_read_char(void) {
if (ringbuf_num_filled(&ringbuf) == 0) { if (ringbuf_num_filled(&ringbuf) == 0 && !usb_serial_jtag_ll_rxfifo_data_available()) {
return -1; return -1;
} }
char c = ringbuf_get(&ringbuf); char c = -1;
if (ringbuf_num_filled(&ringbuf) > 0) {
c = ringbuf_get(&ringbuf);
}
// Maybe re-enable the recv interrupt if we've emptied the ringbuf. // Maybe re-enable the recv interrupt if we've emptied the ringbuf.
if (ringbuf_num_filled(&ringbuf) == 0) { if (ringbuf_num_filled(&ringbuf) == 0) {
usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT); usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT);
_copy_out_of_fifo(); _copy_out_of_fifo();
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT); usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT);
// May have only been ctrl-c.
if (c == -1 && ringbuf_num_filled(&ringbuf) > 0) {
c = ringbuf_get(&ringbuf);
}
} }
return c; return c;
} }
bool usb_serial_jtag_bytes_available(void) { bool usb_serial_jtag_bytes_available(void) {
return ringbuf_num_filled(&ringbuf) > 0; return ringbuf_num_filled(&ringbuf) > 0 || usb_serial_jtag_ll_rxfifo_data_available();
} }
void usb_serial_jtag_write(const char *text, uint32_t length) { void usb_serial_jtag_write(const char *text, uint32_t length) {

View File

@ -50,7 +50,7 @@ CFLAGS += -ftree-vrp -DNDEBUG
# TinyUSB defines # TinyUSB defines
CFLAGS += -DCFG_TUSB_MCU=OPT_MCU_MIMXRT10XX -DCFG_TUD_CDC_RX_BUFSIZE=512 -DCFG_TUD_CDC_TX_BUFSIZE=512 CFLAGS += -DCFG_TUSB_MCU=OPT_MCU_MIMXRT10XX -DCFG_TUD_CDC_RX_BUFSIZE=512 -DCFG_TUD_CDC_TX_BUFSIZE=512
ifeq ($(CHIP_FAMILY),$(filter $(CHIP_FAMILY),MIMXRT1011 MIMXRT1015)) ifeq ($(CHIP_FAMILY),$(filter $(CHIP_FAMILY),MIMXRT1011 MIMXRT1015))
CFLAGS += -DCFG_TUD_MIDI_RX_BUFSIZE=64 -DCFG_TUD_MIDI_TX_BUFSIZE=64 -DCFG_TUD_MSC_BUFSIZE=512 CFLAGS += -DCFG_TUD_MIDI_RX_BUFSIZE=512 -DCFG_TUD_MIDI_TX_BUFSIZE=64 -DCFG_TUD_MSC_BUFSIZE=512
else else
CFLAGS += -DCFG_TUD_MIDI_RX_BUFSIZE=512 -DCFG_TUD_MIDI_TX_BUFSIZE=512 -DCFG_TUD_MSC_BUFSIZE=1024 CFLAGS += -DCFG_TUD_MIDI_RX_BUFSIZE=512 -DCFG_TUD_MIDI_TX_BUFSIZE=512 -DCFG_TUD_MSC_BUFSIZE=1024
endif endif

View File

@ -41,9 +41,6 @@ const mcu_pin_obj_t *mimxrt10xx_reset_forbidden_pins[] = {
&pin_GPIO_SD_08, &pin_GPIO_SD_08,
&pin_GPIO_SD_07, &pin_GPIO_SD_07,
&pin_GPIO_SD_06, &pin_GPIO_SD_06,
// USB Pins
&pin_USB_OTG1_DN,
&pin_USB_OTG1_DP,
NULL, // Must end in NULL. NULL, // Must end in NULL.
}; };

View File

@ -40,9 +40,6 @@ const mcu_pin_obj_t *mimxrt10xx_reset_forbidden_pins[] = {
&pin_GPIO_SD_B1_09, &pin_GPIO_SD_B1_09,
&pin_GPIO_SD_B1_10, &pin_GPIO_SD_B1_10,
&pin_GPIO_SD_B1_11, &pin_GPIO_SD_B1_11,
// USB Pins
&pin_USB_OTG1_DN,
&pin_USB_OTG1_DP,
NULL, // Must end in NULL. NULL, // Must end in NULL.
}; };

View File

@ -42,10 +42,6 @@ const mcu_pin_obj_t *mimxrt10xx_reset_forbidden_pins[] = {
&pin_GPIO_SD_B1_09, &pin_GPIO_SD_B1_09,
&pin_GPIO_SD_B1_10, &pin_GPIO_SD_B1_10,
&pin_GPIO_SD_B1_11, &pin_GPIO_SD_B1_11,
// USB Pins
&pin_USB_OTG1_DN,
&pin_USB_OTG1_DP,
NULL, // Must end in NULL. NULL, // Must end in NULL.
}; };

View File

@ -44,9 +44,6 @@ const mcu_pin_obj_t *mimxrt10xx_reset_forbidden_pins[] = {
&pin_GPIO_SD_B1_10, &pin_GPIO_SD_B1_10,
&pin_GPIO_SD_B1_11, &pin_GPIO_SD_B1_11,
&pin_USB_OTG1_DN,
&pin_USB_OTG1_DP,
NULL, // Must end in NULL. NULL, // Must end in NULL.
}; };

View File

@ -45,8 +45,8 @@ const mcu_pin_obj_t *mimxrt10xx_reset_forbidden_pins[] = {
&pin_GPIO_SD_B1_11, &pin_GPIO_SD_B1_11,
// USB Pins // USB Pins
&pin_GPIO_AD_B0_01, &pin_GPIO_AD_B0_01, // ID Pin
&pin_GPIO_AD_B0_03, &pin_GPIO_AD_B0_03, // OC/Fault Pin
NULL, // Must end in NULL. NULL, // Must end in NULL.
}; };

View File

@ -50,8 +50,8 @@ const mcu_pin_obj_t *mimxrt10xx_reset_forbidden_pins[] = {
&pin_GPIO_SD_B1_11, &pin_GPIO_SD_B1_11,
// USB Pins // USB Pins
&pin_GPIO_AD_B0_01, &pin_GPIO_AD_B0_01, // ID Pin
&pin_GPIO_AD_B0_03, &pin_GPIO_AD_B0_03, // OC/Fault Pin
NULL, // Must end in NULL. NULL, // Must end in NULL.
}; };

View File

@ -31,8 +31,8 @@
#include "py/gc.h" #include "py/gc.h"
STATIC bool claimed_pins[IOMUXC_SW_PAD_CTL_PAD_COUNT]; STATIC bool claimed_pins[PAD_COUNT];
STATIC bool never_reset_pins[IOMUXC_SW_PAD_CTL_PAD_COUNT]; STATIC bool never_reset_pins[PAD_COUNT];
// Default is that no pins are forbidden to reset. // Default is that no pins are forbidden to reset.
MP_WEAK const mcu_pin_obj_t *mimxrt10xx_reset_forbidden_pins[] = { MP_WEAK const mcu_pin_obj_t *mimxrt10xx_reset_forbidden_pins[] = {
@ -55,10 +55,10 @@ STATIC bool _reset_forbidden(const mcu_pin_obj_t *pin) {
// and GPIO port and number, used to store claimed and reset tagging. The two number // and GPIO port and number, used to store claimed and reset tagging. The two number
// systems are not related and one cannot determine the other without a pin object // systems are not related and one cannot determine the other without a pin object
void reset_all_pins(void) { void reset_all_pins(void) {
for (uint8_t i = 0; i < IOMUXC_SW_PAD_CTL_PAD_COUNT; i++) { for (uint8_t i = 0; i < PAD_COUNT; i++) {
claimed_pins[i] = never_reset_pins[i]; claimed_pins[i] = never_reset_pins[i];
} }
for (uint8_t i = 0; i < IOMUXC_SW_PAD_CTL_PAD_COUNT; i++) { for (uint8_t i = 0; i < PAD_COUNT; i++) {
mcu_pin_obj_t *pin = mcu_pin_globals.map.table[i].value; mcu_pin_obj_t *pin = mcu_pin_globals.map.table[i].value;
if (never_reset_pins[pin->mux_idx]) { if (never_reset_pins[pin->mux_idx]) {
continue; continue;
@ -90,6 +90,11 @@ void common_hal_reset_pin(const mcu_pin_obj_t *pin) {
disable_pin_change_interrupt(pin); disable_pin_change_interrupt(pin);
never_reset_pins[pin->mux_idx] = false; never_reset_pins[pin->mux_idx] = false;
claimed_pins[pin->mux_idx] = false; claimed_pins[pin->mux_idx] = false;
// This should never be true, but protect against it anyway.
if (pin->mux_reg == 0) {
return;
}
*(uint32_t *)pin->mux_reg = pin->mux_reset; *(uint32_t *)pin->mux_reg = pin->mux_reset;
*(uint32_t *)pin->cfg_reg = pin->pad_reset; *(uint32_t *)pin->cfg_reg = pin->pad_reset;
} }

View File

@ -40,5 +40,7 @@
#include "pin_names.h" #include "pin_names.h"
#undef FORMAT_PIN #undef FORMAT_PIN
#define PIN_COUNT (IOMUXC_SW_PAD_CTL_PAD_COUNT + 2) // Pads can be reset. Other pins like USB cannot be.
#define PAD_COUNT (43)
#define PIN_COUNT (PAD_COUNT + 2)
extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT]; extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT];

View File

@ -40,5 +40,7 @@
#include "pin_names.h" #include "pin_names.h"
#undef FORMAT_PIN #undef FORMAT_PIN
#define PIN_COUNT (IOMUXC_SW_PAD_CTL_PAD_COUNT + 2) // Pads can be reset. Other pins like USB cannot be.
#define PAD_COUNT (56)
#define PIN_COUNT (PAD_COUNT + 2)
extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT]; extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT];

View File

@ -40,5 +40,7 @@
#include "pin_names.h" #include "pin_names.h"
#undef FORMAT_PIN #undef FORMAT_PIN
#define PIN_COUNT (IOMUXC_SW_PAD_CTL_PAD_COUNT + 2) // Pads can be reset. Other pins like USB cannot be.
#define PAD_COUNT (93)
#define PIN_COUNT (PAD_COUNT + 2)
extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT]; extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT];

View File

@ -40,5 +40,7 @@
#include "pin_names.h" #include "pin_names.h"
#undef FORMAT_PIN #undef FORMAT_PIN
#define PIN_COUNT (IOMUXC_SW_PAD_CTL_PAD_COUNT + 2) // Pads can be reset. Other pins like USB cannot be.
#define PAD_COUNT (112)
#define PIN_COUNT (PAD_COUNT + 2)
extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT]; extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT];

View File

@ -40,5 +40,7 @@
#include "pin_names.h" #include "pin_names.h"
#undef FORMAT_PIN #undef FORMAT_PIN
#define PIN_COUNT (IOMUXC_SW_PAD_CTL_PAD_COUNT + 4) // Pads can be reset. Other pins like USB cannot be.
#define PAD_COUNT (124)
#define PIN_COUNT (PAD_COUNT + 4)
extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT]; extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT];

View File

@ -40,5 +40,7 @@
#include "pin_names.h" #include "pin_names.h"
#undef FORMAT_PIN #undef FORMAT_PIN
#define PIN_COUNT (IOMUXC_SW_PAD_CTL_PAD_COUNT + 4) // Pads can be reset. Other pins like USB cannot be.
#define PAD_COUNT (124)
#define PIN_COUNT (PAD_COUNT + 4)
extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT]; extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT];

View File

@ -40,5 +40,7 @@
#include "pin_names.h" #include "pin_names.h"
#undef FORMAT_PIN #undef FORMAT_PIN
#define PIN_COUNT (IOMUXC_SW_PAD_CTL_PAD_COUNT + 0) // Pads can be reset. Other pins like USB cannot be.
#define PAD_COUNT (145)
#define PIN_COUNT (PAD_COUNT + 0)
extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT]; extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT];

View File

@ -190,6 +190,7 @@ for device in devices:
split_pin = name.split("_") split_pin = name.split("_")
pin_name = "_".join(split_pin[4:]) pin_name = "_".join(split_pin[4:])
if pin_name not in all_pins: if pin_name not in all_pins:
print("skip", pin_name)
continue continue
gpio_base = "_".join(split_pin[4:-1]) gpio_base = "_".join(split_pin[4:-1])
@ -278,7 +279,10 @@ for device in devices:
pins_c.append("") pins_c.append("")
pins_h.append("") pins_h.append("")
pins_h.append(f"#define PIN_COUNT (IOMUXC_SW_PAD_CTL_PAD_COUNT + {len(usb_pins)})")
pins_h.append("// Pads can be reset. Other pins like USB cannot be.")
pins_h.append(f"#define PAD_COUNT ({pin_number})")
pins_h.append(f"#define PIN_COUNT (PAD_COUNT + {len(usb_pins)})")
pins_h.append(f"extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT];") pins_h.append(f"extern const mcu_pin_obj_t mcu_pin_list[PIN_COUNT];")
pins_h.append("") pins_h.append("")

View File

@ -0,0 +1,136 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2020 microDev
*
* 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 <string.h>
#include "nrf.h"
#include "shared-bindings/memorymap/AddressRange.h"
#include "py/runtime.h"
#ifdef NRF51_SERIES
size_t allow_ranges[][2] = {
// FLASH
{0x00000000, 0x00040000},
// FICR & UICR ranges
{0x10000000, 0x10002000},
// RAM
{0x20000000, 0x20010000},
// PERIPHERALS
{0x40000000, 0x60000000}
};
#elif defined NRF52_SERIES
size_t allow_ranges[][2] = {
// FLASH
{0x00000000, 0x00100000},
// FICR & UICR ranges
{0x10000000, 0x10002000},
// RAM
{0x20000000, 0x20040000},
// PERIPHERALS
{0x40000000, 0x60000000}
};
#elif defined NRF53_SERIES
size_t allow_ranges[][2] = {
// FLASH
{0x00000000, 0x00100000},
// FICR & UICR ranges
{0x00FF0000, 0x01000000},
// RAM
{0x20000000, 0x20080000},
// PERIPHERALS
{0x40000000, 0x60000000},
{0xE0000000, 0xE0100000}
};
#else
#error "Unsupported nRF variant"
#endif
void common_hal_memorymap_addressrange_construct(memorymap_addressrange_obj_t *self, uint8_t *start_address, size_t length) {
bool allowed = false;
for (size_t i = 0; i < MP_ARRAY_SIZE(allow_ranges); i++) {
uint8_t *allowed_start = (uint8_t *)allow_ranges[i][0];
uint8_t *allowed_end = (uint8_t *)allow_ranges[i][1];
if (allowed_start <= start_address &&
(start_address + length) <= allowed_end) {
allowed = true;
break;
}
}
if (!allowed) {
mp_raise_ValueError(translate("Address range not allowed"));
}
self->start_address = start_address;
self->len = length;
}
uint32_t common_hal_memorymap_addressrange_get_length(const memorymap_addressrange_obj_t *self) {
return self->len;
}
bool common_hal_memorymap_addressrange_set_bytes(const memorymap_addressrange_obj_t *self,
uint32_t start_index, uint8_t *values, uint32_t len) {
uint8_t *address = self->start_address + start_index;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-align"
if (len == 1) {
*((uint8_t *)address) = values[0];
} else if (len == sizeof(uint16_t) && (((size_t)address) % sizeof(uint16_t)) == 0) {
*((uint16_t *)address) = ((uint16_t *)values)[0];
} else if (len == sizeof(uint32_t) && (((size_t)address) % sizeof(uint32_t)) == 0) {
*((uint32_t *)address) = ((uint32_t *)values)[0];
} else if (len == sizeof(uint64_t) && (((size_t)address) % sizeof(uint64_t)) == 0) {
*((uint64_t *)address) = ((uint64_t *)values)[0];
} else {
memcpy(address, values, len);
}
#pragma GCC diagnostic pop
return true;
}
void common_hal_memorymap_addressrange_get_bytes(const memorymap_addressrange_obj_t *self,
uint32_t start_index, uint32_t len, uint8_t *values) {
uint8_t *address = self->start_address + start_index;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-align"
if (len == 1) {
values[0] = *((uint8_t *)address);
} else if (len == sizeof(uint16_t) && (((size_t)address) % sizeof(uint16_t)) == 0) {
((uint16_t *)values)[0] = *((uint16_t *)address);
} else if (len == sizeof(uint32_t) && (((size_t)address) % sizeof(uint32_t)) == 0) {
((uint32_t *)values)[0] = *((uint32_t *)address);
} else if (len == sizeof(uint64_t) && (((size_t)address) % sizeof(uint64_t)) == 0) {
((uint64_t *)values)[0] = *((uint64_t *)address);
} else {
memcpy(values, address, len);
}
#pragma GCC diagnostic pop
}

View File

@ -0,0 +1,38 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2020 microDev
*
* 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_NRF_COMMON_HAL_MEMORYMAP_ADDRESSRANGE_H
#define MICROPY_INCLUDED_NRF_COMMON_HAL_MEMORYMAP_ADDRESSRANGE_H
#include "py/obj.h"
typedef struct {
mp_obj_base_t base;
uint8_t *start_address;
size_t len;
} memorymap_addressrange_obj_t;
#endif // MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL_MEMORYMAP_ADDRESSRANGE_H

View File

@ -0,0 +1 @@
// No memorymap module functions.

View File

@ -47,6 +47,7 @@ CIRCUITPY_SERIAL_BLE ?= 1
CIRCUITPY_COMPUTED_GOTO_SAVE_SPACE ?= 1 CIRCUITPY_COMPUTED_GOTO_SAVE_SPACE ?= 1
# nRF52840-specific # nRF52840-specific
ifeq ($(MCU_CHIP),nrf52840) ifeq ($(MCU_CHIP),nrf52840)
@ -56,6 +57,7 @@ MCU_SUB_VARIANT = nrf52840
# Fits on nrf52840 but space is tight on nrf52833. # Fits on nrf52840 but space is tight on nrf52833.
CIRCUITPY_AESIO ?= 1 CIRCUITPY_AESIO ?= 1
CIRCUITPY_MEMORYMAP ?= 1
CIRCUITPY_RGBMATRIX ?= 1 CIRCUITPY_RGBMATRIX ?= 1
CIRCUITPY_FRAMEBUFFERIO ?= 1 CIRCUITPY_FRAMEBUFFERIO ?= 1

View File

@ -26,8 +26,11 @@ STATIC const mp_rom_map_elem_t board_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_D10), MP_ROM_PTR(&pin_GPIO10) }, { MP_ROM_QSTR(MP_QSTR_D10), MP_ROM_PTR(&pin_GPIO10) },
{ MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_GPIO11) }, { MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_GPIO11) },
{ MP_ROM_QSTR(MP_QSTR_D12), MP_ROM_PTR(&pin_GPIO12) }, { MP_ROM_QSTR(MP_QSTR_D12), MP_ROM_PTR(&pin_GPIO12) },
{ MP_ROM_QSTR(MP_QSTR_D13), MP_ROM_PTR(&pin_GPIO13) },
{ MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_GPIO13) }, { MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_GPIO13) },
{ MP_ROM_QSTR(MP_QSTR_D13), MP_ROM_PTR(&pin_GPIO13) },
{ MP_ROM_QSTR(MP_QSTR_BUTTON), MP_ROM_PTR(&pin_GPIO4) },
{ MP_ROM_QSTR(MP_QSTR_BOOT), MP_ROM_PTR(&pin_GPIO4) },
{ MP_ROM_QSTR(MP_QSTR_NEOPIXEL), MP_ROM_PTR(&pin_GPIO16) }, { MP_ROM_QSTR(MP_QSTR_NEOPIXEL), MP_ROM_PTR(&pin_GPIO16) },

View File

@ -1,5 +1,8 @@
#include "shared-bindings/board/__init__.h" #include "shared-bindings/board/__init__.h"
#include "supervisor/board.h"
#include "shared-module/displayio/__init__.h"
STATIC const mp_rom_map_elem_t board_module_globals_table[] = { STATIC const mp_rom_map_elem_t board_module_globals_table[] = {
CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS
@ -81,5 +84,7 @@ STATIC const mp_rom_map_elem_t board_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_WHITE_LED), MP_ROM_PTR(&pin_GPIO28) }, { MP_ROM_QSTR(MP_QSTR_WHITE_LED), MP_ROM_PTR(&pin_GPIO28) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY), MP_ROM_PTR(&displays[0].display)},
}; };
MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table); MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table);

View File

@ -0,0 +1,151 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Scott Shawcroft 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 "supervisor/board.h"
#include "mpconfigboard.h"
#include "shared-bindings/microcontroller/Pin.h"
#include "shared-module/displayio/__init__.h"
#include "shared-module/displayio/mipi_constants.h"
#define DELAY 0x80
#define LCD_POWER 22
// display init sequence according to LilyGO example app
uint8_t display_init_sequence[] = {
// sw reset
0x01, 0 | DELAY, 150,
// sleep out
0x11, 0 | DELAY, 255,
// normal display mode on
0x13, 0,
// display and color format settings
0x36, 1, 0x08,
0xB6, 2, 0x0A, 0x82,
0x3A, 1 | DELAY, 0x55, 10,
// ST7789V frame rate setting
0xB2, 5, 0x0C, 0x0C, 0x00, 0x33, 0x33,
// voltages: VGH / VGL
0xB7, 1, 0x35,
// ST7789V power setting
0xBB, 1, 0x28,
0xC0, 1, 0x0C,
0xC2, 2, 0x01, 0xFF,
0xC3, 1, 0x10,
0xC4, 1, 0x20,
0xC6, 1, 0x0F,
0xD0, 2, 0xA4, 0xA1,
// ST7789V gamma setting
0xE0, 14, 0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x32, 0x44, 0x42, 0x06, 0x0E, 0x12, 0x14, 0x17,
0xE1, 14, 0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x31, 0x54, 0x47, 0x0E, 0x1C, 0x17, 0x1B, 0x1E,
0x21, 0,
// display on
0x29, 0 | DELAY, 255,
};
static void display_init(void) {
displayio_fourwire_obj_t *bus = &allocate_display_bus()->fourwire_bus;
busio_spi_obj_t *spi = &bus->inline_bus;
common_hal_busio_spi_construct(
spi,
&pin_GPIO2, // CLK
&pin_GPIO3, // MOSI
NULL, // MISO not connected
false); // Not half-duplex
common_hal_busio_spi_never_reset(spi);
bus->base.type = &displayio_fourwire_type;
common_hal_displayio_fourwire_construct(
bus,
spi,
&pin_GPIO1, // DC
&pin_GPIO5, // CS
NULL, // RST (Reset pin tie to 0, do not set here)
40000000, // baudrate
1, // polarity
0 // phase
);
displayio_display_obj_t *display = &allocate_display()->display;
display->base.type = &displayio_display_type;
common_hal_displayio_display_construct(
display,
bus,
240, // width (after rotation)
135, // height (after rotation)
52, // column start
40, // row start
90, // rotation
16, // color depth
false, // grayscale
false, // pixels in a byte share a row. Only valid for depths < 8
1, // bytes per cell. Only valid for depths < 8
false, // reverse_pixels_in_byte. Only valid for depths < 8
true, // reverse_pixels_in_word
MIPI_COMMAND_SET_COLUMN_ADDRESS, // set column command
MIPI_COMMAND_SET_PAGE_ADDRESS, // set row command
MIPI_COMMAND_WRITE_MEMORY_START, // write memory command
display_init_sequence,
sizeof(display_init_sequence),
&pin_GPIO4, // backlight pin
NO_BRIGHTNESS_COMMAND,
1.0f, // brightness (ignored)
false, // auto_brightness
false, // single_byte_bounds
false, // data_as_commands
true, // auto_refresh
60, // native_frames_per_second
true, // backlight_on_high
false // SH1107_addressing
);
common_hal_never_reset_pin(&pin_GPIO4); // backlight pin
}
void board_init(void) {
// Pin 22 has to be pulled high to turn on the LCD
const uint PWR_PIN = LCD_POWER;
gpio_init(PWR_PIN);
gpio_set_dir(PWR_PIN, GPIO_OUT);
gpio_put(PWR_PIN, 1);
common_hal_never_reset_pin(&pin_GPIO22);
// Display
display_init();
}
bool board_requests_safe_mode(void) {
return false;
}
void reset_board(void) {
}
void board_deinit(void) {
}

View File

@ -0,0 +1,2 @@
#define MICROPY_HW_BOARD_NAME "LILYGO T-DISPLAY"
#define MICROPY_HW_MCU_NAME "rp2040"

View File

@ -0,0 +1,11 @@
USB_VID = 0x1209
USB_PID = 0x2023
USB_PRODUCT = "T-Display"
USB_MANUFACTURER = "Lilygo"
CHIP_VARIANT = RP2040
CHIP_FAMILY = rp2
EXTERNAL_FLASH_DEVICES = "W25Q32JVxQ"
CIRCUITPY__EVE = 1

View File

@ -0,0 +1 @@
// Put board-specific pico-sdk definitions here. This file must exist.

View File

@ -0,0 +1,65 @@
#include "shared-bindings/board/__init__.h"
#include "shared-module/displayio/__init__.h"
STATIC const mp_rom_map_elem_t board_module_globals_table[] = {
CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS
{ MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO1) },
{ MP_ROM_QSTR(MP_QSTR_GP1), MP_ROM_PTR(&pin_GPIO1) },
{ MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO2) },
{ MP_ROM_QSTR(MP_QSTR_GP2), MP_ROM_PTR(&pin_GPIO2) },
{ MP_ROM_QSTR(MP_QSTR_BUTTON_L), MP_ROM_PTR(&pin_GPIO6) },
{ MP_ROM_QSTR(MP_QSTR_GP6), MP_ROM_PTR(&pin_GPIO6) },
{ MP_ROM_QSTR(MP_QSTR_BUTTON_R), MP_ROM_PTR(&pin_GPIO6) },
{ MP_ROM_QSTR(MP_QSTR_GP7), MP_ROM_PTR(&pin_GPIO7) },
{ MP_ROM_QSTR(MP_QSTR_GP8), MP_ROM_PTR(&pin_GPIO8) },
{ MP_ROM_QSTR(MP_QSTR_GP9), MP_ROM_PTR(&pin_GPIO9) },
{ MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO10) },
{ MP_ROM_QSTR(MP_QSTR_GP10), MP_ROM_PTR(&pin_GPIO10) },
{ MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO11) },
{ MP_ROM_QSTR(MP_QSTR_GP11), MP_ROM_PTR(&pin_GPIO11) },
{ MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO12) },
{ MP_ROM_QSTR(MP_QSTR_GP12), MP_ROM_PTR(&pin_GPIO12) },
{ MP_ROM_QSTR(MP_QSTR_GP13), MP_ROM_PTR(&pin_GPIO13) },
{ MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO14) },
{ MP_ROM_QSTR(MP_QSTR_GP14), MP_ROM_PTR(&pin_GPIO14) },
{ MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO15) },
{ MP_ROM_QSTR(MP_QSTR_GP15), MP_ROM_PTR(&pin_GPIO15) },
{ MP_ROM_QSTR(MP_QSTR_GP16), MP_ROM_PTR(&pin_GPIO16) },
{ MP_ROM_QSTR(MP_QSTR_GP18), MP_ROM_PTR(&pin_GPIO18) },
{ MP_ROM_QSTR(MP_QSTR_GP21), MP_ROM_PTR(&pin_GPIO21) },
{ MP_ROM_QSTR(MP_QSTR_TFT_POWER), MP_ROM_PTR(&pin_GPIO22) },
{ MP_ROM_QSTR(MP_QSTR_GP22), MP_ROM_PTR(&pin_GPIO22) },
{ MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_GPIO25) },
{ MP_ROM_QSTR(MP_QSTR_GP25), MP_ROM_PTR(&pin_GPIO25) },
// 1.14 inch LCD ST7789
{ MP_ROM_QSTR(MP_QSTR_LCD_MOSI), MP_ROM_PTR(&pin_GPIO3) },
{ MP_ROM_QSTR(MP_QSTR_LCD_CLK), MP_ROM_PTR(&pin_GPIO2) },
{ MP_ROM_QSTR(MP_QSTR_LCD_CS), MP_ROM_PTR(&pin_GPIO5) },
{ MP_ROM_QSTR(MP_QSTR_LCD_RESET), MP_ROM_PTR(&pin_GPIO0) },
{ MP_ROM_QSTR(MP_QSTR_LCD_BACKLIGHT), MP_ROM_PTR(&pin_GPIO4) },
{ MP_ROM_QSTR(MP_QSTR_LCD_DC), MP_ROM_PTR(&pin_GPIO1) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY), MP_ROM_PTR(&displays[0].display) },
{ MP_ROM_QSTR(MP_QSTR_VOLTAGE_MONITOR), MP_ROM_PTR(&pin_GPIO29) },
{ MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) },
{ MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&board_spi_obj) },
};
MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table);

View File

@ -0,0 +1,114 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Scott Shawcroft 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 "supervisor/board.h"
#include "mpconfigboard.h"
#include "shared-bindings/busio/SPI.h"
#include "shared-bindings/displayio/FourWire.h"
#include "shared-bindings/microcontroller/Pin.h"
#include "shared-module/displayio/__init__.h"
#include "shared-bindings/board/__init__.h"
#include "supervisor/shared/board.h"
#define DELAY 0x80
// This is an SPD1656 control chip. The display is a 5.7" ACeP EInk.
const uint8_t display_start_sequence[] = {
0x01, 4, 0x37, 0x00, 0x23, 0x23, // power setting
0x00, 2, 0xef, 0x08, // panel setting (PSR)
0x03, 1, 0x00, // PFS
0x06, 3, 0xc7, 0xc7, 0x1d, // booster
0x30, 1, 0x3c, // PLL setting
0x41, 1, 0x00, // TSE
0x50, 1, 0x37, // vcom and data interval setting
0x60, 1, 0x22, // tcon setting
0x61, 4, 0x02, 0x58, 0x01, 0xc0, // tres
0xe3, 1, 0xaa, // PWS
0x04, DELAY | 0, 0xc8, // VCM DC and delay 200ms
};
const uint8_t display_stop_sequence[] = {
0x02, 1, 0x00, // power off
0x07, 1, 0xa5 // deep sleep
};
const uint8_t refresh_sequence[] = {
0x12, 0x00
};
void board_init(void) {
displayio_fourwire_obj_t *bus = &allocate_display_bus()->fourwire_bus;
busio_spi_obj_t *spi = common_hal_board_create_spi(0);
bus->base.type = &displayio_fourwire_type;
common_hal_displayio_fourwire_construct(bus,
spi,
&pin_GPIO28, // EPD_DC Command or data
&pin_GPIO17, // EPD_CS Chip select
&pin_GPIO27, // EPD_RST Reset
1000000, // Baudrate
0, // Polarity
0); // Phase
displayio_epaperdisplay_obj_t *display = &allocate_display()->epaper_display;
display->base.type = &displayio_epaperdisplay_type;
common_hal_displayio_epaperdisplay_construct(
display,
bus,
display_start_sequence, sizeof(display_start_sequence),
1.0, // start up time
display_stop_sequence, sizeof(display_stop_sequence),
600, // width
448, // height
640, // ram_width
480, // ram_height
0, // colstart
0, // rowstart
180, // rotation
NO_COMMAND, // set_column_window_command
NO_COMMAND, // set_row_window_command
NO_COMMAND, // set_current_column_command
NO_COMMAND, // set_current_row_command
0x10, // write_black_ram_command
false, // black_bits_inverted
NO_COMMAND, // write_color_ram_command
false, // color_bits_inverted
0x000000, // highlight_color
refresh_sequence, sizeof(refresh_sequence),
28.0, // refresh_time
NULL, // busy_pin
false, // busy_state
30.0, // seconds_per_frame
false, // always_toggle_chip_select
false, // grayscale
true, // acep
false, // two_byte_sequence_length
false); // address_little_endian
}
// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here.

View File

@ -0,0 +1 @@
firmware_size = 1532k;

View File

@ -0,0 +1,17 @@
#define MICROPY_HW_BOARD_NAME "Pimoroni Inky Frame 5.7"
#define MICROPY_HW_MCU_NAME "rp2040"
#define CIRCUITPY_DIGITALIO_HAVE_INVALID_PULL (1)
#define CIRCUITPY_DIGITALIO_HAVE_INVALID_DRIVE_MODE (1)
#define MICROPY_HW_LED_STATUS (&pin_GPIO6)
#define DEFAULT_I2C_BUS_SCL (&pin_GPIO4)
#define DEFAULT_I2C_BUS_SDA (&pin_GPIO5)
#define DEFAULT_UART_BUS_TX (&pin_GPIO0)
#define DEFAULT_UART_BUS_RX (&pin_GPIO1)
#define DEFAULT_SPI_BUS_SCK (&pin_GPIO18)
#define DEFAULT_SPI_BUS_MOSI (&pin_GPIO19)
#define DEFAULT_SPI_BUS_MISO (&pin_GPIO16)

View File

@ -0,0 +1,26 @@
USB_VID = 0x2E8A
USB_PID = 0x1018
USB_PRODUCT = "Inky Frame 5.7"
USB_MANUFACTURER = "Pimoroni"
CHIP_VARIANT = RP2040
CHIP_FAMILY = rp2
EXTERNAL_FLASH_DEVICES = "W25Q16JVxQ"
CIRCUITPY__EVE = 1
CIRCUITPY_CYW43 = 1
CIRCUITPY_SSL = 1
CIRCUITPY_SSL_MBEDTLS = 1
CIRCUITPY_HASHLIB = 1
CIRCUITPY_WEB_WORKFLOW = 1
CIRCUITPY_MDNS = 1
CIRCUITPY_SOCKETPOOL = 1
CIRCUITPY_WIFI = 1
CFLAGS += -DCYW43_PIN_WL_HOST_WAKE=24 -DCYW43_PIN_WL_REG_ON=23 -DCYW43_WL_GPIO_COUNT=3 -DCYW43_WL_GPIO_LED_PIN=0
# Must be accompanied by a linker script change
CFLAGS += -DCIRCUITPY_FIRMWARE_SIZE='(1536 * 1024)'
FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel

View File

@ -0,0 +1 @@
// Put board-specific pico-sdk definitions here. This file must exist.

View File

@ -0,0 +1,52 @@
#include "shared-bindings/board/__init__.h"
#include "supervisor/board.h"
#include "shared-module/displayio/__init__.h"
STATIC const mp_rom_map_elem_t board_module_globals_table[] = {
CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS
{ MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO0) },
{ MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO1) },
{ MP_ROM_QSTR(MP_QSTR_HOLD_SYS_EN), MP_ROM_PTR(&pin_GPIO2) },
{ MP_ROM_QSTR(MP_QSTR_I2C_INT), MP_ROM_PTR(&pin_GPIO3) },
{ MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO4) },
{ MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO5) },
{ MP_ROM_QSTR(MP_QSTR_LED_ACT), MP_ROM_PTR(&pin_GPIO6) },
{ MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_GPIO6) },
{ MP_ROM_QSTR(MP_QSTR_LED_CONN), MP_ROM_PTR(&pin_GPIO7) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_CLK), MP_ROM_PTR(&pin_GPIO8) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_LATCH), MP_ROM_PTR(&pin_GPIO9) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_OUT), MP_ROM_PTR(&pin_GPIO10) },
{ MP_ROM_QSTR(MP_QSTR_LED_A), MP_ROM_PTR(&pin_GPIO11) },
{ MP_ROM_QSTR(MP_QSTR_LED_B), MP_ROM_PTR(&pin_GPIO12) },
{ MP_ROM_QSTR(MP_QSTR_LED_C), MP_ROM_PTR(&pin_GPIO13) },
{ MP_ROM_QSTR(MP_QSTR_LED_D), MP_ROM_PTR(&pin_GPIO14) },
{ MP_ROM_QSTR(MP_QSTR_LED_E), MP_ROM_PTR(&pin_GPIO15) },
{ MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO16) },
{ MP_ROM_QSTR(MP_QSTR_INKY_CS), MP_ROM_PTR(&pin_GPIO17) },
{ MP_ROM_QSTR(MP_QSTR_SCLK), MP_ROM_PTR(&pin_GPIO18) },
{ MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO19) },
{ MP_ROM_QSTR(MP_QSTR_SD_DAT1), MP_ROM_PTR(&pin_GPIO20) },
{ MP_ROM_QSTR(MP_QSTR_SD_DAT2), MP_ROM_PTR(&pin_GPIO21) },
{ MP_ROM_QSTR(MP_QSTR_SD_CS), MP_ROM_PTR(&pin_GPIO22) },
{ MP_ROM_QSTR(MP_QSTR_A0), MP_ROM_PTR(&pin_GPIO26) },
{ MP_ROM_QSTR(MP_QSTR_INKY_RES), MP_ROM_PTR(&pin_GPIO27) },
{ MP_ROM_QSTR(MP_QSTR_INKY_DC), MP_ROM_PTR(&pin_GPIO28) },
{ MP_ROM_QSTR(MP_QSTR_SMPS_MODE), MP_ROM_PTR(&pin_CYW1) },
{ MP_ROM_QSTR(MP_QSTR_VBUS_SENSE), MP_ROM_PTR(&pin_CYW2) },
{ MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) },
{ MP_ROM_QSTR(MP_QSTR_STEMMA_I2C), MP_ROM_PTR(&board_i2c_obj) },
{ MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&board_spi_obj) },
{ MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&board_uart_obj) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY), MP_ROM_PTR(&displays[0].epaper_display)},
};
MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table);

View File

@ -48,6 +48,8 @@
#include "lwip/raw.h" #include "lwip/raw.h"
#include "lwip_src/ping.h" #include "lwip_src/ping.h"
#include "shared/netutils/dhcpserver.h"
#define MAC_ADDRESS_LENGTH 6 #define MAC_ADDRESS_LENGTH 6
#define NETIF_STA (&cyw43_state.netif[CYW43_ITF_STA]) #define NETIF_STA (&cyw43_state.netif[CYW43_ITF_STA])
@ -161,7 +163,15 @@ void common_hal_wifi_radio_stop_station(wifi_radio_obj_t *self) {
// (by tcpip_link_status). However since ap disconnection isn't working // (by tcpip_link_status). However since ap disconnection isn't working
// either, this is not an issue. // either, this is not an issue.
cyw43_wifi_leave(&cyw43_state, CYW43_ITF_AP); cyw43_wifi_leave(&cyw43_state, CYW43_ITF_AP);
const size_t timeout_ms = 500;
uint64_t start = port_get_raw_ticks(NULL);
uint64_t deadline = start + timeout_ms;
while (port_get_raw_ticks(NULL) < deadline && (cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_STA) != CYW43_LINK_DOWN)) {
RUN_BACKGROUND_TASKS;
if (mp_hal_is_interrupted()) {
break;
}
}
bindings_cyw43_wifi_enforce_pm(); bindings_cyw43_wifi_enforce_pm();
} }
@ -358,6 +368,17 @@ void common_hal_wifi_radio_stop_dhcp_client(wifi_radio_obj_t *self) {
dhcp_stop(NETIF_STA); dhcp_stop(NETIF_STA);
} }
void common_hal_wifi_radio_start_dhcp_server(wifi_radio_obj_t *self) {
ip4_addr_t ipv4_addr, netmask_addr;
ipaddress_ipaddress_to_lwip(common_hal_wifi_radio_get_ipv4_address_ap(self), &ipv4_addr);
ipaddress_ipaddress_to_lwip(common_hal_wifi_radio_get_ipv4_subnet_ap(self), &netmask_addr);
dhcp_server_init(&cyw43_state.dhcp_server, &ipv4_addr, &netmask_addr);
}
void common_hal_wifi_radio_stop_dhcp_server(wifi_radio_obj_t *self) {
dhcp_server_deinit(&cyw43_state.dhcp_server);
}
void common_hal_wifi_radio_set_ipv4_address(wifi_radio_obj_t *self, mp_obj_t ipv4, mp_obj_t netmask, mp_obj_t gateway, mp_obj_t ipv4_dns) { void common_hal_wifi_radio_set_ipv4_address(wifi_radio_obj_t *self, mp_obj_t ipv4, mp_obj_t netmask, mp_obj_t gateway, mp_obj_t ipv4_dns) {
common_hal_wifi_radio_stop_dhcp_client(self); common_hal_wifi_radio_stop_dhcp_client(self);
@ -371,6 +392,18 @@ void common_hal_wifi_radio_set_ipv4_address(wifi_radio_obj_t *self, mp_obj_t ipv
} }
} }
void common_hal_wifi_radio_set_ipv4_address_ap(wifi_radio_obj_t *self, mp_obj_t ipv4, mp_obj_t netmask, mp_obj_t gateway) {
common_hal_wifi_radio_stop_dhcp_server(self);
ip4_addr_t ipv4_addr, netmask_addr, gateway_addr;
ipaddress_ipaddress_to_lwip(ipv4, &ipv4_addr);
ipaddress_ipaddress_to_lwip(netmask, &netmask_addr);
ipaddress_ipaddress_to_lwip(gateway, &gateway_addr);
netif_set_addr(NETIF_AP, &ipv4_addr, &netmask_addr, &gateway_addr);
common_hal_wifi_radio_start_dhcp_server(self);
}
volatile bool ping_received; volatile bool ping_received;
uint32_t ping_time; uint32_t ping_time;

View File

@ -95,14 +95,16 @@ void port_internal_flash_flush(void) {
if (_cache_lba == NO_CACHE) { if (_cache_lba == NO_CACHE) {
return; return;
} }
// Make sure we don't have an interrupt while we do flash operations.
common_hal_mcu_disable_interrupts(); common_hal_mcu_disable_interrupts();
flash_range_erase(CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + _cache_lba, SECTOR_SIZE); flash_range_erase(CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + _cache_lba, SECTOR_SIZE);
flash_range_program(CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + _cache_lba, _cache, SECTOR_SIZE); flash_range_program(CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + _cache_lba, _cache, SECTOR_SIZE);
common_hal_mcu_enable_interrupts();
_cache_lba = NO_CACHE; _cache_lba = NO_CACHE;
common_hal_mcu_enable_interrupts();
} }
mp_uint_t supervisor_flash_read_blocks(uint8_t *dest, uint32_t block, uint32_t num_blocks) { mp_uint_t supervisor_flash_read_blocks(uint8_t *dest, uint32_t block, uint32_t num_blocks) {
port_internal_flash_flush(); // we never read out of the cache, so we have to write it if dirty
memcpy(dest, memcpy(dest,
(void *)(XIP_BASE + CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + block * FILESYSTEM_BLOCK_SIZE), (void *)(XIP_BASE + CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + block * FILESYSTEM_BLOCK_SIZE),
num_blocks * FILESYSTEM_BLOCK_SIZE); num_blocks * FILESYSTEM_BLOCK_SIZE);
@ -118,6 +120,7 @@ mp_uint_t supervisor_flash_write_blocks(const uint8_t *src, uint32_t lba, uint32
uint8_t block_offset = block_address % blocks_per_sector; uint8_t block_offset = block_address % blocks_per_sector;
if (_cache_lba != block_address) { if (_cache_lba != block_address) {
port_internal_flash_flush();
memcpy(_cache, memcpy(_cache,
(void *)(XIP_BASE + CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + sector_offset), (void *)(XIP_BASE + CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + sector_offset),
SECTOR_SIZE); SECTOR_SIZE);
@ -133,11 +136,6 @@ mp_uint_t supervisor_flash_write_blocks(const uint8_t *src, uint32_t lba, uint32
FILESYSTEM_BLOCK_SIZE); FILESYSTEM_BLOCK_SIZE);
block++; block++;
} }
// Make sure we don't have an interrupt while we do flash operations.
common_hal_mcu_disable_interrupts();
flash_range_erase(CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + sector_offset, SECTOR_SIZE);
flash_range_program(CIRCUITPY_CIRCUITPY_DRIVE_START_ADDR + sector_offset, _cache, SECTOR_SIZE);
common_hal_mcu_enable_interrupts();
} }
return 0; // success return 0; // success

View File

@ -104,22 +104,23 @@ safe_mode_t port_init(void) {
_binary_info(); _binary_info();
// Set brown out. // Set brown out.
// Load from the XIP memory space that doesn't cache. That way we don't
// evict anything else. The code we're loading is linked to the RAM address
// anyway.
size_t nocache = 0x03000000;
// Copy all of the "tightly coupled memory" code and data to run from RAM. // Copy all of the "tightly coupled memory" code and data to run from RAM.
// This lets us use the 16k cache for dynamically used data and code. // This lets us use the 16k cache for dynamically used data and code.
// We must do this before we try and call any of its code or load the data. // We must do this before we try and call any of its code or load the data.
uint32_t *itcm_flash_copy = (uint32_t *)(((size_t)&_ld_itcm_flash_copy) | nocache);
for (uint32_t i = 0; i < ((size_t)&_ld_itcm_size) / 4; i++) { for (uint32_t i = 0; i < ((size_t)&_ld_itcm_size) / 4; i++) {
(&_ld_itcm_destination)[i] = (&_ld_itcm_flash_copy)[i]; (&_ld_itcm_destination)[i] = itcm_flash_copy[i];
// Now zero it out to evict the line from the XIP cache. Without this,
// it'll stay in the XIP cache anyway.
(&_ld_itcm_flash_copy)[i] = 0x0;
} }
// Copy all of the data to run from DTCM. // Copy all of the data to run from DTCM.
uint32_t *dtcm_flash_copy = (uint32_t *)(((size_t)&_ld_dtcm_data_flash_copy) | nocache);
for (uint32_t i = 0; i < ((size_t)&_ld_dtcm_data_size) / 4; i++) { for (uint32_t i = 0; i < ((size_t)&_ld_dtcm_data_size) / 4; i++) {
(&_ld_dtcm_data_destination)[i] = (&_ld_dtcm_data_flash_copy)[i]; (&_ld_dtcm_data_destination)[i] = dtcm_flash_copy[i];
// Now zero it out to evict the line from the XIP cache. Without this,
// it'll stay in the XIP cache anyway.
(&_ld_dtcm_data_flash_copy)[i] = 0x0;
} }
// Clear DTCM bss. // Clear DTCM bss.

View File

@ -188,6 +188,10 @@ void reset_port(void) {
#if CIRCUITPY_BLEIO #if CIRCUITPY_BLEIO
bleio_reset(); bleio_reset();
#endif #endif
#if CIRCUITPY_RTC
rtc_reset();
#endif
} }
void reset_to_bootloader(void) { void reset_to_bootloader(void) {

View File

@ -32,7 +32,9 @@
#include "shared/timeutils/timeutils.h" #include "shared/timeutils/timeutils.h"
// Default period for ticks is 1/1024 second // Default period for ticks is 1/1024 second
#define TICK_DIVISOR 1024 #define TICKS_PER_SECOND 1024
// Based on a 32768 kHz clock
#define SUBTICKS_PER_TICK 32
STATIC RTC_HandleTypeDef hrtc; STATIC RTC_HandleTypeDef hrtc;
@ -47,6 +49,13 @@ volatile uint32_t cached_date = 0;
volatile uint32_t seconds_to_minute = 0; volatile uint32_t seconds_to_minute = 0;
volatile uint32_t cached_hours_minutes = 0; volatile uint32_t cached_hours_minutes = 0;
// The RTC starts at 2000-01-01 when it comes up.
// If the RTC is set to a later time, the ticks the RTC returns will be offset by the new time.
// Remember that offset so it can be removed when returning a monotonic tick count.
static int64_t rtc_ticks_offset;
// Normalized to be 0-31 inclusive, so always positive.
static uint8_t rtc_subticks_offset;
volatile bool alarmed_already[2]; volatile bool alarmed_already[2];
bool peripherals_wkup_on = false; bool peripherals_wkup_on = false;
@ -59,6 +68,9 @@ uint32_t stm32_peripherals_get_rtc_freq(void) {
} }
void stm32_peripherals_rtc_init(void) { void stm32_peripherals_rtc_init(void) {
rtc_ticks_offset = 0;
rtc_subticks_offset = 0;
// RTC oscillator selection is handled in peripherals/<family>/<line>/clocks.c // RTC oscillator selection is handled in peripherals/<family>/<line>/clocks.c
__HAL_RCC_RTC_ENABLE(); __HAL_RCC_RTC_ENABLE();
hrtc.Instance = RTC; hrtc.Instance = RTC;
@ -74,49 +86,9 @@ void stm32_peripherals_rtc_init(void) {
HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn); HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
} }
#if CIRCUITPY_RTC
void stm32_peripherals_rtc_get_time(timeutils_struct_time_t *tm) {
RTC_DateTypeDef date = {0};
RTC_TimeTypeDef time = {0};
int code;
if ((code = HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN)) == HAL_OK &&
(code = HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN)) == HAL_OK) {
tm->tm_hour = time.Hours;
tm->tm_min = time.Minutes;
tm->tm_sec = time.Seconds;
tm->tm_wday = date.WeekDay - 1;
tm->tm_mday = date.Date;
tm->tm_mon = date.Month;
tm->tm_year = date.Year + 2000;
tm->tm_yday = -1;
}
}
void stm32_peripherals_rtc_set_time(timeutils_struct_time_t *tm) {
RTC_DateTypeDef date = {0};
RTC_TimeTypeDef time = {0};
time.Hours = tm->tm_hour;
time.Minutes = tm->tm_min;
time.Seconds = tm->tm_sec;
date.WeekDay = tm->tm_wday + 1;
date.Date = tm->tm_mday;
date.Month = tm->tm_mon;
date.Year = tm->tm_year - 2000;
time.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
time.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(&hrtc, &time, RTC_FORMAT_BIN) != HAL_OK ||
HAL_RTC_SetDate(&hrtc, &date, RTC_FORMAT_BIN) != HAL_OK) {
// todo - throw an exception
}
}
#endif
// This function is called often for timing so we cache the seconds elapsed computation based on the // This function is called often for timing so we cache the seconds elapsed computation based on the
// register value. The STM HAL always does shifts and conversion if we use it directly. // register value. The STM HAL always does shifts and conversion if we use it directly.
uint64_t stm32_peripherals_rtc_raw_ticks(uint8_t *subticks) { STATIC uint64_t stm32_peripherals_rtc_raw_ticks(uint8_t *subticks) {
// Disable IRQs to ensure we read all of the RTC registers as close in time as possible. Read // Disable IRQs to ensure we read all of the RTC registers as close in time as possible. Read
// SSR twice to make sure we didn't read across a tick. // SSR twice to make sure we didn't read across a tick.
__disable_irq(); __disable_irq();
@ -157,13 +129,84 @@ uint64_t stm32_peripherals_rtc_raw_ticks(uint8_t *subticks) {
uint8_t seconds = (uint8_t)(time & (RTC_TR_ST | RTC_TR_SU)); uint8_t seconds = (uint8_t)(time & (RTC_TR_ST | RTC_TR_SU));
seconds = (uint8_t)RTC_Bcd2ToByte(seconds); seconds = (uint8_t)RTC_Bcd2ToByte(seconds);
if (subticks != NULL) { if (subticks != NULL) {
*subticks = subseconds % 32; *subticks = subseconds % SUBTICKS_PER_TICK;
} }
uint64_t raw_ticks = ((uint64_t)TICK_DIVISOR) * (seconds_to_date + seconds_to_minute + seconds) + subseconds / 32; uint64_t raw_ticks = ((uint64_t)TICKS_PER_SECOND) * (seconds_to_date + seconds_to_minute + seconds) + subseconds / SUBTICKS_PER_TICK;
return raw_ticks; return raw_ticks;
} }
// This function returns monotonically increasing ticks by adjusting away the RTC tick offset
// from the last time the date was set.
uint64_t stm32_peripherals_rtc_monotonic_ticks(uint8_t *subticks) {
uint8_t raw_subticks;
uint64_t monotonic_ticks = stm32_peripherals_rtc_raw_ticks(&raw_subticks) - rtc_ticks_offset;
int8_t monotonic_subticks = raw_subticks - rtc_subticks_offset;
// Difference might be negative. Normalize to 0-31.
// `while` not really necessary; should only loop 0 or 1 times.
while (monotonic_subticks < 0) {
monotonic_ticks--;
monotonic_subticks += SUBTICKS_PER_TICK;
}
*subticks = (uint8_t)monotonic_subticks;
return monotonic_ticks;
}
#if CIRCUITPY_RTC
void stm32_peripherals_rtc_get_time(timeutils_struct_time_t *tm) {
RTC_DateTypeDef date = {0};
RTC_TimeTypeDef time = {0};
int code;
if ((code = HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN)) == HAL_OK &&
(code = HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN)) == HAL_OK) {
tm->tm_hour = time.Hours;
tm->tm_min = time.Minutes;
tm->tm_sec = time.Seconds;
tm->tm_wday = date.WeekDay - 1;
tm->tm_mday = date.Date;
tm->tm_mon = date.Month;
tm->tm_year = date.Year + 2000;
tm->tm_yday = -1;
}
}
void stm32_peripherals_rtc_set_time(timeutils_struct_time_t *tm) {
RTC_DateTypeDef date = {0};
RTC_TimeTypeDef time = {0};
uint8_t current_monotonic_subticks;
uint64_t current_monotonic_ticks = stm32_peripherals_rtc_monotonic_ticks(&current_monotonic_subticks);
// SubSeconds will always be set to zero.
time.Hours = tm->tm_hour;
time.Minutes = tm->tm_min;
time.Seconds = tm->tm_sec;
date.WeekDay = tm->tm_wday + 1;
date.Date = tm->tm_mday;
date.Month = tm->tm_mon;
date.Year = tm->tm_year - 2000;
time.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
time.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(&hrtc, &time, RTC_FORMAT_BIN) != HAL_OK ||
HAL_RTC_SetDate(&hrtc, &date, RTC_FORMAT_BIN) != HAL_OK) {
// todo - throw an exception
}
uint8_t raw_subticks;
rtc_ticks_offset = stm32_peripherals_rtc_raw_ticks(&raw_subticks) - current_monotonic_ticks;
int8_t rtc_subticks_offset_signed = raw_subticks - current_monotonic_subticks;
// Difference might be negative. Normalize subticks to 0-31.
// `while` not really necessary; should only loop 0 or 1 times.
while (rtc_subticks_offset_signed < 0) {
rtc_ticks_offset--;
rtc_subticks_offset_signed += SUBTICKS_PER_TICK;
}
rtc_subticks_offset = (uint8_t)rtc_subticks_offset_signed;
}
#endif
void stm32_peripherals_rtc_assign_wkup_callback(void (*callback)(void)) { void stm32_peripherals_rtc_assign_wkup_callback(void (*callback)(void)) {
wkup_callback = callback; wkup_callback = callback;
} }
@ -177,7 +220,7 @@ void stm32_peripherals_rtc_set_wakeup_mode_seconds(uint32_t seconds) {
} }
void stm32_peripherals_rtc_set_wakeup_mode_tick(void) { void stm32_peripherals_rtc_set_wakeup_mode_tick(void) {
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, (rtc_clock_frequency / 16) / TICK_DIVISOR, RTC_WAKEUPCLOCK_RTCCLK_DIV2); HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, (rtc_clock_frequency / 16) / TICKS_PER_SECOND, RTC_WAKEUPCLOCK_RTCCLK_DIV2);
} }
void stm32_peripherals_rtc_enable_wakeup_timer(void) { void stm32_peripherals_rtc_enable_wakeup_timer(void) {
@ -205,9 +248,9 @@ void stm32_peripherals_rtc_set_alarm(uint8_t alarm_idx, uint32_t ticks) {
uint64_t raw_ticks = stm32_peripherals_rtc_raw_ticks(NULL) + ticks; uint64_t raw_ticks = stm32_peripherals_rtc_raw_ticks(NULL) + ticks;
RTC_AlarmTypeDef alarm; RTC_AlarmTypeDef alarm;
if (ticks > TICK_DIVISOR) { if (ticks > TICKS_PER_SECOND) {
timeutils_struct_time_t tm; timeutils_struct_time_t tm;
timeutils_seconds_since_2000_to_struct_time(raw_ticks / TICK_DIVISOR, &tm); timeutils_seconds_since_2000_to_struct_time(raw_ticks / TICKS_PER_SECOND, &tm);
alarm.AlarmTime.Hours = tm.tm_hour; alarm.AlarmTime.Hours = tm.tm_hour;
alarm.AlarmTime.Minutes = tm.tm_min; alarm.AlarmTime.Minutes = tm.tm_min;
alarm.AlarmTime.Seconds = tm.tm_sec; alarm.AlarmTime.Seconds = tm.tm_sec;
@ -221,7 +264,7 @@ void stm32_peripherals_rtc_set_alarm(uint8_t alarm_idx, uint32_t ticks) {
} }
alarm.AlarmTime.SubSeconds = rtc_clock_frequency - 1 - alarm.AlarmTime.SubSeconds = rtc_clock_frequency - 1 -
((raw_ticks % TICK_DIVISOR) * 32); ((raw_ticks % TICKS_PER_SECOND) * SUBTICKS_PER_TICK);
if (alarm.AlarmTime.SubSeconds > rtc_clock_frequency) { if (alarm.AlarmTime.SubSeconds > rtc_clock_frequency) {
alarm.AlarmTime.SubSeconds = alarm.AlarmTime.SubSeconds + alarm.AlarmTime.SubSeconds = alarm.AlarmTime.SubSeconds +
rtc_clock_frequency; rtc_clock_frequency;

View File

@ -36,7 +36,7 @@
uint32_t stm32_peripherals_get_rtc_freq(void); uint32_t stm32_peripherals_get_rtc_freq(void);
void stm32_peripherals_rtc_init(void); void stm32_peripherals_rtc_init(void);
uint64_t stm32_peripherals_rtc_raw_ticks(uint8_t *subticks); uint64_t stm32_peripherals_rtc_monotonic_ticks(uint8_t *subticks);
void stm32_peripherals_rtc_assign_wkup_callback(void (*callback)(void)); void stm32_peripherals_rtc_assign_wkup_callback(void (*callback)(void));
void stm32_peripherals_rtc_set_wakeup_mode_seconds(uint32_t seconds); void stm32_peripherals_rtc_set_wakeup_mode_seconds(uint32_t seconds);

View File

@ -385,7 +385,7 @@ __attribute__((used)) void HardFault_Handler(void) {
} }
uint64_t port_get_raw_ticks(uint8_t *subticks) { uint64_t port_get_raw_ticks(uint8_t *subticks) {
return stm32_peripherals_rtc_raw_ticks(subticks); return stm32_peripherals_rtc_monotonic_ticks(subticks);
} }
// Enable 1/1024 second tick. // Enable 1/1024 second tick.

View File

@ -42,6 +42,7 @@ SRC_BITMAP := \
shared-bindings/struct/__init__.c \ shared-bindings/struct/__init__.c \
shared-bindings/synthio/__init__.c \ shared-bindings/synthio/__init__.c \
shared-bindings/synthio/MidiTrack.c \ shared-bindings/synthio/MidiTrack.c \
shared-bindings/synthio/Note.c \
shared-bindings/synthio/Synthesizer.c \ shared-bindings/synthio/Synthesizer.c \
shared-bindings/traceback/__init__.c \ shared-bindings/traceback/__init__.c \
shared-bindings/util.c \ shared-bindings/util.c \
@ -64,6 +65,7 @@ SRC_BITMAP := \
shared-module/struct/__init__.c \ shared-module/struct/__init__.c \
shared-module/synthio/__init__.c \ shared-module/synthio/__init__.c \
shared-module/synthio/MidiTrack.c \ shared-module/synthio/MidiTrack.c \
shared-module/synthio/Note.c \
shared-module/synthio/Synthesizer.c \ shared-module/synthio/Synthesizer.c \
shared-module/traceback/__init__.c \ shared-module/traceback/__init__.c \
shared-module/zlib/__init__.c \ shared-module/zlib/__init__.c \

View File

@ -194,11 +194,16 @@ mp_float_t mp_arg_validate_type_float(mp_obj_t obj, qstr arg_name) {
return a_float; return a_float;
} }
void mp_arg_validate_obj_float_range(mp_obj_t float_in, mp_int_t min, mp_int_t max, qstr arg_name) { mp_float_t mp_arg_validate_obj_float_range(mp_obj_t float_in, mp_int_t min, mp_int_t max, qstr arg_name) {
const mp_float_t f = mp_arg_validate_type_float(float_in, arg_name); const mp_float_t f = mp_arg_validate_type_float(float_in, arg_name);
return mp_arg_validate_float_range(f, min, max, arg_name);
}
mp_float_t mp_arg_validate_float_range(mp_float_t f, mp_int_t min, mp_int_t max, qstr arg_name) {
if (f < (mp_float_t)min || f > (mp_float_t)max) { if (f < (mp_float_t)min || f > (mp_float_t)max) {
mp_raise_ValueError_varg(translate("%q must be %d-%d"), arg_name, min, max); mp_raise_ValueError_varg(translate("%q must be %d-%d"), arg_name, min, max);
} }
return f;
} }
mp_float_t mp_arg_validate_obj_float_non_negative(mp_obj_t float_in, mp_float_t default_for_null, qstr arg_name) { mp_float_t mp_arg_validate_obj_float_non_negative(mp_obj_t float_in, mp_float_t default_for_null, qstr arg_name) {

View File

@ -651,6 +651,7 @@ SRC_SHARED_MODULE_ALL = \
supervisor/__init__.c \ supervisor/__init__.c \
supervisor/StatusBar.c \ supervisor/StatusBar.c \
synthio/MidiTrack.c \ synthio/MidiTrack.c \
synthio/Note.c \
synthio/Synthesizer.c \ synthio/Synthesizer.c \
synthio/__init__.c \ synthio/__init__.c \
terminalio/Terminal.c \ terminalio/Terminal.c \

View File

@ -1013,6 +1013,7 @@ bool gc_never_free(void *ptr) {
// Pointers are stored in a linked list where each block is BYTES_PER_BLOCK long and the first // Pointers are stored in a linked list where each block is BYTES_PER_BLOCK long and the first
// pointer is the next block of pointers. // pointer is the next block of pointers.
void **current_reference_block = MP_STATE_MEM(permanent_pointers); void **current_reference_block = MP_STATE_MEM(permanent_pointers);
void **last_reference_block = NULL;
while (current_reference_block != NULL) { while (current_reference_block != NULL) {
for (size_t i = 1; i < BYTES_PER_BLOCK / sizeof(void *); i++) { for (size_t i = 1; i < BYTES_PER_BLOCK / sizeof(void *); i++) {
if (current_reference_block[i] == NULL) { if (current_reference_block[i] == NULL) {
@ -1020,6 +1021,7 @@ bool gc_never_free(void *ptr) {
return true; return true;
} }
} }
last_reference_block = current_reference_block; // keep a record of last "proper" reference block
current_reference_block = current_reference_block[0]; current_reference_block = current_reference_block[0];
} }
void **next_block = gc_alloc(BYTES_PER_BLOCK, false, true); void **next_block = gc_alloc(BYTES_PER_BLOCK, false, true);
@ -1029,7 +1031,7 @@ bool gc_never_free(void *ptr) {
if (MP_STATE_MEM(permanent_pointers) == NULL) { if (MP_STATE_MEM(permanent_pointers) == NULL) {
MP_STATE_MEM(permanent_pointers) = next_block; MP_STATE_MEM(permanent_pointers) = next_block;
} else { } else {
current_reference_block[0] = next_block; last_reference_block[0] = next_block;
} }
next_block[1] = ptr; next_block[1] = ptr;
return true; return true;

View File

@ -103,7 +103,8 @@ mp_int_t mp_arg_validate_int_max(mp_int_t i, mp_int_t j, qstr arg_name);
mp_int_t mp_arg_validate_int_range(mp_int_t i, mp_int_t min, mp_int_t max, qstr arg_name); mp_int_t mp_arg_validate_int_range(mp_int_t i, mp_int_t min, mp_int_t max, qstr arg_name);
#if MICROPY_PY_BUILTINS_FLOAT #if MICROPY_PY_BUILTINS_FLOAT
mp_float_t mp_arg_validate_obj_float_non_negative(mp_obj_t float_in, mp_float_t default_for_null, qstr arg_name); mp_float_t mp_arg_validate_obj_float_non_negative(mp_obj_t float_in, mp_float_t default_for_null, qstr arg_name);
void mp_arg_validate_obj_float_range(mp_obj_t float_in, mp_int_t min, mp_int_t max, qstr arg_name); mp_float_t mp_arg_validate_obj_float_range(mp_obj_t float_in, mp_int_t min, mp_int_t max, qstr arg_name);
mp_float_t mp_arg_validate_float_range(mp_float_t float_in, mp_int_t min, mp_int_t max, qstr arg_name);
mp_float_t mp_arg_validate_type_float(mp_obj_t obj, qstr arg_name); mp_float_t mp_arg_validate_type_float(mp_obj_t obj, qstr arg_name);
#endif #endif
mp_uint_t mp_arg_validate_length_min(mp_uint_t length, mp_uint_t min, qstr arg_name); mp_uint_t mp_arg_validate_length_min(mp_uint_t length, mp_uint_t min, qstr arg_name);

View File

@ -3,6 +3,7 @@ mypy
black black
isort isort
twine twine
build
wheel wheel
astroid astroid
setuptools>=45 setuptools>=45

View File

@ -44,7 +44,7 @@
//| def __init__( //| def __init__(
//| self, //| self,
//| spi: busio.SPI, //| spi: busio.SPI,
//| chip_select: digitalio.DigitalInOut, //| chip_select: Optional[digitalio.DigitalInOut] = None,
//| *, //| *,
//| baudrate: int = 100000, //| baudrate: int = 100000,
//| polarity: int = 0, //| polarity: int = 0,
@ -55,7 +55,7 @@
//| Represents a single SPI device and manages locking the bus and the device address. //| Represents a single SPI device and manages locking the bus and the device address.
//| //|
//| :param ~busio.SPI spi: The SPI bus the device is on //| :param ~busio.SPI spi: The SPI bus the device is on
//| :param ~digitalio.DigitalInOut chip_select: The chip select pin object that implements the DigitalInOut API. //| :param ~digitalio.DigitalInOut chip_select: The chip select pin object that implements the DigitalInOut API. ``None`` if a chip select pin is not being used.
//| :param bool cs_active_value: Set to true if your device requires CS to be active high. Defaults to false. //| :param bool cs_active_value: Set to true if your device requires CS to be active high. Defaults to false.
//| :param int extra_clocks: The minimum number of clock cycles to cycle the bus after CS is high. (Used for SD cards.) //| :param int extra_clocks: The minimum number of clock cycles to cycle the bus after CS is high. (Used for SD cards.)
//| //|
@ -84,7 +84,7 @@ STATIC mp_obj_t adafruit_bus_device_spidevice_make_new(const mp_obj_type_t *type
enum { ARG_spi, ARG_chip_select, ARG_cs_active_value, ARG_baudrate, ARG_polarity, ARG_phase, ARG_extra_clocks }; enum { ARG_spi, ARG_chip_select, ARG_cs_active_value, ARG_baudrate, ARG_polarity, ARG_phase, ARG_extra_clocks };
static const mp_arg_t allowed_args[] = { static const mp_arg_t allowed_args[] = {
{ MP_QSTR_spi, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_spi, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_chip_select, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_chip_select, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_cs_active_value, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, { MP_QSTR_cs_active_value, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
{ MP_QSTR_baudrate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 100000} }, { MP_QSTR_baudrate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 100000} },
{ MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
@ -96,12 +96,12 @@ STATIC mp_obj_t adafruit_bus_device_spidevice_make_new(const mp_obj_type_t *type
busio_spi_obj_t *spi = args[ARG_spi].u_obj; busio_spi_obj_t *spi = args[ARG_spi].u_obj;
mp_arg_validate_type(args[ARG_chip_select].u_obj, &digitalio_digitalinout_type, MP_QSTR_chip_select); mp_arg_validate_type_or_none(args[ARG_chip_select].u_obj, &digitalio_digitalinout_type, MP_QSTR_chip_select);
common_hal_adafruit_bus_device_spidevice_construct(MP_OBJ_TO_PTR(self), spi, args[ARG_chip_select].u_obj, args[ARG_cs_active_value].u_bool, args[ARG_baudrate].u_int, args[ARG_polarity].u_int, common_hal_adafruit_bus_device_spidevice_construct(MP_OBJ_TO_PTR(self), spi, args[ARG_chip_select].u_obj, args[ARG_cs_active_value].u_bool, args[ARG_baudrate].u_int, args[ARG_polarity].u_int,
args[ARG_phase].u_int, args[ARG_extra_clocks].u_int); args[ARG_phase].u_int, args[ARG_extra_clocks].u_int);
if (args[ARG_chip_select].u_obj != MP_OBJ_NULL) { if (args[ARG_chip_select].u_obj != mp_const_none) {
digitalinout_result_t result = common_hal_digitalio_digitalinout_switch_to_output(MP_OBJ_TO_PTR(args[ARG_chip_select].u_obj), digitalinout_result_t result = common_hal_digitalio_digitalinout_switch_to_output(MP_OBJ_TO_PTR(args[ARG_chip_select].u_obj),
true, DRIVE_MODE_PUSH_PULL); true, DRIVE_MODE_PUSH_PULL);
#if CIRCUITPY_DIGITALIO_HAVE_INPUT_ONLY #if CIRCUITPY_DIGITALIO_HAVE_INPUT_ONLY

View File

@ -249,6 +249,15 @@ STATIC mp_obj_t displayio_group_obj_remove(mp_obj_t self_in, mp_obj_t layer) {
MP_DEFINE_CONST_FUN_OBJ_2(displayio_group_remove_obj, displayio_group_obj_remove); MP_DEFINE_CONST_FUN_OBJ_2(displayio_group_remove_obj, displayio_group_obj_remove);
//| def __bool__(self) -> bool: ... //| def __bool__(self) -> bool: ...
//| def __contains__(
//| self,
//| item: Union[vectorio.Circle, vectorio.Rectangle, vectorio.Polygon, Group, TileGrid],
//| ) -> bool: ...
//| def __iter__(
//| self,
//| ) -> Iterator[
//| Union[vectorio.Circle, vectorio.Rectangle, vectorio.Polygon, Group, TileGrid]
//| ]: ...
//| def __len__(self) -> int: //| def __len__(self) -> int:
//| """Returns the number of layers in a Group""" //| """Returns the number of layers in a Group"""
//| ... //| ...

View File

@ -88,9 +88,6 @@ STATIC mp_obj_t synthio_miditrack_make_new(const mp_obj_type_t *type, size_t n_a
mp_buffer_info_t bufinfo; mp_buffer_info_t bufinfo;
mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ); mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ);
mp_buffer_info_t bufinfo_waveform;
synthio_synth_parse_waveform(&bufinfo_waveform, args[ARG_waveform].u_obj);
synthio_miditrack_obj_t *self = m_new_obj(synthio_miditrack_obj_t); synthio_miditrack_obj_t *self = m_new_obj(synthio_miditrack_obj_t);
self->base.type = &synthio_miditrack_type; self->base.type = &synthio_miditrack_type;
@ -98,8 +95,8 @@ STATIC mp_obj_t synthio_miditrack_make_new(const mp_obj_type_t *type, size_t n_a
(uint8_t *)bufinfo.buf, bufinfo.len, (uint8_t *)bufinfo.buf, bufinfo.len,
args[ARG_tempo].u_int, args[ARG_tempo].u_int,
args[ARG_sample_rate].u_int, args[ARG_sample_rate].u_int,
bufinfo_waveform.buf, args[ARG_waveform].u_obj,
bufinfo_waveform.len / 2, mp_const_none,
args[ARG_envelope].u_obj args[ARG_envelope].u_obj
); );

View File

@ -27,12 +27,11 @@
#pragma once #pragma once
#include "shared-module/synthio/MidiTrack.h" #include "shared-module/synthio/MidiTrack.h"
#include "py/obj.h"
extern const mp_obj_type_t synthio_miditrack_type; extern const mp_obj_type_t synthio_miditrack_type;
void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, const uint8_t *buffer, uint32_t len, uint32_t tempo, uint32_t sample_rate, mp_obj_t waveform_obj, mp_obj_t filter_obj, mp_obj_t envelope_obj);
const uint8_t *buffer, uint32_t len, uint32_t tempo, uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_len,
mp_obj_t envelope);
void common_hal_synthio_miditrack_deinit(synthio_miditrack_obj_t *self); void common_hal_synthio_miditrack_deinit(synthio_miditrack_obj_t *self);
bool common_hal_synthio_miditrack_deinited(synthio_miditrack_obj_t *self); bool common_hal_synthio_miditrack_deinited(synthio_miditrack_obj_t *self);

View File

@ -0,0 +1,359 @@
/*
* This file is part of the Micro Python project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Artyom Skrobov
* Copyright (c) 2023 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 <stdint.h>
#include "py/objproperty.h"
#include "py/runtime.h"
#include "shared-bindings/util.h"
#include "shared-bindings/synthio/__init__.h"
#include "shared-bindings/synthio/Note.h"
#include "shared-module/synthio/Note.h"
static const mp_arg_t note_properties[] = {
{ MP_QSTR_frequency, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL } },
{ MP_QSTR_panning, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0) } },
{ MP_QSTR_tremolo_rate, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
{ MP_QSTR_tremolo_depth, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
{ MP_QSTR_bend_rate, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
{ MP_QSTR_bend_depth, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
{ MP_QSTR_bend_mode, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = (mp_obj_t)MP_ROM_PTR(&bend_mode_VIBRATO_obj) } },
{ MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
{ MP_QSTR_envelope, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
{ MP_QSTR_filter, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } },
{ MP_QSTR_ring_frequency, MP_ARG_OBJ, {.u_obj = NULL } },
{ MP_QSTR_ring_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
};
//| class Note:
//| def __init__(
//| self,
//| *,
//| frequency: float,
//| panning: float = 0.0,
//| waveform: Optional[ReadableBuffer] = None,
//| envelope: Optional[Envelope] = None,
//| tremolo_depth: float = 0.0,
//| tremolo_rate: float = 0.0,
//| bend_depth: float = 0.0,
//| bend_rate: float = 0.0,
//| bend_mode: "BendMode" = BendMode.VIBRATO,
//| ) -> None:
//| """Construct a Note object, with a frequency in Hz, and optional panning, waveform, envelope, tremolo (volume change) and bend (frequency change).
//|
//| If waveform or envelope are `None` the synthesizer object's default waveform or envelope are used.
//|
//| If the same Note object is played on multiple Synthesizer objects, the result is undefined.
//| """
STATIC mp_obj_t synthio_note_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
mp_arg_val_t args[MP_ARRAY_SIZE(note_properties)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(note_properties), note_properties, args);
synthio_note_obj_t *self = m_new_obj(synthio_note_obj_t);
self->base.type = &synthio_note_type;
mp_obj_t result = MP_OBJ_FROM_PTR(self);
for (size_t i = 0; i < MP_ARRAY_SIZE(note_properties); i++) {
if (args[i].u_obj != NULL) {
mp_store_attr(result, note_properties[i].qst, args[i].u_obj);
}
}
return result;
};
//| frequency: float
//| """The base frequency of the note, in Hz."""
STATIC mp_obj_t synthio_note_get_frequency(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_note_get_frequency(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_frequency_obj, synthio_note_get_frequency);
STATIC mp_obj_t synthio_note_set_frequency(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_frequency(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_frequency_obj, synthio_note_set_frequency);
MP_PROPERTY_GETSET(synthio_note_frequency_obj,
(mp_obj_t)&synthio_note_get_frequency_obj,
(mp_obj_t)&synthio_note_set_frequency_obj);
//| filter: bool
//| """True if the note should be processed via the synthesizer's FIR filter."""
STATIC mp_obj_t synthio_note_get_filter(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_bool(common_hal_synthio_note_get_filter(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_filter_obj, synthio_note_get_filter);
STATIC mp_obj_t synthio_note_set_filter(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_filter(self, mp_obj_is_true(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_filter_obj, synthio_note_set_filter);
MP_PROPERTY_GETSET(synthio_note_filter_obj,
(mp_obj_t)&synthio_note_get_filter_obj,
(mp_obj_t)&synthio_note_set_filter_obj);
//| panning: float
//| """Defines the channel(s) in which the note appears.
//|
//| -1 is left channel only, 0 is both channels, and 1 is right channel.
//| For fractional values, the note plays at full amplitude in one channel
//| and partial amplitude in the other channel. For instance -.5 plays at full
//| amplitude in the left channel and 1/2 amplitude in the right channel."""
STATIC mp_obj_t synthio_note_get_panning(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_note_get_panning(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_panning_obj, synthio_note_get_panning);
STATIC mp_obj_t synthio_note_set_panning(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_panning(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_panning_obj, synthio_note_set_panning);
MP_PROPERTY_GETSET(synthio_note_panning_obj,
(mp_obj_t)&synthio_note_get_panning_obj,
(mp_obj_t)&synthio_note_set_panning_obj);
//| tremolo_depth: float
//| """The tremolo depth of the note, from 0 to 1
//|
//| A depth of 0 disables tremolo. A nonzero value enables tremolo,
//| with the maximum decrease in amplitude being equal to the tremolo
//| depth. A note with a tremolo depth of 1 will fade out to nothing, while
//| a tremolo depth of 0.1 will give a minimum amplitude of 0.9."""
STATIC mp_obj_t synthio_note_get_tremolo_depth(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_note_get_tremolo_depth(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_tremolo_depth_obj, synthio_note_get_tremolo_depth);
STATIC mp_obj_t synthio_note_set_tremolo_depth(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_tremolo_depth(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_tremolo_depth_obj, synthio_note_set_tremolo_depth);
MP_PROPERTY_GETSET(synthio_note_tremolo_depth_obj,
(mp_obj_t)&synthio_note_get_tremolo_depth_obj,
(mp_obj_t)&synthio_note_set_tremolo_depth_obj);
//| tremolo_rate: float
//| """The tremolo rate of the note, in Hz."""
STATIC mp_obj_t synthio_note_get_tremolo_rate(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_note_get_tremolo_rate(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_tremolo_rate_obj, synthio_note_get_tremolo_rate);
STATIC mp_obj_t synthio_note_set_tremolo_rate(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_tremolo_rate(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_tremolo_rate_obj, synthio_note_set_tremolo_rate);
MP_PROPERTY_GETSET(synthio_note_tremolo_rate_obj,
(mp_obj_t)&synthio_note_get_tremolo_rate_obj,
(mp_obj_t)&synthio_note_set_tremolo_rate_obj);
//|
//| bend_mode: BendMode
//| """The type of bend operation"""
STATIC mp_obj_t synthio_note_get_bend_mode(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return cp_enum_find(&synthio_bend_mode_type, common_hal_synthio_note_get_bend_mode(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_bend_mode_obj, synthio_note_get_bend_mode);
STATIC mp_obj_t synthio_note_set_bend_mode(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_bend_mode(self, cp_enum_value(&synthio_bend_mode_type, arg, MP_QSTR_bend_mode));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_bend_mode_obj, synthio_note_set_bend_mode);
MP_PROPERTY_GETSET(synthio_note_bend_mode_obj,
(mp_obj_t)&synthio_note_get_bend_mode_obj,
(mp_obj_t)&synthio_note_set_bend_mode_obj);
//
//|
//| bend_depth: float
//| """The bend depth of the note, from -1 to +1
//|
//| A depth of 0 disables bend. A depth of 1 corresponds to a bend of 1
//| octave. A depth of (1/12) = 0.833 corresponds to a bend of 1 semitone,
//| and a depth of .00833 corresponds to one musical cent.
//| """
STATIC mp_obj_t synthio_note_get_bend_depth(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_note_get_bend_depth(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_bend_depth_obj, synthio_note_get_bend_depth);
STATIC mp_obj_t synthio_note_set_bend_depth(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_bend_depth(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_bend_depth_obj, synthio_note_set_bend_depth);
MP_PROPERTY_GETSET(synthio_note_bend_depth_obj,
(mp_obj_t)&synthio_note_get_bend_depth_obj,
(mp_obj_t)&synthio_note_set_bend_depth_obj);
//| bend_rate: float
//| """The bend rate of the note, in Hz."""
STATIC mp_obj_t synthio_note_get_bend_rate(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_note_get_bend_rate(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_bend_rate_obj, synthio_note_get_bend_rate);
STATIC mp_obj_t synthio_note_set_bend_rate(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_bend_rate(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_bend_rate_obj, synthio_note_set_bend_rate);
MP_PROPERTY_GETSET(synthio_note_bend_rate_obj,
(mp_obj_t)&synthio_note_get_bend_rate_obj,
(mp_obj_t)&synthio_note_set_bend_rate_obj);
//| waveform: Optional[ReadableBuffer]
//| """The waveform of this note. Setting the waveform to a buffer of a different size resets the note's phase."""
STATIC mp_obj_t synthio_note_get_waveform(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return common_hal_synthio_note_get_waveform_obj(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_waveform_obj, synthio_note_get_waveform);
STATIC mp_obj_t synthio_note_set_waveform(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_waveform(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_waveform_obj, synthio_note_set_waveform);
MP_PROPERTY_GETSET(synthio_note_waveform_obj,
(mp_obj_t)&synthio_note_get_waveform_obj,
(mp_obj_t)&synthio_note_set_waveform_obj);
//| envelope: Envelope
//| """The envelope of this note"""
//|
STATIC mp_obj_t synthio_note_get_envelope(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return common_hal_synthio_note_get_envelope_obj(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_envelope_obj, synthio_note_get_envelope);
STATIC mp_obj_t synthio_note_set_envelope(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_envelope(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_envelope_obj, synthio_note_set_envelope);
MP_PROPERTY_GETSET(synthio_note_envelope_obj,
(mp_obj_t)&synthio_note_get_envelope_obj,
(mp_obj_t)&synthio_note_set_envelope_obj);
//| ring_frequency: float
//| """The ring frequency of the note, in Hz. Zero disables.
//|
//| For ring to take effect, both ``ring_frequency`` and ``ring_waveform`` must be set."""
STATIC mp_obj_t synthio_note_get_ring_frequency(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_note_get_ring_frequency(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_ring_frequency_obj, synthio_note_get_ring_frequency);
STATIC mp_obj_t synthio_note_set_ring_frequency(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_ring_frequency(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_ring_frequency_obj, synthio_note_set_ring_frequency);
MP_PROPERTY_GETSET(synthio_note_ring_frequency_obj,
(mp_obj_t)&synthio_note_get_ring_frequency_obj,
(mp_obj_t)&synthio_note_set_ring_frequency_obj);
//| ring_waveform: Optional[ReadableBuffer]
//| """The ring waveform of this note. Setting the ring_waveform to a buffer of a different size resets the note's phase.
//|
//| For ring to take effect, both ``ring_frequency`` and ``ring_waveform`` must be set."""
//|
STATIC mp_obj_t synthio_note_get_ring_waveform(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return common_hal_synthio_note_get_ring_waveform_obj(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_ring_waveform_obj, synthio_note_get_ring_waveform);
STATIC mp_obj_t synthio_note_set_ring_waveform(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_ring_waveform(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_ring_waveform_obj, synthio_note_set_ring_waveform);
MP_PROPERTY_GETSET(synthio_note_ring_waveform_obj,
(mp_obj_t)&synthio_note_get_ring_waveform_obj,
(mp_obj_t)&synthio_note_set_ring_waveform_obj);
static void note_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind;
properties_print_helper(print, self_in, note_properties, MP_ARRAY_SIZE(note_properties));
}
STATIC const mp_rom_map_elem_t synthio_note_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&synthio_note_frequency_obj) },
{ MP_ROM_QSTR(MP_QSTR_filter), MP_ROM_PTR(&synthio_note_filter_obj) },
{ MP_ROM_QSTR(MP_QSTR_panning), MP_ROM_PTR(&synthio_note_panning_obj) },
{ MP_ROM_QSTR(MP_QSTR_waveform), MP_ROM_PTR(&synthio_note_waveform_obj) },
{ MP_ROM_QSTR(MP_QSTR_envelope), MP_ROM_PTR(&synthio_note_envelope_obj) },
{ MP_ROM_QSTR(MP_QSTR_tremolo_depth), MP_ROM_PTR(&synthio_note_tremolo_depth_obj) },
{ MP_ROM_QSTR(MP_QSTR_tremolo_rate), MP_ROM_PTR(&synthio_note_tremolo_rate_obj) },
{ MP_ROM_QSTR(MP_QSTR_bend_depth), MP_ROM_PTR(&synthio_note_bend_depth_obj) },
{ MP_ROM_QSTR(MP_QSTR_bend_rate), MP_ROM_PTR(&synthio_note_bend_rate_obj) },
{ MP_ROM_QSTR(MP_QSTR_bend_mode), MP_ROM_PTR(&synthio_note_bend_mode_obj) },
{ MP_ROM_QSTR(MP_QSTR_ring_frequency), MP_ROM_PTR(&synthio_note_ring_frequency_obj) },
{ MP_ROM_QSTR(MP_QSTR_ring_waveform), MP_ROM_PTR(&synthio_note_ring_waveform_obj) },
};
STATIC MP_DEFINE_CONST_DICT(synthio_note_locals_dict, synthio_note_locals_dict_table);
const mp_obj_type_t synthio_note_type = {
{ &mp_type_type },
.name = MP_QSTR_Note,
.make_new = synthio_note_make_new,
.locals_dict = (mp_obj_dict_t *)&synthio_note_locals_dict,
.print = note_print,
};

View File

@ -0,0 +1,43 @@
#pragma once
#include "py/obj.h"
typedef struct synthio_note_obj synthio_note_obj_t;
extern const mp_obj_type_t synthio_note_type;
typedef enum synthio_bend_mode_e synthio_bend_mode_t;
mp_float_t common_hal_synthio_note_get_frequency(synthio_note_obj_t *self);
void common_hal_synthio_note_set_frequency(synthio_note_obj_t *self, mp_float_t value);
bool common_hal_synthio_note_get_filter(synthio_note_obj_t *self);
void common_hal_synthio_note_set_filter(synthio_note_obj_t *self, bool value);
mp_float_t common_hal_synthio_note_get_ring_frequency(synthio_note_obj_t *self);
void common_hal_synthio_note_set_ring_frequency(synthio_note_obj_t *self, mp_float_t value);
mp_float_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self);
void common_hal_synthio_note_set_panning(synthio_note_obj_t *self, mp_float_t value);
mp_float_t common_hal_synthio_note_get_tremolo_rate(synthio_note_obj_t *self);
void common_hal_synthio_note_set_tremolo_rate(synthio_note_obj_t *self, mp_float_t value);
mp_float_t common_hal_synthio_note_get_tremolo_depth(synthio_note_obj_t *self);
void common_hal_synthio_note_set_tremolo_depth(synthio_note_obj_t *self, mp_float_t value);
synthio_bend_mode_t common_hal_synthio_note_get_bend_mode(synthio_note_obj_t *self);
void common_hal_synthio_note_set_bend_mode(synthio_note_obj_t *self, synthio_bend_mode_t value);
mp_float_t common_hal_synthio_note_get_bend_rate(synthio_note_obj_t *self);
void common_hal_synthio_note_set_bend_rate(synthio_note_obj_t *self, mp_float_t value);
mp_float_t common_hal_synthio_note_get_bend_depth(synthio_note_obj_t *self);
void common_hal_synthio_note_set_bend_depth(synthio_note_obj_t *self, mp_float_t value);
mp_obj_t common_hal_synthio_note_get_waveform_obj(synthio_note_obj_t *self);
void common_hal_synthio_note_set_waveform(synthio_note_obj_t *self, mp_obj_t value);
mp_obj_t common_hal_synthio_note_get_ring_waveform_obj(synthio_note_obj_t *self);
void common_hal_synthio_note_set_ring_waveform(synthio_note_obj_t *self, mp_obj_t value);
mp_obj_t common_hal_synthio_note_get_envelope_obj(synthio_note_obj_t *self);
void common_hal_synthio_note_set_envelope(synthio_note_obj_t *self, mp_obj_t value);

View File

@ -36,11 +36,15 @@
#include "shared-bindings/synthio/__init__.h" #include "shared-bindings/synthio/__init__.h"
#include "supervisor/shared/translate/translate.h" #include "supervisor/shared/translate/translate.h"
//| NoteSequence = Sequence[Union[int, Note]]
//| """A sequence of notes, which can each be integer MIDI notes or `Note` objects"""
//|
//| class Synthesizer: //| class Synthesizer:
//| def __init__( //| def __init__(
//| self, //| self,
//| *, //| *,
//| sample_rate: int = 11025, //| sample_rate: int = 11025,
//| channel_count: int = 1,
//| waveform: Optional[ReadableBuffer] = None, //| waveform: Optional[ReadableBuffer] = None,
//| envelope: Optional[Envelope] = None, //| envelope: Optional[Envelope] = None,
//| ) -> None: //| ) -> None:
@ -48,35 +52,38 @@
//| //|
//| This API is experimental. //| This API is experimental.
//| //|
//| Notes use MIDI note numbering, with 60 being C4 or Middle C, approximately 262Hz. //| Integer notes use MIDI note numbering, with 60 being C4 or Middle C,
//| approximately 262Hz. Integer notes use the given waveform & envelope,
//| and do not support advanced features like tremolo or vibrato.
//| //|
//| :param int sample_rate: The desired playback sample rate; higher sample rate requires more memory //| :param int sample_rate: The desired playback sample rate; higher sample rate requires more memory
//| :param int channel_count: The number of output channels (1=mono, 2=stereo)
//| :param ReadableBuffer waveform: A single-cycle waveform. Default is a 50% duty cycle square wave. If specified, must be a ReadableBuffer of type 'h' (signed 16 bit) //| :param ReadableBuffer waveform: A single-cycle waveform. Default is a 50% duty cycle square wave. If specified, must be a ReadableBuffer of type 'h' (signed 16 bit)
//| :param ReadableBuffer filter: Coefficients of an FIR filter to apply to notes with ``filter=True``. If specified, must be a ReadableBuffer of type 'h' (signed 16 bit)
//| :param Optional[Envelope] envelope: An object that defines the loudness of a note over time. The default envelope, `None` provides no ramping, voices turn instantly on and off. //| :param Optional[Envelope] envelope: An object that defines the loudness of a note over time. The default envelope, `None` provides no ramping, voices turn instantly on and off.
//| """ //| """
STATIC mp_obj_t synthio_synthesizer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { STATIC mp_obj_t synthio_synthesizer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
enum { ARG_sample_rate, ARG_waveform, ARG_envelope }; enum { ARG_sample_rate, ARG_channel_count, ARG_waveform, ARG_envelope, ARG_filter };
static const mp_arg_t allowed_args[] = { static const mp_arg_t allowed_args[] = {
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 11025} }, { MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 11025} },
{ MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1} },
{ MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } }, { MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } },
{ MP_QSTR_envelope, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } }, { MP_QSTR_envelope, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } },
{ MP_QSTR_filter, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } },
}; };
mp_arg_val_t args[MP_ARRAY_SIZE(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); mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
mp_buffer_info_t bufinfo_waveform;
synthio_synth_parse_waveform(&bufinfo_waveform, args[ARG_waveform].u_obj);
synthio_synthesizer_obj_t *self = m_new_obj(synthio_synthesizer_obj_t); synthio_synthesizer_obj_t *self = m_new_obj(synthio_synthesizer_obj_t);
self->base.type = &synthio_synthesizer_type; self->base.type = &synthio_synthesizer_type;
common_hal_synthio_synthesizer_construct(self, common_hal_synthio_synthesizer_construct(self,
args[ARG_sample_rate].u_int, args[ARG_sample_rate].u_int,
bufinfo_waveform.buf, args[ARG_channel_count].u_int,
bufinfo_waveform.len / 2, args[ARG_waveform].u_obj,
args[ARG_filter].u_obj,
args[ARG_envelope].u_obj); args[ARG_envelope].u_obj);
return MP_OBJ_FROM_PTR(self); return MP_OBJ_FROM_PTR(self);
} }
@ -86,12 +93,12 @@ STATIC void check_for_deinit(synthio_synthesizer_obj_t *self) {
} }
} }
//| def press(self, /, press: Sequence[int] = ()) -> None: //| def press(self, /, press: NoteSequence = ()) -> None:
//| """Turn some notes on. Notes use MIDI numbering, with 60 being middle C, approximately 262Hz. //| """Turn some notes on.
//| //|
//| Pressing a note that was already pressed has no effect. //| Pressing a note that was already pressed has no effect.
//| //|
//| :param Sequence[int] press: Any sequence of integer notes.""" //| :param NoteSequence press: Any sequence of notes."""
STATIC mp_obj_t synthio_synthesizer_press(mp_obj_t self_in, mp_obj_t press) { STATIC mp_obj_t synthio_synthesizer_press(mp_obj_t self_in, mp_obj_t press) {
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in); synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self); check_for_deinit(self);
@ -99,12 +106,12 @@ STATIC mp_obj_t synthio_synthesizer_press(mp_obj_t self_in, mp_obj_t press) {
return mp_const_none; return mp_const_none;
} }
STATIC MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_press_obj, synthio_synthesizer_press); STATIC MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_press_obj, synthio_synthesizer_press);
//| def release(self, /, release: Sequence[int] = ()) -> None: //| def release(self, /, release: NoteSequence = ()) -> None:
//| """Turn some notes off. Notes use MIDI numbering, with 60 being middle C, approximately 262Hz. //| """Turn some notes off.
//| //|
//| Releasing a note that was already released has no effect. //| Releasing a note that was already released has no effect.
//| //|
//| :param Sequence[int] release: Any sequence of integer notes.""" //| :param NoteSequence release: Any sequence of notes."""
STATIC mp_obj_t synthio_synthesizer_release(mp_obj_t self_in, mp_obj_t release) { STATIC mp_obj_t synthio_synthesizer_release(mp_obj_t self_in, mp_obj_t release) {
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in); synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self); check_for_deinit(self);
@ -113,10 +120,8 @@ STATIC mp_obj_t synthio_synthesizer_release(mp_obj_t self_in, mp_obj_t release)
} }
STATIC MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_release_obj, synthio_synthesizer_release); STATIC MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_release_obj, synthio_synthesizer_release);
//| def release_then_press( //| def release_then_press(self, release: NoteSequence = (), press: NoteSequence = ()) -> None:
//| self, release: Sequence[int] = (), press: Sequence[int] = () //| """Turn some notes on and/or off.
//| ) -> None:
//| """Turn some notes on and/or off. Notes use MIDI numbering, with 60 being middle C.
//| //|
//| It is OK to release note that was not actually turned on. //| It is OK to release note that was not actually turned on.
//| //|
@ -125,8 +130,8 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_release_obj, synthio_synthe
//| Releasing and pressing the note again has little effect, but does reset the phase //| Releasing and pressing the note again has little effect, but does reset the phase
//| of the note, which may be perceptible as a small glitch. //| of the note, which may be perceptible as a small glitch.
//| //|
//| :param Sequence[int] release: Any sequence of integer notes. //| :param NoteSequence release: Any sequence of notes.
//| :param Sequence[int] press: Any sequence of integer notes.""" //| :param NoteSequence press: Any sequence of notes."""
STATIC mp_obj_t synthio_synthesizer_release_then_press(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { STATIC mp_obj_t synthio_synthesizer_release_then_press(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_release, ARG_press }; enum { ARG_release, ARG_press };
static const mp_arg_t allowed_args[] = { static const mp_arg_t allowed_args[] = {
@ -146,13 +151,13 @@ STATIC mp_obj_t synthio_synthesizer_release_then_press(mp_uint_t n_args, const m
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(synthio_synthesizer_release_then_press_obj, 1, synthio_synthesizer_release_then_press); STATIC MP_DEFINE_CONST_FUN_OBJ_KW(synthio_synthesizer_release_then_press_obj, 1, synthio_synthesizer_release_then_press);
// //
//| def release_all_then_press(self, /, press: Sequence[int]) -> None: //| def release_all_then_press(self, /, press: NoteSequence) -> None:
//| """Turn any currently-playing notes off, then turn on the given notes //| """Turn any currently-playing notes off, then turn on the given notes
//| //|
//| Releasing and pressing the note again has little effect, but does reset the phase //| Releasing and pressing the note again has little effect, but does reset the phase
//| of the note, which may be perceptible as a small glitch. //| of the note, which may be perceptible as a small glitch.
//| //|
//| :param Sequence[int] press: Any sequence of integer notes.""" //| :param NoteSequence press: Any sequence of notes."""
STATIC mp_obj_t synthio_synthesizer_release_all_then_press(mp_obj_t self_in, mp_obj_t press) { STATIC mp_obj_t synthio_synthesizer_release_all_then_press(mp_obj_t self_in, mp_obj_t press) {
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in); synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self); check_for_deinit(self);
@ -183,7 +188,7 @@ STATIC mp_obj_t synthio_synthesizer_deinit(mp_obj_t self_in) {
} }
STATIC MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_deinit_obj, synthio_synthesizer_deinit); STATIC MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_deinit_obj, synthio_synthesizer_deinit);
//| def __enter__(self) -> MidiTrack: //| def __enter__(self) -> Synthesizer:
//| """No-op used by Context Managers.""" //| """No-op used by Context Managers."""
//| ... //| ...
// Provided by context manager helper. // Provided by context manager helper.
@ -232,7 +237,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_get_sample_rate_obj, synthio_synth
MP_PROPERTY_GETTER(synthio_synthesizer_sample_rate_obj, MP_PROPERTY_GETTER(synthio_synthesizer_sample_rate_obj,
(mp_obj_t)&synthio_synthesizer_get_sample_rate_obj); (mp_obj_t)&synthio_synthesizer_get_sample_rate_obj);
//| pressed: Tuple[int] //| pressed: NoteSequence
//| """A sequence of the currently pressed notes (read-only property)""" //| """A sequence of the currently pressed notes (read-only property)"""
//| //|
STATIC mp_obj_t synthio_synthesizer_obj_get_pressed(mp_obj_t self_in) { STATIC mp_obj_t synthio_synthesizer_obj_get_pressed(mp_obj_t self_in) {

View File

@ -32,8 +32,8 @@
extern const mp_obj_type_t synthio_synthesizer_type; extern const mp_obj_type_t synthio_synthesizer_type;
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self, void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length, uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj,
mp_obj_t envelope); mp_obj_t envelope_obj);
void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self); void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self);
bool common_hal_synthio_synthesizer_deinited(synthio_synthesizer_obj_t *self); bool common_hal_synthio_synthesizer_deinited(synthio_synthesizer_obj_t *self);
uint32_t common_hal_synthio_synthesizer_get_sample_rate(synthio_synthesizer_obj_t *self); uint32_t common_hal_synthio_synthesizer_get_sample_rate(synthio_synthesizer_obj_t *self);

View File

@ -26,6 +26,7 @@
#include <string.h> #include <string.h>
#include "py/enum.h"
#include "py/mperrno.h" #include "py/mperrno.h"
#include "py/obj.h" #include "py/obj.h"
#include "py/objnamedtuple.h" #include "py/objnamedtuple.h"
@ -35,6 +36,7 @@
#include "shared-bindings/synthio/__init__.h" #include "shared-bindings/synthio/__init__.h"
#include "shared-bindings/synthio/MidiTrack.h" #include "shared-bindings/synthio/MidiTrack.h"
#include "shared-bindings/synthio/Note.h"
#include "shared-bindings/synthio/Synthesizer.h" #include "shared-bindings/synthio/Synthesizer.h"
#define default_attack_time (MICROPY_FLOAT_CONST(0.1)) #define default_attack_time (MICROPY_FLOAT_CONST(0.1))
@ -51,6 +53,7 @@ static const mp_arg_t envelope_properties[] = {
{ MP_QSTR_sustain_level, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL } }, { MP_QSTR_sustain_level, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL } },
}; };
//|
//| """Support for multi-channel audio synthesis //| """Support for multi-channel audio synthesis
//| //|
//| At least 2 simultaneous notes are supported. samd5x, mimxrt10xx and rp2040 platforms support up to 12 notes. //| At least 2 simultaneous notes are supported. samd5x, mimxrt10xx and rp2040 platforms support up to 12 notes.
@ -77,9 +80,9 @@ static const mp_arg_t envelope_properties[] = {
//| //|
//| :param float attack_time: The time in seconds it takes to ramp from 0 volume to attack_volume //| :param float attack_time: The time in seconds it takes to ramp from 0 volume to attack_volume
//| :param float decay_time: The time in seconds it takes to ramp from attack_volume to sustain_volume //| :param float decay_time: The time in seconds it takes to ramp from attack_volume to sustain_volume
//| :param float release_time: The time in seconds it takes to ramp from sustain_volume to release_volume. When a note is released before it has reached the sustain phase, the release is done with the same slope indicated by ``release_time`` and ``sustain_level`` //| :param float release_time: The time in seconds it takes to ramp from sustain_volume to release_volume. When a note is released before it has reached the sustain phase, the release is done with the same slope indicated by ``release_time`` and ``sustain_level``. If the ``sustain_level`` is ``0.0`` then the release slope calculations use the ``attack_level`` instead.
//| :param float attack_level: The relative level, in the range ``0.0`` to ``1.0`` of the peak volume of the attack phase //| :param float attack_level: The level, in the range ``0.0`` to ``1.0`` of the peak volume of the attack phase
//| :param float sustain_level: The relative level, in the range ``0.0`` to ``1.0`` of the volume of the sustain phase //| :param float sustain_level: The level, in the range ``0.0`` to ``1.0`` of the volume of the sustain phase relative to the attack level
//| """ //| """
//| attack_time: float //| attack_time: float
//| """The time in seconds it takes to ramp from 0 volume to attack_volume""" //| """The time in seconds it takes to ramp from 0 volume to attack_volume"""
@ -91,10 +94,10 @@ static const mp_arg_t envelope_properties[] = {
//| """The time in seconds it takes to ramp from sustain_volume to release_volume. When a note is released before it has reached the sustain phase, the release is done with the same slope indicated by ``release_time`` and ``sustain_level``""" //| """The time in seconds it takes to ramp from sustain_volume to release_volume. When a note is released before it has reached the sustain phase, the release is done with the same slope indicated by ``release_time`` and ``sustain_level``"""
//| //|
//| attack_level: float //| attack_level: float
//| """The relative level, in the range ``0.0`` to ``1.0`` of the peak volume of the attack phase""" //| """The level, in the range ``0.0`` to ``1.0`` of the peak volume of the attack phase"""
//| //|
//| sustain_level: float //| sustain_level: float
//| """The relative level, in the range ``0.0`` to ``1.0`` of the volume of the sustain phase""" //| """The level, in the range ``0.0`` to ``1.0`` of the volume of the sustain phase relative to the attack level"""
//| //|
STATIC mp_obj_t synthio_envelope_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { STATIC mp_obj_t synthio_envelope_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
@ -157,7 +160,6 @@ const mp_obj_namedtuple_type_t synthio_envelope_type_obj = {
}, },
}; };
//| def from_file( //| def from_file(
//| file: typing.BinaryIO, //| file: typing.BinaryIO,
//| *, //| *,
@ -205,10 +207,6 @@ STATIC mp_obj_t synthio_from_file(size_t n_args, const mp_obj_t *pos_args, mp_ma
} }
pyb_file_obj_t *file = MP_OBJ_TO_PTR(args[ARG_file].u_obj); pyb_file_obj_t *file = MP_OBJ_TO_PTR(args[ARG_file].u_obj);
mp_buffer_info_t bufinfo_waveform;
synthio_synth_parse_waveform(&bufinfo_waveform, args[ARG_waveform].u_obj);
uint8_t chunk_header[14]; uint8_t chunk_header[14];
f_rewind(&file->fp); f_rewind(&file->fp);
UINT bytes_read; UINT bytes_read;
@ -248,7 +246,8 @@ STATIC mp_obj_t synthio_from_file(size_t n_args, const mp_obj_t *pos_args, mp_ma
result->base.type = &synthio_miditrack_type; result->base.type = &synthio_miditrack_type;
common_hal_synthio_miditrack_construct(result, buffer, track_size, common_hal_synthio_miditrack_construct(result, buffer, track_size,
tempo, args[ARG_sample_rate].u_int, bufinfo_waveform.buf, bufinfo_waveform.len / 2, tempo, args[ARG_sample_rate].u_int, args[ARG_waveform].u_obj,
mp_const_none,
args[ARG_envelope].u_obj args[ARG_envelope].u_obj
); );
@ -262,12 +261,73 @@ STATIC mp_obj_t synthio_from_file(size_t n_args, const mp_obj_t *pos_args, mp_ma
} }
MP_DEFINE_CONST_FUN_OBJ_KW(synthio_from_file_obj, 1, synthio_from_file); MP_DEFINE_CONST_FUN_OBJ_KW(synthio_from_file_obj, 1, synthio_from_file);
//| def midi_to_hz(midi_note: float) -> float:
//| """Converts the given midi note (60 = middle C, 69 = concert A) to Hz"""
//|
STATIC mp_obj_t midi_to_hz(mp_obj_t arg) {
mp_float_t note = mp_arg_validate_obj_float_range(arg, 1, 127, MP_QSTR_note);
return mp_obj_new_float(common_hal_synthio_midi_to_hz_float(note));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_midi_to_hz_obj, midi_to_hz);
//| def onevo_to_hz(ctrl: float) -> float:
//| """Converts a 1v/octave signal to Hz.
//|
//| 60/12 (5.0) corresponds to middle C, 69/12 is concert A."""
//|
STATIC mp_obj_t onevo_to_hz(mp_obj_t arg) {
mp_float_t note = mp_arg_validate_obj_float_range(arg, 0, 11, MP_QSTR_ctrl);
return mp_obj_new_float(common_hal_synthio_onevo_to_hz_float(note));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_onevo_to_hz_obj, onevo_to_hz);
MAKE_ENUM_VALUE(synthio_bend_mode_type, bend_mode, STATIC, SYNTHIO_BEND_MODE_STATIC);
MAKE_ENUM_VALUE(synthio_bend_mode_type, bend_mode, VIBRATO, SYNTHIO_BEND_MODE_VIBRATO);
MAKE_ENUM_VALUE(synthio_bend_mode_type, bend_mode, SWEEP, SYNTHIO_BEND_MODE_SWEEP);
MAKE_ENUM_VALUE(synthio_bend_mode_type, bend_mode, SWEEP_IN, SYNTHIO_BEND_MODE_SWEEP_IN);
//|
//| class BendMode:
//| """Controls the way the ``Note.pitch_bend_depth`` and ``Note.pitch_bend_rate`` properties are interpreted."""
//|
//| STATIC: "BendMode"
//| """The Note's pitch is modified by its ``pitch_bend_depth``. ``pitch_bend_rate`` is ignored."""
//|
//| VIBRATO: "BendMode"
//| """The Note's pitch varies by ``±pitch_bend_depth`` at a rate of ``pitch_bend_rate`` Hz."""
//|
//| SWEEP: "BendMode"
//| """The Note's pitch starts at ``Note.frequency`` then sweeps up or down by ``pitch_bend_depth`` over ``1/pitch_bend_rate`` seconds."""
//|
//| SWEEP_IN: "BendMode"
//| """The Note's pitch sweep is the reverse of ``SWEEP`` mode, starting at the bent pitch and arriving at the tuned pitch."""
//|
MAKE_ENUM_MAP(synthio_bend_mode) {
MAKE_ENUM_MAP_ENTRY(bend_mode, STATIC),
MAKE_ENUM_MAP_ENTRY(bend_mode, VIBRATO),
MAKE_ENUM_MAP_ENTRY(bend_mode, SWEEP),
MAKE_ENUM_MAP_ENTRY(bend_mode, SWEEP_IN),
};
STATIC MP_DEFINE_CONST_DICT(synthio_bend_mode_locals_dict, synthio_bend_mode_locals_table);
MAKE_PRINTER(synthio, synthio_bend_mode);
MAKE_ENUM_TYPE(synthio, BendMode, synthio_bend_mode);
STATIC const mp_rom_map_elem_t synthio_module_globals_table[] = { STATIC const mp_rom_map_elem_t synthio_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_synthio) }, { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_synthio) },
{ MP_ROM_QSTR(MP_QSTR_BendMode), MP_ROM_PTR(&synthio_bend_mode_type) },
{ MP_ROM_QSTR(MP_QSTR_MidiTrack), MP_ROM_PTR(&synthio_miditrack_type) }, { MP_ROM_QSTR(MP_QSTR_MidiTrack), MP_ROM_PTR(&synthio_miditrack_type) },
{ MP_ROM_QSTR(MP_QSTR_Note), MP_ROM_PTR(&synthio_note_type) },
{ MP_ROM_QSTR(MP_QSTR_Synthesizer), MP_ROM_PTR(&synthio_synthesizer_type) }, { MP_ROM_QSTR(MP_QSTR_Synthesizer), MP_ROM_PTR(&synthio_synthesizer_type) },
{ MP_ROM_QSTR(MP_QSTR_from_file), MP_ROM_PTR(&synthio_from_file_obj) }, { MP_ROM_QSTR(MP_QSTR_from_file), MP_ROM_PTR(&synthio_from_file_obj) },
{ MP_ROM_QSTR(MP_QSTR_Envelope), MP_ROM_PTR(&synthio_envelope_type_obj) }, { MP_ROM_QSTR(MP_QSTR_Envelope), MP_ROM_PTR(&synthio_envelope_type_obj) },
{ MP_ROM_QSTR(MP_QSTR_midi_to_hz), MP_ROM_PTR(&synthio_midi_to_hz_obj) },
{ MP_ROM_QSTR(MP_QSTR_onevo_to_hz), MP_ROM_PTR(&synthio_midi_to_hz_obj) },
}; };
STATIC MP_DEFINE_CONST_DICT(synthio_module_globals, synthio_module_globals_table); STATIC MP_DEFINE_CONST_DICT(synthio_module_globals, synthio_module_globals_table);

View File

@ -27,9 +27,18 @@
#pragma once #pragma once
#include "py/objnamedtuple.h" #include "py/objnamedtuple.h"
#include "py/enum.h"
typedef enum synthio_bend_mode_e {
SYNTHIO_BEND_MODE_STATIC, SYNTHIO_BEND_MODE_VIBRATO, SYNTHIO_BEND_MODE_SWEEP, SYNTHIO_BEND_MODE_SWEEP_IN
} synthio_bend_mode_t;
extern const cp_enum_obj_t bend_mode_VIBRATO_obj;
extern const mp_obj_type_t synthio_bend_mode_type;
typedef struct synthio_synth synthio_synth_t; typedef struct synthio_synth synthio_synth_t;
extern int16_t shared_bindings_synthio_square_wave[]; extern int16_t shared_bindings_synthio_square_wave[];
extern const mp_obj_namedtuple_type_t synthio_envelope_type_obj; extern const mp_obj_namedtuple_type_t synthio_envelope_type_obj;
void synthio_synth_envelope_set(synthio_synth_t *synth, mp_obj_t envelope_obj); void synthio_synth_envelope_set(synthio_synth_t *synth, mp_obj_t envelope_obj);
mp_obj_t synthio_synth_envelope_get(synthio_synth_t *synth); mp_obj_t synthio_synth_envelope_get(synthio_synth_t *synth);
mp_float_t common_hal_synthio_midi_to_hz_float(mp_float_t note);
mp_float_t common_hal_synthio_onevo_to_hz_float(mp_float_t note);

View File

@ -37,5 +37,17 @@ void raise_deinited_error(void) {
mp_raise_ValueError(translate("Object has been deinitialized and can no longer be used. Create a new object.")); mp_raise_ValueError(translate("Object has been deinitialized and can no longer be used. Create a new object."));
} }
void properties_print_helper(const mp_print_t *print, mp_obj_t self_in, const mp_arg_t *properties, size_t n_properties) {
const mp_obj_type_t *type = mp_obj_get_type(self_in);
mp_printf(print, "%q(", type->name);
for (size_t i = 0; i < n_properties; i++) {
if (i > 0) {
mp_print_str(print, ", ");
}
mp_printf(print, "%q=", properties[i].qst);
mp_obj_print_helper(print, mp_load_attr(self_in, properties[i].qst), PRINT_REPR);
}
mp_print_str(print, ")");
}
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_UTIL_H #endif // MICROPY_INCLUDED_SHARED_BINDINGS_UTIL_H

View File

@ -27,7 +27,11 @@
#ifndef MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_UTIL_H #ifndef MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_UTIL_H
#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_UTIL_H #define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_UTIL_H
#include "py/mpprint.h"
#include "py/runtime.h"
void raise_deinited_error(void); void raise_deinited_error(void);
void properties_print_helper(const mp_print_t *print, mp_obj_t self_in, const mp_arg_t *properties, size_t n_properties);
#endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_UTIL_H #endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_UTIL_H

View File

@ -317,7 +317,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(wifi_radio_stop_station_obj, wifi_radio_stop_station);
//| *, //| *,
//| channel: int = 1, //| channel: int = 1,
//| authmode: Optional[AuthMode] = None, //| authmode: Optional[AuthMode] = None,
//| max_connections: Optional[int] = 4 //| max_connections: Optional[int] = 4,
//| ) -> None: //| ) -> None:
//| """Starts running an access point with the specified ssid and password. //| """Starts running an access point with the specified ssid and password.
//| //|
@ -416,7 +416,7 @@ MP_PROPERTY_GETTER(wifi_radio_ap_active_obj,
//| *, //| *,
//| channel: int = 0, //| channel: int = 0,
//| bssid: Optional[Union[str | ReadableBuffer]] = None, //| bssid: Optional[Union[str | ReadableBuffer]] = None,
//| timeout: Optional[float] = None //| timeout: Optional[float] = None,
//| ) -> None: //| ) -> None:
//| """Connects to the given ssid and waits for an ip address. Reconnections are handled //| """Connects to the given ssid and waits for an ip address. Reconnections are handled
//| automatically once one connection succeeds. //| automatically once one connection succeeds.
@ -551,7 +551,7 @@ MP_PROPERTY_GETTER(wifi_radio_ipv4_subnet_ap_obj,
//| ipv4: ipaddress.IPv4Address, //| ipv4: ipaddress.IPv4Address,
//| netmask: ipaddress.IPv4Address, //| netmask: ipaddress.IPv4Address,
//| gateway: ipaddress.IPv4Address, //| gateway: ipaddress.IPv4Address,
//| ipv4_dns: Optional[ipaddress.IPv4Address] //| ipv4_dns: Optional[ipaddress.IPv4Address],
//| ) -> None: //| ) -> None:
//| """Sets the IP v4 address of the station. Must include the netmask and gateway. DNS address is optional. //| """Sets the IP v4 address of the station. Must include the netmask and gateway. DNS address is optional.
//| Setting the address manually will stop the DHCP client.""" //| Setting the address manually will stop the DHCP client."""
@ -574,6 +574,32 @@ STATIC mp_obj_t wifi_radio_set_ipv4_address(size_t n_args, const mp_obj_t *pos_a
} }
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(wifi_radio_set_ipv4_address_obj, 1, wifi_radio_set_ipv4_address); STATIC MP_DEFINE_CONST_FUN_OBJ_KW(wifi_radio_set_ipv4_address_obj, 1, wifi_radio_set_ipv4_address);
//| def set_ipv4_address_ap(
//| self,
//| *,
//| ipv4: ipaddress.IPv4Address,
//| netmask: ipaddress.IPv4Address,
//| gateway: ipaddress.IPv4Address,
//| ) -> None:
//| """Sets the IP v4 address of the access point. Must include the netmask and gateway."""
//| ...
STATIC mp_obj_t wifi_radio_set_ipv4_address_ap(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_ipv4, ARG_netmask, ARG_gateway };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_ipv4, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, },
{ MP_QSTR_netmask, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, },
{ MP_QSTR_gateway, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, },
};
wifi_radio_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
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_wifi_radio_set_ipv4_address_ap(self, args[ARG_ipv4].u_obj, args[ARG_netmask].u_obj, args[ARG_gateway].u_obj);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(wifi_radio_set_ipv4_address_ap_obj, 1, wifi_radio_set_ipv4_address_ap);
//| ipv4_address: Optional[ipaddress.IPv4Address] //| ipv4_address: Optional[ipaddress.IPv4Address]
//| """IP v4 Address of the station when connected to an access point. None otherwise. (read-only)""" //| """IP v4 Address of the station when connected to an access point. None otherwise. (read-only)"""
STATIC mp_obj_t _wifi_radio_get_ipv4_address(mp_obj_t self) { STATIC mp_obj_t _wifi_radio_get_ipv4_address(mp_obj_t self) {
@ -620,7 +646,7 @@ STATIC mp_obj_t wifi_radio_get_ap_info(mp_obj_t self) {
MP_DEFINE_CONST_FUN_OBJ_1(wifi_radio_get_ap_info_obj, wifi_radio_get_ap_info); MP_DEFINE_CONST_FUN_OBJ_1(wifi_radio_get_ap_info_obj, wifi_radio_get_ap_info);
//| def start_dhcp(self) -> None: //| def start_dhcp(self) -> None:
//| """Starts the DHCP client.""" //| """Starts the station DHCP client."""
//| ... //| ...
STATIC mp_obj_t wifi_radio_start_dhcp_client(mp_obj_t self) { STATIC mp_obj_t wifi_radio_start_dhcp_client(mp_obj_t self) {
common_hal_wifi_radio_start_dhcp_client(self); common_hal_wifi_radio_start_dhcp_client(self);
@ -629,7 +655,7 @@ STATIC mp_obj_t wifi_radio_start_dhcp_client(mp_obj_t self) {
MP_DEFINE_CONST_FUN_OBJ_1(wifi_radio_start_dhcp_client_obj, wifi_radio_start_dhcp_client); MP_DEFINE_CONST_FUN_OBJ_1(wifi_radio_start_dhcp_client_obj, wifi_radio_start_dhcp_client);
//| def stop_dhcp(self) -> None: //| def stop_dhcp(self) -> None:
//| """Stops the DHCP client. Needed to assign a static IP address.""" //| """Stops the station DHCP client. Needed to assign a static IP address."""
//| ... //| ...
STATIC mp_obj_t wifi_radio_stop_dhcp_client(mp_obj_t self) { STATIC mp_obj_t wifi_radio_stop_dhcp_client(mp_obj_t self) {
common_hal_wifi_radio_stop_dhcp_client(self); common_hal_wifi_radio_stop_dhcp_client(self);
@ -637,6 +663,24 @@ STATIC mp_obj_t wifi_radio_stop_dhcp_client(mp_obj_t self) {
} }
MP_DEFINE_CONST_FUN_OBJ_1(wifi_radio_stop_dhcp_client_obj, wifi_radio_stop_dhcp_client); MP_DEFINE_CONST_FUN_OBJ_1(wifi_radio_stop_dhcp_client_obj, wifi_radio_stop_dhcp_client);
//| def start_dhcp_ap(self) -> None:
//| """Starts the access point DHCP server."""
//| ...
STATIC mp_obj_t wifi_radio_start_dhcp_server(mp_obj_t self) {
common_hal_wifi_radio_start_dhcp_server(self);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(wifi_radio_start_dhcp_server_obj, wifi_radio_start_dhcp_server);
//| def stop_dhcp_ap(self) -> None:
//| """Stops the access point DHCP server. Needed to assign a static IP address."""
//| ...
STATIC mp_obj_t wifi_radio_stop_dhcp_server(mp_obj_t self) {
common_hal_wifi_radio_stop_dhcp_server(self);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(wifi_radio_stop_dhcp_server_obj, wifi_radio_stop_dhcp_server);
MP_PROPERTY_GETTER(wifi_radio_ap_info_obj, MP_PROPERTY_GETTER(wifi_radio_ap_info_obj,
(mp_obj_t)&wifi_radio_get_ap_info_obj); (mp_obj_t)&wifi_radio_get_ap_info_obj);
@ -693,6 +737,8 @@ STATIC const mp_rom_map_elem_t wifi_radio_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_start_dhcp), MP_ROM_PTR(&wifi_radio_start_dhcp_client_obj) }, { MP_ROM_QSTR(MP_QSTR_start_dhcp), MP_ROM_PTR(&wifi_radio_start_dhcp_client_obj) },
{ MP_ROM_QSTR(MP_QSTR_stop_dhcp), MP_ROM_PTR(&wifi_radio_stop_dhcp_client_obj) }, { MP_ROM_QSTR(MP_QSTR_stop_dhcp), MP_ROM_PTR(&wifi_radio_stop_dhcp_client_obj) },
{ MP_ROM_QSTR(MP_QSTR_start_dhcp_ap), MP_ROM_PTR(&wifi_radio_start_dhcp_server_obj) },
{ MP_ROM_QSTR(MP_QSTR_stop_dhcp_ap), MP_ROM_PTR(&wifi_radio_stop_dhcp_server_obj) },
{ MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&wifi_radio_connect_obj) }, { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&wifi_radio_connect_obj) },
// { MP_ROM_QSTR(MP_QSTR_connect_to_enterprise), MP_ROM_PTR(&wifi_radio_connect_to_enterprise_obj) }, // { MP_ROM_QSTR(MP_QSTR_connect_to_enterprise), MP_ROM_PTR(&wifi_radio_connect_to_enterprise_obj) },
@ -708,6 +754,7 @@ STATIC const mp_rom_map_elem_t wifi_radio_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_ipv4_address_ap), MP_ROM_PTR(&wifi_radio_ipv4_address_ap_obj) }, { MP_ROM_QSTR(MP_QSTR_ipv4_address_ap), MP_ROM_PTR(&wifi_radio_ipv4_address_ap_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_ipv4_address), MP_ROM_PTR(&wifi_radio_set_ipv4_address_obj) }, { MP_ROM_QSTR(MP_QSTR_set_ipv4_address), MP_ROM_PTR(&wifi_radio_set_ipv4_address_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_ipv4_address_ap), MP_ROM_PTR(&wifi_radio_set_ipv4_address_ap_obj) },
{ MP_ROM_QSTR(MP_QSTR_ping), MP_ROM_PTR(&wifi_radio_ping_obj) }, { MP_ROM_QSTR(MP_QSTR_ping), MP_ROM_PTR(&wifi_radio_ping_obj) },
}; };

View File

@ -99,6 +99,8 @@ extern bool common_hal_wifi_radio_get_ap_active(wifi_radio_obj_t *self);
extern void common_hal_wifi_radio_start_dhcp_client(wifi_radio_obj_t *self); extern void common_hal_wifi_radio_start_dhcp_client(wifi_radio_obj_t *self);
extern void common_hal_wifi_radio_stop_dhcp_client(wifi_radio_obj_t *self); extern void common_hal_wifi_radio_stop_dhcp_client(wifi_radio_obj_t *self);
extern void common_hal_wifi_radio_start_dhcp_server(wifi_radio_obj_t *self);
extern void common_hal_wifi_radio_stop_dhcp_server(wifi_radio_obj_t *self);
extern wifi_radio_error_t common_hal_wifi_radio_connect(wifi_radio_obj_t *self, uint8_t *ssid, size_t ssid_len, uint8_t *password, size_t password_len, uint8_t channel, mp_float_t timeout, uint8_t *bssid, size_t bssid_len); extern wifi_radio_error_t common_hal_wifi_radio_connect(wifi_radio_obj_t *self, uint8_t *ssid, size_t ssid_len, uint8_t *password, size_t password_len, uint8_t channel, mp_float_t timeout, uint8_t *bssid, size_t bssid_len);
extern bool common_hal_wifi_radio_get_connected(wifi_radio_obj_t *self); extern bool common_hal_wifi_radio_get_connected(wifi_radio_obj_t *self);
@ -115,6 +117,7 @@ extern mp_obj_t common_hal_wifi_radio_get_ipv4_address(wifi_radio_obj_t *self);
extern mp_obj_t common_hal_wifi_radio_get_ipv4_address_ap(wifi_radio_obj_t *self); extern mp_obj_t common_hal_wifi_radio_get_ipv4_address_ap(wifi_radio_obj_t *self);
extern void common_hal_wifi_radio_set_ipv4_address(wifi_radio_obj_t *self, mp_obj_t ipv4, mp_obj_t netmask, mp_obj_t gateway, mp_obj_t ipv4_dns_addr); extern void common_hal_wifi_radio_set_ipv4_address(wifi_radio_obj_t *self, mp_obj_t ipv4, mp_obj_t netmask, mp_obj_t gateway, mp_obj_t ipv4_dns_addr);
extern void common_hal_wifi_radio_set_ipv4_address_ap(wifi_radio_obj_t *self, mp_obj_t ipv4, mp_obj_t netmask, mp_obj_t gateway);
extern mp_int_t common_hal_wifi_radio_ping(wifi_radio_obj_t *self, mp_obj_t ip_address, mp_float_t timeout); extern mp_int_t common_hal_wifi_radio_ping(wifi_radio_obj_t *self, mp_obj_t ip_address, mp_float_t timeout);

View File

@ -38,6 +38,7 @@ void common_hal_adafruit_bus_device_spidevice_construct(adafruit_bus_device_spid
self->polarity = polarity; self->polarity = polarity;
self->phase = phase; self->phase = phase;
self->extra_clocks = extra_clocks; self->extra_clocks = extra_clocks;
// May be mp_const_none if CS not used.
self->chip_select = cs; self->chip_select = cs;
self->cs_active_value = cs_active_value; self->cs_active_value = cs_active_value;
} }
@ -66,14 +67,14 @@ mp_obj_t common_hal_adafruit_bus_device_spidevice_enter(adafruit_bus_device_spid
mp_call_method_n_kw(0, 4, dest); mp_call_method_n_kw(0, 4, dest);
} }
if (self->chip_select != MP_OBJ_NULL) { if (self->chip_select != mp_const_none) {
common_hal_digitalio_digitalinout_set_value(MP_OBJ_TO_PTR(self->chip_select), self->cs_active_value); common_hal_digitalio_digitalinout_set_value(MP_OBJ_TO_PTR(self->chip_select), self->cs_active_value);
} }
return self->spi; return self->spi;
} }
void common_hal_adafruit_bus_device_spidevice_exit(adafruit_bus_device_spidevice_obj_t *self) { void common_hal_adafruit_bus_device_spidevice_exit(adafruit_bus_device_spidevice_obj_t *self) {
if (self->chip_select != MP_OBJ_NULL) { if (self->chip_select != mp_const_none) {
common_hal_digitalio_digitalinout_set_value(MP_OBJ_TO_PTR(self->chip_select), !(self->cs_active_value)); common_hal_digitalio_digitalinout_set_value(MP_OBJ_TO_PTR(self->chip_select), !(self->cs_active_value));
} }

View File

@ -28,22 +28,22 @@
#include "shared-bindings/synthio/MidiTrack.h" #include "shared-bindings/synthio/MidiTrack.h"
STATIC void print_midi_stream_error(synthio_miditrack_obj_t *self) { STATIC void record_midi_stream_error(synthio_miditrack_obj_t *self) {
self->error_location = self->pos; self->error_location = self->pos;
self->pos = self->track.len; self->pos = self->track.len;
} }
STATIC uint8_t parse_note(synthio_miditrack_obj_t *self) { STATIC mp_obj_t parse_note(synthio_miditrack_obj_t *self) {
uint8_t *buffer = self->track.buf; uint8_t *buffer = self->track.buf;
size_t len = self->track.len; size_t len = self->track.len;
if (self->pos + 1 >= len) { if (self->pos + 1 >= len) {
print_midi_stream_error(self); record_midi_stream_error(self);
} }
uint8_t note = buffer[(self->pos)++]; uint8_t note = buffer[(self->pos)++];
if (note > 127 || buffer[(self->pos)++] > 127) { if (note > 127 || buffer[(self->pos)++] > 127) {
print_midi_stream_error(self); record_midi_stream_error(self);
} }
return note; return MP_OBJ_NEW_SMALL_INT(note);
} }
static int decode_duration(synthio_miditrack_obj_t *self) { static int decode_duration(synthio_miditrack_obj_t *self) {
@ -60,7 +60,7 @@ static int decode_duration(synthio_miditrack_obj_t *self) {
// errors cannot be raised from the background task, so simply end the track. // errors cannot be raised from the background task, so simply end the track.
if (c & 0x80) { if (c & 0x80) {
self->pos = self->track.len; self->pos = self->track.len;
print_midi_stream_error(self); record_midi_stream_error(self);
} }
return delta * self->synth.sample_rate / self->tempo; return delta * self->synth.sample_rate / self->tempo;
} }
@ -72,12 +72,12 @@ static void decode_until_pause(synthio_miditrack_obj_t *self) {
do { do {
switch (buffer[self->pos++] >> 4) { switch (buffer[self->pos++] >> 4) {
case 8: { // Note Off case 8: { // Note Off
uint8_t note = parse_note(self); mp_obj_t note = parse_note(self);
synthio_span_change_note(&self->synth, note, SYNTHIO_SILENCE); synthio_span_change_note(&self->synth, note, SYNTHIO_SILENCE);
break; break;
} }
case 9: { // Note On case 9: { // Note On
uint8_t note = parse_note(self); mp_obj_t note = parse_note(self);
synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, note); synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, note);
break; break;
} }
@ -89,14 +89,14 @@ static void decode_until_pause(synthio_miditrack_obj_t *self) {
case 12: case 12:
case 13: // one data byte to ignore case 13: // one data byte to ignore
if (self->pos >= len || buffer[self->pos++] > 127) { if (self->pos >= len || buffer[self->pos++] > 127) {
print_midi_stream_error(self); record_midi_stream_error(self);
} }
break; break;
case 15: // the full syntax is too complicated, just assume it's "End of Track" event case 15: // the full syntax is too complicated, just assume it's "End of Track" event
self->pos = len; self->pos = len;
break; break;
default: // invalid event default: // invalid event
print_midi_stream_error(self); record_midi_stream_error(self);
} }
if (self->pos < len) { if (self->pos < len) {
self->synth.span.dur = decode_duration(self); self->synth.span.dur = decode_duration(self);
@ -116,14 +116,13 @@ STATIC void start_parse(synthio_miditrack_obj_t *self) {
void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self,
const uint8_t *buffer, uint32_t len, uint32_t tempo, uint32_t sample_rate, const uint8_t *buffer, uint32_t len, uint32_t tempo, uint32_t sample_rate,
const int16_t *waveform, uint16_t waveform_length, mp_obj_t waveform_obj, mp_obj_t filter_obj, mp_obj_t envelope_obj) {
mp_obj_t envelope) {
self->tempo = tempo; self->tempo = tempo;
self->track.buf = (void *)buffer; self->track.buf = (void *)buffer;
self->track.len = len; self->track.len = len;
synthio_synth_init(&self->synth, sample_rate, waveform, waveform_length, envelope); synthio_synth_init(&self->synth, sample_rate, 1, waveform_obj, mp_const_none, envelope_obj);
start_parse(self); start_parse(self);
} }
@ -158,6 +157,10 @@ void synthio_miditrack_reset_buffer(synthio_miditrack_obj_t *self,
audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t *self, audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t *self,
bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) { bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) {
if (common_hal_synthio_miditrack_deinited(self)) {
*buffer_length = 0;
return GET_BUFFER_ERROR;
}
synthio_synth_synthesize(&self->synth, buffer, buffer_length, single_channel_output ? 0 : channel); synthio_synth_synthesize(&self->synth, buffer, buffer_length, single_channel_output ? 0 : channel);
if (self->synth.span.dur == 0) { if (self->synth.span.dur == 0) {

View File

@ -0,0 +1,252 @@
/*
* This file is part of the Micro Python project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2023 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 <math.h>
#include "py/runtime.h"
#include "shared-module/synthio/Note.h"
#include "shared-bindings/synthio/Note.h"
#include "shared-bindings/synthio/__init__.h"
static int32_t round_float_to_int(mp_float_t f) {
return (int32_t)(f + MICROPY_FLOAT_CONST(0.5));
}
mp_float_t common_hal_synthio_note_get_frequency(synthio_note_obj_t *self) {
return self->frequency;
}
void common_hal_synthio_note_set_frequency(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 32767, MP_QSTR_frequency);
self->frequency = val;
self->frequency_scaled = synthio_frequency_convert_float_to_scaled(val);
}
bool common_hal_synthio_note_get_filter(synthio_note_obj_t *self) {
return self->filter;
}
void common_hal_synthio_note_set_filter(synthio_note_obj_t *self, bool value_in) {
self->filter = value_in;
}
mp_float_t common_hal_synthio_note_get_ring_frequency(synthio_note_obj_t *self) {
return self->ring_frequency;
}
void common_hal_synthio_note_set_ring_frequency(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 32767, MP_QSTR_ring_frequency);
self->ring_frequency = val;
self->ring_frequency_scaled = synthio_frequency_convert_float_to_scaled(val);
}
mp_float_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self) {
return self->panning;
}
void common_hal_synthio_note_set_panning(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, -1, 1, MP_QSTR_panning);
self->panning = val;
if (val >= 0) {
self->left_panning_scaled = 32768;
self->right_panning_scaled = 32768 - round_float_to_int(val * 32768);
} else {
self->right_panning_scaled = 32768;
self->left_panning_scaled = 32768 + round_float_to_int(val * 32768);
}
}
mp_float_t common_hal_synthio_note_get_tremolo_depth(synthio_note_obj_t *self) {
return self->tremolo_descr.amplitude;
}
void common_hal_synthio_note_set_tremolo_depth(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 1, MP_QSTR_tremolo_depth);
self->tremolo_descr.amplitude = val;
self->tremolo_state.amplitude_scaled = round_float_to_int(val * 32768);
}
mp_float_t common_hal_synthio_note_get_tremolo_rate(synthio_note_obj_t *self) {
return self->tremolo_descr.frequency;
}
void common_hal_synthio_note_set_tremolo_rate(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 60, MP_QSTR_tremolo_rate);
self->tremolo_descr.frequency = val;
if (self->sample_rate != 0) {
self->tremolo_state.dds = synthio_frequency_convert_float_to_dds(val * 65536, self->sample_rate);
}
}
mp_float_t common_hal_synthio_note_get_bend_depth(synthio_note_obj_t *self) {
return self->bend_descr.amplitude;
}
void common_hal_synthio_note_set_bend_depth(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, -1, 1, MP_QSTR_bend_depth);
self->bend_descr.amplitude = val;
self->bend_state.amplitude_scaled = round_float_to_int(val * 32768);
}
mp_float_t common_hal_synthio_note_get_bend_rate(synthio_note_obj_t *self) {
return self->bend_descr.frequency;
}
synthio_bend_mode_t common_hal_synthio_note_get_bend_mode(synthio_note_obj_t *self) {
return self->bend_mode;
}
void common_hal_synthio_note_set_bend_mode(synthio_note_obj_t *self, synthio_bend_mode_t value) {
self->bend_mode = value;
}
void common_hal_synthio_note_set_bend_rate(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 60, MP_QSTR_bend_rate);
self->bend_descr.frequency = val;
if (self->sample_rate != 0) {
self->bend_state.dds = synthio_frequency_convert_float_to_dds(val * 65536, self->sample_rate);
}
}
mp_obj_t common_hal_synthio_note_get_envelope_obj(synthio_note_obj_t *self) {
return self->envelope_obj;
}
void common_hal_synthio_note_set_envelope(synthio_note_obj_t *self, mp_obj_t envelope_in) {
if (envelope_in != mp_const_none) {
mp_arg_validate_type(envelope_in, (mp_obj_type_t *)&synthio_envelope_type_obj, MP_QSTR_envelope);
if (self->sample_rate != 0) {
synthio_envelope_definition_set(&self->envelope_def, envelope_in, self->sample_rate);
}
}
self->envelope_obj = envelope_in;
}
mp_obj_t common_hal_synthio_note_get_waveform_obj(synthio_note_obj_t *self) {
return self->waveform_obj;
}
void common_hal_synthio_note_set_waveform(synthio_note_obj_t *self, mp_obj_t waveform_in) {
if (waveform_in == mp_const_none) {
memset(&self->waveform_buf, 0, sizeof(self->waveform_buf));
} else {
mp_buffer_info_t bufinfo_waveform;
synthio_synth_parse_waveform(&bufinfo_waveform, waveform_in);
self->waveform_buf = bufinfo_waveform;
}
self->waveform_obj = waveform_in;
}
mp_obj_t common_hal_synthio_note_get_ring_waveform_obj(synthio_note_obj_t *self) {
return self->ring_waveform_obj;
}
void common_hal_synthio_note_set_ring_waveform(synthio_note_obj_t *self, mp_obj_t ring_waveform_in) {
if (ring_waveform_in == mp_const_none) {
memset(&self->ring_waveform_buf, 0, sizeof(self->ring_waveform_buf));
} else {
mp_buffer_info_t bufinfo_ring_waveform;
synthio_synth_parse_waveform(&bufinfo_ring_waveform, ring_waveform_in);
self->ring_waveform_buf = bufinfo_ring_waveform;
}
self->ring_waveform_obj = ring_waveform_in;
}
void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate) {
if (sample_rate == self->sample_rate) {
return;
}
self->sample_rate = sample_rate;
if (self->envelope_obj != mp_const_none) {
synthio_envelope_definition_set(&self->envelope_def, self->envelope_obj, sample_rate);
}
synthio_lfo_set(&self->tremolo_state, &self->tremolo_descr, sample_rate);
self->tremolo_state.offset_scaled = 32768 - self->tremolo_state.amplitude_scaled;
synthio_lfo_set(&self->bend_state, &self->bend_descr, sample_rate);
self->bend_state.offset_scaled = 32768;
}
void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate) {
synthio_note_recalculate(self, sample_rate);
if (self->bend_mode != SYNTHIO_BEND_MODE_VIBRATO) {
self->bend_state.phase = 0;
}
}
uint32_t synthio_note_envelope(synthio_note_obj_t *self) {
return self->amplitude_scaled;
}
// Perform a pitch bend operation
//
// bend_value is in the range [0, 65535]. "no change" is 32768. The bend unit is 32768/octave.
//
// compare to (frequency_scaled * pow(2, (bend_value-32768)/32768))
// a 13-entry pitch table
#define BEND_SCALE (32768)
#define BEND_OFFSET (BEND_SCALE)
STATIC uint16_t pitch_bend_table[] = { 0, 1948, 4013, 6200, 8517, 10972, 13573, 16329, 19248, 22341, 25618, 29090, 32768 };
STATIC uint32_t pitch_bend(uint32_t frequency_scaled, uint16_t bend_value) {
bool down = (bend_value < 32768);
if (!down) {
bend_value -= 32768;
}
uint32_t bend_value_semitone = (uint32_t)bend_value * 24; // 65536/semitone
uint32_t semitone = bend_value_semitone >> 16;
uint32_t fractone = bend_value_semitone & 0xffff;
uint32_t f_lo = pitch_bend_table[semitone];
uint32_t f_hi = pitch_bend_table[semitone + 1]; // table has 13 entries, indexing with semitone=12 is OK
uint32_t f = ((f_lo * (65535 - fractone) + f_hi * fractone) >> 16) + BEND_OFFSET;
return (frequency_scaled * (uint64_t)f) >> (15 + down);
}
STATIC int synthio_bend_value(synthio_note_obj_t *self, int16_t dur) {
switch (self->bend_mode) {
case SYNTHIO_BEND_MODE_STATIC:
return self->bend_state.amplitude_scaled + self->bend_state.offset_scaled;
case SYNTHIO_BEND_MODE_VIBRATO:
return synthio_lfo_step(&self->bend_state, dur);
case SYNTHIO_BEND_MODE_SWEEP:
return synthio_sweep_step(&self->bend_state, dur);
case SYNTHIO_BEND_MODE_SWEEP_IN:
return synthio_sweep_in_step(&self->bend_state, dur);
default:
return 32768;
}
}
uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t loudness[2]) {
int tremolo_value = synthio_lfo_step(&self->tremolo_state, dur);
loudness[0] = (((loudness[0] * tremolo_value) >> 15) * self->left_panning_scaled) >> 15;
loudness[1] = (((loudness[1] * tremolo_value) >> 15) * self->right_panning_scaled) >> 15;
int bend_value = synthio_bend_value(self, dur);
uint32_t frequency_scaled = pitch_bend(self->frequency_scaled, bend_value);
return frequency_scaled;
}

View File

@ -0,0 +1,59 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2023 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.
*/
#pragma once
#include "shared-module/synthio/__init__.h"
#include "shared-bindings/synthio/__init__.h"
typedef struct synthio_note_obj {
mp_obj_base_t base;
mp_float_t frequency, ring_frequency;
mp_float_t panning;
mp_obj_t waveform_obj, envelope_obj, ring_waveform_obj;
int32_t sample_rate;
int32_t frequency_scaled;
int32_t ring_frequency_scaled;
int32_t amplitude_scaled;
int32_t left_panning_scaled, right_panning_scaled;
bool filter;
synthio_bend_mode_t bend_mode;
synthio_lfo_descr_t tremolo_descr, bend_descr;
synthio_lfo_state_t tremolo_state, bend_state;
mp_buffer_info_t waveform_buf;
mp_buffer_info_t ring_waveform_buf;
synthio_envelope_definition_t envelope_def;
} synthio_note_obj_t;
void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate);
uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t loudness[2]);
void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate);
bool synthio_note_playing(synthio_note_obj_t *self);
uint32_t synthio_note_envelope(synthio_note_obj_t *self);

View File

@ -26,14 +26,16 @@
#include "py/runtime.h" #include "py/runtime.h"
#include "shared-bindings/synthio/Synthesizer.h" #include "shared-bindings/synthio/Synthesizer.h"
#include "shared-bindings/synthio/Note.h"
#include "shared-module/synthio/Note.h"
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self, void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length, uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj,
mp_obj_t envelope) { mp_obj_t envelope_obj) {
synthio_synth_init(&self->synth, sample_rate, waveform, waveform_length, envelope); synthio_synth_init(&self->synth, sample_rate, channel_count, waveform_obj, filter_obj, envelope_obj);
} }
void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self) { void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self) {
@ -50,7 +52,7 @@ uint8_t common_hal_synthio_synthesizer_get_bits_per_sample(synthio_synthesizer_o
return SYNTHIO_BITS_PER_SAMPLE; return SYNTHIO_BITS_PER_SAMPLE;
} }
uint8_t common_hal_synthio_synthesizer_get_channel_count(synthio_synthesizer_obj_t *self) { uint8_t common_hal_synthio_synthesizer_get_channel_count(synthio_synthesizer_obj_t *self) {
return 1; return self->synth.channel_count;
} }
void synthio_synthesizer_reset_buffer(synthio_synthesizer_obj_t *self, void synthio_synthesizer_reset_buffer(synthio_synthesizer_obj_t *self,
@ -60,6 +62,10 @@ void synthio_synthesizer_reset_buffer(synthio_synthesizer_obj_t *self,
audioio_get_buffer_result_t synthio_synthesizer_get_buffer(synthio_synthesizer_obj_t *self, audioio_get_buffer_result_t synthio_synthesizer_get_buffer(synthio_synthesizer_obj_t *self,
bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) { bool single_channel_output, uint8_t channel, uint8_t **buffer, uint32_t *buffer_length) {
if (common_hal_synthio_synthesizer_deinited(self)) {
*buffer_length = 0;
return GET_BUFFER_ERROR;
}
self->synth.span.dur = SYNTHIO_MAX_DUR; self->synth.span.dur = SYNTHIO_MAX_DUR;
synthio_synth_synthesize(&self->synth, buffer, buffer_length, single_channel_output ? channel : 0); synthio_synth_synthesize(&self->synth, buffer, buffer_length, single_channel_output ? channel : 0);
return GET_BUFFER_MORE_DATA; return GET_BUFFER_MORE_DATA;
@ -72,40 +78,58 @@ void synthio_synthesizer_get_buffer_structure(synthio_synthesizer_obj_t *self, b
void common_hal_synthio_synthesizer_release_all(synthio_synthesizer_obj_t *self) { void common_hal_synthio_synthesizer_release_all(synthio_synthesizer_obj_t *self) {
for (size_t i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { for (size_t i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
if (self->synth.span.note[i] != SYNTHIO_SILENCE) { if (self->synth.span.note_obj[i] != SYNTHIO_SILENCE) {
synthio_span_change_note(&self->synth, self->synth.span.note[i], SYNTHIO_SILENCE); synthio_span_change_note(&self->synth, self->synth.span.note_obj[i], SYNTHIO_SILENCE);
} }
} }
} }
STATIC mp_obj_t validate_note(mp_obj_t note_in) {
if (mp_obj_is_small_int(note_in)) {
mp_arg_validate_int_range(mp_obj_get_int(note_in), 0, 127, MP_QSTR_note);
} else {
const mp_obj_type_t *note_type = mp_obj_get_type(note_in);
if (note_type != &synthio_note_type) {
mp_raise_TypeError_varg(translate("%q must be of type %q or %q, not %q"), MP_QSTR_note, MP_QSTR_int, MP_QSTR_Note, note_type->name);
}
}
return note_in;
}
void common_hal_synthio_synthesizer_release(synthio_synthesizer_obj_t *self, mp_obj_t to_release) { void common_hal_synthio_synthesizer_release(synthio_synthesizer_obj_t *self, mp_obj_t to_release) {
mp_obj_iter_buf_t iter_buf; mp_obj_iter_buf_t iter_buf;
mp_obj_t iterable = mp_getiter(to_release, &iter_buf); mp_obj_t iterable = mp_getiter(to_release, &iter_buf);
mp_obj_t item; mp_obj_t item;
while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
synthio_span_change_note(&self->synth, mp_arg_validate_int_range(mp_obj_get_int(item), 0, 127, MP_QSTR_note), SYNTHIO_SILENCE); synthio_span_change_note(&self->synth, validate_note(item), SYNTHIO_SILENCE);
} }
} }
void common_hal_synthio_synthesizer_press(synthio_synthesizer_obj_t *self, mp_obj_t to_press) { void common_hal_synthio_synthesizer_press(synthio_synthesizer_obj_t *self, mp_obj_t to_press) {
mp_obj_iter_buf_t iter_buf; mp_obj_iter_buf_t iter_buf;
mp_obj_t iterable = mp_getiter(to_press, &iter_buf); mp_obj_t iterable = mp_getiter(to_press, &iter_buf);
mp_obj_t item; mp_obj_t note_obj;
while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { while ((note_obj = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, mp_arg_validate_int_range(mp_obj_get_int(item), 0, 127, MP_QSTR_note)); note_obj = validate_note(note_obj);
if (!mp_obj_is_small_int(note_obj)) {
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
synthio_note_start(note, self->synth.sample_rate);
}
synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, note_obj);
} }
} }
mp_obj_t common_hal_synthio_synthesizer_get_pressed_notes(synthio_synthesizer_obj_t *self) { mp_obj_t common_hal_synthio_synthesizer_get_pressed_notes(synthio_synthesizer_obj_t *self) {
int count = 0; int count = 0;
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
if (self->synth.span.note[chan] != SYNTHIO_SILENCE && self->synth.envelope_state[chan].state != SYNTHIO_ENVELOPE_STATE_RELEASE) { if (self->synth.span.note_obj[chan] != SYNTHIO_SILENCE && SYNTHIO_NOTE_IS_PLAYING(&self->synth, chan)) {
count += 1; count += 1;
} }
} }
mp_obj_tuple_t *result = MP_OBJ_TO_PTR(mp_obj_new_tuple(count, NULL)); mp_obj_tuple_t *result = MP_OBJ_TO_PTR(mp_obj_new_tuple(count, NULL));
for (size_t chan = 0, j = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { for (size_t chan = 0, j = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
if (self->synth.span.note[chan] != SYNTHIO_SILENCE && self->synth.envelope_state[chan].state != SYNTHIO_ENVELOPE_STATE_RELEASE) { if (self->synth.span.note_obj[chan] != SYNTHIO_SILENCE && SYNTHIO_NOTE_IS_PLAYING(&self->synth, chan)) {
result->items[j++] = MP_OBJ_NEW_SMALL_INT(self->synth.span.note[chan]); result->items[j++] = self->synth.span.note_obj[chan];
} }
} }
return MP_OBJ_FROM_PTR(result); return MP_OBJ_FROM_PTR(result);

View File

@ -27,6 +27,7 @@
#include "shared-module/synthio/__init__.h" #include "shared-module/synthio/__init__.h"
#include "shared-bindings/synthio/__init__.h" #include "shared-bindings/synthio/__init__.h"
#include "shared-module/synthio/Note.h"
#include "py/runtime.h" #include "py/runtime.h"
#include <math.h> #include <math.h>
#include <stdlib.h> #include <stdlib.h>
@ -36,17 +37,33 @@ STATIC const int16_t square_wave[] = {-32768, 32767};
STATIC const uint16_t notes[] = {8372, 8870, 9397, 9956, 10548, 11175, 11840, STATIC const uint16_t notes[] = {8372, 8870, 9397, 9956, 10548, 11175, 11840,
12544, 13290, 14080, 14917, 15804}; // 9th octave 12544, 13290, 14080, 14917, 15804}; // 9th octave
STATIC int32_t round_float_to_int(mp_float_t f) {
return (int32_t)(f + MICROPY_FLOAT_CONST(0.5));
}
STATIC int64_t round_float_to_int64(mp_float_t f) {
return (int64_t)(f + MICROPY_FLOAT_CONST(0.5));
}
mp_float_t common_hal_synthio_midi_to_hz_float(mp_float_t arg) {
return common_hal_synthio_onevo_to_hz_float(arg / 12.);
}
mp_float_t common_hal_synthio_onevo_to_hz_float(mp_float_t octave) {
return notes[0] * MICROPY_FLOAT_C_FUN(pow)(2., octave - 10);
}
STATIC int16_t convert_time_to_rate(uint32_t sample_rate, mp_obj_t time_in, int16_t difference) { STATIC int16_t convert_time_to_rate(uint32_t sample_rate, mp_obj_t time_in, int16_t difference) {
mp_float_t time = mp_obj_get_float(time_in); mp_float_t time = mp_obj_get_float(time_in);
int num_samples = (int)MICROPY_FLOAT_C_FUN(round)(time * sample_rate); int num_samples = (int)MICROPY_FLOAT_C_FUN(round)(time * sample_rate);
if (num_samples == 0) { if (num_samples == 0) {
return 0; return 32767;
} }
int16_t result = MIN(32767, MAX(1, abs(difference * SYNTHIO_MAX_DUR) / num_samples)); int16_t result = MIN(32767, MAX(1, abs(difference * SYNTHIO_MAX_DUR) / num_samples));
return (difference < 0) ? -result : result; return (difference < 0) ? -result : result;
} }
STATIC void synthio_envelope_definition_set(synthio_envelope_definition_t *envelope, mp_obj_t obj, uint32_t sample_rate) { void synthio_envelope_definition_set(synthio_envelope_definition_t *envelope, mp_obj_t obj, uint32_t sample_rate) {
if (obj == mp_const_none) { if (obj == mp_const_none) {
envelope->attack_level = 32767; envelope->attack_level = 32767;
envelope->sustain_level = 32767; envelope->sustain_level = 32767;
@ -62,7 +79,7 @@ STATIC void synthio_envelope_definition_set(synthio_envelope_definition_t *envel
mp_obj_tuple_get(obj, &len, &fields); mp_obj_tuple_get(obj, &len, &fields);
envelope->attack_level = (int)(32767 * mp_obj_get_float(fields[3])); envelope->attack_level = (int)(32767 * mp_obj_get_float(fields[3]));
envelope->sustain_level = (int)(32767 * mp_obj_get_float(fields[4])); envelope->sustain_level = (int)(32767 * mp_obj_get_float(fields[4]) * mp_obj_get_float(fields[3]));
envelope->attack_step = convert_time_to_rate( envelope->attack_step = convert_time_to_rate(
sample_rate, fields[0], envelope->attack_level); sample_rate, fields[0], envelope->attack_level);
@ -72,7 +89,7 @@ STATIC void synthio_envelope_definition_set(synthio_envelope_definition_t *envel
envelope->release_step = -convert_time_to_rate( envelope->release_step = -convert_time_to_rate(
sample_rate, fields[2], sample_rate, fields[2],
envelope->decay_step envelope->sustain_level
? envelope->sustain_level ? envelope->sustain_level
: envelope->attack_level); : envelope->attack_level);
} }
@ -87,34 +104,19 @@ STATIC void synthio_envelope_state_step(synthio_envelope_state_t *state, synthio
case SYNTHIO_ENVELOPE_STATE_SUSTAIN: case SYNTHIO_ENVELOPE_STATE_SUSTAIN:
break; break;
case SYNTHIO_ENVELOPE_STATE_ATTACK: case SYNTHIO_ENVELOPE_STATE_ATTACK:
if (def->attack_step != 0) { state->level = MIN(state->level + def->attack_step, def->attack_level);
state->level = MIN(state->level + def->attack_step, def->attack_level); if (state->level == def->attack_level) {
if (state->level == def->attack_level) { state->state = SYNTHIO_ENVELOPE_STATE_DECAY;
state->state = SYNTHIO_ENVELOPE_STATE_DECAY;
}
break;
}
state->state = SYNTHIO_ENVELOPE_STATE_DECAY;
MP_FALLTHROUGH;
case SYNTHIO_ENVELOPE_STATE_DECAY:
if (def->decay_step != 0) {
state->level = MAX(state->level + def->decay_step, def->sustain_level);
assert(state->level >= 0);
if (state->level == def->sustain_level) {
state->state = SYNTHIO_ENVELOPE_STATE_SUSTAIN;
}
break;
}
state->state = SYNTHIO_ENVELOPE_STATE_RELEASE;
MP_FALLTHROUGH;
case SYNTHIO_ENVELOPE_STATE_RELEASE:
if (def->release_step != 0) {
int delta = def->release_step;
state->level = MAX(state->level + delta, 0);
} else {
state->level = 0;
} }
break; break;
case SYNTHIO_ENVELOPE_STATE_DECAY:
state->level = MAX(state->level + def->decay_step, def->sustain_level);
if (state->level == def->sustain_level) {
state->state = SYNTHIO_ENVELOPE_STATE_SUSTAIN;
}
break;
case SYNTHIO_ENVELOPE_STATE_RELEASE:
state->level = MAX(state->level + def->release_step, 0);
} }
} }
} }
@ -131,18 +133,215 @@ STATIC void synthio_envelope_state_release(synthio_envelope_state_t *state, synt
state->state = SYNTHIO_ENVELOPE_STATE_RELEASE; state->state = SYNTHIO_ENVELOPE_STATE_RELEASE;
} }
STATIC synthio_envelope_definition_t *synthio_synth_get_note_envelope(synthio_synth_t *synth, mp_obj_t note_obj) {
STATIC uint32_t synthio_synth_sum_envelope(synthio_synth_t *synth) { synthio_envelope_definition_t *def = &synth->global_envelope_definition;
uint32_t result = 0; if (!mp_obj_is_small_int(note_obj)) {
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
if (synth->span.note[chan] != SYNTHIO_SILENCE) { if (note->envelope_obj != mp_const_none) {
result += synth->envelope_state[chan].level; def = &note->envelope_def;
} }
} }
return result; return def;
} }
#define RANGE_LOW (-28000)
#define RANGE_HIGH (28000)
#define RANGE_SHIFT (16)
#define RANGE_SCALE (0xfffffff / (32768 * CIRCUITPY_SYNTHIO_MAX_CHANNELS - RANGE_HIGH))
// dynamic range compression via a downward compressor with hard knee
//
// When the output value is within the range +-28000 (about 85% of full scale),
// it is unchanged. Otherwise, it undergoes a gain reduction so that the
// largest possible values, (+32768,-32767) * CIRCUITPY_SYNTHIO_MAX_CHANNELS,
// still fit within the output range
//
// This produces a much louder overall volume with multiple voices, without
// much additional processing.
//
// https://en.wikipedia.org/wiki/Dynamic_range_compression
STATIC
int16_t mix_down_sample(int32_t sample) {
if (sample < RANGE_LOW) {
sample = (((sample - RANGE_LOW) * RANGE_SCALE) >> RANGE_SHIFT) + RANGE_LOW;
} else if (sample > RANGE_HIGH) {
sample = (((sample - RANGE_HIGH) * RANGE_SCALE) >> RANGE_SHIFT) + RANGE_HIGH;
}
return sample;
}
static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *out_buffer32, int16_t dur) {
mp_obj_t note_obj = synth->span.note_obj[chan];
if (note_obj == SYNTHIO_SILENCE) {
synth->accum[chan] = 0;
return;
}
if (synth->envelope_state[chan].level == 0) {
// note is truly finished, but we only just noticed
synth->span.note_obj[chan] = SYNTHIO_SILENCE;
return;
}
int32_t sample_rate = synth->sample_rate;
// adjust loudness by envelope
uint16_t loudness[2] = {synth->envelope_state[chan].level,synth->envelope_state[chan].level};
uint32_t dds_rate;
const int16_t *waveform = synth->waveform_bufinfo.buf;
uint32_t waveform_length = synth->waveform_bufinfo.len / sizeof(int16_t);
uint32_t ring_dds_rate = 0;
const int16_t *ring_waveform = NULL;
uint32_t ring_waveform_length = 0;
if (mp_obj_is_small_int(note_obj)) {
uint8_t note = mp_obj_get_int(note_obj);
uint8_t octave = note / 12;
uint16_t base_freq = notes[note % 12];
// rate = base_freq * waveform_length
// den = sample_rate * 2 ^ (10 - octave)
// den = sample_rate * 2 ^ 10 / 2^octave
// dds_rate = 2^SHIFT * rate / den
// dds_rate = 2^(SHIFT-10+octave) * base_freq * waveform_length / sample_rate
dds_rate = (sample_rate / 2 + ((uint64_t)(base_freq * waveform_length) << (SYNTHIO_FREQUENCY_SHIFT - 10 + octave))) / sample_rate;
} else {
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
int32_t frequency_scaled = synthio_note_step(note, sample_rate, dur, loudness);
if (note->waveform_buf.buf) {
waveform = note->waveform_buf.buf;
waveform_length = note->waveform_buf.len / sizeof(int16_t);
}
dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)frequency_scaled * waveform_length, sample_rate);
if (note->ring_frequency_scaled != 0 && note->ring_waveform_buf.buf) {
ring_waveform = note->ring_waveform_buf.buf;
ring_waveform_length = note->ring_waveform_buf.len / sizeof(int16_t);
ring_dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)note->ring_frequency_scaled * ring_waveform_length, sample_rate);
uint32_t lim = ring_waveform_length << SYNTHIO_FREQUENCY_SHIFT;
if (ring_dds_rate > lim / sizeof(int16_t)) {
ring_dds_rate = 0; // can't ring at that frequency
}
}
}
int synth_chan = synth->channel_count;
if (ring_dds_rate) {
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
uint32_t accum = synth->accum[chan];
if (dds_rate > lim / 2) {
// beyond nyquist, can't play note
return;
}
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
if (accum > lim) {
accum %= lim;
}
int32_t ring_buffer[dur];
// first, fill with waveform
for (uint16_t i = 0; i < dur; i++) {
accum += dds_rate;
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
if (accum > lim) {
accum -= lim;
}
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
ring_buffer[i] = waveform[idx];
}
synth->accum[chan] = accum;
// now modulate by ring and accumulate
accum = synth->ring_accum[chan];
lim = ring_waveform_length << SYNTHIO_FREQUENCY_SHIFT;
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
if (accum > lim) {
accum %= lim;
}
for (uint16_t i = 0, j = 0; i < dur; i++) {
accum += ring_dds_rate;
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
if (accum > lim) {
accum -= lim;
}
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
int16_t wi = (ring_waveform[idx] * ring_buffer[i]) / 32768;
for (int c = 0; c < synth_chan; c++) {
out_buffer32[j] += (wi * loudness[c]) / 32768;
j++;
}
}
synth->ring_accum[chan] = accum;
} else {
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
uint32_t accum = synth->accum[chan];
if (dds_rate > lim / 2) {
// beyond nyquist, can't play note
return;
}
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
if (accum > lim) {
accum %= lim;
}
for (uint16_t i = 0, j = 0; i < dur; i++) {
accum += dds_rate;
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
if (accum > lim) {
accum -= lim;
}
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
int16_t wi = waveform[idx];
for (int c = 0; c < synth_chan; c++) {
out_buffer32[j] += (wi * loudness[c]) / 65536;
j++;
}
}
synth->accum[chan] = accum;
}
}
STATIC void run_fir(synthio_synth_t *synth, int32_t *out_buffer32, uint16_t dur) {
int16_t *coeff = (int16_t *)synth->filter_bufinfo.buf;
size_t fir_len = synth->filter_bufinfo.len / sizeof(int16_t);
int32_t *in_buf = synth->filter_buffer;
int synth_chan = synth->channel_count;
// FIR and copy values to output buffer
for (int16_t i = 0; i < dur * synth_chan; i++) {
int32_t acc = 0;
for (size_t j = 0; j < fir_len; j++) {
// shift 5 here is good for up to 32 filtered voices, else might wrap
acc = acc + (in_buf[j * synth_chan] * (coeff[j] >> 5));
}
*out_buffer32++ = acc >> 10;
in_buf++;
}
// Move values down so that they get filtered next time
memmove(synth->filter_buffer, &synth->filter_buffer[dur * synth_chan], fir_len * sizeof(int32_t) * synth_chan);
}
STATIC bool synthio_synth_get_note_filtered(mp_obj_t note_obj) {
if (note_obj == mp_const_none) {
return false;
}
if (!mp_obj_is_small_int(note_obj)) {
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
return note->filter;
}
return true;
}
void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t *buffer_length, uint8_t channel) { void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t *buffer_length, uint8_t channel) {
if (channel == synth->other_channel) { if (channel == synth->other_channel) {
@ -154,60 +353,56 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
synth->buffer_index = !synth->buffer_index; synth->buffer_index = !synth->buffer_index;
synth->other_channel = 1 - channel; synth->other_channel = 1 - channel;
synth->other_buffer_index = synth->buffer_index; synth->other_buffer_index = synth->buffer_index;
int16_t *out_buffer = (int16_t *)(void *)synth->buffers[synth->buffer_index];
uint16_t dur = MIN(SYNTHIO_MAX_DUR, synth->span.dur); uint16_t dur = MIN(SYNTHIO_MAX_DUR, synth->span.dur);
synth->span.dur -= dur; synth->span.dur -= dur;
memset(out_buffer, 0, synth->buffer_length);
int32_t sample_rate = synth->sample_rate; int32_t out_buffer32[dur * synth->channel_count];
uint32_t total_envelope = synthio_synth_sum_envelope(synth);
const int16_t *waveform = synth->waveform; if (synth->filter_buffer) {
uint32_t waveform_length = synth->waveform_length; int32_t *filter_start = &synth->filter_buffer[synth->filter_bufinfo.len * synth->channel_count / sizeof(int16_t)];
if (total_envelope > 0) { memset(filter_start, 0, dur * synth->channel_count * sizeof(int32_t));
uint16_t ovl_loudness = 0x7fffffff / MAX(0x8000, total_envelope);
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
if (synth->span.note[chan] == SYNTHIO_SILENCE) { mp_obj_t note_obj = synth->span.note_obj[chan];
synth->accum[chan] = 0; if (!synthio_synth_get_note_filtered(note_obj)) {
continue; continue;
} }
// adjust loudness by envelope synth_note_into_buffer(synth, chan, filter_start, dur);
uint16_t loudness = (ovl_loudness * synth->envelope_state[chan].level) >> 16;
if (synth->envelope_state[chan].level == 0) {
// note is truly finished
synth->span.note[chan] = SYNTHIO_SILENCE;
}
uint8_t octave = synth->span.note[chan] / 12;
uint16_t base_freq = notes[synth->span.note[chan] % 12];
uint32_t accum = synth->accum[chan];
#define SHIFT (16)
// rate = base_freq * waveform_length
// den = sample_rate * 2 ^ (10 - octave)
// den = sample_rate * 2 ^ 10 / 2^octave
// dds_rate = 2^SHIFT * rate / den
// dds_rate = 2^(SHIFT-10+octave) * base_freq * waveform_length / sample_rate
uint32_t dds_rate = (sample_rate / 2 + ((uint64_t)(base_freq * waveform_length) << (SHIFT - 10 + octave))) / sample_rate;
for (uint16_t i = 0; i < dur; i++) {
accum += dds_rate;
if (accum > waveform_length << SHIFT) {
accum -= waveform_length << SHIFT;
}
int16_t idx = accum >> SHIFT;
out_buffer[i] += (waveform[idx] * loudness) / 65536;
}
synth->accum[chan] = accum;
} }
run_fir(synth, out_buffer32, dur);
} else {
memset(out_buffer32, 0, sizeof(out_buffer32));
}
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
mp_obj_t note_obj = synth->span.note_obj[chan];
if (synth->filter_buffer && synthio_synth_get_note_filtered(note_obj)) {
continue;
}
synth_note_into_buffer(synth, chan, out_buffer32, dur);
}
int16_t *out_buffer16 = (int16_t *)(void *)synth->buffers[synth->buffer_index];
// mix down audio
for (size_t i = 0; i < MP_ARRAY_SIZE(out_buffer32); i++) {
int32_t sample = out_buffer32[i];
out_buffer16[i] = mix_down_sample(sample);
} }
// advance envelope states // advance envelope states
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
synthio_envelope_state_step(&synth->envelope_state[chan], &synth->envelope_definition, dur); mp_obj_t note_obj = synth->span.note_obj[chan];
if (note_obj == SYNTHIO_SILENCE) {
continue;
}
synthio_envelope_state_step(&synth->envelope_state[chan], synthio_synth_get_note_envelope(synth, note_obj), dur);
} }
*buffer_length = synth->last_buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE; *buffer_length = synth->last_buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE * synth->channel_count;
*bufptr = (uint8_t *)out_buffer; *bufptr = (uint8_t *)out_buffer16;
} }
void synthio_synth_reset_buffer(synthio_synth_t *synth, bool single_channel_output, uint8_t channel) { void synthio_synth_reset_buffer(synthio_synth_t *synth, bool single_channel_output, uint8_t channel) {
@ -222,14 +417,13 @@ bool synthio_synth_deinited(synthio_synth_t *synth) {
} }
void synthio_synth_deinit(synthio_synth_t *synth) { void synthio_synth_deinit(synthio_synth_t *synth) {
m_del(uint8_t, synth->buffers[0], synth->buffer_length); synth->filter_buffer = NULL;
m_del(uint8_t, synth->buffers[1], synth->buffer_length);
synth->buffers[0] = NULL; synth->buffers[0] = NULL;
synth->buffers[1] = NULL; synth->buffers[1] = NULL;
} }
void synthio_synth_envelope_set(synthio_synth_t *synth, mp_obj_t envelope_obj) { void synthio_synth_envelope_set(synthio_synth_t *synth, mp_obj_t envelope_obj) {
synthio_envelope_definition_set(&synth->envelope_definition, envelope_obj, synth->sample_rate); synthio_envelope_definition_set(&synth->global_envelope_definition, envelope_obj, synth->sample_rate);
synth->envelope_obj = envelope_obj; synth->envelope_obj = envelope_obj;
} }
@ -237,18 +431,25 @@ mp_obj_t synthio_synth_envelope_get(synthio_synth_t *synth) {
return synth->envelope_obj; return synth->envelope_obj;
} }
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length, mp_obj_t envelope_obj) { void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj, mp_obj_t envelope_obj) {
synth->buffer_length = SYNTHIO_MAX_DUR * SYNTHIO_BYTES_PER_SAMPLE; synthio_synth_parse_waveform(&synth->waveform_bufinfo, waveform_obj);
synthio_synth_parse_filter(&synth->filter_bufinfo, filter_obj);
mp_arg_validate_int_range(channel_count, 1, 2, MP_QSTR_channel_count);
synth->buffer_length = SYNTHIO_MAX_DUR * SYNTHIO_BYTES_PER_SAMPLE * channel_count;
synth->buffers[0] = m_malloc(synth->buffer_length, false); synth->buffers[0] = m_malloc(synth->buffer_length, false);
synth->buffers[1] = m_malloc(synth->buffer_length, false); synth->buffers[1] = m_malloc(synth->buffer_length, false);
if (synth->filter_bufinfo.len) {
synth->filter_buffer_length = (synth->filter_bufinfo.len / 2 + SYNTHIO_MAX_DUR) * channel_count * sizeof(int32_t);
synth->filter_buffer = m_malloc(synth->filter_buffer_length, false);
}
synth->channel_count = channel_count;
synth->other_channel = -1; synth->other_channel = -1;
synth->waveform = waveform; synth->waveform_obj = waveform_obj;
synth->waveform_length = waveform_length;
synth->sample_rate = sample_rate; synth->sample_rate = sample_rate;
synthio_synth_envelope_set(synth, envelope_obj); synthio_synth_envelope_set(synth, envelope_obj);
for (size_t i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { for (size_t i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
synth->span.note[i] = SYNTHIO_SILENCE; synth->span.note_obj[i] = SYNTHIO_SILENCE;
} }
} }
@ -257,61 +458,132 @@ void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_chan
*single_buffer = false; *single_buffer = false;
*samples_signed = true; *samples_signed = true;
*max_buffer_length = synth->buffer_length; *max_buffer_length = synth->buffer_length;
*spacing = 1; if (single_channel_output) {
*spacing = synth->channel_count;
} else {
*spacing = 1;
}
} }
static bool parse_common(mp_buffer_info_t *bufinfo, mp_obj_t o, int16_t what) { STATIC void parse_common(mp_buffer_info_t *bufinfo, mp_obj_t o, int16_t what, mp_int_t max_len) {
if (o != mp_const_none) { if (o != mp_const_none) {
mp_get_buffer_raise(o, bufinfo, MP_BUFFER_READ); mp_get_buffer_raise(o, bufinfo, MP_BUFFER_READ);
if (bufinfo->typecode != 'h') { if (bufinfo->typecode != 'h') {
mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), what); mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), what);
} }
mp_arg_validate_length_range(bufinfo->len / 2, 2, 1024, what); mp_arg_validate_length_range(bufinfo->len / sizeof(int16_t), 2, max_len, what);
return true;
} }
return false;
} }
void synthio_synth_parse_waveform(mp_buffer_info_t *bufinfo_waveform, mp_obj_t waveform_obj) { void synthio_synth_parse_waveform(mp_buffer_info_t *bufinfo_waveform, mp_obj_t waveform_obj) {
*bufinfo_waveform = ((mp_buffer_info_t) { .buf = (void *)square_wave, .len = 4 }); *bufinfo_waveform = ((mp_buffer_info_t) { .buf = (void *)square_wave, .len = 4 });
parse_common(bufinfo_waveform, waveform_obj, MP_QSTR_waveform); parse_common(bufinfo_waveform, waveform_obj, MP_QSTR_waveform, 16384);
} }
STATIC int find_channel_with_note(synthio_synth_t *synth, uint8_t note) { void synthio_synth_parse_filter(mp_buffer_info_t *bufinfo_filter, mp_obj_t filter_obj) {
*bufinfo_filter = ((mp_buffer_info_t) { .buf = NULL, .len = 0 });
parse_common(bufinfo_filter, filter_obj, MP_QSTR_filter, 128);
}
STATIC int find_channel_with_note(synthio_synth_t *synth, mp_obj_t note) {
for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) {
if (synth->span.note[i] == note) { if (synth->span.note_obj[i] == note) {
return i; return i;
} }
} }
int result = -1;
if (note == SYNTHIO_SILENCE) { if (note == SYNTHIO_SILENCE) {
// we need a victim note that is releasing. simple algorithm: lowest numbered slot // replace the releasing note with lowest volume level
for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { int level = 32768;
if (SYNTHIO_VOICE_IS_RELEASING(synth, i)) { for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
return i; if (!SYNTHIO_NOTE_IS_PLAYING(synth, chan)) {
synthio_envelope_state_t *state = &synth->envelope_state[chan];
if (state->level < level) {
result = chan;
level = state->level;
}
} }
} }
} }
return -1; return result;
} }
bool synthio_span_change_note(synthio_synth_t *synth, uint8_t old_note, uint8_t new_note) { bool synthio_span_change_note(synthio_synth_t *synth, mp_obj_t old_note, mp_obj_t new_note) {
int channel; int channel;
if (new_note != SYNTHIO_SILENCE && (channel = find_channel_with_note(synth, new_note)) != -1) { if (new_note != SYNTHIO_SILENCE && (channel = find_channel_with_note(synth, new_note)) != -1) {
// note already playing, re-strike // note already playing, re-enter attack phase
synthio_envelope_state_init(&synth->envelope_state[channel], &synth->envelope_definition); synth->envelope_state[channel].state = SYNTHIO_ENVELOPE_STATE_ATTACK;
synth->accum[channel] = 0;
return true; return true;
} }
channel = find_channel_with_note(synth, old_note); channel = find_channel_with_note(synth, old_note);
if (channel != -1) { if (channel != -1) {
if (new_note == SYNTHIO_SILENCE) { if (new_note == SYNTHIO_SILENCE) {
synthio_envelope_state_release(&synth->envelope_state[channel], &synth->envelope_definition); synthio_envelope_state_release(&synth->envelope_state[channel], synthio_synth_get_note_envelope(synth, old_note));
} else { } else {
synth->span.note[channel] = new_note; synth->span.note_obj[channel] = new_note;
synthio_envelope_state_init(&synth->envelope_state[channel], &synth->envelope_definition); synthio_envelope_state_init(&synth->envelope_state[channel], synthio_synth_get_note_envelope(synth, new_note));
synth->accum[channel] = 0; synth->accum[channel] = 0;
} }
return true; return true;
} }
return false; return false;
} }
uint64_t synthio_frequency_convert_float_to_scaled(mp_float_t val) {
return round_float_to_int64(val * (1 << SYNTHIO_FREQUENCY_SHIFT));
}
uint32_t synthio_frequency_convert_float_to_dds(mp_float_t frequency_hz, int32_t sample_rate) {
return synthio_frequency_convert_scaled_to_dds(synthio_frequency_convert_float_to_scaled(frequency_hz), sample_rate);
}
uint32_t synthio_frequency_convert_scaled_to_dds(uint64_t frequency_scaled, int32_t sample_rate) {
return (sample_rate / 2 + frequency_scaled) / sample_rate;
}
void synthio_lfo_set(synthio_lfo_state_t *state, const synthio_lfo_descr_t *descr, uint32_t sample_rate) {
state->amplitude_scaled = round_float_to_int(descr->amplitude * 32768);
state->dds = synthio_frequency_convert_float_to_dds(descr->frequency * 65536, sample_rate);
}
STATIC int synthio_lfo_step_common(synthio_lfo_state_t *state, uint16_t dur) {
uint32_t phase = state->phase;
uint16_t whole_phase = phase >> 16;
// advance the phase accumulator
state->phase = phase + state->dds * dur;
return whole_phase;
}
STATIC int synthio_lfo_sweep_common(synthio_lfo_state_t *state, uint16_t dur) {
uint32_t old_phase = state->phase;
uint16_t whole_phase = synthio_lfo_step_common(state, dur);
if (state->phase < old_phase) {
state->phase = 0xffffffff;
}
return whole_phase;
}
int synthio_sweep_step(synthio_lfo_state_t *state, uint16_t dur) {
uint16_t whole_phase = synthio_lfo_sweep_common(state, dur);
return (state->amplitude_scaled * whole_phase) / 65536 + state->offset_scaled;
}
int synthio_sweep_in_step(synthio_lfo_state_t *state, uint16_t dur) {
uint16_t whole_phase = 65535 - synthio_lfo_sweep_common(state, dur);
return (state->amplitude_scaled * whole_phase) / 65536 + state->offset_scaled;
}
int synthio_lfo_step(synthio_lfo_state_t *state, uint16_t dur) {
uint16_t whole_phase = synthio_lfo_step_common(state, dur);
// create a triangle wave, it's quick and easy
int v;
if (whole_phase < 16384) { // ramp from 0 to amplitude
v = (state->amplitude_scaled * whole_phase);
} else if (whole_phase < 49152) { // ramp from +amplitude to -amplitude
v = (state->amplitude_scaled * (32768 - whole_phase));
} else { // from -amplitude to 0
v = (state->amplitude_scaled * (whole_phase - 65536));
}
return v / 16384 + state->offset_scaled;
}

View File

@ -29,14 +29,16 @@
#define SYNTHIO_BITS_PER_SAMPLE (16) #define SYNTHIO_BITS_PER_SAMPLE (16)
#define SYNTHIO_BYTES_PER_SAMPLE (SYNTHIO_BITS_PER_SAMPLE / 8) #define SYNTHIO_BYTES_PER_SAMPLE (SYNTHIO_BITS_PER_SAMPLE / 8)
#define SYNTHIO_MAX_DUR (256) #define SYNTHIO_MAX_DUR (256)
#define SYNTHIO_SILENCE (0x80) #define SYNTHIO_SILENCE (mp_const_none)
#define SYNTHIO_VOICE_IS_RELEASING(synth, i) (synth->envelope_state[i].state == SYNTHIO_ENVELOPE_STATE_RELEASE) #define SYNTHIO_NOTE_IS_SIMPLE(note) (mp_obj_is_small_int(note))
#define SYNTHIO_NOTE_IS_PLAYING(synth, i) ((synth)->envelope_state[(i)].state != SYNTHIO_ENVELOPE_STATE_RELEASE)
#define SYNTHIO_FREQUENCY_SHIFT (16)
#include "shared-module/audiocore/__init__.h" #include "shared-module/audiocore/__init__.h"
typedef struct { typedef struct {
uint16_t dur; uint16_t dur;
uint8_t note[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; mp_obj_t note_obj[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
} synthio_midi_span_t; } synthio_midi_span_t;
typedef struct { typedef struct {
@ -60,30 +62,53 @@ typedef struct {
typedef struct synthio_synth { typedef struct synthio_synth {
uint32_t sample_rate; uint32_t sample_rate;
uint32_t total_envelope;
int16_t *buffers[2]; int16_t *buffers[2];
const int16_t *waveform; int32_t *filter_buffer;
uint16_t buffer_length; uint8_t channel_count;
uint16_t buffer_length, filter_buffer_length;
uint16_t last_buffer_length; uint16_t last_buffer_length;
uint8_t other_channel, buffer_index, other_buffer_index; uint8_t other_channel, buffer_index, other_buffer_index;
uint16_t waveform_length; mp_buffer_info_t waveform_bufinfo, filter_bufinfo;
synthio_envelope_definition_t envelope_definition; synthio_envelope_definition_t global_envelope_definition;
mp_obj_t envelope_obj; mp_obj_t waveform_obj, filter_obj, envelope_obj;
synthio_midi_span_t span; synthio_midi_span_t span;
uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
uint32_t ring_accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
synthio_envelope_state_t envelope_state[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; synthio_envelope_state_t envelope_state[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
} synthio_synth_t; } synthio_synth_t;
typedef struct {
mp_float_t amplitude, frequency;
} synthio_lfo_descr_t;
typedef struct {
int32_t amplitude_scaled;
uint32_t offset_scaled, dds, phase;
} synthio_lfo_state_t;
void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t *buffer_length, uint8_t channel); void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t *buffer_length, uint8_t channel);
void synthio_synth_deinit(synthio_synth_t *synth); void synthio_synth_deinit(synthio_synth_t *synth);
bool synthio_synth_deinited(synthio_synth_t *synth); bool synthio_synth_deinited(synthio_synth_t *synth);
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length, void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj, mp_obj_t envelope);
mp_obj_t envelope);
void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_channel_output, void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_channel_output,
bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing); bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing);
void synthio_synth_reset_buffer(synthio_synth_t *synth, bool single_channel_output, uint8_t channel); void synthio_synth_reset_buffer(synthio_synth_t *synth, bool single_channel_output, uint8_t channel);
void synthio_synth_parse_waveform(mp_buffer_info_t *bufinfo_waveform, mp_obj_t waveform_obj); void synthio_synth_parse_waveform(mp_buffer_info_t *bufinfo_waveform, mp_obj_t waveform_obj);
void synthio_synth_parse_filter(mp_buffer_info_t *bufinfo_filter, mp_obj_t filter_obj);
void synthio_synth_parse_envelope(uint16_t *envelope_sustain_index, mp_buffer_info_t *bufinfo_envelope, mp_obj_t envelope_obj, mp_obj_t envelope_hold_obj); void synthio_synth_parse_envelope(uint16_t *envelope_sustain_index, mp_buffer_info_t *bufinfo_envelope, mp_obj_t envelope_obj, mp_obj_t envelope_hold_obj);
bool synthio_span_change_note(synthio_synth_t *synth, uint8_t old_note, uint8_t new_note); bool synthio_span_change_note(synthio_synth_t *synth, mp_obj_t old_note, mp_obj_t new_note);
void synthio_envelope_step(synthio_envelope_definition_t *definition, synthio_envelope_state_t *state, int n_samples); void synthio_envelope_step(synthio_envelope_definition_t *definition, synthio_envelope_state_t *state, int n_samples);
void synthio_envelope_definition_set(synthio_envelope_definition_t *envelope, mp_obj_t obj, uint32_t sample_rate);
uint64_t synthio_frequency_convert_float_to_scaled(mp_float_t frequency_hz);
uint32_t synthio_frequency_convert_float_to_dds(mp_float_t frequency_hz, int32_t sample_rate);
uint32_t synthio_frequency_convert_scaled_to_dds(uint64_t frequency_scaled, int32_t sample_rate);
void synthio_lfo_set(synthio_lfo_state_t *state, const synthio_lfo_descr_t *descr, uint32_t sample_rate);
int synthio_lfo_step(synthio_lfo_state_t *state, uint16_t dur);
int synthio_sweep_step(synthio_lfo_state_t *state, uint16_t dur);
int synthio_sweep_in_step(synthio_lfo_state_t *state, uint16_t dur);

View File

@ -38,6 +38,8 @@ uint64_t common_hal_time_monotonic_ns(void) {
uint64_t ticks = port_get_raw_ticks(&subticks); uint64_t ticks = port_get_raw_ticks(&subticks);
// A tick is 976562.5 nanoseconds so multiply it by the base and add half instead of doing float // A tick is 976562.5 nanoseconds so multiply it by the base and add half instead of doing float
// math. // math.
// A subtick is 1/32 of a tick.
// 30518 is 1e9 / 32768
return 976562 * ticks + ticks / 2 + 30518 * subticks; return 976562 * ticks + ticks / 2 + 30518 * subticks;
} }

View File

@ -36,6 +36,7 @@ static void _clobber_points_list(vectorio_polygon_t *self, mp_obj_t points_tuple
for (uint16_t i = 0; i < len; ++i) { for (uint16_t i = 0; i < len; ++i) {
size_t tuple_len = 0; size_t tuple_len = 0;
mp_obj_t *tuple_items; mp_obj_t *tuple_items;
mp_arg_validate_type(items[i], &mp_type_tuple, MP_QSTR_point);
mp_obj_tuple_get(items[i], &tuple_len, &tuple_items); mp_obj_tuple_get(items[i], &tuple_len, &tuple_items);
mp_arg_validate_length(tuple_len, 2, MP_QSTR_point); mp_arg_validate_length(tuple_len, 2, MP_QSTR_point);

View File

@ -258,7 +258,7 @@ void supervisor_web_workflow_status(void) {
} }
#endif #endif
void supervisor_start_web_workflow(void) { bool supervisor_start_web_workflow(void) {
#if CIRCUITPY_WEB_WORKFLOW && CIRCUITPY_WIFI && CIRCUITPY_OS_GETENV #if CIRCUITPY_WEB_WORKFLOW && CIRCUITPY_WIFI && CIRCUITPY_OS_GETENV
// Skip starting the workflow if we're not starting from power on or reset. // Skip starting the workflow if we're not starting from power on or reset.
@ -268,7 +268,7 @@ void supervisor_start_web_workflow(void) {
reset_reason != RESET_REASON_DEEP_SLEEP_ALARM && reset_reason != RESET_REASON_DEEP_SLEEP_ALARM &&
reset_reason != RESET_REASON_UNKNOWN && reset_reason != RESET_REASON_UNKNOWN &&
reset_reason != RESET_REASON_SOFTWARE) { reset_reason != RESET_REASON_SOFTWARE) {
return; return false;
} }
char ssid[33]; char ssid[33];
@ -276,7 +276,7 @@ void supervisor_start_web_workflow(void) {
os_getenv_err_t result = common_hal_os_getenv_str("CIRCUITPY_WIFI_SSID", ssid, sizeof(ssid)); os_getenv_err_t result = common_hal_os_getenv_str("CIRCUITPY_WIFI_SSID", ssid, sizeof(ssid));
if (result != GETENV_OK) { if (result != GETENV_OK) {
return; return false;
} }
result = common_hal_os_getenv_str("CIRCUITPY_WIFI_PASSWORD", password, sizeof(password)); result = common_hal_os_getenv_str("CIRCUITPY_WIFI_PASSWORD", password, sizeof(password));
@ -284,7 +284,7 @@ void supervisor_start_web_workflow(void) {
// if password is unspecified, assume an open network // if password is unspecified, assume an open network
password[0] = '\0'; password[0] = '\0';
} else if (result != GETENV_OK) { } else if (result != GETENV_OK) {
return; return false;
} }
result = common_hal_os_getenv_str("CIRCUITPY_WEB_INSTANCE_NAME", web_instance_name, sizeof(web_instance_name)); result = common_hal_os_getenv_str("CIRCUITPY_WEB_INSTANCE_NAME", web_instance_name, sizeof(web_instance_name));
@ -309,7 +309,7 @@ void supervisor_start_web_workflow(void) {
if (_wifi_status != WIFI_RADIO_ERROR_NONE) { if (_wifi_status != WIFI_RADIO_ERROR_NONE) {
common_hal_wifi_radio_set_enabled(&common_hal_wifi_radio_obj, false); common_hal_wifi_radio_set_enabled(&common_hal_wifi_radio_obj, false);
return; return false;
} }
// (leaves new_port unchanged on any failure) // (leaves new_port unchanged on any failure)
@ -363,6 +363,7 @@ void supervisor_start_web_workflow(void) {
// Wake polling thread (maybe) // Wake polling thread (maybe)
socketpool_socket_poll_resume(); socketpool_socket_poll_resume();
#endif #endif
return true;
} }
void web_workflow_send_raw(socketpool_socket_obj_t *socket, const uint8_t *buf, int len) { void web_workflow_send_raw(socketpool_socket_obj_t *socket, const uint8_t *buf, int len) {

View File

@ -36,7 +36,7 @@
void supervisor_web_workflow_background(void *data); void supervisor_web_workflow_background(void *data);
bool supervisor_web_workflow_status_dirty(void); bool supervisor_web_workflow_status_dirty(void);
void supervisor_web_workflow_status(void); void supervisor_web_workflow_status(void);
void supervisor_start_web_workflow(void); bool supervisor_start_web_workflow(void);
void supervisor_stop_web_workflow(void); void supervisor_stop_web_workflow(void);
// Share the MDNS object with user code. // Share the MDNS object with user code.

View File

@ -45,8 +45,9 @@
#if CIRCUITPY_WEB_WORKFLOW #if CIRCUITPY_WEB_WORKFLOW
#include "supervisor/shared/web_workflow/web_workflow.h" #include "supervisor/shared/web_workflow/web_workflow.h"
#endif
static background_callback_t workflow_background_cb = {NULL, NULL}; static background_callback_t workflow_background_cb = {NULL, NULL};
#endif
// Called during a VM reset. Doesn't actually reset things. // Called during a VM reset. Doesn't actually reset things.
void supervisor_workflow_reset(void) { void supervisor_workflow_reset(void) {
@ -56,17 +57,23 @@ void supervisor_workflow_reset(void) {
#if CIRCUITPY_WEB_WORKFLOW #if CIRCUITPY_WEB_WORKFLOW
if (workflow_background_cb.fun) { if (workflow_background_cb.fun) {
supervisor_start_web_workflow(); if (supervisor_start_web_workflow()) {
supervisor_workflow_request_background(); supervisor_workflow_request_background();
}
} }
#endif #endif
} }
void supervisor_workflow_request_background(void) { void supervisor_workflow_request_background(void) {
#if CIRCUITPY_WEB_WORKFLOW
if (workflow_background_cb.fun) { if (workflow_background_cb.fun) {
workflow_background_cb.data = NULL; workflow_background_cb.data = NULL;
background_callback_add_core(&workflow_background_cb); background_callback_add_core(&workflow_background_cb);
} else {
// Unblock polling thread if necessary
socketpool_socket_poll_resume();
} }
#endif
} }
// Return true if host has completed connection to us (such as USB enumeration). // Return true if host has completed connection to us (such as USB enumeration).
@ -98,9 +105,11 @@ void supervisor_workflow_start(void) {
#endif #endif
#if CIRCUITPY_WEB_WORKFLOW #if CIRCUITPY_WEB_WORKFLOW
supervisor_start_web_workflow(); if (supervisor_start_web_workflow()) {
memset(&workflow_background_cb, 0, sizeof(workflow_background_cb)); // Enable background callbacks if web_workflow startup successful
workflow_background_cb.fun = supervisor_web_workflow_background; memset(&workflow_background_cb, 0, sizeof(workflow_background_cb));
workflow_background_cb.fun = supervisor_web_workflow_background;
}
#endif #endif
} }

View File

@ -0,0 +1,5 @@
# Test synthio without hardware
Build the unix port then run `....../ports/unix/micropython-coverage code.py`.
This will create `tune.wav` as output, which you can listen to using any old audio player.

View File

@ -0,0 +1,142 @@
import sys
sys.path.insert(
0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave"
)
import random
import audiocore
import synthio
from ulab import numpy as np
import adafruit_wave as wave
SAMPLE_SIZE = 1024
VOLUME = 14700
sine = np.array(
np.sin(np.linspace(0, 2 * np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME,
dtype=np.int16,
)
envelope = synthio.Envelope(
attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=0.8, sustain_level=0.8
)
synth = synthio.Synthesizer(sample_rate=48000)
def synthesize(synth):
random.seed(3)
target_notes = [synthio.midi_to_hz(n + o) for n in (60, 64, 67, 70) for o in (-12, 12, 0)]
print(target_notes)
notes = [
synthio.Note(frequency=random.randint(60, 20000), waveform=sine, envelope=envelope)
for note in target_notes
]
synth.press(notes)
target = synthio.midi_to_hz(72)
factor = 0.98
for i in range(600):
yield 1
for ni, ti in zip(notes, target_notes):
print(ni.frequency, ti)
break
for ni, ti in zip(notes, target_notes):
ni.frequency = (ni.frequency * factor) + (ti * (1 - factor))
synth.release_all()
yield 36
def synthesize2(synth):
n = synthio.Note(
frequency=synthio.midi_to_hz(60),
tremolo_depth=0.2,
tremolo_rate=2,
waveform=sine,
envelope=envelope,
)
synth.press((n,))
yield 360
synth.release_all()
yield 36
def synthesize3(synth):
n = synthio.Note(
frequency=synthio.midi_to_hz(60),
bend_depth=0.1,
bend_rate=8,
waveform=sine,
envelope=envelope,
)
synth.press((n,))
yield 360
synth.release_all()
yield 36
def synthesize4(synth):
n = synthio.Note(
frequency=synthio.midi_to_hz(60),
tremolo_depth=0.1,
tremolo_rate=1.5,
bend_depth=0.1,
bend_rate=3,
waveform=sine,
envelope=envelope,
)
synth.press((n,))
yield 720
synth.release_all()
yield 36
def synthesize5(synth):
notes = [
synthio.Note(
frequency=synthio.midi_to_hz(60 + i + o),
waveform=sine,
envelope=envelope,
)
for i in [0, 4, 7]
for o in [0, -12, 12]
]
for n in notes:
print(n)
synth.press((n,))
yield 120
synth.release_all()
yield 36
def synthesize6(synth):
n = synthio.Note(
frequency=synthio.midi_to_hz(60),
tremolo_depth=0.1,
tremolo_rate=1.5,
bend_depth=-5 / 12,
bend_rate=1 / 2,
bend_mode=synthio.BendMode.SWEEP,
waveform=sine,
envelope=envelope,
)
synth.press((n,))
yield 720
synth.release_all()
yield 36
def chain(*args):
for a in args:
yield from a
# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav
with wave.open("tune-noenv.wav", "w") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(48000)
for n in chain(synthesize6(synth)):
for i in range(n):
result, data = audiocore.get_buffer(synth)
f.writeframes(data)

View File

@ -0,0 +1,282 @@
import random
import audiocore
import synthio
from ulab import numpy as np
import adafruit_wave as wave
h = np.array(
[
-0.001229734800309099,
-0.008235561806605458,
-0.015082497016061390,
-0.020940136918319988,
-0.024981800822463429,
-0.026464233332370746,
-0.024803890156806906,
-0.019642276775473012,
-0.010893620860173042,
0.001230341899766145,
0.016221637398855598,
0.033304135659230648,
0.051486665261155681,
0.069636961761409016,
0.086570197432542767,
0.101144354207918147,
0.112353938422488253,
0.119413577288191297,
0.121823886314051028,
0.119413577288191297,
0.112353938422488253,
0.101144354207918147,
0.086570197432542767,
0.069636961761409016,
0.051486665261155681,
0.033304135659230648,
0.016221637398855598,
0.001230341899766145,
-0.010893620860173042,
-0.019642276775473012,
-0.024803890156806906,
-0.026464233332370746,
-0.024981800822463429,
-0.020940136918319988,
-0.015082497016061390,
-0.008235561806605458,
-0.001229734800309099,
]
)
filter_coeffs = np.array(h[::-1] * 32768, dtype=np.int16)
def randf(lo, hi):
return random.random() * (hi - lo) + lo
SAMPLE_SIZE = 1024
VOLUME = 14700
sine = np.array(
np.sin(np.linspace(0, 2 * np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME, dtype=np.int16
)
square = np.array([24000] * (SAMPLE_SIZE // 2) + [-24000] * (SAMPLE_SIZE // 2), dtype=np.int16)
noise = np.array(
[random.randint(-32768, 32767) for i in range(SAMPLE_SIZE)],
dtype=np.int16,
)
envelope = synthio.Envelope(
attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=1, sustain_level=0.8
)
instant = synthio.Envelope(
attack_time=0, decay_time=0, release_time=0, attack_level=1, sustain_level=1
)
synth = synthio.Synthesizer(
sample_rate=48000, envelope=None, filter=filter_coeffs, channel_count=2
)
def synthesize(synth):
print(
"""you can use arbitrary waveforms, including ones calculated on the fly or read from wave files (up to 1024 points)"""
)
waveform = np.zeros(SAMPLE_SIZE, dtype=np.int16)
chord = [
synthio.Note(synthio.midi_to_hz(n), waveform=waveform, envelope=envelope)
for n in (60, 64, 67, 70)
]
synth.press(chord)
for i in range(256):
ctrl = i / 255
waveform[:] = np.array(square * (1 - ctrl) + sine * ctrl, dtype=np.int16)
yield 4
def synthesize2(synth):
print("""Envelope controls how notes fade in or out""")
chord = [
synthio.Note(synthio.midi_to_hz(n), waveform=sine, envelope=instant)
for n in (60, 64, 67, 70)
]
for i in range(4):
for c in chord:
synth.release_all_then_press((c,))
yield 24
synth.release_all()
chord = [
synthio.Note(synthio.midi_to_hz(n), waveform=sine, envelope=envelope)
for n in (60, 64, 67, 70)
]
for i in range(4):
for c in chord:
old = (c,)
synth.release_all_then_press((c,))
yield 24
def synthesize3(synth):
print("""A noise waveform creates percussive sounds""")
env = synthio.Envelope(
attack_time=0,
decay_time=0.2,
sustain_level=0,
)
notes = [
synthio.Note(
frequency=synthio.midi_to_hz(1 + i),
waveform=noise,
envelope=env,
)
for i in range(12)
]
random.seed(9)
for _ in range(16):
n = random.choice(notes)
d = random.randint(30, 60)
synth.press((n,))
yield d
def synthesize4(synth):
print("""Tremolo varies the note volume within a range at a low frequency""")
chord = [
synthio.Note(synthio.midi_to_hz(n), waveform=sine, envelope=None) for n in (60, 64, 67, 70)
]
synth.press(chord)
for i in range(16):
for c in chord:
c.tremolo_depth = i / 50
c.tremolo_rate = (i + 1) / 4
yield 48
yield 36
def synthesize5(synth):
print("""You can add vibrato or frequency sweep to notes""")
chord = [synthio.Note(synthio.midi_to_hz(n), waveform=sine) for n in (60, 64, 67, 70)]
synth.press(chord)
for i in range(16):
for c in chord:
c.bend_depth = 1 / 24
c.bend_rate = (i + 1) / 2
yield 24
synth.release_all()
yield 100
for c in chord:
synth.release_all()
c.bend_mode = synthio.BendMode.SWEEP_IN
c.bend_depth = randf(-1, 1)
c.bend_rate = 1 / 2
synth.press(chord)
yield 320
def synthesize6(synth):
print("""Ring modulation multiplies two waveforms together to create rich sounds""")
chord = [
synthio.Note(synthio.midi_to_hz(n), waveform=square, ring_waveform=sine, envelope=envelope)
for n in (60,)
]
synth.press(chord)
yield 200
random.seed(75)
for _ in range(3):
synth.release_all()
yield 36
for note in chord:
note.ring_frequency = note.frequency * (random.random() * 35 / 1200 + 8)
synth.press(chord)
yield 200
def synthesize7(synth):
print("""FIR filtering can reproduce low, high, notch and band filters""")
chord = [
synthio.Note(synthio.midi_to_hz(n), waveform=square, filter=False)
for n in (60, 64, 67, 70)
]
for i in range(4):
for c in chord:
synth.release_all_then_press((c,))
yield 24
synth.release_all()
for note in chord:
note.filter = True
for i in range(4):
for c in chord:
synth.release_all_then_press((c,))
yield 24
def synthesize8(synth):
print("""Notes can be panned between channels""")
chord = [
synthio.Note(synthio.midi_to_hz(n), waveform=square, envelope=envelope)
for n in (60, 64, 67, 70)
]
synth.press(chord)
for p in range(-10, 11, 1):
for note in chord:
note.panning = p / 10
yield 36
def delay(synth):
synth.release_all()
yield 200
def chain(*args):
for a in args:
yield from a
# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav
with wave.open("demo.wav", "w") as f:
f.setnchannels(2)
f.setsampwidth(2)
f.setframerate(48000)
import array
for n in chain(
synthesize(synth),
delay(synth),
synthesize2(synth),
delay(synth),
synthesize3(synth),
delay(synth),
synthesize4(synth),
delay(synth),
synthesize5(synth),
delay(synth),
synthesize6(synth),
delay(synth),
synthesize7(synth),
delay(synth),
synthesize8(synth),
):
for i in range(n):
result, data = audiocore.get_buffer(synth)
f.writeframes(data)

View File

@ -0,0 +1,58 @@
import sys
sys.path.insert(
0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave"
)
import random
import audiocore
import synthio
from ulab import numpy as np
import adafruit_wave as wave
SAMPLE_SIZE = 1024
VOLUME = 32767
sine = np.array(
np.sin(np.linspace(0, 2 * np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME,
dtype=np.int16,
)
envelope = synthio.Envelope(attack_time=0.05, decay_time=8, release_time=0.25, sustain_level=0)
fast_decay_envelope = synthio.Envelope(
attack_time=0.05, decay_time=0.25, release_time=0.25, sustain_level=0
)
synth = synthio.Synthesizer(sample_rate=48000)
def synthesize(synth):
notes = (synthio.Note(frequency=440, waveform=sine, envelope=envelope),)
synth.press(notes)
yield 360
notes[0].envelope = fast_decay_envelope
yield 180
synth.release_all()
def synthesize2(synth):
notes = (synthio.Note(frequency=440, waveform=sine, envelope=envelope),)
synth.press(notes)
yield 360
synth.release_all()
yield 180
def chain(*args):
for a in args:
yield from a
# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav
with wave.open("envelope.wav", "w") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(48000)
for n in chain(synthesize(synth), synthesize2(synth)):
for i in range(n):
result, data = audiocore.get_buffer(synth)
f.writeframes(data)

View File

@ -0,0 +1,104 @@
import sys
sys.path.insert(
0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave"
)
import random
import audiocore
import synthio
from ulab import numpy as np
import adafruit_wave as wave
random.seed(9)
envelope = synthio.Envelope(
attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=0.8, sustain_level=0.8
)
h = np.array(
[
-0.001229734800309099,
-0.008235561806605458,
-0.015082497016061390,
-0.020940136918319988,
-0.024981800822463429,
-0.026464233332370746,
-0.024803890156806906,
-0.019642276775473012,
-0.010893620860173042,
0.001230341899766145,
0.016221637398855598,
0.033304135659230648,
0.051486665261155681,
0.069636961761409016,
0.086570197432542767,
0.101144354207918147,
0.112353938422488253,
0.119413577288191297,
0.121823886314051028,
0.119413577288191297,
0.112353938422488253,
0.101144354207918147,
0.086570197432542767,
0.069636961761409016,
0.051486665261155681,
0.033304135659230648,
0.016221637398855598,
0.001230341899766145,
-0.010893620860173042,
-0.019642276775473012,
-0.024803890156806906,
-0.026464233332370746,
-0.024981800822463429,
-0.020940136918319988,
-0.015082497016061390,
-0.008235561806605458,
-0.001229734800309099,
]
)
filter_coeffs = np.array(h[::-1] * 32768, dtype=np.int16)
synth = synthio.Synthesizer(sample_rate=48000, filter=filter_coeffs)
def synthesize(synth):
n = synthio.Note(
frequency=120,
envelope=envelope,
filter=False,
)
print(synth, n)
synth.press((n,))
for _ in range(20):
n.frequency *= 1.0595
yield 36
synth.release_all()
yield 36
n.filter = True
n.frequency = 120
synth.press((n,))
for _ in range(20):
n.frequency *= 1.0595
yield 36
synth.release_all()
yield 36
def chain(*args):
for a in args:
yield from a
# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav
with wave.open("fir.wav", "w") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(48000)
for n in chain(synthesize(synth)):
for i in range(n):
result, data = audiocore.get_buffer(synth)
f.writeframes(data)

View File

@ -0,0 +1,71 @@
import sys
sys.path.insert(
0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave"
)
import random
import audiocore
import synthio
from ulab import numpy as np
import adafruit_wave as wave
random.seed(9)
SAMPLE_SIZE = 1024
VOLUME = 14700
noise = np.array(
[random.randint(-32768, 32767) for i in range(SAMPLE_SIZE)],
dtype=np.int16,
)
envelope = synthio.Envelope(
attack_time=0,
decay_time=0.2,
sustain_level=0,
)
synth = synthio.Synthesizer(sample_rate=48000)
def randf(lo, hi):
return random.random() * (hi - lo) + lo
def synthesize(synth):
notes = [
synthio.Note(
frequency=synthio.midi_to_hz(1 + i),
waveform=noise,
envelope=envelope,
bend_mode=synthio.BendMode.SWEEP,
bend_depth=random.choice((-1, 1)),
bend_rate=randf(4, 12),
)
for i in range(12)
]
random.seed(9)
for _ in range(16):
n = random.choice(notes)
d = random.randint(30, 60)
print(n, d)
synth.press((n,))
yield d
synth.release_all()
yield 36
def chain(*args):
for a in args:
yield from a
# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav
with wave.open("noise.wav", "w") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(48000)
for n in chain(synthesize(synth)):
for i in range(n):
result, data = audiocore.get_buffer(synth)
f.writeframes(data)

View File

@ -0,0 +1,57 @@
import sys
sys.path.insert(
0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave"
)
import random
import audiocore
import synthio
from ulab import numpy as np
import adafruit_wave as wave
random.seed(9)
SAMPLE_SIZE = 1024
VOLUME = 14700
sine = np.array(
np.sin(np.linspace(0, 2 * np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME,
dtype=np.int16,
)
envelope = synthio.Envelope(
attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=0.8, sustain_level=0.8
)
synth = synthio.Synthesizer(sample_rate=48000, channel_count=2)
def synthesize(synth):
n = synthio.Note(
frequency=440,
waveform=sine,
envelope=envelope,
)
print(synth, n)
synth.press((n,))
for p in range(-10, 11, 1):
n.panning = p / 10
yield 36
synth.release_all()
yield 36
def chain(*args):
for a in args:
yield from a
# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav
with wave.open("panning.wav", "w") as f:
f.setnchannels(2)
f.setsampwidth(2)
f.setframerate(48000)
for n in chain(synthesize(synth)):
for i in range(n):
result, data = audiocore.get_buffer(synth)
f.writeframes(data)

View File

@ -0,0 +1,60 @@
import sys
sys.path.insert(
0, f"{__file__.rpartition('/')[0] or '.'}/../../../../frozen/Adafruit_CircuitPython_Wave"
)
import random
import audiocore
import synthio
from ulab import numpy as np
import adafruit_wave as wave
random.seed(9)
SAMPLE_SIZE = 1024
VOLUME = 32767
sine = np.array(
np.sin(np.linspace(0, 2 * np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME,
dtype=np.int16,
)
envelope = synthio.Envelope(
attack_time=0.1, decay_time=0.05, release_time=0.2, attack_level=0.8, sustain_level=0.8
)
synth = synthio.Synthesizer(sample_rate=48000)
def synthesize(synth):
n = synthio.Note(
frequency=120,
waveform=sine,
ring_waveform=sine,
ring_frequency=769,
envelope=envelope,
bend_mode=synthio.BendType.VIBRATO,
bend_depth=50 / 1200,
bend_rate=7,
)
print(synth, n)
synth.press((n,))
yield 720
synth.release_all()
yield 36
def chain(*args):
for a in args:
yield from a
# sox -r 48000 -e signed -b 16 -c 1 tune.raw tune.wav
with wave.open("ring.wav", "w") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(48000)
for n in chain(synthesize(synth)):
for i in range(n):
result, data = audiocore.get_buffer(synth)
f.writeframes(data)

View File

@ -1,5 +1,5 @@
# Test synthio without hardware # Test synthio without hardware
Build the uninx port then run `....../ports/unix/micropython-coverage midi2wav.py`. Build the unix port then run `....../ports/unix/micropython-coverage midi2wav.py`.
This will create `tune.wav` as output, which you can listen to using any old audio player. This will create `tune.wav` as output, which you can listen to using any old audio player.

View File

@ -1,11 +0,0 @@
# SPDX-FileCopyrightText: 2023 Guido van Rossum <guido@cwi.nl> and others.
#
# SPDX-License-Identifier: PSF-2.0
import struct
def byteswap(data, sampwidth):
print(data)
raise
ch = "I" if sampwidth == 16 else "H"

View File

@ -1,173 +0,0 @@
# SPDX-FileCopyrightText: 2023 Guido van Rossum <guido@cwi.nl> and others.
#
# SPDX-License-Identifier: PSF-2.0
"""Simple class to read IFF chunks.
An IFF chunk (used in formats such as AIFF, TIFF, RMFF (RealMedia File
Format)) has the following structure:
+----------------+
| ID (4 bytes) |
+----------------+
| size (4 bytes) |
+----------------+
| data |
| ... |
+----------------+
The ID is a 4-byte string which identifies the type of chunk.
The size field (a 32-bit value, encoded using big-endian byte order)
gives the size of the whole chunk, including the 8-byte header.
Usually an IFF-type file consists of one or more chunks. The proposed
usage of the Chunk class defined here is to instantiate an instance at
the start of each chunk and read from the instance until it reaches
the end, after which a new instance can be instantiated. At the end
of the file, creating a new instance will fail with an EOFError
exception.
Usage:
while True:
try:
chunk = Chunk(file)
except EOFError:
break
chunktype = chunk.getname()
while True:
data = chunk.read(nbytes)
if not data:
pass
# do something with data
The interface is file-like. The implemented methods are:
read, close, seek, tell, isatty.
Extra methods are: skip() (called by close, skips to the end of the chunk),
getname() (returns the name (ID) of the chunk)
The __init__ method has one required argument, a file-like object
(including a chunk instance), and one optional argument, a flag which
specifies whether or not chunks are aligned on 2-byte boundaries. The
default is 1, i.e. aligned.
"""
class Chunk:
def __init__(self, file, align=True, bigendian=True, inclheader=False):
import struct
self.closed = False
self.align = align # whether to align to word (2-byte) boundaries
if bigendian:
strflag = ">"
else:
strflag = "<"
self.file = file
self.chunkname = file.read(4)
if len(self.chunkname) < 4:
raise EOFError
try:
self.chunksize = struct.unpack_from(strflag + "L", file.read(4))[0]
except struct.error:
raise EOFError from None
if inclheader:
self.chunksize = self.chunksize - 8 # subtract header
self.size_read = 0
try:
self.offset = self.file.tell()
except (AttributeError, OSError):
self.seekable = False
else:
self.seekable = True
def getname(self):
"""Return the name (ID) of the current chunk."""
return self.chunkname
def getsize(self):
"""Return the size of the current chunk."""
return self.chunksize
def close(self):
if not self.closed:
try:
self.skip()
finally:
self.closed = True
def isatty(self):
if self.closed:
raise ValueError("I/O operation on closed file")
return False
def seek(self, pos, whence=0):
"""Seek to specified position into the chunk.
Default position is 0 (start of chunk).
If the file is not seekable, this will result in an error.
"""
if self.closed:
raise ValueError("I/O operation on closed file")
if not self.seekable:
raise OSError("cannot seek")
if whence == 1:
pos = pos + self.size_read
elif whence == 2:
pos = pos + self.chunksize
if pos < 0 or pos > self.chunksize:
raise RuntimeError
self.file.seek(self.offset + pos, 0)
self.size_read = pos
def tell(self):
if self.closed:
raise ValueError("I/O operation on closed file")
return self.size_read
def read(self, size=-1):
"""Read at most size bytes from the chunk.
If size is omitted or negative, read until the end
of the chunk.
"""
if self.closed:
raise ValueError("I/O operation on closed file")
if self.size_read >= self.chunksize:
return b""
if size < 0:
size = self.chunksize - self.size_read
if size > self.chunksize - self.size_read:
size = self.chunksize - self.size_read
data = self.file.read(size)
self.size_read = self.size_read + len(data)
if self.size_read == self.chunksize and self.align and (self.chunksize & 1):
dummy = self.file.read(1)
self.size_read = self.size_read + len(dummy)
return data
def skip(self):
"""Skip the rest of the chunk.
If you are not interested in the contents of the chunk,
this method should be called so that the file points to
the start of the next chunk.
"""
if self.closed:
raise ValueError("I/O operation on closed file")
if self.seekable:
try:
n = self.chunksize - self.size_read
# maybe fix alignment
if self.align and (self.chunksize & 1):
n = n + 1
self.file.seek(n, 1)
self.size_read = self.size_read + n
return
except OSError:
pass
while self.size_read < self.chunksize:
n = min(8192, self.chunksize - self.size_read)
dummy = self.read(n)
if not dummy:
raise EOFError

Some files were not shown because too many files have changed in this diff Show More