From 02f8a45dfad42dbaa9d1049e65eb4eefbc1cf5c0 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 31 Mar 2023 10:51:03 -0500 Subject: [PATCH 01/22] synthio: allow increasing number of channels 12 channels works well on metro m7 --- ports/mimxrt10xx/mpconfigport.mk | 1 + py/circuitpy_mpconfig.mk | 4 + shared-bindings/audiocore/RawSample.c | 2 +- shared-module/synthio/MidiTrack.c | 105 +++++++++++++++----------- shared-module/synthio/MidiTrack.h | 2 +- 5 files changed, 68 insertions(+), 46 deletions(-) diff --git a/ports/mimxrt10xx/mpconfigport.mk b/ports/mimxrt10xx/mpconfigport.mk index b03535b6b2a1..ae22cb69f483 100644 --- a/ports/mimxrt10xx/mpconfigport.mk +++ b/ports/mimxrt10xx/mpconfigport.mk @@ -18,6 +18,7 @@ CIRCUITPY_AUDIOIO = 0 CIRCUITPY_AUDIOMIXER = 1 CIRCUITPY_AUDIOMP3 = 1 CIRCUITPY_AUDIOPWMIO = 1 +CIRCUITPY_SYNTHIO_MAX_CHANNELS = 12 CIRCUITPY_BUSDEVICE = 1 CIRCUITPY_COUNTIO = 0 CIRCUITPY_FREQUENCYIO = 0 diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index b8e217887012..3e5acd908c1d 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -437,6 +437,10 @@ CFLAGS += -DCIRCUITPY_SUPERVISOR=$(CIRCUITPY_SUPERVISOR) CIRCUITPY_SYNTHIO ?= $(CIRCUITPY_AUDIOCORE) CFLAGS += -DCIRCUITPY_SYNTHIO=$(CIRCUITPY_SYNTHIO) +CIRCUITPY_SYNTHIO_MAX_CHANNELS ?= 2 +CFLAGS += -DCIRCUITPY_SYNTHIO_MAX_CHANNELS=$(CIRCUITPY_SYNTHIO_MAX_CHANNELS) + + CIRCUITPY_SYS ?= 1 CFLAGS += -DCIRCUITPY_SYS=$(CIRCUITPY_SYS) diff --git a/shared-bindings/audiocore/RawSample.c b/shared-bindings/audiocore/RawSample.c index d185476f0366..39992f87c179 100644 --- a/shared-bindings/audiocore/RawSample.c +++ b/shared-bindings/audiocore/RawSample.c @@ -40,7 +40,7 @@ //| def __init__( //| self, buffer: ReadableBuffer, *, channel_count: int = 1, sample_rate: int = 8000 //| ) -> None: -//| """Create a RawSample based on the given buffer of signed values. If channel_count is more than +//| """Create a RawSample based on the given buffer of values. If channel_count is more than //| 1 then each channel's samples should alternate. In other words, for a two channel buffer, the //| first sample will be for channel 1, the second sample will be for channel two, the third for //| channel 1 and so on. diff --git a/shared-module/synthio/MidiTrack.c b/shared-module/synthio/MidiTrack.c index f02747d7349e..da810b99f34e 100644 --- a/shared-module/synthio/MidiTrack.c +++ b/shared-module/synthio/MidiTrack.c @@ -27,7 +27,6 @@ #include "py/runtime.h" #include "shared-bindings/synthio/MidiTrack.h" -#define LOUDNESS 0x4000 // 0.5 #define BITS_PER_SAMPLE 16 #define BYTES_PER_SAMPLE (BITS_PER_SAMPLE / 8) #define SILENCE 0x80 @@ -47,36 +46,51 @@ STATIC uint8_t parse_note(const uint8_t *buffer, uint32_t len, uint32_t *pos) { return note; } -STATIC void terminate_span(synthio_miditrack_obj_t *self, uint16_t *dur, uint16_t *max_dur) { +STATIC void terminate_span(synthio_miditrack_obj_t *self, uint16_t *dur) { if (*dur) { self->track[self->total_spans - 1].dur = *dur; - if (*dur > *max_dur) { - *max_dur = *dur; - } *dur = 0; } else { self->total_spans--; } } -STATIC void add_span(synthio_miditrack_obj_t *self, uint8_t note1, uint8_t note2) { - synthio_midi_span_t span = { 0, {note1, note2} }; +STATIC void add_span(synthio_miditrack_obj_t *self, const synthio_midi_span_t *span) { self->track = m_realloc(self->track, (self->total_spans + 1) * sizeof(synthio_midi_span_t)); - self->track[self->total_spans++] = span; + self->track[self->total_spans++] = *span; +} + +STATIC int find_channel_with_note(const synthio_midi_span_t *span, uint8_t note) { + for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { + if (span->note[i] == note) { + return i; + } + } + return -1; +} + +STATIC void change_span_note(synthio_miditrack_obj_t *self, uint8_t old_note, uint8_t new_note, uint16_t *dur) { + synthio_midi_span_t span = self->track[self->total_spans - 1]; + int channel = find_channel_with_note(&span, old_note); + if (channel != -1) { + terminate_span(self, dur); + span.note[channel] = new_note; + add_span(self, &span); + } } 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) { - synthio_midi_span_t initial = { 0, {SILENCE, SILENCE} }; + synthio_midi_span_t initial = { 0, {[0 ... (CIRCUITPY_SYNTHIO_MAX_CHANNELS - 1)] = SILENCE} }; self->sample_rate = sample_rate; self->track = m_malloc(sizeof(synthio_midi_span_t), false); self->next_span = 0; self->total_spans = 1; *self->track = initial; - uint16_t dur = 0, max_dur = 0; + uint16_t dur = 0; uint32_t pos = 0; while (pos < len) { uint8_t c; @@ -91,37 +105,19 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, raise_midi_stream_error(pos); } + // dur is carried over here so that if a note on/off message doesn't actually produce a change, the + // underlying "span" is extended. Otherwise, it is zeroed out in the call to `terminate_span`. dur += delta * sample_rate / tempo; switch (buffer[pos++] >> 4) { case 8: { // Note Off uint8_t note = parse_note(buffer, len, &pos); - - // Ignore if not a note which is playing - synthio_midi_span_t last_span = self->track[self->total_spans - 1]; - if (last_span.note[0] == note || last_span.note[1] == note) { - terminate_span(self, &dur, &max_dur); - if (last_span.note[0] == note) { - add_span(self, last_span.note[1], SILENCE); - } else { - add_span(self, last_span.note[0], SILENCE); - } - } + change_span_note(self, note, SILENCE, &dur); break; } case 9: { // Note On uint8_t note = parse_note(buffer, len, &pos); - - // Ignore if two notes are already playing - synthio_midi_span_t last_span = self->track[self->total_spans - 1]; - if (last_span.note[1] == SILENCE) { - terminate_span(self, &dur, &max_dur); - if (last_span.note[0] == SILENCE) { - add_span(self, note, SILENCE); - } else { - add_span(self, last_span.note[0], note); - } - } + change_span_note(self, SILENCE, note, &dur); break; } case 10: @@ -142,8 +138,12 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, raise_midi_stream_error(pos); } } - terminate_span(self, &dur, &max_dur); + terminate_span(self, &dur); + uint16_t max_dur = 0; + for (int i = 0; i < self->total_spans; i++) { + max_dur = MAX(self->track[i].dur, max_dur); + } self->buffer_length = max_dur * BYTES_PER_SAMPLE; self->buffer = m_malloc(self->buffer_length, false); } @@ -177,6 +177,16 @@ void synthio_miditrack_reset_buffer(synthio_miditrack_obj_t *self, STATIC const uint16_t notes[] = {8372, 8870, 9397, 9956, 10548, 11175, 11840, 12544, 13290, 14080, 14917, 15804}; // 9th octave +static int count_active_channels(synthio_midi_span_t *span) { + int result = 0; + for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { + if (span->note[i] != SILENCE) { + result += 1; + } + } + return result; +} + 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) { @@ -187,19 +197,26 @@ audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t synthio_midi_span_t span = self->track[self->next_span++]; *buffer_length = span.dur * BYTES_PER_SAMPLE; - uint8_t octave1 = span.note[0] / 12; // 0..10 - uint8_t octave2 = span.note[1] / 12; // 0..10 - int32_t base_freq1 = notes[span.note[0] % 12]; - int32_t base_freq2 = notes[span.note[1] % 12]; - int32_t sample_rate = self->sample_rate; + memset(self->buffer, 0, *buffer_length); - for (uint16_t i = 0; i < span.dur; i++) { - int16_t semiperiod1 = span.note[0] == SILENCE ? 0 : - ((base_freq1 * i * 2) / sample_rate) >> (10 - octave1); - int16_t semiperiod2 = span.note[1] == SILENCE ? semiperiod1 : - ((base_freq2 * i * 2) / sample_rate) >> (10 - octave2); - self->buffer[i] = ((semiperiod1 % 2 + semiperiod2 % 2) - 1) * LOUDNESS; + int32_t sample_rate = self->sample_rate; + int active_channels = count_active_channels(&span); + if (active_channels) { + int16_t loudness = 0x3fff / (1 + active_channels); + for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { + uint8_t octave = span.note[chan] / 12; + uint16_t base_freq = notes[span.note[chan] % 12]; + if (span.note[chan] == SILENCE) { + continue; + } + for (uint16_t i = 0; i < span.dur; i++) { + int16_t semiperiod = + ((base_freq * i * 2) / sample_rate) >> (10 - octave); + self->buffer[i] += semiperiod % 2 ? loudness : -loudness; + } + } } + *buffer = (uint8_t *)self->buffer; return self->next_span >= self->total_spans ? diff --git a/shared-module/synthio/MidiTrack.h b/shared-module/synthio/MidiTrack.h index 0af174ebc13a..39e61b046ebb 100644 --- a/shared-module/synthio/MidiTrack.h +++ b/shared-module/synthio/MidiTrack.h @@ -33,7 +33,7 @@ typedef struct { uint16_t dur; - uint8_t note[2]; + uint8_t note[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; } synthio_midi_span_t; typedef struct { From 7f73ff1b2d7a5fb8d8e9f4312c92c165d5e53384 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 31 Mar 2023 10:51:03 -0500 Subject: [PATCH 02/22] hold accumulator phase across span this fixes grunk in held notes --- shared-module/synthio/MidiTrack.c | 12 ++++++++---- shared-module/synthio/MidiTrack.h | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/shared-module/synthio/MidiTrack.c b/shared-module/synthio/MidiTrack.c index da810b99f34e..dd36e7c45425 100644 --- a/shared-module/synthio/MidiTrack.c +++ b/shared-module/synthio/MidiTrack.c @@ -204,16 +204,20 @@ audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t if (active_channels) { int16_t loudness = 0x3fff / (1 + active_channels); for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { - uint8_t octave = span.note[chan] / 12; - uint16_t base_freq = notes[span.note[chan] % 12]; if (span.note[chan] == SILENCE) { + self->accum[chan] = 0; continue; } + uint8_t octave = span.note[chan] / 12; + uint16_t base_freq = notes[span.note[chan] % 12]; + uint32_t accum = self->accum[chan]; + uint32_t rate = base_freq * 2; for (uint16_t i = 0; i < span.dur; i++) { - int16_t semiperiod = - ((base_freq * i * 2) / sample_rate) >> (10 - octave); + accum += rate; + int16_t semiperiod = (accum / sample_rate) >> (10 - octave); self->buffer[i] += semiperiod % 2 ? loudness : -loudness; } + self->accum[chan] = accum; } } diff --git a/shared-module/synthio/MidiTrack.h b/shared-module/synthio/MidiTrack.h index 39e61b046ebb..083392e608bc 100644 --- a/shared-module/synthio/MidiTrack.h +++ b/shared-module/synthio/MidiTrack.h @@ -44,6 +44,7 @@ typedef struct { synthio_midi_span_t *track; uint16_t next_span; uint16_t total_spans; + uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; } synthio_miditrack_obj_t; From db1e01c462876846ae52ae98ca4da17d0b7f3208 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 31 Mar 2023 16:41:05 -0500 Subject: [PATCH 03/22] Don't require huge buffers for long-held notes --- shared-module/synthio/MidiTrack.c | 29 +++++++++++++++++++---------- shared-module/synthio/MidiTrack.h | 3 ++- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/shared-module/synthio/MidiTrack.c b/shared-module/synthio/MidiTrack.c index dd36e7c45425..c745479c4a30 100644 --- a/shared-module/synthio/MidiTrack.c +++ b/shared-module/synthio/MidiTrack.c @@ -27,9 +27,10 @@ #include "py/runtime.h" #include "shared-bindings/synthio/MidiTrack.h" -#define BITS_PER_SAMPLE 16 +#define BITS_PER_SAMPLE (16) #define BYTES_PER_SAMPLE (BITS_PER_SAMPLE / 8) -#define SILENCE 0x80 +#define MAX_DUR (512) +#define SILENCE (0x80) STATIC NORETURN void raise_midi_stream_error(uint32_t pos) { mp_raise_ValueError_varg(translate("Error in MIDI stream at position %d"), pos); @@ -77,6 +78,7 @@ STATIC void change_span_note(synthio_miditrack_obj_t *self, uint8_t old_note, ui terminate_span(self, dur); span.note[channel] = new_note; add_span(self, &span); + *dur = 0; } } @@ -144,7 +146,7 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, for (int i = 0; i < self->total_spans; i++) { max_dur = MAX(self->track[i].dur, max_dur); } - self->buffer_length = max_dur * BYTES_PER_SAMPLE; + self->buffer_length = MIN(MAX_DUR, max_dur) * BYTES_PER_SAMPLE; self->buffer = m_malloc(self->buffer_length, false); } @@ -171,6 +173,7 @@ uint8_t common_hal_synthio_miditrack_get_channel_count(synthio_miditrack_obj_t * void synthio_miditrack_reset_buffer(synthio_miditrack_obj_t *self, bool single_channel_output, uint8_t channel) { + self->remaining_dur = 0; self->next_span = 0; } @@ -189,14 +192,20 @@ static int count_active_channels(synthio_midi_span_t *span) { 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) { + synthio_midi_span_t span = self->track[self->next_span - !!self->remaining_dur]; - if (self->next_span >= self->total_spans) { - *buffer_length = 0; - return GET_BUFFER_DONE; + if (self->remaining_dur == 0) { + if (self->next_span >= self->total_spans) { + *buffer_length = 0; + return GET_BUFFER_DONE; + } + self->next_span++; + self->remaining_dur = span.dur; } - synthio_midi_span_t span = self->track[self->next_span++]; - *buffer_length = span.dur * BYTES_PER_SAMPLE; + uint16_t dur = MIN(MAX_DUR, self->remaining_dur); + self->remaining_dur -= dur; + *buffer_length = dur * BYTES_PER_SAMPLE; memset(self->buffer, 0, *buffer_length); int32_t sample_rate = self->sample_rate; @@ -212,7 +221,7 @@ audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t uint16_t base_freq = notes[span.note[chan] % 12]; uint32_t accum = self->accum[chan]; uint32_t rate = base_freq * 2; - for (uint16_t i = 0; i < span.dur; i++) { + for (uint16_t i = 0; i < dur; i++) { accum += rate; int16_t semiperiod = (accum / sample_rate) >> (10 - octave); self->buffer[i] += semiperiod % 2 ? loudness : -loudness; @@ -223,7 +232,7 @@ audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t *buffer = (uint8_t *)self->buffer; - return self->next_span >= self->total_spans ? + return (self->remaining_dur == 0 && self->next_span >= self->total_spans) ? GET_BUFFER_DONE : GET_BUFFER_MORE_DATA; } diff --git a/shared-module/synthio/MidiTrack.h b/shared-module/synthio/MidiTrack.h index 083392e608bc..42ef9c93ae39 100644 --- a/shared-module/synthio/MidiTrack.h +++ b/shared-module/synthio/MidiTrack.h @@ -41,10 +41,11 @@ typedef struct { uint32_t sample_rate; uint16_t *buffer; uint16_t buffer_length; - synthio_midi_span_t *track; + uint16_t remaining_dur; uint16_t next_span; uint16_t total_spans; uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; + synthio_midi_span_t *track; } synthio_miditrack_obj_t; From 13e17e6dcd7a3c73e7dbff49ef0c32160f563a28 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 1 Apr 2023 10:19:44 -0500 Subject: [PATCH 04/22] Make synthio debuggable in unix coverage port --- .../unix/variants/coverage/mpconfigvariant.mk | 16 ++++++- py/circuitpy_mpconfig.mk | 5 ++ shared-bindings/audiocore/WaveFile.c | 1 + shared-bindings/audiocore/__init__.c | 48 ++++++++++++++++++- shared-bindings/audiomixer/Mixer.h | 1 - shared-bindings/synthio/__init__.c | 5 ++ shared-module/synthio/MidiTrack.c | 7 ++- 7 files changed, 76 insertions(+), 7 deletions(-) diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index 20320916bca9..fca9a99518fc 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -30,15 +30,23 @@ SRC_BITMAP := \ displayio_min.c \ shared-bindings/aesio/aes.c \ shared-bindings/aesio/__init__.c \ + shared-bindings/audiocore/__init__.c \ + shared-bindings/audiocore/RawSample.c \ + shared-bindings/audiocore/WaveFile.c \ shared-bindings/bitmaptools/__init__.c \ shared-bindings/displayio/Bitmap.c \ shared-bindings/rainbowio/__init__.c \ shared-bindings/struct/__init__.c \ + shared-bindings/synthio/__init__.c \ + shared-bindings/synthio/MidiTrack.c \ shared-bindings/traceback/__init__.c \ shared-bindings/util.c \ shared-bindings/zlib/__init__.c \ shared-module/aesio/aes.c \ shared-module/aesio/__init__.c \ + shared-module/audiocore/__init__.c \ + shared-module/audiocore/RawSample.c \ + shared-module/audiocore/WaveFile.c \ shared-module/bitmaptools/__init__.c \ shared-module/displayio/area.c \ shared-module/displayio/Bitmap.c \ @@ -47,6 +55,8 @@ SRC_BITMAP := \ shared-module/os/getenv.c \ shared-module/rainbowio/__init__.c \ shared-module/struct/__init__.c \ + shared-module/synthio/__init__.c \ + shared-module/synthio/MidiTrack.c \ shared-module/traceback/__init__.c \ shared-module/zlib/__init__.c \ @@ -54,12 +64,16 @@ SRC_C += $(SRC_BITMAP) CFLAGS += \ -DCIRCUITPY_AESIO=1 \ + -DCIRCUITPY_AUDIOCORE=1 \ + -DCIRCUITPY_AUDIOCORE_DEBUG=1 \ -DCIRCUITPY_BITMAPTOOLS=1 \ -DCIRCUITPY_DISPLAYIO_UNIX=1 \ - -DCIRCUITPY_OS_GETENV=1 \ -DCIRCUITPY_GIFIO=1 \ + -DCIRCUITPY_OS_GETENV=1 \ -DCIRCUITPY_RAINBOWIO=1 \ -DCIRCUITPY_STRUCT=1 \ + -DCIRCUITPY_SYNTHIO=1 \ + -DCIRCUITPY_SYNTHIO_MAX_CHANNELS=14 \ -DCIRCUITPY_TRACEBACK=1 \ -DCIRCUITPY_ZLIB=1 diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 3e5acd908c1d..dcd870597e5d 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -107,6 +107,11 @@ CFLAGS += -DCIRCUITPY_AUDIOCORE=$(CIRCUITPY_AUDIOCORE) CIRCUITPY_AUDIOMIXER ?= $(CIRCUITPY_AUDIOIO) CFLAGS += -DCIRCUITPY_AUDIOMIXER=$(CIRCUITPY_AUDIOMIXER) +ifndef CIRCUITPY_AUDIOCORE_DEBUG +CIRCUITPY_AUDIOCORE_DEBUG ?= 0 +endif +CFLAGS += -DCIRCUITPY_AUDIOCORE_DEBUG=$(CIRCUITPY_AUDIOCORE_DEBUG) + ifndef CIRCUITPY_AUDIOMP3 ifeq ($(CIRCUITPY_FULL_BUILD),1) CIRCUITPY_AUDIOMP3 = $(CIRCUITPY_AUDIOCORE) diff --git a/shared-bindings/audiocore/WaveFile.c b/shared-bindings/audiocore/WaveFile.c index 36204fa2b2e7..3a13db72a02e 100644 --- a/shared-bindings/audiocore/WaveFile.c +++ b/shared-bindings/audiocore/WaveFile.c @@ -32,6 +32,7 @@ #include "shared-bindings/audiocore/WaveFile.h" #include "shared-bindings/util.h" #include "supervisor/shared/translate/translate.h" +#include "extmod/vfs_posix.h" //| class WaveFile: //| """Load a wave file for audio playback diff --git a/shared-bindings/audiocore/__init__.c b/shared-bindings/audiocore/__init__.c index 2e3b479cf6b2..6f32979099db 100644 --- a/shared-bindings/audiocore/__init__.c +++ b/shared-bindings/audiocore/__init__.c @@ -29,7 +29,6 @@ #include "py/obj.h" #include "py/runtime.h" -#include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/audiocore/__init__.h" #include "shared-bindings/audiocore/RawSample.h" #include "shared-bindings/audiocore/WaveFile.h" @@ -37,10 +36,57 @@ //| """Support for audio samples""" +#if CIRCUITPY_AUDIOCORE_DEBUG +// (no docstrings so that the debug functions are not shown on docs.circuitpython.org) +STATIC mp_obj_t audiocore_get_buffer(mp_obj_t sample_in) { + uint8_t *buffer = NULL; + uint32_t buffer_length = 0; + audioio_get_buffer_result_t gbr = audiosample_get_buffer(sample_in, false, 0, &buffer, &buffer_length); + + mp_obj_t result[2] = {mp_obj_new_int_from_uint(gbr), mp_const_none}; + + if (gbr != GET_BUFFER_ERROR) { + // copies the data because the gc semantics of get_buffer are unclear + result[1] = mp_obj_new_bytes(buffer, buffer_length); + } + + return mp_obj_new_tuple(2, result); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(audiocore_get_buffer_obj, audiocore_get_buffer); + +STATIC mp_obj_t audiocore_get_structure(mp_obj_t sample_in) { + bool single_buffer, samples_signed; + uint32_t max_buffer_length; + uint8_t spacing; + + audiosample_get_buffer_structure(sample_in, false, &single_buffer, &samples_signed, &max_buffer_length, &spacing); + mp_obj_t result[4] = { + mp_obj_new_int_from_uint(single_buffer), + mp_obj_new_int_from_uint(samples_signed), + mp_obj_new_int_from_uint(max_buffer_length), + mp_obj_new_int_from_uint(spacing), + }; + return mp_obj_new_tuple(4, result); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(audiocore_get_structure_obj, audiocore_get_structure); + +STATIC mp_obj_t audiocore_reset_buffer(mp_obj_t sample_in) { + audiosample_reset_buffer(sample_in, false, 0); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(audiocore_reset_buffer_obj, audiocore_reset_buffer); + +#endif + STATIC const mp_rom_map_elem_t audiocore_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiocore) }, { MP_ROM_QSTR(MP_QSTR_RawSample), MP_ROM_PTR(&audioio_rawsample_type) }, { MP_ROM_QSTR(MP_QSTR_WaveFile), MP_ROM_PTR(&audioio_wavefile_type) }, + #if CIRCUITPY_AUDIOCORE_DEBUG + { MP_ROM_QSTR(MP_QSTR_get_buffer), MP_ROM_PTR(&audiocore_get_buffer_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset_buffer), MP_ROM_PTR(&audiocore_reset_buffer_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_structure), MP_ROM_PTR(&audiocore_get_structure_obj) }, + #endif }; STATIC MP_DEFINE_CONST_DICT(audiocore_module_globals, audiocore_module_globals_table); diff --git a/shared-bindings/audiomixer/Mixer.h b/shared-bindings/audiomixer/Mixer.h index 88592a1cb3cd..fd1154cc433c 100644 --- a/shared-bindings/audiomixer/Mixer.h +++ b/shared-bindings/audiomixer/Mixer.h @@ -27,7 +27,6 @@ #ifndef MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOMIXER_MIXER_H #define MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOMIXER_MIXER_H -#include "common-hal/microcontroller/Pin.h" #include "shared-module/audiomixer/Mixer.h" #include "shared-bindings/audiocore/RawSample.h" diff --git a/shared-bindings/synthio/__init__.c b/shared-bindings/synthio/__init__.c index 106a073535fd..29a0c4d293df 100644 --- a/shared-bindings/synthio/__init__.c +++ b/shared-bindings/synthio/__init__.c @@ -30,6 +30,7 @@ #include "py/obj.h" #include "py/runtime.h" #include "extmod/vfs_fat.h" +#include "extmod/vfs_posix.h" #include "shared-bindings/synthio/__init__.h" #include "shared-bindings/synthio/MidiTrack.h" @@ -115,7 +116,11 @@ STATIC mp_obj_t synthio_from_file(size_t n_args, const mp_obj_t *pos_args, mp_ma common_hal_synthio_miditrack_construct(result, buffer, track_size, tempo, args[ARG_sample_rate].u_int); + #if MICROPY_MALLOC_USES_ALLOCATED_SIZE + m_free(buffer, track_size); + #else m_free(buffer); + #endif return MP_OBJ_FROM_PTR(result); } diff --git a/shared-module/synthio/MidiTrack.c b/shared-module/synthio/MidiTrack.c index c745479c4a30..dbc2d4ced13f 100644 --- a/shared-module/synthio/MidiTrack.c +++ b/shared-module/synthio/MidiTrack.c @@ -57,8 +57,7 @@ STATIC void terminate_span(synthio_miditrack_obj_t *self, uint16_t *dur) { } STATIC void add_span(synthio_miditrack_obj_t *self, const synthio_midi_span_t *span) { - self->track = m_realloc(self->track, - (self->total_spans + 1) * sizeof(synthio_midi_span_t)); + self->track = m_renew(synthio_midi_span_t, self->track, self->total_spans, self->total_spans + 1); self->track[self->total_spans++] = *span; } @@ -151,9 +150,9 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, } void common_hal_synthio_miditrack_deinit(synthio_miditrack_obj_t *self) { - m_free(self->buffer); + m_del(uint8_t, self->buffer, self->buffer_length); self->buffer = NULL; - m_free(self->track); + m_del(synthio_midi_span_t, self->track, self->total_spans + 1); self->track = NULL; } bool common_hal_synthio_miditrack_deinited(synthio_miditrack_obj_t *self) { From e9e4ce9546806de4e9f4b1dd6bf37d825391b673 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 1 Apr 2023 10:21:22 -0500 Subject: [PATCH 05/22] add waveform support in synthio a waveform object (array of 'h') can be passed in, replacing the standard square wave. This waveform must be a 'single cycle waveform' and some obvious things to pass in are sine, triangle or sawtooth waves, but you can construct whatever you like. --- locale/circuitpython.pot | 23 +++++++++------------- shared-bindings/synthio/MidiTrack.c | 30 +++++++++++++++++++++++++---- shared-bindings/synthio/MidiTrack.h | 2 +- shared-bindings/synthio/__init__.c | 20 +++++++++++++++++-- shared-bindings/synthio/__init__.h | 9 ++------- shared-module/synthio/MidiTrack.c | 21 ++++++++++++++++---- shared-module/synthio/MidiTrack.h | 4 +++- 7 files changed, 76 insertions(+), 33 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index b5026f78292b..f87fc029752d 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -122,6 +122,7 @@ msgid "%q in %q must be of type %q, not %q" msgstr "" #: ports/espressif/common-hal/espulp/ULP.c +#: ports/mimxrt10xx/common-hal/audiobusio/__init__.c #: ports/raspberrypi/common-hal/rp2pio/StateMachine.c #: shared-bindings/digitalio/DigitalInOut.c #: shared-bindings/microcontroller/Pin.c @@ -192,6 +193,10 @@ msgstr "" msgid "%q must be array of type 'H'" msgstr "" +#: shared-bindings/synthio/MidiTrack.c shared-bindings/synthio/__init__.c +msgid "%q must be array of type 'h'" +msgstr "" + #: ports/raspberrypi/bindings/cyw43/__init__.c py/argcheck.c py/objexcept.c #: shared-bindings/canio/CAN.c shared-bindings/digitalio/Pull.c msgid "%q must be of type %q or %q, not %q" @@ -1217,6 +1222,7 @@ msgstr "" msgid "Interrupt error." msgstr "" +#: ports/mimxrt10xx/common-hal/audiobusio/__init__.c #: ports/mimxrt10xx/common-hal/pwmio/PWMOut.c py/argcheck.c #: shared-bindings/digitalio/DigitalInOut.c #: shared-bindings/displayio/EPaperDisplay.c @@ -1224,6 +1230,7 @@ msgid "Invalid %q" msgstr "" #: ports/atmel-samd/common-hal/microcontroller/Pin.c +#: ports/mimxrt10xx/common-hal/microcontroller/Pin.c #: shared-bindings/microcontroller/Pin.c msgid "Invalid %q pin" msgstr "" @@ -3827,11 +3834,7 @@ msgstr "" msgid "out must be a float dense array" msgstr "" -#: shared-bindings/displayio/Bitmap.c -msgid "out of range of source" -msgstr "" - -#: shared-bindings/bitmaptools/__init__.c shared-bindings/displayio/Bitmap.c +#: shared-bindings/bitmaptools/__init__.c msgid "out of range of target" msgstr "" @@ -3856,14 +3859,10 @@ msgstr "" msgid "parameters must be registers in sequence r0 to r3" msgstr "" -#: shared-bindings/bitmaptools/__init__.c shared-bindings/displayio/Bitmap.c +#: shared-bindings/bitmaptools/__init__.c msgid "pixel coordinates out of bounds" msgstr "" -#: shared-bindings/displayio/Bitmap.c -msgid "pixel value requires too many bits" -msgstr "" - #: shared-bindings/displayio/TileGrid.c shared-bindings/vectorio/VectorShape.c msgid "pixel_shader must be displayio.Palette or displayio.ColorConverter" msgstr "" @@ -4276,10 +4275,6 @@ msgstr "" msgid "value out of range of target" msgstr "" -#: shared-bindings/displayio/Bitmap.c -msgid "value_count must be > 0" -msgstr "" - #: ports/espressif/common-hal/watchdog/WatchDogTimer.c msgid "watchdog not initialized" msgstr "" diff --git a/shared-bindings/synthio/MidiTrack.c b/shared-bindings/synthio/MidiTrack.c index b987337eaaf7..727b9ba3358f 100644 --- a/shared-bindings/synthio/MidiTrack.c +++ b/shared-bindings/synthio/MidiTrack.c @@ -32,13 +32,19 @@ #include "py/runtime.h" #include "shared-bindings/util.h" #include "shared-bindings/synthio/MidiTrack.h" +#include "shared-bindings/synthio/__init__.h" #include "supervisor/shared/translate/translate.h" //| class MidiTrack: -//| """Simple square-wave MIDI synth""" +//| """Simple MIDI synth""" //| //| def __init__( -//| self, buffer: ReadableBuffer, tempo: int, *, sample_rate: int = 11025 +//| self, +//| buffer: ReadableBuffer, +//| tempo: int, +//| *, +//| sample_rate: int = 11025, +//| waveform: ReadableBuffer = None //| ) -> None: //| """Create a MidiTrack from the given stream of MIDI events. Only "Note On" and "Note Off" events //| are supported; channel numbers and key velocities are ignored. Up to two notes may be on at the @@ -47,6 +53,7 @@ //| :param ~circuitpython_typing.ReadableBuffer buffer: Stream of MIDI events, as stored in a MIDI file track chunk //| :param int tempo: Tempo of the streamed events, in MIDI ticks per second //| :param int sample_rate: The desired playback sample rate; higher sample rate requires more memory +//| :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) //| //| Simple melody:: //| @@ -65,11 +72,12 @@ //| print("stopped")""" //| ... STATIC mp_obj_t synthio_miditrack_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_buffer, ARG_tempo, ARG_sample_rate }; + enum { ARG_buffer, ARG_tempo, ARG_sample_rate, ARG_waveform }; static const mp_arg_t allowed_args[] = { { MP_QSTR_buffer, MP_ARG_OBJ | MP_ARG_REQUIRED }, { MP_QSTR_tempo, MP_ARG_INT | MP_ARG_REQUIRED }, { MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 11025} }, + { MP_QSTR_waveform, 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); @@ -77,13 +85,27 @@ STATIC mp_obj_t synthio_miditrack_make_new(const mp_obj_type_t *type, size_t n_a mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ); + mp_buffer_info_t bufinfo_waveform = { + .buf = shared_bindings_synthio_square_wave, + .len = 4 + }; + + if (args[ARG_waveform].u_obj != mp_const_none) { + mp_get_buffer_raise(args[ARG_waveform].u_obj, &bufinfo_waveform, MP_BUFFER_READ); + if (bufinfo_waveform.typecode != 'h') { + mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), MP_QSTR_waveform); + } + } + synthio_miditrack_obj_t *self = m_new_obj(synthio_miditrack_obj_t); self->base.type = &synthio_miditrack_type; common_hal_synthio_miditrack_construct(self, (uint8_t *)bufinfo.buf, bufinfo.len, args[ARG_tempo].u_int, - args[ARG_sample_rate].u_int); + args[ARG_sample_rate].u_int, + bufinfo_waveform.buf, + bufinfo_waveform.len / 2); return MP_OBJ_FROM_PTR(self); } diff --git a/shared-bindings/synthio/MidiTrack.h b/shared-bindings/synthio/MidiTrack.h index d44d4c3bb7bd..90ddab8728cc 100644 --- a/shared-bindings/synthio/MidiTrack.h +++ b/shared-bindings/synthio/MidiTrack.h @@ -32,7 +32,7 @@ extern const mp_obj_type_t synthio_miditrack_type; 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_len); void common_hal_synthio_miditrack_deinit(synthio_miditrack_obj_t *self); bool common_hal_synthio_miditrack_deinited(synthio_miditrack_obj_t *self); diff --git a/shared-bindings/synthio/__init__.c b/shared-bindings/synthio/__init__.c index 29a0c4d293df..dc627adb36e0 100644 --- a/shared-bindings/synthio/__init__.c +++ b/shared-bindings/synthio/__init__.c @@ -35,6 +35,8 @@ #include "shared-bindings/synthio/__init__.h" #include "shared-bindings/synthio/MidiTrack.h" +int16_t shared_bindings_synthio_square_wave[] = {-32768, 32767}; + //| """Support for MIDI synthesis""" //| //| def from_file(file: typing.BinaryIO, *, sample_rate: int = 11025) -> MidiTrack: @@ -43,6 +45,7 @@ //| //| :param typing.BinaryIO file: Already opened MIDI file //| :param int sample_rate: The desired playback sample rate; higher sample rate requires more memory +//| :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) //| //| //| Playing a MIDI file from flash:: @@ -63,10 +66,11 @@ //| ... //| STATIC mp_obj_t synthio_from_file(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_file, ARG_sample_rate }; + enum { ARG_file, ARG_sample_rate, ARG_waveform }; static const mp_arg_t allowed_args[] = { { MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED }, { MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 11025} }, + { MP_QSTR_waveform, 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(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); @@ -75,6 +79,18 @@ 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); + mp_buffer_info_t bufinfo_waveform = { + .buf = shared_bindings_synthio_square_wave, + .len = 4 + }; + + if (args[ARG_waveform].u_obj != mp_const_none) { + mp_get_buffer_raise(args[ARG_waveform].u_obj, &bufinfo_waveform, MP_BUFFER_READ); + if (bufinfo_waveform.typecode != 'h') { + mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), MP_QSTR_waveform); + } + } + uint8_t chunk_header[14]; f_rewind(&file->fp); UINT bytes_read; @@ -114,7 +130,7 @@ 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; common_hal_synthio_miditrack_construct(result, buffer, track_size, - tempo, args[ARG_sample_rate].u_int); + tempo, args[ARG_sample_rate].u_int, bufinfo_waveform.buf, bufinfo_waveform.len / 2); #if MICROPY_MALLOC_USES_ALLOCATED_SIZE m_free(buffer, track_size); diff --git a/shared-bindings/synthio/__init__.h b/shared-bindings/synthio/__init__.h index 14af1a5805eb..3b7d47db285b 100644 --- a/shared-bindings/synthio/__init__.h +++ b/shared-bindings/synthio/__init__.h @@ -24,11 +24,6 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_SYNTHIO___INIT___H -#define MICROPY_INCLUDED_SHARED_BINDINGS_SYNTHIO___INIT___H +#pragma once -#include "py/obj.h" - -// Nothing now. - -#endif // MICROPY_INCLUDED_SHARED_BINDINGS_SYNTHIO___INIT___H +extern int16_t shared_bindings_synthio_square_wave[]; diff --git a/shared-module/synthio/MidiTrack.c b/shared-module/synthio/MidiTrack.c index dbc2d4ced13f..f7c17427d18d 100644 --- a/shared-module/synthio/MidiTrack.c +++ b/shared-module/synthio/MidiTrack.c @@ -32,6 +32,7 @@ #define MAX_DUR (512) #define SILENCE (0x80) + STATIC NORETURN void raise_midi_stream_error(uint32_t pos) { mp_raise_ValueError_varg(translate("Error in MIDI stream at position %d"), pos); } @@ -82,7 +83,8 @@ STATIC void change_span_note(synthio_miditrack_obj_t *self, uint8_t old_note, ui } 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) { synthio_midi_span_t initial = { 0, {[0 ... (CIRCUITPY_SYNTHIO_MAX_CHANNELS - 1)] = SILENCE} }; self->sample_rate = sample_rate; @@ -90,6 +92,11 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, self->next_span = 0; self->total_spans = 1; *self->track = initial; + self->waveform = waveform; + self->waveform_length = waveform_length; + (void)mp_arg_validate_length_min(waveform_length, 2, MP_QSTR_waveform); + mp_printf(&mp_plat_print, "note: waveform_length = %d\n", waveform_length); + mp_printf(&mp_plat_print, "waveform[0:2] = %d %d\n", waveform[0], waveform[1]); uint16_t dur = 0; uint32_t pos = 0; @@ -209,6 +216,9 @@ audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t int32_t sample_rate = self->sample_rate; int active_channels = count_active_channels(&span); + const int16_t *waveform = self->waveform; + int16_t waveform_length = self->waveform_length; + int16_t *out_buffer = self->buffer; if (active_channels) { int16_t loudness = 0x3fff / (1 + active_channels); for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { @@ -219,11 +229,14 @@ audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t uint8_t octave = span.note[chan] / 12; uint16_t base_freq = notes[span.note[chan] % 12]; uint32_t accum = self->accum[chan]; - uint32_t rate = base_freq * 2; + uint32_t rate = base_freq * waveform_length; for (uint16_t i = 0; i < dur; i++) { accum += rate; - int16_t semiperiod = (accum / sample_rate) >> (10 - octave); - self->buffer[i] += semiperiod % 2 ? loudness : -loudness; + int16_t idx = ((accum / sample_rate) >> (10 - octave)) % waveform_length; + if (chan == 0 && i < 10) { + mp_printf(&mp_plat_print, "%d %d %d\n", accum, idx, waveform[idx]); + } + out_buffer[i] += (waveform[idx] * loudness) / 65536; } self->accum[chan] = accum; } diff --git a/shared-module/synthio/MidiTrack.h b/shared-module/synthio/MidiTrack.h index 42ef9c93ae39..c9206c69bb40 100644 --- a/shared-module/synthio/MidiTrack.h +++ b/shared-module/synthio/MidiTrack.h @@ -39,11 +39,13 @@ typedef struct { typedef struct { mp_obj_base_t base; uint32_t sample_rate; - uint16_t *buffer; + int16_t *buffer; uint16_t buffer_length; uint16_t remaining_dur; uint16_t next_span; uint16_t total_spans; + uint16_t waveform_length; + const int16_t *waveform; uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; synthio_midi_span_t *track; } synthio_miditrack_obj_t; From e728a0c1b9eccb75d1c5edb452e75abb2931d57b Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 1 Apr 2023 11:42:51 -0500 Subject: [PATCH 06/22] switch to 16.16 fixed point for synthesis --- shared-module/synthio/MidiTrack.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/shared-module/synthio/MidiTrack.c b/shared-module/synthio/MidiTrack.c index f7c17427d18d..e2614bd0a31e 100644 --- a/shared-module/synthio/MidiTrack.c +++ b/shared-module/synthio/MidiTrack.c @@ -94,9 +94,7 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, *self->track = initial; self->waveform = waveform; self->waveform_length = waveform_length; - (void)mp_arg_validate_length_min(waveform_length, 2, MP_QSTR_waveform); - mp_printf(&mp_plat_print, "note: waveform_length = %d\n", waveform_length); - mp_printf(&mp_plat_print, "waveform[0:2] = %d %d\n", waveform[0], waveform[1]); + mp_arg_validate_length_range(waveform_length, 2, 1024, MP_QSTR_waveform); uint16_t dur = 0; uint32_t pos = 0; @@ -217,7 +215,7 @@ audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t int32_t sample_rate = self->sample_rate; int active_channels = count_active_channels(&span); const int16_t *waveform = self->waveform; - int16_t waveform_length = self->waveform_length; + uint32_t waveform_length = self->waveform_length; int16_t *out_buffer = self->buffer; if (active_channels) { int16_t loudness = 0x3fff / (1 + active_channels); @@ -229,13 +227,20 @@ audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t uint8_t octave = span.note[chan] / 12; uint16_t base_freq = notes[span.note[chan] % 12]; uint32_t accum = self->accum[chan]; - uint32_t rate = base_freq * waveform_length; +#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 += rate; - int16_t idx = ((accum / sample_rate) >> (10 - octave)) % waveform_length; - if (chan == 0 && i < 10) { - mp_printf(&mp_plat_print, "%d %d %d\n", accum, idx, waveform[idx]); + accum += dds_rate; + if (accum > waveform_length << SHIFT) { + accum -= waveform_length << SHIFT; } + int16_t idx = accum >> SHIFT; out_buffer[i] += (waveform[idx] * loudness) / 65536; } self->accum[chan] = accum; From 736af5b0c80e1ce79935c72e3bdf545ff3a38cc8 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 1 Apr 2023 13:12:00 -0500 Subject: [PATCH 07/22] update expected result --- tests/unix/extra_coverage.py.exp | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index 8014dd301022..0baf8e1f4458 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -30,20 +30,21 @@ ame__ mport builtins micropython _asyncio _thread -_uasyncio aesio array binascii -bitmaptools btree cexample cmath -collections cppexample displayio errno -ffi framebuf gc hashlib -json math qrio rainbowio -re struct sys termios -traceback ubinascii uctypes uerrno -uheapq uio ujson ulab -ulab.numpy ulab.numpy.fft ulab.numpy.linalg -ulab.scipy ulab.scipy.linalg -ulab.scipy.optimize ulab.scipy.signal -ulab.scipy.special ulab.utils uos -urandom ure uselect utime -utimeq uzlib zlib +_uasyncio aesio array audiocore +binascii bitmaptools btree cexample +cmath collections cppexample displayio +errno ffi framebuf gc +hashlib json math qrio +rainbowio re struct synthio +sys termios traceback ubinascii +uctypes uerrno uheapq uio +ujson ulab ulab.numpy ulab.numpy.fft +ulab.numpy.linalg ulab.scipy +ulab.scipy.linalg ulab.scipy.optimize +ulab.scipy.signal ulab.scipy.special +ulab.utils uos urandom ure +uselect utime utimeq uzlib +zlib ime utime utimeq From 05cf5213e20568f269f4fab678b307b06bf97fe3 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 1 Apr 2023 19:22:52 -0500 Subject: [PATCH 08/22] Allow disabling each class in keypad .. for boards like pewpewm4 which need a specific kind. And need some space. --- ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk | 2 ++ py/circuitpy_mpconfig.mk | 9 +++++++++ shared-bindings/keypad/KeyMatrix.c | 10 ++++++++++ shared-bindings/keypad/Keys.c | 9 +++++++++ shared-bindings/keypad/ShiftRegisterKeys.c | 8 ++++++++ 5 files changed, 38 insertions(+) diff --git a/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk b/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk index f7e7be88d51e..49be084ddfd7 100644 --- a/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk @@ -44,6 +44,8 @@ CIRCUITPY_AUDIOIO = 1 CIRCUITPY_AUDIOMIXER = 1 CIRCUITPY_DISPLAYIO = 1 CIRCUITPY_KEYPAD = 1 +CIRCUITPY_KEYPAD_SHIFTREGISTERKEYS = 0 +CIRCUITPY_KEYPAD_KEYMATRIX = 0 CIRCUITPY_MATH = 1 CIRCUITPY_STAGE = 1 CIRCUITPY_SYNTHIO = 1 diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index dcd870597e5d..0aec2ebe74f5 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -291,6 +291,15 @@ CFLAGS += -DCIRCUITPY_JSON=$(CIRCUITPY_JSON) CIRCUITPY_KEYPAD ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_KEYPAD=$(CIRCUITPY_KEYPAD) +CIRCUITPY_KEYPAD_KEYS ?= $(CIRCUITPY_KEYPAD) +CFLAGS += -DCIRCUITPY_KEYPAD_KEYS=$(CIRCUITPY_KEYPAD_KEYS) + +CIRCUITPY_KEYPAD_KEYMATRIX ?= $(CIRCUITPY_KEYPAD) +CFLAGS += -DCIRCUITPY_KEYPAD_KEYMATRIX=$(CIRCUITPY_KEYPAD_KEYMATRIX) + +CIRCUITPY_KEYPAD_SHIFTREGISTERKEYS ?= $(CIRCUITPY_KEYPAD) +CFLAGS += -DCIRCUITPY_KEYPAD_SHIFTREGISTERKEYS=$(CIRCUITPY_KEYPAD_SHIFTREGISTERKEYS) + CIRCUITPY_MATH ?= 1 CFLAGS += -DCIRCUITPY_MATH=$(CIRCUITPY_MATH) diff --git a/shared-bindings/keypad/KeyMatrix.c b/shared-bindings/keypad/KeyMatrix.c index 1c623f4bede8..2209f3f68fd7 100644 --- a/shared-bindings/keypad/KeyMatrix.c +++ b/shared-bindings/keypad/KeyMatrix.c @@ -71,6 +71,7 @@ //| ... STATIC mp_obj_t keypad_keymatrix_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + #if CIRCUITPY_KEYPAD_KEYMATRIX keypad_keymatrix_obj_t *self = m_new_obj(keypad_keymatrix_obj_t); self->base.type = &keypad_keymatrix_type; enum { ARG_row_pins, ARG_column_pins, ARG_columns_to_anodes, ARG_interval, ARG_max_events }; @@ -114,8 +115,13 @@ STATIC mp_obj_t keypad_keymatrix_make_new(const mp_obj_type_t *type, size_t n_ar common_hal_keypad_keymatrix_construct(self, num_row_pins, row_pins_array, num_column_pins, column_pins_array, args[ARG_columns_to_anodes].u_bool, interval, max_events); return MP_OBJ_FROM_PTR(self); + #else + mp_raise_NotImplementedError_varg(translate("%q"), MP_QSTR_KeyMatrix); + + #endif } +#if CIRCUITPY_KEYPAD_KEYMATRIX //| def deinit(self) -> None: //| """Stop scanning and release the pins.""" //| ... @@ -228,9 +234,13 @@ STATIC const mp_rom_map_elem_t keypad_keymatrix_locals_dict_table[] = { STATIC MP_DEFINE_CONST_DICT(keypad_keymatrix_locals_dict, keypad_keymatrix_locals_dict_table); +#endif + const mp_obj_type_t keypad_keymatrix_type = { { &mp_type_type }, .name = MP_QSTR_KeyMatrix, .make_new = keypad_keymatrix_make_new, + #if CIRCUITPY_KEYPAD_KEYMATRIX .locals_dict = (mp_obj_t)&keypad_keymatrix_locals_dict, + #endif }; diff --git a/shared-bindings/keypad/Keys.c b/shared-bindings/keypad/Keys.c index 10519958c703..2481fedc6b55 100644 --- a/shared-bindings/keypad/Keys.c +++ b/shared-bindings/keypad/Keys.c @@ -73,6 +73,7 @@ //| ... STATIC mp_obj_t keypad_keys_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + #if CIRCUITPY_KEYPAD_KEYS keypad_keys_obj_t *self = m_new_obj(keypad_keys_obj_t); self->base.type = &keypad_keys_type; enum { ARG_pins, ARG_value_when_pressed, ARG_pull, ARG_interval, ARG_max_events }; @@ -106,8 +107,13 @@ STATIC mp_obj_t keypad_keys_make_new(const mp_obj_type_t *type, size_t n_args, s common_hal_keypad_keys_construct(self, num_pins, pins_array, value_when_pressed, args[ARG_pull].u_bool, interval, max_events); return MP_OBJ_FROM_PTR(self); + #else + mp_raise_NotImplementedError_varg(translate("%q"), MP_QSTR_Keys); + + #endif } +#if CIRCUITPY_KEYPAD_KEYS //| def deinit(self) -> None: //| """Stop scanning and release the pins.""" //| ... @@ -162,10 +168,13 @@ STATIC const mp_rom_map_elem_t keypad_keys_locals_dict_table[] = { }; STATIC MP_DEFINE_CONST_DICT(keypad_keys_locals_dict, keypad_keys_locals_dict_table); +#endif const mp_obj_type_t keypad_keys_type = { { &mp_type_type }, .name = MP_QSTR_Keys, .make_new = keypad_keys_make_new, + #if CIRCUITPY_KEYPAD_KEYS .locals_dict = (mp_obj_t)&keypad_keys_locals_dict, + #endif }; diff --git a/shared-bindings/keypad/ShiftRegisterKeys.c b/shared-bindings/keypad/ShiftRegisterKeys.c index 9113be33d2f9..9c44717a41c3 100644 --- a/shared-bindings/keypad/ShiftRegisterKeys.c +++ b/shared-bindings/keypad/ShiftRegisterKeys.c @@ -82,6 +82,7 @@ //| ... STATIC mp_obj_t keypad_shiftregisterkeys_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + #if CIRCUITPY_KEYPAD_KEYMATRIX keypad_shiftregisterkeys_obj_t *self = m_new_obj(keypad_shiftregisterkeys_obj_t); self->base.type = &keypad_shiftregisterkeys_type; enum { ARG_clock, ARG_data, ARG_latch, ARG_value_to_latch, ARG_key_count, ARG_value_when_pressed, ARG_interval, ARG_max_events }; @@ -113,8 +114,12 @@ STATIC mp_obj_t keypad_shiftregisterkeys_make_new(const mp_obj_type_t *type, siz self, clock, data, latch, value_to_latch, key_count, value_when_pressed, interval, max_events); return MP_OBJ_FROM_PTR(self); + #else + mp_raise_NotImplementedError_varg(translate("%q"), MP_QSTR_ShiftRegisterKeys); + #endif } +#if CIRCUITPY_KEYPAD_KEYMATRIX //| def deinit(self) -> None: //| """Stop scanning and release the pins.""" //| ... @@ -169,10 +174,13 @@ STATIC const mp_rom_map_elem_t keypad_shiftregisterkeys_locals_dict_table[] = { }; STATIC MP_DEFINE_CONST_DICT(keypad_shiftregisterkeys_locals_dict, keypad_shiftregisterkeys_locals_dict_table); +#endif const mp_obj_type_t keypad_shiftregisterkeys_type = { { &mp_type_type }, .name = MP_QSTR_ShiftRegisterKeys, .make_new = keypad_shiftregisterkeys_make_new, + #if CIRCUITPY_KEYPAD_KEYMATRIX .locals_dict = (mp_obj_t)&keypad_shiftregisterkeys_locals_dict, + #endif }; From 62636cf722bd7c775f217e6f80b10cc5eb35673a Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 1 Apr 2023 19:24:06 -0500 Subject: [PATCH 09/22] Make a message more terse This matches a bunch of other NotImplementedErrors --- shared-bindings/audiobusio/I2SOut.c | 2 +- shared-bindings/audiobusio/PDMIn.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shared-bindings/audiobusio/I2SOut.c b/shared-bindings/audiobusio/I2SOut.c index 3713caeb7b0a..e0d46de8e15d 100644 --- a/shared-bindings/audiobusio/I2SOut.c +++ b/shared-bindings/audiobusio/I2SOut.c @@ -97,7 +97,7 @@ //| ... STATIC mp_obj_t audiobusio_i2sout_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { #if !CIRCUITPY_AUDIOBUSIO_I2SOUT - mp_raise_NotImplementedError(translate("I2SOut not available")); + mp_raise_NotImplementedError(translate("%q"), MP_QSTR_I2SOut); return NULL; // Not reachable. #else enum { ARG_bit_clock, ARG_word_select, ARG_data, ARG_left_justified }; diff --git a/shared-bindings/audiobusio/PDMIn.c b/shared-bindings/audiobusio/PDMIn.c index b6a316fadbf0..f26a5f4cc219 100644 --- a/shared-bindings/audiobusio/PDMIn.c +++ b/shared-bindings/audiobusio/PDMIn.c @@ -94,7 +94,7 @@ //| ... STATIC mp_obj_t audiobusio_pdmin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { #if !CIRCUITPY_AUDIOBUSIO_PDMIN - mp_raise_NotImplementedError(translate("PDMIn not available")); + mp_raise_NotImplementedError(translate("%q"), MP_QSTR_PDMIn); #else enum { ARG_clock_pin, ARG_data_pin, ARG_sample_rate, ARG_bit_depth, ARG_mono, ARG_oversample, ARG_startup_delay }; static const mp_arg_t allowed_args[] = { From 8b05d11538cb45f8734274fedecc6a4bc4e98c4a Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 1 Apr 2023 19:25:30 -0500 Subject: [PATCH 10/22] save 4 bytes memory since qstr constants are smol --- shared-bindings/keypad/Event.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared-bindings/keypad/Event.c b/shared-bindings/keypad/Event.c index 8264ebe612cc..52c574e692ce 100644 --- a/shared-bindings/keypad/Event.c +++ b/shared-bindings/keypad/Event.c @@ -167,9 +167,9 @@ STATIC mp_obj_t keypad_event_unary_op(mp_unary_op_t op, mp_obj_t self_in) { STATIC void keypad_event_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { keypad_event_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "", + mp_printf(print, "", common_hal_keypad_event_get_key_number(self), - common_hal_keypad_event_get_pressed(self) ? "pressed" : "released"); + common_hal_keypad_event_get_pressed(self) ? MP_QSTR_pressed : MP_QSTR_released); } STATIC const mp_rom_map_elem_t keypad_event_locals_dict_table[] = { From 975c981e4f32638051832973ba1f1c8a33cf021a Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 2 Apr 2023 11:55:14 -0500 Subject: [PATCH 11/22] fix some calls that needed to be _varg --- shared-bindings/audiobusio/I2SOut.c | 2 +- shared-bindings/audiobusio/PDMIn.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shared-bindings/audiobusio/I2SOut.c b/shared-bindings/audiobusio/I2SOut.c index e0d46de8e15d..8c6f1a3a7a72 100644 --- a/shared-bindings/audiobusio/I2SOut.c +++ b/shared-bindings/audiobusio/I2SOut.c @@ -97,7 +97,7 @@ //| ... STATIC mp_obj_t audiobusio_i2sout_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { #if !CIRCUITPY_AUDIOBUSIO_I2SOUT - mp_raise_NotImplementedError(translate("%q"), MP_QSTR_I2SOut); + mp_raise_NotImplementedError_varg(translate("%q"), MP_QSTR_I2SOut); return NULL; // Not reachable. #else enum { ARG_bit_clock, ARG_word_select, ARG_data, ARG_left_justified }; diff --git a/shared-bindings/audiobusio/PDMIn.c b/shared-bindings/audiobusio/PDMIn.c index f26a5f4cc219..1f0e50029203 100644 --- a/shared-bindings/audiobusio/PDMIn.c +++ b/shared-bindings/audiobusio/PDMIn.c @@ -94,7 +94,7 @@ //| ... STATIC mp_obj_t audiobusio_pdmin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { #if !CIRCUITPY_AUDIOBUSIO_PDMIN - mp_raise_NotImplementedError(translate("%q"), MP_QSTR_PDMIn); + mp_raise_NotImplementedError_varg(translate("%q"), MP_QSTR_PDMIn); #else enum { ARG_clock_pin, ARG_data_pin, ARG_sample_rate, ARG_bit_depth, ARG_mono, ARG_oversample, ARG_startup_delay }; static const mp_arg_t allowed_args[] = { From 7bbb2ed898695a054cb1175e1743fbe33a9a73f3 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 2 Apr 2023 13:48:23 -0500 Subject: [PATCH 12/22] add a basic acceptance test of MidiTrack --- tests/circuitpython/miditrack.py | 20 ++++++++++++++++++++ tests/circuitpython/miditrack.py.exp | 4 ++++ 2 files changed, 24 insertions(+) create mode 100644 tests/circuitpython/miditrack.py create mode 100644 tests/circuitpython/miditrack.py.exp diff --git a/tests/circuitpython/miditrack.py b/tests/circuitpython/miditrack.py new file mode 100644 index 000000000000..53a8ca631df7 --- /dev/null +++ b/tests/circuitpython/miditrack.py @@ -0,0 +1,20 @@ +import array + +try: + from synthio import MidiTrack + from audiocore import get_buffer, get_structure +except ImportError: + print("SKIP") + raise SystemExit + +SCORE = b"\0\x90@\0\x20\x90b\0\x20\x80@\0\0\x80\b\0" + +with MidiTrack(SCORE, sample_rate=8000, tempo=640) as m: + print(get_structure(m)) + print(get_buffer(m)) + +with MidiTrack( + SCORE, sample_rate=8000, tempo=640, waveform=array.array("h", [0, 32767, 0, -32768]) +) as m: + print(get_structure(m)) + print(get_buffer(m)) diff --git a/tests/circuitpython/miditrack.py.exp b/tests/circuitpython/miditrack.py.exp new file mode 100644 index 000000000000..c5dc447e74d5 --- /dev/null +++ b/tests/circuitpython/miditrack.py.exp @@ -0,0 +1,4 @@ +(1, 1, 800, 1) +(1, b'\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0') +(1, 1, 800, 1) +(1, b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f') From e0984fa195c1a45de9e7774d4948467c397d1692 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 2 Apr 2023 18:54:07 -0500 Subject: [PATCH 13/22] Factor out the synthesizer from midi track --- shared-module/synthio/MidiTrack.c | 101 +++++------------------------- shared-module/synthio/MidiTrack.h | 13 +--- shared-module/synthio/__init__.c | 81 ++++++++++++++++++++++++ shared-module/synthio/__init__.h | 30 ++++++++- 4 files changed, 126 insertions(+), 99 deletions(-) diff --git a/shared-module/synthio/MidiTrack.c b/shared-module/synthio/MidiTrack.c index e2614bd0a31e..9cc224d96b97 100644 --- a/shared-module/synthio/MidiTrack.c +++ b/shared-module/synthio/MidiTrack.c @@ -27,11 +27,6 @@ #include "py/runtime.h" #include "shared-bindings/synthio/MidiTrack.h" -#define BITS_PER_SAMPLE (16) -#define BYTES_PER_SAMPLE (BITS_PER_SAMPLE / 8) -#define MAX_DUR (512) -#define SILENCE (0x80) - STATIC NORETURN void raise_midi_stream_error(uint32_t pos) { mp_raise_ValueError_varg(translate("Error in MIDI stream at position %d"), pos); @@ -86,14 +81,13 @@ 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 int16_t *waveform, uint16_t waveform_length) { - synthio_midi_span_t initial = { 0, {[0 ... (CIRCUITPY_SYNTHIO_MAX_CHANNELS - 1)] = SILENCE} }; - self->sample_rate = sample_rate; + self->synth.sample_rate = sample_rate; self->track = m_malloc(sizeof(synthio_midi_span_t), false); + *self->track = ((synthio_midi_span_t) { 0, {[0 ... (CIRCUITPY_SYNTHIO_MAX_CHANNELS - 1)] = SYNTHIO_SILENCE} }); self->next_span = 0; self->total_spans = 1; - *self->track = initial; - self->waveform = waveform; - self->waveform_length = waveform_length; + self->synth.waveform = waveform; + self->synth.waveform_length = waveform_length; mp_arg_validate_length_range(waveform_length, 2, 1024, MP_QSTR_waveform); uint16_t dur = 0; @@ -118,12 +112,12 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, switch (buffer[pos++] >> 4) { case 8: { // Note Off uint8_t note = parse_note(buffer, len, &pos); - change_span_note(self, note, SILENCE, &dur); + change_span_note(self, note, SYNTHIO_SILENCE, &dur); break; } case 9: { // Note On uint8_t note = parse_note(buffer, len, &pos); - change_span_note(self, SILENCE, note, &dur); + change_span_note(self, SYNTHIO_SILENCE, note, &dur); break; } case 10: @@ -150,25 +144,23 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, for (int i = 0; i < self->total_spans; i++) { max_dur = MAX(self->track[i].dur, max_dur); } - self->buffer_length = MIN(MAX_DUR, max_dur) * BYTES_PER_SAMPLE; - self->buffer = m_malloc(self->buffer_length, false); + synthio_synth_init(&self->synth, max_dur); } void common_hal_synthio_miditrack_deinit(synthio_miditrack_obj_t *self) { - m_del(uint8_t, self->buffer, self->buffer_length); - self->buffer = NULL; + synthio_synth_deinit(&self->synth); m_del(synthio_midi_span_t, self->track, self->total_spans + 1); self->track = NULL; } bool common_hal_synthio_miditrack_deinited(synthio_miditrack_obj_t *self) { - return self->buffer == NULL; + return synthio_synth_deinited(&self->synth); } uint32_t common_hal_synthio_miditrack_get_sample_rate(synthio_miditrack_obj_t *self) { - return self->sample_rate; + return self->synth.sample_rate; } uint8_t common_hal_synthio_miditrack_get_bits_per_sample(synthio_miditrack_obj_t *self) { - return BITS_PER_SAMPLE; + return SYNTHIO_BITS_PER_SAMPLE; } uint8_t common_hal_synthio_miditrack_get_channel_count(synthio_miditrack_obj_t *self) { return 1; @@ -177,87 +169,28 @@ uint8_t common_hal_synthio_miditrack_get_channel_count(synthio_miditrack_obj_t * void synthio_miditrack_reset_buffer(synthio_miditrack_obj_t *self, bool single_channel_output, uint8_t channel) { - self->remaining_dur = 0; + self->synth.span.dur = 0; self->next_span = 0; } -STATIC const uint16_t notes[] = {8372, 8870, 9397, 9956, 10548, 11175, 11840, - 12544, 13290, 14080, 14917, 15804}; // 9th octave - -static int count_active_channels(synthio_midi_span_t *span) { - int result = 0; - for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { - if (span->note[i] != SILENCE) { - result += 1; - } - } - return result; -} - 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) { - synthio_midi_span_t span = self->track[self->next_span - !!self->remaining_dur]; - if (self->remaining_dur == 0) { + if (self->synth.span.dur == 0) { if (self->next_span >= self->total_spans) { *buffer_length = 0; return GET_BUFFER_DONE; } - self->next_span++; - self->remaining_dur = span.dur; - } - - uint16_t dur = MIN(MAX_DUR, self->remaining_dur); - self->remaining_dur -= dur; - *buffer_length = dur * BYTES_PER_SAMPLE; - memset(self->buffer, 0, *buffer_length); - - int32_t sample_rate = self->sample_rate; - int active_channels = count_active_channels(&span); - const int16_t *waveform = self->waveform; - uint32_t waveform_length = self->waveform_length; - int16_t *out_buffer = self->buffer; - if (active_channels) { - int16_t loudness = 0x3fff / (1 + active_channels); - for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { - if (span.note[chan] == SILENCE) { - self->accum[chan] = 0; - continue; - } - uint8_t octave = span.note[chan] / 12; - uint16_t base_freq = notes[span.note[chan] % 12]; - uint32_t accum = self->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; - } - self->accum[chan] = accum; - } + self->synth.span = self->track[self->next_span++]; } - *buffer = (uint8_t *)self->buffer; + synthio_synth_synthesize(&self->synth, buffer, buffer_length); - return (self->remaining_dur == 0 && self->next_span >= self->total_spans) ? + return (self->synth.span.dur == 0 && self->next_span >= self->total_spans) ? GET_BUFFER_DONE : GET_BUFFER_MORE_DATA; } void synthio_miditrack_get_buffer_structure(synthio_miditrack_obj_t *self, bool single_channel_output, bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing) { - - *single_buffer = true; - *samples_signed = true; - *max_buffer_length = self->buffer_length; - *spacing = 1; + return synthio_synth_get_buffer_structure(&self->synth, single_channel_output, single_buffer, samples_signed, max_buffer_length, spacing); } diff --git a/shared-module/synthio/MidiTrack.h b/shared-module/synthio/MidiTrack.h index c9206c69bb40..e301ef355f42 100644 --- a/shared-module/synthio/MidiTrack.h +++ b/shared-module/synthio/MidiTrack.h @@ -31,22 +31,11 @@ #include "shared-module/synthio/__init__.h" -typedef struct { - uint16_t dur; - uint8_t note[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; -} synthio_midi_span_t; - typedef struct { mp_obj_base_t base; - uint32_t sample_rate; - int16_t *buffer; - uint16_t buffer_length; - uint16_t remaining_dur; + synthio_synth_t synth; uint16_t next_span; uint16_t total_spans; - uint16_t waveform_length; - const int16_t *waveform; - uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; synthio_midi_span_t *track; } synthio_miditrack_obj_t; diff --git a/shared-module/synthio/__init__.c b/shared-module/synthio/__init__.c index e69de29bb2d1..8c58d5b2dac9 100644 --- a/shared-module/synthio/__init__.c +++ b/shared-module/synthio/__init__.c @@ -0,0 +1,81 @@ +#include "shared-module/synthio/__init__.h" + +STATIC const uint16_t notes[] = {8372, 8870, 9397, 9956, 10548, 11175, 11840, + 12544, 13290, 14080, 14917, 15804}; // 9th octave + +STATIC int count_active_channels(synthio_midi_span_t *span) { + int result = 0; + for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { + if (span->note[i] != SYNTHIO_SILENCE) { + result += 1; + } + } + return result; +} + + +void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t *buffer_length) { + uint16_t dur = MIN(SYNTHIO_MAX_DUR, synth->span.dur); + synth->span.dur -= dur; + memset(synth->buffer, 0, synth->buffer_length); + + int32_t sample_rate = synth->sample_rate; + int active_channels = count_active_channels(&synth->span); + const int16_t *waveform = synth->waveform; + uint32_t waveform_length = synth->waveform_length; + int16_t *out_buffer = synth->buffer; + if (active_channels) { + int16_t loudness = 0x3fff / (1 + active_channels); + for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { + if (synth->span.note[chan] == SYNTHIO_SILENCE) { + synth->accum[chan] = 0; + continue; + } + 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; + } + } + + *buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE; + *buffer = (uint8_t *)synth->buffer; +} + +bool synthio_synth_deinited(synthio_synth_t *synth) { + return synth->buffer == NULL; +} + +void synthio_synth_deinit(synthio_synth_t *synth) { + m_del(uint8_t, synth->buffer, synth->buffer_length); + synth->buffer = NULL; +} + +void synthio_synth_init(synthio_synth_t *synth, uint16_t max_dur) { + synth->buffer_length = MIN(SYNTHIO_MAX_DUR, max_dur) * SYNTHIO_BYTES_PER_SAMPLE; + synth->buffer = m_malloc(synth->buffer_length, false); +} + +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) { + *single_buffer = true; + *samples_signed = true; + *max_buffer_length = synth->buffer_length; + *spacing = 1; +} diff --git a/shared-module/synthio/__init__.h b/shared-module/synthio/__init__.h index d9d98a534166..88be7e689e73 100644 --- a/shared-module/synthio/__init__.h +++ b/shared-module/synthio/__init__.h @@ -24,9 +24,33 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_SHARED_MODULE_SYNTHIO__INIT__H -#define MICROPY_INCLUDED_SHARED_MODULE_SYNTHIO__INIT__H +#pragma once + +#define SYNTHIO_BITS_PER_SAMPLE (16) +#define SYNTHIO_BYTES_PER_SAMPLE (SYNTHIO_BITS_PER_SAMPLE / 8) +#define SYNTHIO_MAX_DUR (512) +#define SYNTHIO_SILENCE (0x80) #include "shared-module/audiocore/__init__.h" -#endif // MICROPY_INCLUDED_SHARED_MODULE_SYNTHIO__INIT__H +typedef struct { + uint16_t dur; + uint8_t note[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; +} synthio_midi_span_t; + +typedef struct { + uint32_t sample_rate; + int16_t *buffer; + const int16_t *waveform; + uint16_t buffer_length; + uint16_t waveform_length; + synthio_midi_span_t span; + uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; +} synthio_synth_t; + +void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t *buffer_length); +void synthio_synth_deinit(synthio_synth_t *synth); +bool synthio_synth_deinited(synthio_synth_t *synth); +void synthio_synth_init(synthio_synth_t *synth, uint16_t max_dur); +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); From b01146825175c5738de17fb8387124150eaba768 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 2 Apr 2023 20:37:23 -0500 Subject: [PATCH 14/22] Add synthio.Synthesizer In contrast to MidiTrack, this can be controlled from Python code, turning notes on/off as desired. Not tested on real HW yet, just the acceptance test based on checking which notes it thinks are held internally. --- .../unix/variants/coverage/mpconfigvariant.mk | 2 + py/circuitpy_defns.mk | 1 + shared-bindings/synthio/MidiTrack.c | 13 +- shared-bindings/synthio/Synthesizer.c | 211 ++++++++++++++++++ shared-bindings/synthio/Synthesizer.h | 45 ++++ shared-bindings/synthio/__init__.c | 16 +- shared-module/synthio/MidiTrack.c | 16 +- shared-module/synthio/Synthesizer.c | 104 +++++++++ shared-module/synthio/Synthesizer.h | 53 +++++ shared-module/synthio/__init__.c | 70 +++++- shared-module/synthio/__init__.h | 4 + tests/circuitpython/synthesizer.py | 24 ++ tests/circuitpython/synthesizer.py.exp | 8 + 13 files changed, 528 insertions(+), 39 deletions(-) create mode 100644 shared-bindings/synthio/Synthesizer.c create mode 100644 shared-bindings/synthio/Synthesizer.h create mode 100644 shared-module/synthio/Synthesizer.c create mode 100644 shared-module/synthio/Synthesizer.h create mode 100644 tests/circuitpython/synthesizer.py create mode 100644 tests/circuitpython/synthesizer.py.exp diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index fca9a99518fc..8b25ddb7057b 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -39,6 +39,7 @@ SRC_BITMAP := \ shared-bindings/struct/__init__.c \ shared-bindings/synthio/__init__.c \ shared-bindings/synthio/MidiTrack.c \ + shared-bindings/synthio/Synthesizer.c \ shared-bindings/traceback/__init__.c \ shared-bindings/util.c \ shared-bindings/zlib/__init__.c \ @@ -57,6 +58,7 @@ SRC_BITMAP := \ shared-module/struct/__init__.c \ shared-module/synthio/__init__.c \ shared-module/synthio/MidiTrack.c \ + shared-module/synthio/Synthesizer.c \ shared-module/traceback/__init__.c \ shared-module/zlib/__init__.c \ diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 94377238a04a..fb5d745ebb99 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -648,6 +648,7 @@ SRC_SHARED_MODULE_ALL = \ supervisor/__init__.c \ supervisor/StatusBar.c \ synthio/MidiTrack.c \ + synthio/Synthesizer.c \ synthio/__init__.c \ terminalio/Terminal.c \ terminalio/__init__.c \ diff --git a/shared-bindings/synthio/MidiTrack.c b/shared-bindings/synthio/MidiTrack.c index 727b9ba3358f..20a203ce9c07 100644 --- a/shared-bindings/synthio/MidiTrack.c +++ b/shared-bindings/synthio/MidiTrack.c @@ -85,17 +85,8 @@ STATIC mp_obj_t synthio_miditrack_make_new(const mp_obj_type_t *type, size_t n_a mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ); - mp_buffer_info_t bufinfo_waveform = { - .buf = shared_bindings_synthio_square_wave, - .len = 4 - }; - - if (args[ARG_waveform].u_obj != mp_const_none) { - mp_get_buffer_raise(args[ARG_waveform].u_obj, &bufinfo_waveform, MP_BUFFER_READ); - if (bufinfo_waveform.typecode != 'h') { - mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), MP_QSTR_waveform); - } - } + 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); self->base.type = &synthio_miditrack_type; diff --git a/shared-bindings/synthio/Synthesizer.c b/shared-bindings/synthio/Synthesizer.c new file mode 100644 index 000000000000..2ad5cbcd42b2 --- /dev/null +++ b/shared-bindings/synthio/Synthesizer.c @@ -0,0 +1,211 @@ +/* + * 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 + +#include "shared/runtime/context_manager_helpers.h" +#include "py/binary.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "shared-bindings/util.h" +#include "shared-bindings/synthio/Synthesizer.h" +#include "shared-bindings/synthio/__init__.h" +#include "supervisor/shared/translate/translate.h" + +//| class Synth: +//| def __init__(self, *, sample_rate: int = 11025, waveform: ReadableBuffer = None) -> None: +//| """Create a synthesizer object.""" +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 }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 11025} }, + { MP_QSTR_waveform, 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); + + 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); + self->base.type = &synthio_synthesizer_type; + + common_hal_synthio_synthesizer_construct(self, + args[ARG_sample_rate].u_int, + bufinfo_waveform.buf, + bufinfo_waveform.len / 2); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC void check_for_deinit(synthio_synthesizer_obj_t *self) { + if (common_hal_synthio_synthesizer_deinited(self)) { + raise_deinited_error(); + } +} + +//| def press(self, /, press: Sequence[int] = ()) -> None: +//| """Turn some notes on. Notes use MIDI numbering, with 60 being middle C.""" +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); + check_for_deinit(self); + common_hal_synthio_synthesizer_press(self, press); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_press_obj, synthio_synthesizer_press); +// +//| def release_then_press( +//| self, release: Sequence[int] = (), press: Sequence[int] = () +//| ) -> None: +//| """Turn some notes on and/or off. Notes use MIDI numbering, with 60 being middle C.""" +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 }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_release, MP_ARG_OBJ, {.u_obj = mp_const_empty_tuple } }, + { MP_QSTR_press, MP_ARG_OBJ, {.u_obj = mp_const_empty_tuple } }, + }; + + 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); + + synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + check_for_deinit(self); + common_hal_synthio_synthesizer_release(self, args[ARG_release].u_obj); + common_hal_synthio_synthesizer_press(self, args[ARG_press].u_obj); + return mp_const_none; +} +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: +//| """Turn any currently-playing notes off, then turn on the given notes""" +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); + check_for_deinit(self); + common_hal_synthio_synthesizer_release_all(self); + common_hal_synthio_synthesizer_press(self, press); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_release_all_then_press_obj, synthio_synthesizer_release_all_then_press); + +// +//| def release_all(self) -> None: +//| """Turn any currently-playing notes off""" +STATIC mp_obj_t synthio_synthesizer_release_all(mp_obj_t self_in) { + synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + common_hal_synthio_synthesizer_release_all(self); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_release_all_obj, synthio_synthesizer_release_all); + +//| def deinit(self) -> None: +//| """Deinitialises the object and releases any memory resources for reuse.""" +//| ... +STATIC mp_obj_t synthio_synthesizer_deinit(mp_obj_t self_in) { + synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_synthio_synthesizer_deinit(self); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_deinit_obj, synthio_synthesizer_deinit); + +//| def __enter__(self) -> MidiTrack: +//| """No-op used by Context Managers.""" +//| ... +// Provided by context manager helper. +//| +//| def __exit__(self) -> None: +//| """Automatically deinitializes the hardware when exiting a context. See +//| :ref:`lifetime-and-contextmanagers` for more info.""" +//| ... +STATIC mp_obj_t synthio_synthesizer_obj___exit__(size_t n_args, const mp_obj_t *args) { + (void)n_args; + common_hal_synthio_synthesizer_deinit(args[0]); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(synthio_synthesizer___exit___obj, 4, 4, synthio_synthesizer_obj___exit__); +//| sample_rate: int +//| """32 bit value that tells how quickly samples are played in Hertz (cycles per second).""" +STATIC mp_obj_t synthio_synthesizer_obj_get_sample_rate(mp_obj_t self_in) { + synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return MP_OBJ_NEW_SMALL_INT(common_hal_synthio_synthesizer_get_sample_rate(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_get_sample_rate_obj, synthio_synthesizer_obj_get_sample_rate); + +MP_PROPERTY_GETTER(synthio_synthesizer_sample_rate_obj, + (mp_obj_t)&synthio_synthesizer_get_sample_rate_obj); + +//| pressed: Tuple[int] +//| """A sequence of the currently pressed notes (read-only property)""" +//| +STATIC mp_obj_t synthio_synthesizer_obj_get_pressed(mp_obj_t self_in) { + synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return common_hal_synthio_synthesizer_get_pressed_notes(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_get_pressed_obj, synthio_synthesizer_obj_get_pressed); + +MP_PROPERTY_GETTER(synthio_synthesizer_pressed_obj, + (mp_obj_t)&synthio_synthesizer_get_pressed_obj); + +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) }, + { MP_ROM_QSTR(MP_QSTR_release_all), MP_ROM_PTR(&synthio_synthesizer_release_all_obj) }, + { MP_ROM_QSTR(MP_QSTR_release_then_press), MP_ROM_PTR(&synthio_synthesizer_release_then_press_obj) }, + { MP_ROM_QSTR(MP_QSTR_release_all_then_press), MP_ROM_PTR(&synthio_synthesizer_release_all_then_press_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&synthio_synthesizer_deinit_obj) }, + { 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) }, + + // Properties + { MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&synthio_synthesizer_sample_rate_obj) }, + { MP_ROM_QSTR(MP_QSTR_pressed), MP_ROM_PTR(&synthio_synthesizer_pressed_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(synthio_synthesizer_locals_dict, synthio_synthesizer_locals_dict_table); + +STATIC const audiosample_p_t synthio_synthesizer_proto = { + MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample) + .sample_rate = (audiosample_sample_rate_fun)common_hal_synthio_synthesizer_get_sample_rate, + .bits_per_sample = (audiosample_bits_per_sample_fun)common_hal_synthio_synthesizer_get_bits_per_sample, + .channel_count = (audiosample_channel_count_fun)common_hal_synthio_synthesizer_get_channel_count, + .reset_buffer = (audiosample_reset_buffer_fun)synthio_synthesizer_reset_buffer, + .get_buffer = (audiosample_get_buffer_fun)synthio_synthesizer_get_buffer, + .get_buffer_structure = (audiosample_get_buffer_structure_fun)synthio_synthesizer_get_buffer_structure, +}; + +const mp_obj_type_t synthio_synthesizer_type = { + { &mp_type_type }, + .name = MP_QSTR_Synthesizer, + .flags = MP_TYPE_FLAG_EXTENDED, + .make_new = synthio_synthesizer_make_new, + .locals_dict = (mp_obj_dict_t *)&synthio_synthesizer_locals_dict, + MP_TYPE_EXTENDED_FIELDS( + .protocol = &synthio_synthesizer_proto, + ), +}; diff --git a/shared-bindings/synthio/Synthesizer.h b/shared-bindings/synthio/Synthesizer.h new file mode 100644 index 000000000000..92e75a1c23a5 --- /dev/null +++ b/shared-bindings/synthio/Synthesizer.h @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#pragma once + +#include "shared-module/synthio/Synthesizer.h" + +extern const mp_obj_type_t synthio_synthesizer_type; + +void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self, + uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_len); + +void common_hal_synthio_synthesizer_deinit(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); +uint8_t common_hal_synthio_synthesizer_get_bits_per_sample(synthio_synthesizer_obj_t *self); +uint8_t common_hal_synthio_synthesizer_get_channel_count(synthio_synthesizer_obj_t *self); +void common_hal_synthio_synthesizer_release(synthio_synthesizer_obj_t *self, mp_obj_t to_release); +void common_hal_synthio_synthesizer_press(synthio_synthesizer_obj_t *self, mp_obj_t to_press); +void common_hal_synthio_synthesizer_release_all(synthio_synthesizer_obj_t *self); +mp_obj_t common_hal_synthio_synthesizer_get_pressed_notes(synthio_synthesizer_obj_t *self); diff --git a/shared-bindings/synthio/__init__.c b/shared-bindings/synthio/__init__.c index dc627adb36e0..3c50377ff8c0 100644 --- a/shared-bindings/synthio/__init__.c +++ b/shared-bindings/synthio/__init__.c @@ -34,8 +34,7 @@ #include "shared-bindings/synthio/__init__.h" #include "shared-bindings/synthio/MidiTrack.h" - -int16_t shared_bindings_synthio_square_wave[] = {-32768, 32767}; +#include "shared-bindings/synthio/Synthesizer.h" //| """Support for MIDI synthesis""" //| @@ -79,17 +78,9 @@ 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); - mp_buffer_info_t bufinfo_waveform = { - .buf = shared_bindings_synthio_square_wave, - .len = 4 - }; - if (args[ARG_waveform].u_obj != mp_const_none) { - mp_get_buffer_raise(args[ARG_waveform].u_obj, &bufinfo_waveform, MP_BUFFER_READ); - if (bufinfo_waveform.typecode != 'h') { - mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), MP_QSTR_waveform); - } - } + mp_buffer_info_t bufinfo_waveform; + synthio_synth_parse_waveform(&bufinfo_waveform, args[ARG_waveform].u_obj); uint8_t chunk_header[14]; f_rewind(&file->fp); @@ -146,6 +137,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(synthio_from_file_obj, 1, synthio_from_file); 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_MidiTrack), MP_ROM_PTR(&synthio_miditrack_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) }, }; diff --git a/shared-module/synthio/MidiTrack.c b/shared-module/synthio/MidiTrack.c index 9cc224d96b97..0d91ee698279 100644 --- a/shared-module/synthio/MidiTrack.c +++ b/shared-module/synthio/MidiTrack.c @@ -57,21 +57,10 @@ STATIC void add_span(synthio_miditrack_obj_t *self, const synthio_midi_span_t *s self->track[self->total_spans++] = *span; } -STATIC int find_channel_with_note(const synthio_midi_span_t *span, uint8_t note) { - for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { - if (span->note[i] == note) { - return i; - } - } - return -1; -} - STATIC void change_span_note(synthio_miditrack_obj_t *self, uint8_t old_note, uint8_t new_note, uint16_t *dur) { synthio_midi_span_t span = self->track[self->total_spans - 1]; - int channel = find_channel_with_note(&span, old_note); - if (channel != -1) { + if (synthio_span_change_note(&span, old_note, new_note)) { terminate_span(self, dur); - span.note[channel] = new_note; add_span(self, &span); *dur = 0; } @@ -83,12 +72,11 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self, self->synth.sample_rate = sample_rate; self->track = m_malloc(sizeof(synthio_midi_span_t), false); - *self->track = ((synthio_midi_span_t) { 0, {[0 ... (CIRCUITPY_SYNTHIO_MAX_CHANNELS - 1)] = SYNTHIO_SILENCE} }); + synthio_span_init(self->track); self->next_span = 0; self->total_spans = 1; self->synth.waveform = waveform; self->synth.waveform_length = waveform_length; - mp_arg_validate_length_range(waveform_length, 2, 1024, MP_QSTR_waveform); uint16_t dur = 0; uint32_t pos = 0; diff --git a/shared-module/synthio/Synthesizer.c b/shared-module/synthio/Synthesizer.c new file mode 100644 index 000000000000..8cfc73a9804d --- /dev/null +++ b/shared-module/synthio/Synthesizer.c @@ -0,0 +1,104 @@ +/* + * This file is part of the Micro Python 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 "py/runtime.h" +#include "shared-bindings/synthio/Synthesizer.h" + + + +void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self, + uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length) { + + self->synth.sample_rate = sample_rate; + self->synth.waveform = waveform; + self->synth.waveform_length = waveform_length; + synthio_synth_init(&self->synth, SYNTHIO_MAX_DUR); + common_hal_synthio_synthesizer_release_all(self); +} + +void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self) { + synthio_synth_deinit(&self->synth); +} +bool common_hal_synthio_synthesizer_deinited(synthio_synthesizer_obj_t *self) { + return synthio_synth_deinited(&self->synth); +} + +uint32_t common_hal_synthio_synthesizer_get_sample_rate(synthio_synthesizer_obj_t *self) { + return self->synth.sample_rate; +} +uint8_t common_hal_synthio_synthesizer_get_bits_per_sample(synthio_synthesizer_obj_t *self) { + return SYNTHIO_BITS_PER_SAMPLE; +} +uint8_t common_hal_synthio_synthesizer_get_channel_count(synthio_synthesizer_obj_t *self) { + return 1; +} + +void synthio_synthesizer_reset_buffer(synthio_synthesizer_obj_t *self, + bool single_channel_output, uint8_t channel) { +} + +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) { + self->synth.span.dur = SYNTHIO_MAX_DUR; + synthio_synth_synthesize(&self->synth, buffer, buffer_length); + return GET_BUFFER_MORE_DATA; +} + +void synthio_synthesizer_get_buffer_structure(synthio_synthesizer_obj_t *self, bool single_channel_output, + bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing) { + return synthio_synth_get_buffer_structure(&self->synth, single_channel_output, single_buffer, samples_signed, max_buffer_length, spacing); +} + +void common_hal_synthio_synthesizer_release_all(synthio_synthesizer_obj_t *self) { + synthio_span_init(&self->synth.span); +} +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_t iterable = mp_getiter(to_release, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + synthio_span_change_note(&self->synth.span, mp_arg_validate_int_range(mp_obj_get_int(item), 0, 127, MP_QSTR_note), SYNTHIO_SILENCE); + } +} + +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_t iterable = mp_getiter(to_press, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + synthio_span_change_note(&self->synth.span, SYNTHIO_SILENCE, mp_arg_validate_int_range(mp_obj_get_int(item), 0, 127, MP_QSTR_note)); + } +} + +mp_obj_t common_hal_synthio_synthesizer_get_pressed_notes(synthio_synthesizer_obj_t *self) { + mp_obj_tuple_t *result = MP_OBJ_TO_PTR(mp_obj_new_tuple(synthio_span_count_active_channels(&self->synth.span), NULL)); + for (size_t i = 0, j = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { + if (self->synth.span.note[i] != SYNTHIO_SILENCE) { + result->items[j++] = MP_OBJ_NEW_SMALL_INT(self->synth.span.note[i]); + } + } + return MP_OBJ_FROM_PTR(result); +} diff --git a/shared-module/synthio/Synthesizer.h b/shared-module/synthio/Synthesizer.h new file mode 100644 index 000000000000..256b6137f208 --- /dev/null +++ b/shared-module/synthio/Synthesizer.h @@ -0,0 +1,53 @@ +/* + * This file is part of the MicroPython 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. + */ + +#pragma once + +#include "py/obj.h" + +#include "shared-module/synthio/__init__.h" + +typedef struct { + mp_obj_base_t base; + synthio_synth_t synth; +} synthio_synthesizer_obj_t; + + +// These are not available from Python because it may be called in an interrupt. +void synthio_synthesizer_reset_buffer(synthio_synthesizer_obj_t *self, + bool single_channel_output, + uint8_t channel); + +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); // length in bytes + +void synthio_synthesizer_get_buffer_structure(synthio_synthesizer_obj_t *self, bool single_channel_output, + bool *single_buffer, bool *samples_signed, + uint32_t *max_buffer_length, uint8_t *spacing); diff --git a/shared-module/synthio/__init__.c b/shared-module/synthio/__init__.c index 8c58d5b2dac9..0dee75deb7b2 100644 --- a/shared-module/synthio/__init__.c +++ b/shared-module/synthio/__init__.c @@ -1,9 +1,39 @@ +/* + * This file is part of the MicroPython 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 "shared-module/synthio/__init__.h" +#include "py/runtime.h" + +STATIC const int16_t square_wave[] = {-32768, 32767}; STATIC const uint16_t notes[] = {8372, 8870, 9397, 9956, 10548, 11175, 11840, 12544, 13290, 14080, 14917, 15804}; // 9th octave -STATIC int count_active_channels(synthio_midi_span_t *span) { +int synthio_span_count_active_channels(synthio_midi_span_t *span) { int result = 0; for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { if (span->note[i] != SYNTHIO_SILENCE) { @@ -20,7 +50,7 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t memset(synth->buffer, 0, synth->buffer_length); int32_t sample_rate = synth->sample_rate; - int active_channels = count_active_channels(&synth->span); + int active_channels = synthio_span_count_active_channels(&synth->span); const int16_t *waveform = synth->waveform; uint32_t waveform_length = synth->waveform_length; int16_t *out_buffer = synth->buffer; @@ -79,3 +109,39 @@ void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_chan *max_buffer_length = synth->buffer_length; *spacing = 1; } + +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 }); + + if (waveform_obj != mp_const_none) { + mp_get_buffer_raise(waveform_obj, bufinfo_waveform, MP_BUFFER_READ); + if (bufinfo_waveform->typecode != 'h') { + mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), MP_QSTR_waveform); + } + } + mp_arg_validate_length_range(bufinfo_waveform->len / 2, 2, 1024, MP_QSTR_waveform); +} + +void synthio_span_init(synthio_midi_span_t *span) { + span->dur = 0; + for (size_t i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { span->note[i] = SYNTHIO_SILENCE; + } +} + +STATIC int find_channel_with_note(const synthio_midi_span_t *span, uint8_t note) { + for (int i = 0; i < CIRCUITPY_SYNTHIO_MAX_CHANNELS; i++) { + if (span->note[i] == note) { + return i; + } + } + return -1; +} + +bool synthio_span_change_note(synthio_midi_span_t *span, uint8_t old_note, uint8_t new_note) { + int channel = find_channel_with_note(span, old_note); + if (channel != -1) { + span->note[channel] = new_note; + return true; + } + return false; +} diff --git a/shared-module/synthio/__init__.h b/shared-module/synthio/__init__.h index 88be7e689e73..9397495f982d 100644 --- a/shared-module/synthio/__init__.h +++ b/shared-module/synthio/__init__.h @@ -48,9 +48,13 @@ typedef struct { uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; } synthio_synth_t; +void synthio_span_init(synthio_midi_span_t *span); void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t *buffer_length); void synthio_synth_deinit(synthio_synth_t *synth); bool synthio_synth_deinited(synthio_synth_t *synth); void synthio_synth_init(synthio_synth_t *synth, uint16_t max_dur); 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_parse_waveform(mp_buffer_info_t *bufinfo_waveform, mp_obj_t waveform_obj); +bool synthio_span_change_note(synthio_midi_span_t *span, uint8_t old_note, uint8_t new_note); +int synthio_span_count_active_channels(synthio_midi_span_t *span); diff --git a/tests/circuitpython/synthesizer.py b/tests/circuitpython/synthesizer.py new file mode 100644 index 000000000000..bdb273f861ab --- /dev/null +++ b/tests/circuitpython/synthesizer.py @@ -0,0 +1,24 @@ +import struct +import synthio +import audiocore + + +def dump_samples(): + print(struct.unpack("12h", audiocore.get_buffer(s)[1][:24])) + + +s = synthio.Synthesizer(sample_rate=8000) +print(s.pressed) +dump_samples() + +s.press((80,)) +print(s.pressed) +dump_samples() + +s.press((91,)) +print(s.pressed) +dump_samples() + +s.release_then_press((80,)) +print(s.pressed) +dump_samples() diff --git a/tests/circuitpython/synthesizer.py.exp b/tests/circuitpython/synthesizer.py.exp new file mode 100644 index 000000000000..aa2567641cbc --- /dev/null +++ b/tests/circuitpython/synthesizer.py.exp @@ -0,0 +1,8 @@ +() +(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +(80,) +(-4095, -4095, -4095, -4095, 4095, 4095, 4095, 4095, 4095, -4095, -4095, -4095) +(80, 91) +(-5460, -5460, 0, 5460, 5460, 0, 0, 5460, 0, 0, -5460, -5460) +(91,) +(4095, 4095, 4095, -4095, -4095, 4095, 4095, 4095, -4095, -4095, 4095, 4095) From b9dea05dbfaa9a55a429892b1cf87e1518083866 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 5 Apr 2023 08:51:55 -0500 Subject: [PATCH 15/22] Enable audiomixer on unix port, for testing --- ports/unix/variants/coverage/mpconfigvariant.mk | 7 +++++++ shared-bindings/audiomixer/Mixer.c | 2 -- shared-bindings/audiomixer/Mixer.h | 1 - shared-bindings/audiomixer/MixerVoice.c | 4 +--- shared-bindings/audiomixer/MixerVoice.h | 7 ++----- shared-bindings/audiomixer/__init__.c | 1 - shared-module/audiomixer/Mixer.c | 6 +++++- shared-module/audiomixer/MixerVoice.c | 8 ++++---- 8 files changed, 19 insertions(+), 17 deletions(-) diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index 8b25ddb7057b..854d052c6c24 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -33,6 +33,9 @@ SRC_BITMAP := \ shared-bindings/audiocore/__init__.c \ shared-bindings/audiocore/RawSample.c \ shared-bindings/audiocore/WaveFile.c \ + shared-bindings/audiomixer/__init__.c \ + shared-bindings/audiomixer/Mixer.c \ + shared-bindings/audiomixer/MixerVoice.c \ shared-bindings/bitmaptools/__init__.c \ shared-bindings/displayio/Bitmap.c \ shared-bindings/rainbowio/__init__.c \ @@ -48,6 +51,9 @@ SRC_BITMAP := \ shared-module/audiocore/__init__.c \ shared-module/audiocore/RawSample.c \ shared-module/audiocore/WaveFile.c \ + shared-module/audiomixer/__init__.c \ + shared-module/audiomixer/Mixer.c \ + shared-module/audiomixer/MixerVoice.c \ shared-module/bitmaptools/__init__.c \ shared-module/displayio/area.c \ shared-module/displayio/Bitmap.c \ @@ -67,6 +73,7 @@ SRC_C += $(SRC_BITMAP) CFLAGS += \ -DCIRCUITPY_AESIO=1 \ -DCIRCUITPY_AUDIOCORE=1 \ + -DCIRCUITPY_AUDIOMIXER=1 \ -DCIRCUITPY_AUDIOCORE_DEBUG=1 \ -DCIRCUITPY_BITMAPTOOLS=1 \ -DCIRCUITPY_DISPLAYIO_UNIX=1 \ diff --git a/shared-bindings/audiomixer/Mixer.c b/shared-bindings/audiomixer/Mixer.c index 58c618fc11e7..83fcc0671238 100644 --- a/shared-bindings/audiomixer/Mixer.c +++ b/shared-bindings/audiomixer/Mixer.c @@ -33,8 +33,6 @@ #include "py/binary.h" #include "py/objproperty.h" #include "py/runtime.h" -#include "shared-bindings/microcontroller/Pin.h" -#include "shared-bindings/audiocore/RawSample.h" #include "shared-bindings/util.h" #include "supervisor/shared/translate/translate.h" diff --git a/shared-bindings/audiomixer/Mixer.h b/shared-bindings/audiomixer/Mixer.h index fd1154cc433c..d04f793ddec7 100644 --- a/shared-bindings/audiomixer/Mixer.h +++ b/shared-bindings/audiomixer/Mixer.h @@ -28,7 +28,6 @@ #define MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOMIXER_MIXER_H #include "shared-module/audiomixer/Mixer.h" -#include "shared-bindings/audiocore/RawSample.h" extern const mp_obj_type_t audiomixer_mixer_type; extern const mp_obj_type_t audiomixer_mixervoice_type; diff --git a/shared-bindings/audiomixer/MixerVoice.c b/shared-bindings/audiomixer/MixerVoice.c index 16a1be520fca..1d6cd7f39a56 100644 --- a/shared-bindings/audiomixer/MixerVoice.c +++ b/shared-bindings/audiomixer/MixerVoice.c @@ -32,8 +32,6 @@ #include "py/binary.h" #include "py/objproperty.h" #include "py/runtime.h" -#include "shared-bindings/microcontroller/Pin.h" -#include "shared-bindings/audiocore/RawSample.h" #include "shared-bindings/util.h" #include "supervisor/shared/translate/translate.h" @@ -115,7 +113,7 @@ STATIC mp_obj_t audiomixer_mixervoice_obj_set_level(size_t n_args, const mp_obj_ 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); - float level = mp_obj_get_float(args[ARG_level].u_obj); + mp_float_t level = mp_obj_get_float(args[ARG_level].u_obj); if (level > 1 || level < 0) { mp_raise_ValueError(translate("level must be between 0 and 1")); diff --git a/shared-bindings/audiomixer/MixerVoice.h b/shared-bindings/audiomixer/MixerVoice.h index bce463276019..0ff8accee442 100644 --- a/shared-bindings/audiomixer/MixerVoice.h +++ b/shared-bindings/audiomixer/MixerVoice.h @@ -26,9 +26,6 @@ #ifndef SHARED_BINDINGS_AUDIOMIXER_MIXERVOICE_H_ #define SHARED_BINDINGS_AUDIOMIXER_MIXERVOICE_H_ -#include "common-hal/microcontroller/Pin.h" -#include "shared-bindings/audiocore/RawSample.h" - #include "shared-module/audiomixer/MixerVoice.h" #include "shared-module/audiomixer/Mixer.h" @@ -39,8 +36,8 @@ void common_hal_audiomixer_mixervoice_construct(audiomixer_mixervoice_obj_t *sel void common_hal_audiomixer_mixervoice_set_parent(audiomixer_mixervoice_obj_t *self, audiomixer_mixer_obj_t *parent); void common_hal_audiomixer_mixervoice_play(audiomixer_mixervoice_obj_t *self, mp_obj_t sample, bool loop); void common_hal_audiomixer_mixervoice_stop(audiomixer_mixervoice_obj_t *self); -float common_hal_audiomixer_mixervoice_get_level(audiomixer_mixervoice_obj_t *self); -void common_hal_audiomixer_mixervoice_set_level(audiomixer_mixervoice_obj_t *self, float gain); +mp_float_t common_hal_audiomixer_mixervoice_get_level(audiomixer_mixervoice_obj_t *self); +void common_hal_audiomixer_mixervoice_set_level(audiomixer_mixervoice_obj_t *self, mp_float_t gain); bool common_hal_audiomixer_mixervoice_get_playing(audiomixer_mixervoice_obj_t *self); diff --git a/shared-bindings/audiomixer/__init__.c b/shared-bindings/audiomixer/__init__.c index 8292be9e95e3..4c8068172bbe 100644 --- a/shared-bindings/audiomixer/__init__.c +++ b/shared-bindings/audiomixer/__init__.c @@ -29,7 +29,6 @@ #include "py/obj.h" #include "py/runtime.h" -#include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/audiomixer/Mixer.h" //| """Support for audio mixing""" diff --git a/shared-module/audiomixer/Mixer.c b/shared-module/audiomixer/Mixer.c index 1c5de4d93425..ce39f8891905 100644 --- a/shared-module/audiomixer/Mixer.c +++ b/shared-module/audiomixer/Mixer.c @@ -35,6 +35,10 @@ #include "shared-module/audiocore/__init__.h" #include "shared-module/audiocore/RawSample.h" +#if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) +#include "periph.h" +#endif + void common_hal_audiomixer_mixer_construct(audiomixer_mixer_obj_t *self, uint8_t voice_count, uint32_t buffer_size, @@ -140,7 +144,7 @@ static inline uint32_t mult16signed(uint32_t val, int32_t mul) { float mod_mul = (float)mul / (float)((1 << 15) - 1); for (int8_t i = 0; i < 2; i++) { int16_t ai = (val >> (sizeof(uint16_t) * 8 * i)); - int32_t intermediate = ai * mod_mul; + int32_t intermediate = (int32_t)(ai * mod_mul); if (intermediate > SHRT_MAX) { intermediate = SHRT_MAX; } else if (intermediate < SHRT_MIN) { diff --git a/shared-module/audiomixer/MixerVoice.c b/shared-module/audiomixer/MixerVoice.c index bb58b0c11117..f4d2a6514e52 100644 --- a/shared-module/audiomixer/MixerVoice.c +++ b/shared-module/audiomixer/MixerVoice.c @@ -42,12 +42,12 @@ void common_hal_audiomixer_mixervoice_set_parent(audiomixer_mixervoice_obj_t *se self->parent = parent; } -float common_hal_audiomixer_mixervoice_get_level(audiomixer_mixervoice_obj_t *self) { - return (float)self->level / (1 << 15); +mp_float_t common_hal_audiomixer_mixervoice_get_level(audiomixer_mixervoice_obj_t *self) { + return (mp_float_t)self->level / (1 << 15); } -void common_hal_audiomixer_mixervoice_set_level(audiomixer_mixervoice_obj_t *self, float level) { - self->level = level * (1 << 15); +void common_hal_audiomixer_mixervoice_set_level(audiomixer_mixervoice_obj_t *self, mp_float_t level) { + self->level = (uint16_t)(level * (1 << 15)); } void common_hal_audiomixer_mixervoice_play(audiomixer_mixervoice_obj_t *self, mp_obj_t sample, bool loop) { From 4c7b962d16f556a9fce9462289379fa243571ce3 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 5 Apr 2023 10:30:57 -0500 Subject: [PATCH 16/22] Increase synthio channels to 12 on rp2040 with the AudioMixer workaround for #7837 this appears to work even up to 48kHz. --- ports/raspberrypi/mpconfigport.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/raspberrypi/mpconfigport.mk b/ports/raspberrypi/mpconfigport.mk index 21dde6d2399a..671d4669bfbe 100644 --- a/ports/raspberrypi/mpconfigport.mk +++ b/ports/raspberrypi/mpconfigport.mk @@ -19,6 +19,7 @@ CIRCUITPY_PWMIO ?= 1 CIRCUITPY_RGBMATRIX ?= $(CIRCUITPY_DISPLAYIO) CIRCUITPY_ROTARYIO ?= 1 CIRCUITPY_ROTARYIO_SOFTENCODER = 1 +CIRCUITPY_SYNTHIO_MAX_CHANNELS = 12 # Things that need to be implemented. # Use PWM internally From 685fdf29e32b5b62f43f037be4d9f1ae89e1df38 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 5 Apr 2023 10:31:05 -0500 Subject: [PATCH 17/22] remove include directory that doesn't exist --- ports/mimxrt10xx/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/mimxrt10xx/Makefile b/ports/mimxrt10xx/Makefile index ff2b6c35c1f1..72f4d191effc 100644 --- a/ports/mimxrt10xx/Makefile +++ b/ports/mimxrt10xx/Makefile @@ -38,7 +38,6 @@ INC += \ -Iboards/$(BOARD) \ -Iperipherals/ \ -Iperipherals/mimxrt10xx/ \ - -Isdk/CMSIS/Include \ -Isdk/devices/$(CHIP_FAMILY) \ -Isdk/devices/$(CHIP_FAMILY)/drivers \ -Isdk/drivers/common From 3038b9a56055190d6a523065877caa08e876883b Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 5 Apr 2023 11:56:21 -0500 Subject: [PATCH 18/22] Update shared-bindings/keypad/ShiftRegisterKeys.c Co-authored-by: Scott Shawcroft --- shared-bindings/keypad/ShiftRegisterKeys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-bindings/keypad/ShiftRegisterKeys.c b/shared-bindings/keypad/ShiftRegisterKeys.c index 9c44717a41c3..465ff9542ce9 100644 --- a/shared-bindings/keypad/ShiftRegisterKeys.c +++ b/shared-bindings/keypad/ShiftRegisterKeys.c @@ -82,7 +82,7 @@ //| ... STATIC mp_obj_t keypad_shiftregisterkeys_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - #if CIRCUITPY_KEYPAD_KEYMATRIX + #if CIRCUITPY_KEYPAD_SHIFTREGISTERKEYS keypad_shiftregisterkeys_obj_t *self = m_new_obj(keypad_shiftregisterkeys_obj_t); self->base.type = &keypad_shiftregisterkeys_type; enum { ARG_clock, ARG_data, ARG_latch, ARG_value_to_latch, ARG_key_count, ARG_value_when_pressed, ARG_interval, ARG_max_events }; From 9679aaa0be1a2b1943430b798c770b5e0377b83b Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 5 Apr 2023 11:57:06 -0500 Subject: [PATCH 19/22] Apply suggestions from code review Co-authored-by: Scott Shawcroft --- shared-bindings/keypad/ShiftRegisterKeys.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared-bindings/keypad/ShiftRegisterKeys.c b/shared-bindings/keypad/ShiftRegisterKeys.c index 465ff9542ce9..1fcf517ac37d 100644 --- a/shared-bindings/keypad/ShiftRegisterKeys.c +++ b/shared-bindings/keypad/ShiftRegisterKeys.c @@ -119,7 +119,7 @@ STATIC mp_obj_t keypad_shiftregisterkeys_make_new(const mp_obj_type_t *type, siz #endif } -#if CIRCUITPY_KEYPAD_KEYMATRIX +#if CIRCUITPY_KEYPAD_SHIFTREGISTERKEYS //| def deinit(self) -> None: //| """Stop scanning and release the pins.""" //| ... @@ -180,7 +180,7 @@ const mp_obj_type_t keypad_shiftregisterkeys_type = { { &mp_type_type }, .name = MP_QSTR_ShiftRegisterKeys, .make_new = keypad_shiftregisterkeys_make_new, - #if CIRCUITPY_KEYPAD_KEYMATRIX + #if CIRCUITPY_KEYPAD_SHIFTREGISTERKEYS .locals_dict = (mp_obj_t)&keypad_shiftregisterkeys_locals_dict, #endif }; From 04f4092e110b50fc03dd55e8a5f1c2151c5d0035 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 5 Apr 2023 11:55:54 -0500 Subject: [PATCH 20/22] synthio: support audio outputs that need double buffering closes #7837 tested on rp2040 pico w on pico dv shield --- shared-module/synthio/MidiTrack.c | 4 +-- shared-module/synthio/Synthesizer.c | 3 ++- shared-module/synthio/__init__.c | 42 ++++++++++++++++++++++------- shared-module/synthio/__init__.h | 10 ++++--- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/shared-module/synthio/MidiTrack.c b/shared-module/synthio/MidiTrack.c index 0d91ee698279..f8b39c29e383 100644 --- a/shared-module/synthio/MidiTrack.c +++ b/shared-module/synthio/MidiTrack.c @@ -156,7 +156,7 @@ uint8_t common_hal_synthio_miditrack_get_channel_count(synthio_miditrack_obj_t * void synthio_miditrack_reset_buffer(synthio_miditrack_obj_t *self, bool single_channel_output, uint8_t channel) { - + synthio_synth_reset_buffer(&self->synth, single_channel_output, channel); self->synth.span.dur = 0; self->next_span = 0; } @@ -172,7 +172,7 @@ audioio_get_buffer_result_t synthio_miditrack_get_buffer(synthio_miditrack_obj_t self->synth.span = self->track[self->next_span++]; } - synthio_synth_synthesize(&self->synth, buffer, buffer_length); + synthio_synth_synthesize(&self->synth, buffer, buffer_length, single_channel_output ? 0 : channel); return (self->synth.span.dur == 0 && self->next_span >= self->total_spans) ? GET_BUFFER_DONE : GET_BUFFER_MORE_DATA; diff --git a/shared-module/synthio/Synthesizer.c b/shared-module/synthio/Synthesizer.c index 8cfc73a9804d..25c91ecbe078 100644 --- a/shared-module/synthio/Synthesizer.c +++ b/shared-module/synthio/Synthesizer.c @@ -58,12 +58,13 @@ uint8_t common_hal_synthio_synthesizer_get_channel_count(synthio_synthesizer_obj void synthio_synthesizer_reset_buffer(synthio_synthesizer_obj_t *self, bool single_channel_output, uint8_t channel) { + synthio_synth_reset_buffer(&self->synth, single_channel_output, channel); } 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) { self->synth.span.dur = SYNTHIO_MAX_DUR; - synthio_synth_synthesize(&self->synth, buffer, buffer_length); + synthio_synth_synthesize(&self->synth, buffer, buffer_length, single_channel_output ? channel : 0); return GET_BUFFER_MORE_DATA; } diff --git a/shared-module/synthio/__init__.c b/shared-module/synthio/__init__.c index 0dee75deb7b2..7ac8e477460f 100644 --- a/shared-module/synthio/__init__.c +++ b/shared-module/synthio/__init__.c @@ -44,16 +44,27 @@ int synthio_span_count_active_channels(synthio_midi_span_t *span) { } -void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t *buffer_length) { +void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t *buffer_length, uint8_t channel) { + + if (channel == synth->other_channel) { + *buffer_length = synth->last_buffer_length; + *bufptr = (uint8_t *)(synth->buffers[synth->other_buffer_index] + channel); + return; + } + + synth->buffer_index = !synth->buffer_index; + synth->other_channel = 1 - channel; + 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); synth->span.dur -= dur; - memset(synth->buffer, 0, synth->buffer_length); + memset(out_buffer, 0, synth->buffer_length); int32_t sample_rate = synth->sample_rate; int active_channels = synthio_span_count_active_channels(&synth->span); const int16_t *waveform = synth->waveform; uint32_t waveform_length = synth->waveform_length; - int16_t *out_buffer = synth->buffer; if (active_channels) { int16_t loudness = 0x3fff / (1 + active_channels); for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) { @@ -84,27 +95,38 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t } } - *buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE; - *buffer = (uint8_t *)synth->buffer; + *buffer_length = synth->last_buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE; + *bufptr = (uint8_t *)out_buffer; +} + +void synthio_synth_reset_buffer(synthio_synth_t *synth, bool single_channel_output, uint8_t channel) { + if (single_channel_output && channel == 1) { + return; + } + synth->other_channel = -1; } bool synthio_synth_deinited(synthio_synth_t *synth) { - return synth->buffer == NULL; + return synth->buffers[0] == NULL; } void synthio_synth_deinit(synthio_synth_t *synth) { - m_del(uint8_t, synth->buffer, synth->buffer_length); - synth->buffer = NULL; + m_del(uint8_t, synth->buffers[0], synth->buffer_length); + m_del(uint8_t, synth->buffers[1], synth->buffer_length); + synth->buffers[0] = NULL; + synth->buffers[1] = NULL; } void synthio_synth_init(synthio_synth_t *synth, uint16_t max_dur) { synth->buffer_length = MIN(SYNTHIO_MAX_DUR, max_dur) * SYNTHIO_BYTES_PER_SAMPLE; - synth->buffer = 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->other_channel = -1; } 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) { - *single_buffer = true; + *single_buffer = false; *samples_signed = true; *max_buffer_length = synth->buffer_length; *spacing = 1; diff --git a/shared-module/synthio/__init__.h b/shared-module/synthio/__init__.h index 9397495f982d..e14e5ead103e 100644 --- a/shared-module/synthio/__init__.h +++ b/shared-module/synthio/__init__.h @@ -28,7 +28,7 @@ #define SYNTHIO_BITS_PER_SAMPLE (16) #define SYNTHIO_BYTES_PER_SAMPLE (SYNTHIO_BITS_PER_SAMPLE / 8) -#define SYNTHIO_MAX_DUR (512) +#define SYNTHIO_MAX_DUR (256) #define SYNTHIO_SILENCE (0x80) #include "shared-module/audiocore/__init__.h" @@ -40,21 +40,25 @@ typedef struct { typedef struct { uint32_t sample_rate; - int16_t *buffer; + int16_t *buffers[2]; const int16_t *waveform; uint16_t buffer_length; + uint16_t last_buffer_length; + uint8_t other_channel, buffer_index, other_buffer_index; uint16_t waveform_length; synthio_midi_span_t span; uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS]; } synthio_synth_t; void synthio_span_init(synthio_midi_span_t *span); -void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t *buffer_length); +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, uint16_t max_dur); 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); void synthio_synth_parse_waveform(mp_buffer_info_t *bufinfo_waveform, mp_obj_t waveform_obj); + bool synthio_span_change_note(synthio_midi_span_t *span, uint8_t old_note, uint8_t new_note); int synthio_span_count_active_channels(synthio_midi_span_t *span); From 14637cd75ad2d5d7e61fa6272dafc0ce0671c648 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 5 Apr 2023 12:00:05 -0500 Subject: [PATCH 21/22] update tests with new expected results --- tests/circuitpython/miditrack.py.exp | 8 ++++---- tests/circuitpython/synthesizer.py.exp | 4 ++-- tests/unix/extra_coverage.py.exp | 18 +++++++++--------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/circuitpython/miditrack.py.exp b/tests/circuitpython/miditrack.py.exp index c5dc447e74d5..be5eef1d0b34 100644 --- a/tests/circuitpython/miditrack.py.exp +++ b/tests/circuitpython/miditrack.py.exp @@ -1,4 +1,4 @@ -(1, 1, 800, 1) -(1, b'\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0') -(1, 1, 800, 1) -(1, b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f') +(0, 1, 512, 1) +(1, b'\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\xff\x0f\xff\x0f') +(0, 1, 512, 1) +(1, b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\xff\x0f\x00\x00\x00\x00') diff --git a/tests/circuitpython/synthesizer.py.exp b/tests/circuitpython/synthesizer.py.exp index aa2567641cbc..4296662d60d3 100644 --- a/tests/circuitpython/synthesizer.py.exp +++ b/tests/circuitpython/synthesizer.py.exp @@ -3,6 +3,6 @@ (80,) (-4095, -4095, -4095, -4095, 4095, 4095, 4095, 4095, 4095, -4095, -4095, -4095) (80, 91) -(-5460, -5460, 0, 5460, 5460, 0, 0, 5460, 0, 0, -5460, -5460) +(0, 0, 5460, 5460, 0, -5460, -5460, 0, 5460, 5460, 0, 0) (91,) -(4095, 4095, 4095, -4095, -4095, 4095, 4095, 4095, -4095, -4095, 4095, 4095) +(-4095, 4095, 4095, 4095, -4095, -4095, 4095, 4095, 4095, -4095, -4095, 4095) diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index 0baf8e1f4458..c750ca0e95af 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -31,15 +31,15 @@ mport builtins micropython _asyncio _thread _uasyncio aesio array audiocore -binascii bitmaptools btree cexample -cmath collections cppexample displayio -errno ffi framebuf gc -hashlib json math qrio -rainbowio re struct synthio -sys termios traceback ubinascii -uctypes uerrno uheapq uio -ujson ulab ulab.numpy ulab.numpy.fft -ulab.numpy.linalg ulab.scipy +audiomixer binascii bitmaptools btree +cexample cmath collections cppexample +displayio errno ffi framebuf +gc hashlib json math +qrio rainbowio re struct +synthio sys termios traceback +ubinascii uctypes uerrno uheapq +uio ujson ulab ulab.numpy +ulab.numpy.fft ulab.numpy.linalg ulab.scipy ulab.scipy.linalg ulab.scipy.optimize ulab.scipy.signal ulab.scipy.special ulab.utils uos urandom ure From f2e26e8bde8790d98e1b9a42c2ac9444237f74bc Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 5 Apr 2023 13:12:13 -0500 Subject: [PATCH 22/22] get the cmsis macros the right way --- shared-module/audiomixer/Mixer.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/shared-module/audiomixer/Mixer.c b/shared-module/audiomixer/Mixer.c index ce39f8891905..397a83713318 100644 --- a/shared-module/audiomixer/Mixer.c +++ b/shared-module/audiomixer/Mixer.c @@ -33,10 +33,9 @@ #include "py/runtime.h" #include "shared-module/audiocore/__init__.h" -#include "shared-module/audiocore/RawSample.h" -#if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) -#include "periph.h" +#if defined(__arm__) && __arm__ +#include "cmsis_compiler.h" #endif void common_hal_audiomixer_mixer_construct(audiomixer_mixer_obj_t *self,