Merge branch 'main' into efr32-doc-fixes

This commit is contained in:
Dan Halbert 2023-06-08 12:57:09 -04:00 committed by GitHub
commit 46ddacddde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 5870 additions and 5418 deletions

View File

@ -27,7 +27,11 @@ This project has a bunch of git submodules. You will need to update them regula
In the root folder of the CircuitPython repository, execute the following:
make fetch-submodules
make fetch-all-submodules
Or, in the ports directory for the particular port you are building, do:
make fetch-port-submodules
### Required Python Packages

View File

@ -61,7 +61,6 @@ TRANSLATE_SOURCES_EXC = -path "ports/*/build-*" \
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " fetch-submodules to fetch dependencies from submodules, run this right after you clone the repo"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@ -84,6 +83,8 @@ help:
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " fetch-all-submodules to fetch submodules for all ports"
@echo " remove-all-submodules remove all submodules, including files and .git/ data"
clean:
rm -rf $(BUILDDIR)/*
@ -324,25 +325,12 @@ clean-stm:
$(MAKE) -C ports/stm BOARD=feather_stm32f405_express clean
# If available, do blobless partial clones of submodules to save time and space.
# A blobless partial clone lazily fetches data as needed, but has all the metadata available (tags, etc.)
# so it does not have the idiosyncrasies of a shallow clone.
#
# If not available, do a fetch that will fail, and then fix it up with a second fetch.
# (Only works for git servers that allow sha fetches.)
.PHONY: fetch-submodules
fetch-submodules:
git submodule sync
#####################################################################################
# NOTE: Ideally, use git version 2.36.0 or later, to do partial clones of submodules.
# If an older git is used, submodules will be cloned with a shallow clone of depth 1.
# You will see a git usage message first if the git version is too old to do
# clones of submodules.
#####################################################################################
git submodule update --init --filter=blob:none || git submodule update --init -N --depth 1 || git submodule foreach 'git fetch --tags --depth 1 origin $$sha1 && git checkout -q $$sha1' || echo 'make fetch-submodules FAILED'
.PHONY: fetch-all-submodules
fetch-all-submodules:
tools/fetch-submodules.sh
.PHONY: remove-submodules
remove-submodules:
.PHONY: remove-all-submodules
remove-all-submodules:
git submodule deinit -f --all
rm -rf .git/modules/*

View File

@ -363,11 +363,15 @@ SRC_QSTR_PREPROCESSOR += peripherals/samd/$(PERIPHERALS_CHIP_FAMILY)/clocks.c
all: $(BUILD)/firmware.bin $(BUILD)/firmware.uf2
ifeq ($(VALID_BOARD),)
$(BUILD)/firmware.elf: invalid-board
else
$(BUILD)/firmware.elf: $(OBJ) $(GENERATED_LD_FILE)
$(STEPECHO) "LINK $@"
$(Q)echo $(OBJ) > $(BUILD)/firmware.objs
$(Q)$(CC) -o $@ $(LDFLAGS) @$(BUILD)/firmware.objs -Wl,--print-memory-usage -Wl,--start-group $(LIBS) -Wl,--end-group
$(Q)$(SIZE) $@ | $(PYTHON) $(TOP)/tools/build_memory_info.py $(GENERATED_LD_FILE) $(BUILD)
endif
$(BUILD)/firmware.bin: $(BUILD)/firmware.elf
$(STEPECHO) "Create $@"

View File

@ -142,10 +142,14 @@ all: $(BUILD)/firmware.kernel$(SUFFIX).img $(BUILD)/firmware.disk.img.zip
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
ifeq ($(VALID_BOARD),)
$(BUILD)/kernel$(SUFFIX).elf: invalid-board
else
$(BUILD)/kernel$(SUFFIX).elf: $(OBJ)
$(STEPECHO) "LINK $@"
$(Q)echo $(OBJ) > $(BUILD)/firmware.objs
$(Q)$(CC) -o $@ $(LDFLAGS) @$(BUILD)/firmware.objs -Wl,--start-group $(LIBS) -Wl,--end-group
endif
$(BUILD)/kernel$(SUFFIX).img: $(BUILD)/kernel$(SUFFIX).elf
$(STEPECHO) "Create $@"

View File

@ -183,9 +183,13 @@ $(BUILD)/firmware.elf: $(BUILD)/libmpy.a
$(MKSPK):
$(MAKE) -C mkspk
ifeq ($(VALID_BOARD),)
$(BUILD)/firmware.spk: invalid-board
else
$(BUILD)/firmware.spk: $(BUILD)/firmware.elf $(MKSPK)
$(ECHO) "Creating $@"
$(MKSPK) -c 2 $(BUILD)/firmware.elf nuttx $(BUILD)/firmware.spk
endif
flash: $(BUILD)/firmware.spk
$(ECHO) "Writing $< to the board"

View File

@ -411,6 +411,7 @@ ESP_IDF_COMPONENTS_EXPANDED += $(BUILD)/esp-idf/esp-idf/esp32-camera/libesp32-ca
#$(error $(ESP_IDF_COMPONENTS_EXPANDED))
endif
ifneq ($(VALID_BOARD),)
# BOOTLOADER_OFFSET is determined by chip type, based on the ROM bootloader, and is not changeable.
ifeq ($(IDF_TARGET),esp32)
BOOTLOADER_OFFSET = 0x1000
@ -423,6 +424,7 @@ BOOTLOADER_OFFSET = 0x1000
else
$(error unknown IDF_TARGET $(IDF_TARGET))
endif
endif
IDF_CMAKE_TARGETS = \
bootloader/bootloader.bin \
@ -457,8 +459,12 @@ $(BUILD)/circuitpython-firmware.bin: $(BUILD)/firmware.elf | tools/build_memory_
$(Q)esptool.py --chip $(IDF_TARGET) elf2image $(FLASH_FLAGS) --elf-sha256-offset 0xb0 -o $@ $^
$(Q)$(PYTHON) tools/build_memory_info.py $< $(BUILD)/esp-idf/sdkconfig $@ $(BUILD)
ifeq ($(VALID_BOARD),)
$(BUILD)/firmware.bin: invalid-board
else
$(BUILD)/firmware.bin: $(BUILD)/circuitpython-firmware.bin | esp-idf-stamp
$(Q)$(PYTHON) ../../tools/join_bins.py $@ $(BOOTLOADER_OFFSET) $(BUILD)/esp-idf/bootloader/bootloader.bin $(PARTITION_TABLE_OFFSET) $(BUILD)/esp-idf/partition_table/partition-table.bin $(FIRMWARE_OFFSET) $(BUILD)/circuitpython-firmware.bin
endif
UF2_FAMILY_ID_esp32s2 = 0xbfdd4eee
UF2_FAMILY_ID_esp32s3 = 0xc47e5767

View File

@ -133,10 +133,14 @@ SRC_QSTR_PREPROCESSOR +=
all: $(BUILD)/firmware.bin $(BUILD)/firmware.dfu
ifeq ($(VALID_BOARD),)
$(BUILD)/firmware.elf: invalid-board
else
$(BUILD)/firmware.elf: $(OBJ)
$(STEPECHO) "LINK $@"
$(Q)$(CC) -o $@ $(LDFLAGS) $^ -Wl,--start-group $(LIBS) -Wl,--end-group
$(Q)$(SIZE) $@ | $(PYTHON) $(TOP)/tools/build_memory_info.py $(LD_FILE) $(BUILD)
endif
$(BUILD)/firmware.bin: $(BUILD)/firmware.elf
$(STEPECHO) "Create $@"

View File

@ -200,9 +200,13 @@ SRC_QSTR += $(SRC_C) $(SRC_SUPERVISOR) $(SRC_COMMON_HAL_EXPANDED) $(SRC_SHARED_M
all: $(BUILD)/firmware.bin $(BUILD)/firmware.uf2 $(BUILD)/firmware.hex
ifeq ($(VALID_BOARD),)
$(BUILD)/firmware.elf: invalid-board
else
$(BUILD)/firmware.elf: $(OBJ) $(LD_FILES)
$(STEPECHO) "LINK $@"
$(Q)$(CC) -o $@ $(LDFLAGS) $(filter-out %.ld, $^) -Wl,--print-memory-usage -Wl,--start-group $(LIBS) -Wl,--end-group
endif
# -R excludes sections from the output files.
$(BUILD)/firmware.bin: $(BUILD)/firmware.elf

View File

@ -208,11 +208,15 @@ UF2_FAMILY_ID_nrf52833 = 0x621E937A
all: $(BUILD)/firmware.bin $(BUILD)/firmware.uf2 $(BUILD)/firmware.combined.hex
ifeq ($(VALID_BOARD),)
$(BUILD)/firmware.elf: invalid-board
else
$(BUILD)/firmware.elf: $(OBJ) $(GENERATED_LD_FILE)
$(STEPECHO) "LINK $@"
$(Q)echo $(OBJ) > $(BUILD)/firmware.objs
$(Q)$(CC) -o $@ $(LDFLAGS) @$(BUILD)/firmware.objs -Wl,--start-group $(LIBS) -Wl,--end-group
$(Q)$(SIZE) $@ | $(PYTHON) $(TOP)/tools/build_memory_info.py $(GENERATED_LD_FILE) $(BUILD)
endif
$(BUILD)/firmware.bin: $(BUILD)/firmware.elf
$(STEPECHO) "Create $@"

View File

@ -31,8 +31,6 @@
#define MICROPY_HW_MCU_NAME "nRF52833"
#define MICROPY_HW_LED_STATUS (&pin_P0_13)
#define MICROPY_HW_LED_TX (&pin_P0_14)
#define MICROPY_HW_LED_RX (&pin_P0_15)
#define CIRCUITPY_INTERNAL_NVM_SIZE 0
#define CIRCUITPY_INTERNAL_FLASH_FILESYSTEM_SIZE (60 * 1024)

View File

@ -45,10 +45,14 @@ STATIC const mp_rom_map_elem_t board_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_LED3), MP_ROM_PTR(&pin_P0_15) },
{ MP_ROM_QSTR(MP_QSTR_LED4), MP_ROM_PTR(&pin_P0_16) },
{ MP_ROM_QSTR(MP_QSTR_BUTTON1), MP_ROM_PTR(&pin_P0_11) },
{ MP_ROM_QSTR(MP_QSTR_BUTTON1_DEFAULT), MP_ROM_PTR(&pin_P0_11) },
{ MP_ROM_QSTR(MP_QSTR_BUTTON1_OPTIONAL), MP_ROM_PTR(&pin_P1_07) },
{ MP_ROM_QSTR(MP_QSTR_BUTTON2), MP_ROM_PTR(&pin_P0_12) },
{ MP_ROM_QSTR(MP_QSTR_BUTTON2_DEFAULT), MP_ROM_PTR(&pin_P0_12) },
{ MP_ROM_QSTR(MP_QSTR_BUTTON2_OPTIONAL), MP_ROM_PTR(&pin_P1_08) },
{ MP_ROM_QSTR(MP_QSTR_BUTTON3), MP_ROM_PTR(&pin_P0_24) },
{ MP_ROM_QSTR(MP_QSTR_BUTTON4), MP_ROM_PTR(&pin_P0_25) },
};

View File

@ -434,11 +434,15 @@ endif
LINKER_SCRIPTS += -Wl,-T,link.ld
ifeq ($(VALID_BOARD),)
$(BUILD)/firmware.elf: invalid-board
else
$(BUILD)/firmware.elf: $(OBJ) $(BOARD_LD) link.ld
$(STEPECHO) "LINK $@"
$(Q)echo $(OBJ) > $(BUILD)/firmware.objs
$(Q)echo $(PICO_LDFLAGS) > $(BUILD)/firmware.ldflags
$(Q)$(CC) -o $@ $(CFLAGS) @$(BUILD)/firmware.ldflags $(LINKER_SCRIPTS) -Wl,--print-memory-usage -Wl,-Map=$@.map -Wl,-cref -Wl,--gc-sections @$(BUILD)/firmware.objs -Wl,-lc
endif
$(BUILD)/firmware.bin: $(BUILD)/firmware.elf
$(STEPECHO) "Create $@"

View File

@ -206,9 +206,9 @@ const uint8_t display_start_sequence[] = {
// Look up tables for voltage sequence for pixel transition
// Common voltage
LUT_VCOM, 44,
0x00, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x00, 0x23, 0x23, 0x00, 0x00, 0x02,
0x00, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x00, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0x00, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -217,9 +217,9 @@ const uint8_t display_start_sequence[] = {
// White to white
LUT_WW, 42,
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -227,9 +227,9 @@ const uint8_t display_start_sequence[] = {
// Black to white
LUT_BW, 42,
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -237,9 +237,9 @@ const uint8_t display_start_sequence[] = {
// White to black
LUT_WB, 42,
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -247,9 +247,9 @@ const uint8_t display_start_sequence[] = {
// Black to black
LUT_BB, 42,
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

View File

@ -137,9 +137,13 @@ MCU_SECTIONS = $^ $@
# Default goal
all: $(OUTPUT_DIR)/firmware.bin
ifeq ($(VALID_BOARD),)
$(BUILD)/firmware.bin: invalid-board
else
$(OUTPUT_DIR)/firmware.bin: $(SILABS_BUILD)/$(PROJECTNAME).Makefile $(OUTPUT_DIR)/firmware.hex
+@$(MAKE) --no-print-directory $(OUTPUT_DIR)/firmware.out
@echo 'Done.'
endif
$(SILABS_BUILD)/$(PROJECTNAME).Makefile:
+@$(MAKE) --no-print-directory slc-generate

View File

@ -48,9 +48,9 @@ Ensure your clone of CircuitPython is ready to build by following the [guide on
Clone the source code of CircuitPython from GitHub:
git clone https://github.com/adafruit/circuitpython.git
cd circuitpython
make fetch-submodules
$ git clone https://github.com/SiliconLabs/circuitpython.git
$ cd circuitpython/ports/silabs
$ make fetch-port-submodules
Checkout the branch or tag you want to build. For example:

View File

@ -52,7 +52,7 @@ INC += -I../../supervisor/shared/usb
#Debugging/Optimization
ifeq ($(DEBUG), 1)
CFLAGS += -ggdb3
# You may want to enable these flags to make setting breakpoints easier.
# You may want to enable these flags to make setting breakpoints easier.
CFLAGS += -fno-inline -fno-ipa-sra
else
CFLAGS += -DNDEBUG
@ -264,11 +264,15 @@ endif
all: $(BUILD)/firmware.bin $(BUILD)/firmware.uf2
ifeq ($(VALID_BOARD),)
$(BUILD)/firmware.elf: invalid-board
else
$(BUILD)/firmware.elf: $(OBJ)
$(STEPECHO) "LINK $@"
$(Q)echo $^ > $(BUILD)/firmware.objs
$(Q)$(CC) -o $@ $(LDFLAGS) @$(BUILD)/firmware.objs -Wl,--start-group $(LIBS) -Wl,--end-group
$(Q)$(SIZE) $@ | $(PYTHON) $(TOP)/tools/build_memory_info.py $(LD_FILE) $(BUILD)
endif
$(BUILD)/firmware.bin: $(BUILD)/firmware.elf
$(STEPECHO) "Create $@"

View File

@ -45,6 +45,7 @@ SRC_BITMAP := \
shared-bindings/synthio/MidiTrack.c \
shared-bindings/synthio/LFO.c \
shared-bindings/synthio/Note.c \
shared-bindings/synthio/Biquad.c \
shared-bindings/synthio/Synthesizer.c \
shared-bindings/traceback/__init__.c \
shared-bindings/util.c \
@ -70,6 +71,7 @@ SRC_BITMAP := \
shared-module/synthio/MidiTrack.c \
shared-module/synthio/LFO.c \
shared-module/synthio/Note.c \
shared-module/synthio/Biquad.c \
shared-module/synthio/Synthesizer.c \
shared-module/traceback/__init__.c \
shared-module/zlib/__init__.c \

View File

@ -650,6 +650,7 @@ SRC_SHARED_MODULE_ALL = \
struct/__init__.c \
supervisor/__init__.c \
supervisor/StatusBar.c \
synthio/Biquad.c \
synthio/LFO.c \
synthio/Math.c \
synthio/MidiTrack.c \
@ -829,3 +830,15 @@ check-release-needs-clean-build:
# Ignore these errors
$(BUILD)/lib/libm/kf_rem_pio2.o: CFLAGS += -Wno-maybe-uninitialized
# Fetch only submodules needed for this particular port.
.PHONY: fetch-port-submodules
fetch-port-submodules:
$(TOP)/tools/fetch-submodules.sh data extmod frozen lib tools ports/$(shell basename $(CURDIR))
.PHONY: invalid-board
invalid-board:
$(Q)if [ -z "$(BOARD)" ] ; then echo "ERROR: No BOARD specified" ; else echo "ERROR: Invalid BOARD $(BOARD) specified"; fi && \
echo "Valid boards:" && \
printf '%s\n' $(ALL_BOARDS_IN_PORT) | column -xc $$(tput cols || echo 80) 1>&2 && \
false

View File

@ -24,22 +24,10 @@
# Common Makefile items that can be shared across CircuitPython ports.
# Select the board to build for.
define show_board_error
$(info Valid boards:)
$(shell printf '%s\n' $(patsubst boards/%/mpconfigboard.mk,%,$(wildcard boards/*/mpconfigboard.mk)) | column -xc $$(tput cols || echo 80) 1>&2)
$(error Rerun with $(MAKE) BOARD=<board>)
endef
ifeq ($(BOARD),)
$(info No BOARD specified)
$(call show_board_error)
else
ifeq ($(wildcard boards/$(BOARD)/.),)
$(info Invalid BOARD specified)
$(call show_board_error)
endif
endif
ALL_BOARDS_IN_PORT := $(patsubst boards/%/mpconfigboard.mk,%,$(wildcard boards/*/mpconfigboard.mk))
# An incorrect BOARD might have been specified, so check against the list.
# There is deliberately no space after the :=
VALID_BOARD :=$(filter $(BOARD),$(ALL_BOARDS_IN_PORT))
# If the flash PORT is not given, use the default /dev/tty.SLAB_USBtoUART.
PORT ?= /dev/tty.SLAB_USBtoUART
@ -47,16 +35,22 @@ PORT ?= /dev/tty.SLAB_USBtoUART
# If the build directory is not given, make it reflect the board name.
BUILD ?= build-$(BOARD)
# First makefile with targets. Defines the default target.
include ../../py/mkenv.mk
# Board-specific
# Board-specific. Skip if the rule requested is not board-specific.
ifneq ($(VALID_BOARD),)
include boards/$(BOARD)/mpconfigboard.mk
endif
# Port-specific
include mpconfigport.mk
# Also board-specific. Skip if the rule requested is not board-specific.
ifneq ($(VALID_BOARD),)
# CircuitPython-specific
include $(TOP)/py/circuitpy_mpconfig.mk
endif
# qstr definitions (must come before including py.mk)
QSTR_DEFS = qstrdefsport.h

View File

@ -59,8 +59,8 @@ extern void common_hal_mcu_enable_interrupts(void);
//
// default is 128; consider raising to reduce fragmentation.
#define MICROPY_ALLOC_PARSE_CHUNK_INIT (16)
// default is 512.
#define MICROPY_ALLOC_PATH_MAX (256)
// default is 512. Longest path in .py bundle as of June 6th, 2023 is 73 characters.
#define MICROPY_ALLOC_PATH_MAX (96)
#define MICROPY_CAN_OVERRIDE_BUILTINS (1)
#define MICROPY_COMP_CONST (1)
#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (1)

View File

@ -201,7 +201,7 @@ mp_obj_t MICROPY_WRAP_MP_LOAD_GLOBAL(mp_load_global)(qstr qst) {
return elem->value;
}
mp_obj_t mp_load_build_class(void) {
mp_obj_t __attribute__((noinline)) mp_load_build_class(void) {
DEBUG_OP_printf("load_build_class\n");
#if MICROPY_CAN_OVERRIDE_BUILTINS
if (MP_STATE_VM(mp_module_builtins_override_dict) != NULL) {
@ -858,7 +858,7 @@ mp_obj_t mp_call_method_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_ob
}
// unpacked items are stored in reverse order into the array pointed to by items
void mp_unpack_sequence(mp_obj_t seq_in, size_t num, mp_obj_t *items) {
void __attribute__((noinline,)) mp_unpack_sequence(mp_obj_t seq_in, size_t num, mp_obj_t *items) {
size_t seq_len;
if (mp_obj_is_type(seq_in, &mp_type_tuple) || mp_obj_is_type(seq_in, &mp_type_list)) {
mp_obj_t *seq_items;
@ -905,7 +905,7 @@ too_long:
}
// unpacked items are stored in reverse order into the array pointed to by items
void mp_unpack_ex(mp_obj_t seq_in, size_t num_in, mp_obj_t *items) {
void __attribute__((noinline)) mp_unpack_ex(mp_obj_t seq_in, size_t num_in, mp_obj_t *items) {
size_t num_left = num_in & 0xff;
size_t num_right = (num_in >> 8) & 0xff;
DEBUG_OP_printf("unpack ex " UINT_FMT " " UINT_FMT "\n", num_left, num_right);
@ -1482,7 +1482,7 @@ mp_obj_t mp_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level) {
return mp_builtin___import__(5, args);
}
mp_obj_t mp_import_from(mp_obj_t module, qstr name) {
mp_obj_t __attribute__((noinline,)) mp_import_from(mp_obj_t module, qstr name) {
DEBUG_printf("import from %p %s\n", module, qstr_str(name));
mp_obj_t dest[2];
@ -1528,7 +1528,7 @@ mp_obj_t mp_import_from(mp_obj_t module, qstr name) {
#endif
}
void mp_import_all(mp_obj_t module) {
void __attribute__((noinline)) mp_import_all(mp_obj_t module) {
DEBUG_printf("import all %p\n", module);
// TODO: Support __all__

View File

@ -0,0 +1,110 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Artyom Skrobov
*
* 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 <string.h>
#include "py/enum.h"
#include "py/mperrno.h"
#include "py/obj.h"
#include "py/objnamedtuple.h"
#include "py/runtime.h"
#include "shared-bindings/synthio/__init__.h"
#include "shared-bindings/synthio/LFO.h"
#include "shared-bindings/synthio/Math.h"
#include "shared-bindings/synthio/MidiTrack.h"
#include "shared-bindings/synthio/Note.h"
#include "shared-bindings/synthio/Synthesizer.h"
#include "shared-module/synthio/LFO.h"
#define default_attack_time (MICROPY_FLOAT_CONST(0.1))
#define default_decay_time (MICROPY_FLOAT_CONST(0.05))
#define default_release_time (MICROPY_FLOAT_CONST(0.2))
#define default_attack_level (MICROPY_FLOAT_CONST(1.))
#define default_sustain_level (MICROPY_FLOAT_CONST(0.8))
static const mp_arg_t biquad_properties[] = {
{ MP_QSTR_a1, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} },
{ MP_QSTR_a2, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} },
{ MP_QSTR_b0, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} },
{ MP_QSTR_b1, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} },
{ MP_QSTR_b2, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} },
};
//| class Biquad:
//| def __init__(self, b0: float, b1: float, b2: float, a1: float, a2: float) -> None:
//| """Construct a normalized biquad filter object.
//|
//| This implements the "direct form 1" biquad filter, where each coefficient
//| has been pre-divided by a0.
//|
//| Biquad objects are usually constructed via one of the related methods on a `Synthesizer` object
//| rather than directly from coefficients.
//|
//| https://github.com/WebAudio/Audio-EQ-Cookbook/blob/main/Audio-EQ-Cookbook.txt
//| """
//|
STATIC mp_obj_t synthio_biquad_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(biquad_properties)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(biquad_properties), biquad_properties, args);
for (size_t i = 0; i < MP_ARRAY_SIZE(biquad_properties); i++) {
args[i].u_obj = mp_obj_new_float(mp_arg_validate_type_float(args[i].u_obj, biquad_properties[i].qst));
}
MP_STATIC_ASSERT(sizeof(mp_arg_val_t) == sizeof(mp_obj_t));
return namedtuple_make_new(type_in, MP_ARRAY_SIZE(args), 0, &args[0].u_obj);
}
const mp_obj_namedtuple_type_t synthio_biquad_type_obj = {
.base = {
.base = {
.type = &mp_type_type
},
.flags = MP_TYPE_FLAG_EXTENDED,
.name = MP_QSTR_Biquad,
.print = namedtuple_print,
.parent = &mp_type_tuple,
.make_new = synthio_biquad_make_new,
.attr = namedtuple_attr,
MP_TYPE_EXTENDED_FIELDS(
.unary_op = mp_obj_tuple_unary_op,
.binary_op = mp_obj_tuple_binary_op,
.subscr = mp_obj_tuple_subscr,
.getiter = mp_obj_tuple_getiter,
),
},
.n_fields = 5,
.fields = {
MP_QSTR_a1,
MP_QSTR_a2,
MP_QSTR_b0,
MP_QSTR_b1,
MP_QSTR_b2,
},
};

View File

@ -0,0 +1,9 @@
#pragma once
#include "py/obj.h"
#include "py/objnamedtuple.h"
extern const mp_obj_namedtuple_type_t synthio_biquad_type_obj;
mp_obj_t common_hal_synthio_new_lpf(mp_float_t w0, mp_float_t Q);
mp_obj_t common_hal_synthio_new_hpf(mp_float_t w0, mp_float_t Q);
mp_obj_t common_hal_synthio_new_bpf(mp_float_t w0, mp_float_t Q);

View File

@ -41,7 +41,7 @@ static const mp_arg_t note_properties[] = {
{ MP_QSTR_bend, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0) } },
{ 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_filter, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
{ MP_QSTR_ring_frequency, MP_ARG_OBJ, {.u_obj = MP_ROM_INT(0) } },
{ MP_QSTR_ring_bend, MP_ARG_OBJ, {.u_obj = MP_ROM_INT(0) } },
{ MP_QSTR_ring_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
@ -56,6 +56,7 @@ static const mp_arg_t note_properties[] = {
//| envelope: Optional[Envelope] = None,
//| amplitude: BlockInput = 0.0,
//| bend: BlockInput = 0.0,
//| filter: Optional[Biquad] = None,
//| ring_frequency: float = 0.0,
//| ring_bend: float = 0.0,
//| ring_waveform: Optional[ReadableBuffer] = 0.0,
@ -97,17 +98,21 @@ 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."""
//| filter: Optional[Biquad]
//| """If not None, the output of this Note is filtered according to the provided coefficients.
//|
//| Construct an appropriate filter by calling a filter-making method on the
//| `Synthesizer` object where you plan to play the note, as filter coefficients depend
//| on the sample rate"""
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));
return common_hal_synthio_note_get_filter_obj(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));
common_hal_synthio_note_set_filter(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_filter_obj, synthio_note_set_filter);

View File

@ -9,8 +9,8 @@ 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_obj_t common_hal_synthio_note_get_filter_obj(synthio_note_obj_t *self);
void common_hal_synthio_note_set_filter(synthio_note_obj_t *self, mp_obj_t biquad);
mp_obj_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_obj_t value);

View File

@ -32,6 +32,7 @@
#include "py/objproperty.h"
#include "py/runtime.h"
#include "shared-bindings/util.h"
#include "shared-bindings/synthio/Biquad.h"
#include "shared-bindings/synthio/Synthesizer.h"
#include "shared-bindings/synthio/LFO.h"
#include "shared-bindings/synthio/__init__.h"
@ -52,7 +53,6 @@
//| channel_count: int = 1,
//| waveform: Optional[ReadableBuffer] = None,
//| envelope: Optional[Envelope] = None,
//| filter: Optional[ReadableBuffer] = None,
//| ) -> None:
//| """Create a synthesizer object.
//|
@ -65,17 +65,15 @@
//| :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 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.
//| """
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_channel_count, ARG_waveform, ARG_envelope, ARG_filter };
enum { ARG_sample_rate, ARG_channel_count, ARG_waveform, ARG_envelope };
static const mp_arg_t allowed_args[] = {
{ 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_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_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
@ -87,7 +85,6 @@ STATIC mp_obj_t synthio_synthesizer_make_new(const mp_obj_type_t *type, size_t n
args[ARG_sample_rate].u_int,
args[ARG_channel_count].u_int,
args[ARG_waveform].u_obj,
args[ARG_filter].u_obj,
args[ARG_envelope].u_obj);
return MP_OBJ_FROM_PTR(self);
@ -292,6 +289,114 @@ MP_PROPERTY_GETTER(synthio_synthesizer_blocks_obj,
//| """Maximum polyphony of the synthesizer (read-only class property)"""
//|
//| def low_pass_filter(cls, frequency: float, q_factor: float = 0.7071067811865475) -> Biquad:
//| """Construct a low-pass filter with the given parameters.
//|
//| ``frequency``, called f0 in the cookbook, is the corner frequency in Hz
//| of the filter.
//|
//| ``q_factor``, called ``Q`` in the cookbook. Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
//| """
enum passfilter_arg_e { ARG_f0, ARG_Q };
// M_PI is not part of the math.h standard and may not be defined
// And by defining our own we can ensure it uses the correct const format.
#define MP_PI MICROPY_FLOAT_CONST(3.14159265358979323846)
static const mp_arg_t passfilter_properties[] = {
{ MP_QSTR_frequency, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_ROM_NONE} },
{ MP_QSTR_Q, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL } },
};
STATIC mp_obj_t synthio_synthesizer_lpf(size_t n_pos, const mp_obj_t *pos_args, mp_map_t *kw_args) {
mp_arg_val_t args[MP_ARRAY_SIZE(passfilter_properties)];
mp_obj_t self_in = pos_args[0];
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_arg_parse_all(n_pos - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(passfilter_properties), passfilter_properties, args);
mp_float_t f0 = mp_arg_validate_type_float(args[ARG_f0].u_obj, MP_QSTR_f0);
mp_float_t Q =
args[ARG_Q].u_obj == MP_OBJ_NULL ? MICROPY_FLOAT_CONST(0.7071067811865475) :
mp_arg_validate_type_float(args[ARG_Q].u_obj, MP_QSTR_Q);
mp_float_t w0 = f0 / self->synth.sample_rate * 2 * MP_PI;
return common_hal_synthio_new_lpf(w0, Q);
}
MP_DEFINE_CONST_FUN_OBJ_KW(synthio_synthesizer_lpf_fun_obj, 1, synthio_synthesizer_lpf);
//| def high_pass_filter(
//| cls, frequency: float, q_factor: float = 0.7071067811865475
//| ) -> Biquad:
//| """Construct a high-pass filter with the given parameters.
//|
//| ``frequency``, called f0 in the cookbook, is the corner frequency in Hz
//| of the filter.
//|
//| ``q_factor``, called ``Q`` in the cookbook. Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
//| """
STATIC mp_obj_t synthio_synthesizer_hpf(size_t n_pos, const mp_obj_t *pos_args, mp_map_t *kw_args) {
mp_arg_val_t args[MP_ARRAY_SIZE(passfilter_properties)];
mp_obj_t self_in = pos_args[0];
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_arg_parse_all(n_pos - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(passfilter_properties), passfilter_properties, args);
mp_float_t f0 = mp_arg_validate_type_float(args[ARG_f0].u_obj, MP_QSTR_f0);
mp_float_t Q =
args[ARG_Q].u_obj == MP_OBJ_NULL ? MICROPY_FLOAT_CONST(0.7071067811865475) :
mp_arg_validate_type_float(args[ARG_Q].u_obj, MP_QSTR_Q);
mp_float_t w0 = f0 / self->synth.sample_rate * 2 * MP_PI;
return common_hal_synthio_new_hpf(w0, Q);
}
//| def band_pass_filter(
//| cls, frequency: float, q_factor: float = 0.7071067811865475
//| ) -> Biquad:
//| """Construct a band-pass filter with the given parameters.
//|
//| ``frequency``, called f0 in the cookbook, is the center frequency in Hz
//| of the filter.
//|
//| ``q_factor``, called ``Q`` in the cookbook. Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
//|
//| The coefficients are scaled such that the filter has a 0dB peak gain.
//| """
//|
MP_DEFINE_CONST_FUN_OBJ_KW(synthio_synthesizer_hpf_fun_obj, 1, synthio_synthesizer_hpf);
STATIC mp_obj_t synthio_synthesizer_bpf(size_t n_pos, const mp_obj_t *pos_args, mp_map_t *kw_args) {
mp_arg_val_t args[MP_ARRAY_SIZE(passfilter_properties)];
mp_obj_t self_in = pos_args[0];
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_arg_parse_all(n_pos - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(passfilter_properties), passfilter_properties, args);
mp_float_t f0 = mp_arg_validate_type_float(args[ARG_f0].u_obj, MP_QSTR_f0);
mp_float_t Q =
args[ARG_Q].u_obj == MP_OBJ_NULL ? MICROPY_FLOAT_CONST(0.7071067811865475) :
mp_arg_validate_type_float(args[ARG_Q].u_obj, MP_QSTR_Q);
mp_float_t w0 = f0 / self->synth.sample_rate * 2 * MP_PI;
return common_hal_synthio_new_bpf(w0, Q);
}
MP_DEFINE_CONST_FUN_OBJ_KW(synthio_synthesizer_bpf_fun_obj, 1, synthio_synthesizer_bpf);
STATIC const mp_rom_map_elem_t synthio_synthesizer_locals_dict_table[] = {
// Methods
{ MP_ROM_QSTR(MP_QSTR_press), MP_ROM_PTR(&synthio_synthesizer_press_obj) },
@ -304,6 +409,9 @@ STATIC const mp_rom_map_elem_t synthio_synthesizer_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&synthio_synthesizer___exit___obj) },
{ MP_ROM_QSTR(MP_QSTR_low_pass_filter), MP_ROM_PTR(&synthio_synthesizer_lpf_fun_obj) },
{ MP_ROM_QSTR(MP_QSTR_high_pass_filter), MP_ROM_PTR(&synthio_synthesizer_hpf_fun_obj) },
{ MP_ROM_QSTR(MP_QSTR_band_pass_filter), MP_ROM_PTR(&synthio_synthesizer_bpf_fun_obj) },
// Properties
{ MP_ROM_QSTR(MP_QSTR_envelope), MP_ROM_PTR(&synthio_synthesizer_envelope_obj) },
{ MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&synthio_synthesizer_sample_rate_obj) },

View File

@ -32,7 +32,7 @@
extern const mp_obj_type_t synthio_synthesizer_type;
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj,
uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj,
mp_obj_t envelope_obj);
void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self);
bool common_hal_synthio_synthesizer_deinited(synthio_synthesizer_obj_t *self);

View File

@ -36,6 +36,7 @@
#include "extmod/vfs_posix.h"
#include "shared-bindings/synthio/__init__.h"
#include "shared-bindings/synthio/Biquad.h"
#include "shared-bindings/synthio/LFO.h"
#include "shared-bindings/synthio/Math.h"
#include "shared-bindings/synthio/MidiTrack.h"
@ -310,6 +311,7 @@ MP_DEFINE_CONST_FUN_OBJ_VAR(synthio_lfo_tick_obj, 1, synthio_lfo_tick);
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_Biquad), MP_ROM_PTR(&synthio_biquad_type_obj) },
{ MP_ROM_QSTR(MP_QSTR_Math), MP_ROM_PTR(&synthio_math_type) },
{ MP_ROM_QSTR(MP_QSTR_MathOperation), MP_ROM_PTR(&synthio_math_operation_type) },
{ MP_ROM_QSTR(MP_QSTR_MidiTrack), MP_ROM_PTR(&synthio_miditrack_type) },

View File

@ -0,0 +1,143 @@
/*
* 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 "shared-bindings/synthio/Biquad.h"
#include "shared-module/synthio/Biquad.h"
mp_obj_t common_hal_synthio_new_lpf(mp_float_t w0, mp_float_t Q) {
mp_float_t s = MICROPY_FLOAT_C_FUN(sin)(w0);
mp_float_t c = MICROPY_FLOAT_C_FUN(cos)(w0);
mp_float_t alpha = s / (2 * Q);
mp_float_t a0 = 1 + alpha;
mp_float_t a1 = -2 * c;
mp_float_t a2 = 1 - alpha;
mp_float_t b0 = (1 - c) / 2;
mp_float_t b1 = 1 - c;
mp_float_t b2 = (1 - c) / 2;
mp_obj_t out_args[] = {
mp_obj_new_float(a1 / a0),
mp_obj_new_float(a2 / a0),
mp_obj_new_float(b0 / a0),
mp_obj_new_float(b1 / a0),
mp_obj_new_float(b2 / a0),
};
return namedtuple_make_new((const mp_obj_type_t *)&synthio_biquad_type_obj, MP_ARRAY_SIZE(out_args), 0, out_args);
}
mp_obj_t common_hal_synthio_new_hpf(mp_float_t w0, mp_float_t Q) {
mp_float_t s = MICROPY_FLOAT_C_FUN(sin)(w0);
mp_float_t c = MICROPY_FLOAT_C_FUN(cos)(w0);
mp_float_t alpha = s / (2 * Q);
mp_float_t a0 = 1 + alpha;
mp_float_t a1 = -2 * c;
mp_float_t a2 = 1 - alpha;
mp_float_t b0 = (1 + c) / 2;
mp_float_t b1 = -(1 + c);
mp_float_t b2 = (1 + c) / 2;
mp_obj_t out_args[] = {
mp_obj_new_float(a1 / a0),
mp_obj_new_float(a2 / a0),
mp_obj_new_float(b0 / a0),
mp_obj_new_float(b1 / a0),
mp_obj_new_float(b2 / a0),
};
return namedtuple_make_new((const mp_obj_type_t *)&synthio_biquad_type_obj, MP_ARRAY_SIZE(out_args), 0, out_args);
}
mp_obj_t common_hal_synthio_new_bpf(mp_float_t w0, mp_float_t Q) {
mp_float_t s = MICROPY_FLOAT_C_FUN(sin)(w0);
mp_float_t c = MICROPY_FLOAT_C_FUN(cos)(w0);
mp_float_t alpha = s / (2 * Q);
mp_float_t a0 = 1 + alpha;
mp_float_t a1 = -2 * c;
mp_float_t a2 = 1 - alpha;
mp_float_t b0 = alpha;
mp_float_t b1 = 0;
mp_float_t b2 = -alpha;
mp_obj_t out_args[] = {
mp_obj_new_float(a1 / a0),
mp_obj_new_float(a2 / a0),
mp_obj_new_float(b0 / a0),
mp_obj_new_float(b1 / a0),
mp_obj_new_float(b2 / a0),
};
return namedtuple_make_new((const mp_obj_type_t *)&synthio_biquad_type_obj, MP_ARRAY_SIZE(out_args), 0, out_args);
}
#define BIQUAD_SHIFT (15)
STATIC int32_t biquad_scale_arg_obj(mp_obj_t arg) {
return (int32_t)MICROPY_FLOAT_C_FUN(round)(MICROPY_FLOAT_C_FUN(ldexp)(mp_obj_get_float(arg), BIQUAD_SHIFT));
}
void synthio_biquad_filter_assign(biquad_filter_state *st, mp_obj_t biquad_obj) {
if (biquad_obj != mp_const_none) {
mp_arg_validate_type(biquad_obj, (const mp_obj_type_t *)&synthio_biquad_type_obj, MP_QSTR_filter);
mp_obj_tuple_t *biquad = (mp_obj_tuple_t *)MP_OBJ_TO_PTR(biquad_obj);
st->a1 = biquad_scale_arg_obj(biquad->items[0]);
st->a2 = biquad_scale_arg_obj(biquad->items[1]);
st->b0 = biquad_scale_arg_obj(biquad->items[2]);
st->b1 = biquad_scale_arg_obj(biquad->items[3]);
st->b2 = biquad_scale_arg_obj(biquad->items[4]);
}
}
void synthio_biquad_filter_reset(biquad_filter_state *st) {
memset(&st->x, 0, 4 * sizeof(int16_t));
}
void synthio_biquad_filter_samples(biquad_filter_state *st, int32_t *buffer, size_t n_samples) {
int32_t a1 = st->a1;
int32_t a2 = st->a2;
int32_t b0 = st->b0;
int32_t b1 = st->b1;
int32_t b2 = st->b2;
int32_t x0 = st->x[0];
int32_t x1 = st->x[1];
int32_t y0 = st->y[0];
int32_t y1 = st->y[1];
for (size_t n = n_samples; n; --n, ++buffer) {
int32_t input = *buffer;
int32_t output = (b0 * input + b1 * x0 + b2 * x1 - a1 * y0 - a2 * y1 + (1 << (BIQUAD_SHIFT - 1))) >> BIQUAD_SHIFT;
x1 = x0;
x0 = input;
y1 = y0;
y0 = output;
*buffer = output;
}
st->x[0] = x0;
st->x[1] = x1;
st->y[0] = y0;
st->y[1] = y1;
}

View File

@ -0,0 +1,38 @@
/*
* 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.
*/
#pragma once
#include "py/obj.h"
typedef struct {
int32_t a1, a2, b0, b1, b2;
int32_t x[2], y[2];
} biquad_filter_state;
void synthio_biquad_filter_assign(biquad_filter_state *st, mp_obj_t biquad_obj);
void synthio_biquad_filter_reset(biquad_filter_state *st);
void synthio_biquad_filter_samples(biquad_filter_state *st, int32_t *buffer, size_t n_samples);

View File

@ -122,7 +122,7 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self,
self->track.buf = (void *)buffer;
self->track.len = len;
synthio_synth_init(&self->synth, sample_rate, 1, waveform_obj, mp_const_none, envelope_obj);
synthio_synth_init(&self->synth, sample_rate, 1, waveform_obj, envelope_obj);
start_parse(self);
}

View File

@ -40,12 +40,13 @@ void common_hal_synthio_note_set_frequency(synthio_note_obj_t *self, mp_float_t
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;
mp_obj_t common_hal_synthio_note_get_filter_obj(synthio_note_obj_t *self) {
return self->filter_obj;
}
void common_hal_synthio_note_set_filter(synthio_note_obj_t *self, bool value_in) {
self->filter = value_in;
void common_hal_synthio_note_set_filter(synthio_note_obj_t *self, mp_obj_t filter_in) {
synthio_biquad_filter_assign(&self->filter_state, filter_in);
self->filter_obj = filter_in;
}
mp_float_t common_hal_synthio_note_get_ring_frequency(synthio_note_obj_t *self) {
@ -147,6 +148,7 @@ void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate) {
void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate) {
synthio_note_recalculate(self, sample_rate);
synthio_biquad_filter_reset(&self->filter_state);
}
// Perform a pitch bend operation

View File

@ -27,6 +27,7 @@
#pragma once
#include "shared-module/synthio/__init__.h"
#include "shared-module/synthio/Biquad.h"
#include "shared-module/synthio/LFO.h"
#include "shared-bindings/synthio/__init__.h"
@ -37,12 +38,14 @@ typedef struct synthio_note_obj {
mp_float_t frequency, ring_frequency;
mp_obj_t waveform_obj, envelope_obj, ring_waveform_obj;
mp_obj_t filter_obj;
biquad_filter_state filter_state;
int32_t sample_rate;
int32_t frequency_scaled;
int32_t ring_frequency_scaled, ring_frequency_bent;
bool filter;
mp_buffer_info_t waveform_buf;
mp_buffer_info_t ring_waveform_buf;

View File

@ -33,10 +33,10 @@
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t filter_obj,
uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj,
mp_obj_t envelope_obj) {
synthio_synth_init(&self->synth, sample_rate, channel_count, waveform_obj, filter_obj, envelope_obj);
synthio_synth_init(&self->synth, sample_rate, channel_count, waveform_obj, envelope_obj);
self->blocks = mp_obj_new_list(0, NULL);
}

View File

@ -27,6 +27,7 @@
#include "shared-module/synthio/__init__.h"
#include "shared-bindings/synthio/__init__.h"
#include "shared-module/synthio/Biquad.h"
#include "shared-module/synthio/Note.h"
#include "py/runtime.h"
#include <math.h>
@ -171,24 +172,11 @@ int16_t mix_down_sample(int32_t sample) {
return sample;
}
static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *out_buffer32, int16_t dur) {
static bool synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *out_buffer32, int16_t dur, uint16_t loudness[2]) {
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;
@ -227,33 +215,37 @@ static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou
}
}
int synth_chan = synth->channel_count;
if (ring_dds_rate) {
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
uint32_t accum = synth->accum[chan];
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;
}
if (dds_rate > lim / 2) {
// beyond nyquist, can't play note
return false;
}
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
if (accum > lim) {
accum %= lim;
}
// 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;
accum -= lim;
}
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
out_buffer32[i] = waveform[idx];
}
synth->accum[chan] = accum;
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];
if (ring_dds_rate) {
if (ring_dds_rate > lim / 2) {
// beyond nyquist, can't play ring (but did synth main sound so
// return true)
return true;
}
synth->accum[chan] = accum;
// now modulate by ring and accumulate
accum = synth->ring_accum[chan];
@ -264,82 +256,43 @@ static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou
accum %= lim;
}
for (uint16_t i = 0, j = 0; i < dur; i++) {
for (uint16_t i = 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++;
}
int16_t wi = (ring_waveform[idx] * out_buffer32[i]) / 32768;
out_buffer32[i] = wi;
}
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;
}
return true;
}
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;
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) {
STATIC mp_obj_t synthio_synth_get_note_filter(mp_obj_t note_obj) {
if (note_obj == mp_const_none) {
return false;
return mp_const_none;
}
if (!mp_obj_is_small_int(note_obj)) {
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
return note->filter;
return note->filter_obj;
}
return mp_const_none;
}
STATIC void sum_with_loudness(int32_t *out_buffer32, int32_t *tmp_buffer32, uint16_t loudness[2], size_t dur, int synth_chan) {
if (synth_chan == 1) {
for (size_t i = 0; i < dur; i++) {
*out_buffer32++ += (*tmp_buffer32++ *loudness[0]) >> 16;
}
} else {
for (size_t i = 0; i < dur; i++) {
*out_buffer32++ += (*tmp_buffer32 * loudness[0]) >> 16;
*out_buffer32++ += (*tmp_buffer32++ *loudness[1]) >> 16;
}
}
return true;
}
void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t *buffer_length, uint8_t channel) {
@ -359,37 +312,44 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
uint16_t dur = MIN(SYNTHIO_MAX_DUR, synth->span.dur);
synth->span.dur -= dur;
int32_t out_buffer32[dur * synth->channel_count];
if (synth->filter_buffer) {
int32_t *filter_start = &synth->filter_buffer[synth->filter_bufinfo.len * synth->channel_count];
memset(filter_start, 0, dur * synth->channel_count * sizeof(int32_t));
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
mp_obj_t note_obj = synth->span.note_obj[chan];
if (!synthio_synth_get_note_filtered(note_obj)) {
continue;
}
synth_note_into_buffer(synth, chan, filter_start, dur);
}
run_fir(synth, out_buffer32, dur);
} else {
memset(out_buffer32, 0, sizeof(out_buffer32));
}
int32_t out_buffer32[SYNTHIO_MAX_DUR * synth->channel_count];
int32_t tmp_buffer32[SYNTHIO_MAX_DUR];
memset(out_buffer32, 0, synth->channel_count * dur * sizeof(int32_t));
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)) {
if (note_obj == SYNTHIO_SILENCE) {
continue;
}
synth_note_into_buffer(synth, chan, out_buffer32, dur);
if (synth->envelope_state[chan].level == 0) {
// note is truly finished, but we only just noticed
synth->span.note_obj[chan] = SYNTHIO_SILENCE;
continue;
}
uint16_t loudness[2] = {synth->envelope_state[chan].level,synth->envelope_state[chan].level};
if (!synth_note_into_buffer(synth, chan, tmp_buffer32, dur, loudness)) {
// for some other reason, such as being above nyquist, note
// couldn't be synthed, so don't filter or sum it in
continue;
}
mp_obj_t filter_obj = synthio_synth_get_note_filter(note_obj);
if (filter_obj != mp_const_none) {
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
synthio_biquad_filter_samples(&note->filter_state, tmp_buffer32, dur);
}
// adjust loudness by envelope
sum_with_loudness(out_buffer32, tmp_buffer32, loudness, dur, synth->channel_count);
}
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++) {
for (size_t i = 0; i < dur * synth->channel_count; i++) {
int32_t sample = out_buffer32[i];
out_buffer16[i] = mix_down_sample(sample);
}
@ -419,7 +379,6 @@ bool synthio_synth_deinited(synthio_synth_t *synth) {
}
void synthio_synth_deinit(synthio_synth_t *synth) {
synth->filter_buffer = NULL;
synth->buffers[0] = NULL;
synth->buffers[1] = NULL;
}
@ -433,17 +392,12 @@ mp_obj_t synthio_synth_envelope_get(synthio_synth_t *synth) {
return synth->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) {
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t envelope_obj) {
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[1] = m_malloc(synth->buffer_length, false);
if (synth->filter_bufinfo.len) {
synth->filter_buffer_length = (synth->filter_bufinfo.len + 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->waveform_obj = waveform_obj;
@ -483,11 +437,6 @@ void synthio_synth_parse_waveform(mp_buffer_info_t *bufinfo_waveform, mp_obj_t w
parse_common(bufinfo_waveform, waveform_obj, MP_QSTR_waveform, 16384);
}
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++) {
if (synth->span.note_obj[i] == note) {

View File

@ -64,12 +64,11 @@ typedef struct synthio_synth {
uint32_t sample_rate;
uint32_t total_envelope;
int16_t *buffers[2];
int32_t *filter_buffer;
uint8_t channel_count;
uint16_t buffer_length, filter_buffer_length;
uint16_t buffer_length;
uint16_t last_buffer_length;
uint8_t other_channel, buffer_index, other_buffer_index;
mp_buffer_info_t waveform_bufinfo, filter_bufinfo;
mp_buffer_info_t waveform_bufinfo;
synthio_envelope_definition_t global_envelope_definition;
mp_obj_t waveform_obj, filter_obj, envelope_obj;
synthio_midi_span_t span;
@ -91,7 +90,7 @@ typedef struct {
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);
bool synthio_synth_deinited(synthio_synth_t *synth);
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);
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, int channel_count, mp_obj_t waveform_obj, mp_obj_t envelope);
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);
void synthio_synth_reset_buffer(synthio_synth_t *synth, bool single_channel_output, uint8_t channel);

View File

@ -0,0 +1,61 @@
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.15, decay_time=0, release_time=0.08, attack_level=1.0, sustain_level=1.0
)
SAMPLE_SIZE = 1024
VOLUME = 14700
sine = np.array(
np.sin(np.linspace(0, 2 * np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME,
dtype=np.int16,
)
noise = np.array([random.randint(-VOLUME, VOLUME) for i in range(SAMPLE_SIZE)], dtype=np.int16)
bend_out = np.linspace(0, 32767, num=SAMPLE_SIZE, endpoint=True, dtype=np.int16)
def synthesize(synth):
for waveform in (sine, None, noise):
for biquad in (
None,
synth.low_pass_filter(120),
):
for midi_note in range(24, 90, 3):
n = synthio.Note(
frequency=synthio.midi_to_hz(midi_note),
envelope=envelope,
filter=biquad,
waveform=waveform,
bend=synthio.LFO(bend_out, once=True, rate=1 / 2, scale=5),
)
synth.press(n)
print(n.frequency)
yield 24
synth.release_all()
yield 16
yield 24
yield 48
with wave.open("biquad.wav", "w") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(48000)
synth = synthio.Synthesizer(sample_rate=48000)
for n in synthesize(synth):
for i in range(n):
result, data = audiocore.get_buffer(synth)
f.writeframes(data)

View File

@ -1,53 +0,0 @@
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
import mkfilter
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
)
SAMPLE_SIZE = 1024
bend_out = np.linspace(0, 32767, num=SAMPLE_SIZE, endpoint=True, dtype=np.int16)
filter_rectangular = mkfilter.LPF(48000, 800, 13)
filter_rectangular_big = mkfilter.LPF(48000, 800, 59)
filter_blackman = mkfilter.LPF(48000, 800, 59, win=mkfilter.blackman)
print(filter_blackman)
def synthesize(synth):
n = synthio.Note(
frequency=120,
envelope=envelope,
filter=True,
bend=synthio.LFO(bend_out, once=True, rate=1 / 2, scale=5),
)
synth.press(n)
print(synth, n)
yield 2 * 48000 // 256
synth.release_all()
yield 36
with wave.open("fir.wav", "w") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(48000)
for filter_coeffs in [None, filter_rectangular, filter_rectangular_big, filter_blackman]:
synth = synthio.Synthesizer(sample_rate=48000, filter=filter_coeffs)
for n in synthesize(synth):
for i in range(n):
result, data = audiocore.get_buffer(synth)
f.writeframes(data)

View File

@ -1,105 +0,0 @@
try:
from ulab import numpy as np
except ImportError:
import numpy as np
def lpf(fS, f, N, win=lambda N: 1):
if not (N & 1):
raise ValueError("filter length must be odd")
h = np.sinc(2 * f / fS * (np.arange(N) - (N - 1) / 2))
h = h * win(N)
return h * (1 / np.sum(h))
def hpf(fS, f, N, win=lambda N: 1):
if not (N & 1):
raise ValueError("filter length must be odd")
h = -lpf(fS, f, N)
h = h * win(N)
h[(N - 1) // 2] += 1
return h
def brf(fS, fL, NL, fH, NH, win=lambda N: 1):
hlpf = lpf(fS, fL, NL, win)
hhpf = hpf(fS, fH, NH, win)
if NH > NL:
h = hhpf
h[(NH - NL) // 2 : (NH - NL) // 2 + NL] += hlpf
else:
h = hlpf
h[(NL - NH) // 2 : (NL - NH) // 2 + NH] += hhpf
return h
def bpf(fS, fL, NL, fH, NH, win=lambda N: 1):
hlpf = lpf(fS, fL, NL, win)
hhpf = hpf(fS, fH, NH, win)
return np.convolve(hlpf, hhpf)
def blackman(M):
n = np.arange(1 - M, M, 2)
return 0.42 + 0.5 * np.cos(np.pi * n / (M - 1)) + 0.08 * np.cos(2.0 * np.pi * n / (M - 1))
def tosynthio(coeffs):
result = np.array(coeffs * 32767, dtype=np.int16)
return trim_zeros(result)
def trim_zeros(arr):
i = 0
j = len(arr) - 1
while i < len(arr) and arr[i] == 0:
i += 1
while j > i and arr[j] == 0:
j -= 1
return arr[i : j + 1]
# fiiir.com uses factor 4.6 for blackman window, 0.91 for rectangular
def ntaps(fS, fB, factor=4.6):
b = fB / fS
return round(factor / b) | 1
def LPF(*args, **kw):
return tosynthio(lpf(*args, **kw))
def HPF(*args, **kw):
return tosynthio(hpf(*args, **kw))
def BRF(*args, **kw):
return tosynthio(brf(*args, **kw))
def BPF(*args, **kw):
return tosynthio(bpf(*args, **kw))
if __name__ == "__main__":
print("lpf(24000, 2040, 13) # 1920Hz transition window")
print(list(lpf(24000, 2040, 13)))
print("hpf(24000, 9600, 13) # 960Hz transition window")
print(list(hpf(24000, 9600, 23)))
print("bpf(24000, 1200, 11, 3960, 15) # 2400Hz, 1600Hz transition windows")
print(list(bpf(24000, 1200, 11, 3960, 15)))
print("brf(24000, 960, 19, 2400, 13) # 1200, 1800Hz transition windows")
brf_tst = brf(24000, 960, 19, 2400, 13)
print(brf_tst)
print("brf(24000, 960, 13, 2400, 19) # 1200, 1800Hz transition windows")
brf_tst = brf(24000, 960, 13, 2400, 19)
print(brf_tst)
print("lpf(1, 0.1, 59, blackman) # 1920Hz transition window, blackman")
print(lpf(1, 0.1, 59, blackman))

View File

@ -23,18 +23,17 @@ envelope = synthio.Envelope(
)
synth = synthio.Synthesizer(sample_rate=48000)
bend_out = np.linspace(0, 32767, num=SAMPLE_SIZE, endpoint=True, dtype=np.int16)
def synthesize(synth):
n = synthio.Note(
frequency=120,
frequency=440,
waveform=sine,
ring_waveform=sine,
ring_frequency=769,
envelope=envelope,
bend_mode=synthio.BendType.VIBRATO,
bend_depth=50 / 1200,
bend_rate=7,
bend=synthio.LFO(bend_out, scale=50 / 1200, rate=7),
)
print(synth, n)
@ -43,6 +42,13 @@ def synthesize(synth):
synth.release_all()
yield 36
n.ring_frequency = 0
print(synth, n)
synth.press((n,))
yield 720
synth.release_all()
yield 36
def chain(*args):
for a in args:

View File

@ -1,4 +1,4 @@
(0, 1, 512, 1)
1 [-16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383]
1 [-16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383]
(0, 1, 512, 1)
1 [0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0]
1 [0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,30 @@
()
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
(80,)
[-16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383]
[-16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384]
(80, 91)
[0, 0, 28045, 28045, 0, -28046, -28046, 0, 28045, 28045, 0, 0, 28045, 0, 0, -28046, -28046, 0, 28045, 28045, 0, 0, 28045, 0]
[-1, -1, 28045, 28045, -1, -28046, -28046, -1, 28045, 28045, -1, -1, 28045, -1, -1, -28046, -28046, -1, 28045, 28045, -1, -1, 28045, -1]
(91,)
[-28046, 0, 0, 28045, 0, 0, 28045, 28045, 0, -28046, -28046, 0, 28045, 28045, 0, 0, 28045, 0, 0, -28046, -28046, -28046, 28045, 28045]
(-5242, 5242)
[-28046, -1, -1, 28045, -1, -1, 28045, 28045, -1, -28046, -28046, -1, 28045, 28045, -1, -1, 28045, -1, -1, -28046, -28046, -28046, 28045, 28045]
(-5243, 5242)
(-10485, 10484)
(-15727, 15727)
(-16383, 16383)
(-14286, 14286)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-13106, 13106)
(-11009, 11009)
(-8912, 8912)
(-6815, 6815)
(-4718, 4718)
(-2621, 2621)
(-524, 524)
(-15728, 15727)
(-16384, 16383)
(-14287, 14286)
(-13107, 13106)
(-13107, 13106)
(-13107, 13106)
(-13107, 13106)
(-13107, 13106)
(-13107, 13106)
(-13107, 13106)
(-13107, 13106)
(-11010, 11009)
(-8913, 8912)
(-6816, 6815)
(-4719, 4718)
(-2622, 2621)
(-525, 524)
(0, 0)
(0, 0)
(0, 0)

View File

@ -1,13 +1,13 @@
()
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
(Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None),)
(Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=None, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None),)
[-16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383, 16382, 16382, 16382, 16382, 16382, -16383, -16383, -16383, -16383, -16383]
(Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None), Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None))
(Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=None, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None), Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=None, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None))
[-1, -1, -1, -1, -1, -1, -1, -1, 28045, -1, -1, -1, -1, -28046, -1, -1, -1, -1, 28045, -1, -1, -1, -1, -28046]
(Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=True, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None),)
(Note(frequency=830.6076004423605, panning=0.0, amplitude=1.0, bend=0.0, waveform=None, envelope=None, filter=None, ring_frequency=0.0, ring_bend=0.0, ring_waveform=None),)
[-1, -1, -1, 28045, -1, -1, -1, -1, -1, -1, -1, -1, 28045, -1, -1, -1, -1, -28046, -1, -1, -1, -1, 28045, -1]
(-5242, 5241)
(-10484, 10484)
(-10485, 10484)
(-15727, 15726)
(-16383, 16382)
(-14286, 14285)

View File

@ -0,0 +1,12 @@
from synthio import Synthesizer
s = Synthesizer(sample_rate=48000)
def print_filter(x):
print(" ".join(f"{v:.4g}" for v in x))
print_filter(s.low_pass_filter(330))
print_filter(s.high_pass_filter(330))
print_filter(s.band_pass_filter(330))

View File

@ -0,0 +1,3 @@
-1.939 0.9407 0.0004526 0.0009052 0.0004526
-1.939 0.9407 0.9699 -1.94 0.9699
-1.939 0.9407 0.02963 0 -0.02963

23
tools/fetch-submodules.sh Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env sh
# Pass the directories of submodules to fetch as command-line arguments. For example:
# ./fetch-submodules.sh lib tools
# If no arguments are passed, all submodules will be fetched.
# This script handles being called at other than the top level by making the paths
# for the submodules absolute.
TOP=$(git rev-parse --show-toplevel)
git submodule sync --quiet
# Prefix all the args with the absolute path to the top of the repo.
abs_submodules=""
for d in "$@"; do
abs_submodules="${abs_submodules} ${TOP}/${d}"
done
echo ${abs_submodules}
# Fetch submodules as partial clones if possible. If that fails due to an older version of git,
# do a shallow init and fetch tags.
git submodule update --init --filter=blob:none ${abs_submodules} || \
git submodule update --init --depth 1 ${abs_submodules} && \
git submodule foreach 'git fetch --tags --depth 1' || \
echo "ERROR: fetch-submodules.sh FAILED"

64
tools/gdb-stack-size.py Normal file
View File

@ -0,0 +1,64 @@
"""Source this file into gdb `source ../../tools/gdb-stack-size.py` then run
`stack-size` to print a backtrace with each frame size next to it."""
class StackSize(gdb.Command):
def __init__(self):
super(StackSize, self).__init__("stack-size", gdb.COMMAND_USER)
def invoke(self, arg, from_tty):
frame = gdb.newest_frame()
total_size = 0
while frame:
sp = frame.read_register("sp")
frame_up = frame.older()
if not frame_up:
break
f = frame.function()
l = frame.level()
if l < 10:
l = "#" + str(l) + " "
else:
l = "#" + str(l)
size = frame_up.read_register("sp") - sp
total_size += size
print(l, sp, frame.type(), f, " " * (40 - len(str(f))), size)
# print(dir(f))
# Tweak this if for more detail for a specific function.
if False and f.name == "mp_execute_bytecode":
b = frame.block()
prev_b = None
while not b.is_static:
print(" block", hex(b.start), hex(b.end), b.function)
for sym in b:
if not sym.needs_frame:
continue
v = sym.value(frame)
print(" ", sym.addr_class, v.address, sym.type.sizeof, sym, sym.type, v)
prev_b = b
b = b.superblock
if b.function == f:
break
b = prev_b
print("pc scan", hex(b.start), hex(b.end))
seen = set()
for pc in range(b.start, b.end, 2):
b = gdb.block_for_pc(pc)
r = (b.start, b.end)
if r in seen:
continue
seen.add(r)
print(" ", hex(pc), hex(b.start), hex(b.end), b.function)
for sym in b:
if not sym.needs_frame:
continue
# if sym.type.sizeof <= 4:
# continue
v = sym.value(frame)
print(" ", sym.addr_class, v.address, sym.type.sizeof, sym, sym.type, v)
frame = frame_up
print("total size:", total_size)
StackSize()

28
tools/stack-loc-to-pc.py Normal file
View File

@ -0,0 +1,28 @@
"""Prints the pcs that access each stack location in a function. Useful for finding
infrequently used stack space.
Pipe in disassembly like so:
arm-none-eabi-objdump --disassemble=mp_execute_bytecode build-metro_m0_express/firmware.elf | python ../../tools/stack-loc-to-pc.py
"""
import sys
import re
offset = re.compile(r"sp, #(\d+)")
offsets = {}
for line in sys.stdin:
if "sp" in line:
m = offset.search(line)
o = int(m.groups()[0])
pc = line.split(":")[0]
if o not in offsets:
offsets[o] = []
offsets[o].append(pc.strip())
print("Offset", "Size", "PCs", sep="\t")
last_o = 0
for o in sorted(offsets):
print(o, o - last_o, offsets[o], sep="\t")
last_o = o