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.
This commit is contained in:
parent
13e17e6dcd
commit
e9e4ce9546
|
@ -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 ""
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue