Skip to content

Commit 8056af8

Browse files
committed
[synthio] add a simple MidiTrack implementation
1 parent 155b61f commit 8056af8

File tree

18 files changed

+733
-24
lines changed

18 files changed

+733
-24
lines changed

locale/circuitpython.pot

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,11 @@ msgstr ""
874874
msgid "EXTINT channel already in use"
875875
msgstr ""
876876

877+
#: shared-module/synthio/MidiTrack.c
878+
#, c-format
879+
msgid "Error in MIDI stream at position %d"
880+
msgstr ""
881+
877882
#: extmod/modure.c
878883
msgid "Error in regex"
879884
msgstr ""
@@ -1197,6 +1202,10 @@ msgstr ""
11971202
msgid "Invalid DAC pin supplied"
11981203
msgstr ""
11991204

1205+
#: shared-bindings/synthio/__init__.c
1206+
msgid "Invalid MIDI file"
1207+
msgstr ""
1208+
12001209
#: ports/atmel-samd/common-hal/pwmio/PWMOut.c
12011210
#: ports/cxd56/common-hal/pwmio/PWMOut.c ports/nrf/common-hal/pwmio/PWMOut.c
12021211
#: ports/raspberrypi/common-hal/pwmio/PWMOut.c shared-bindings/pwmio/PWMOut.c
@@ -1877,7 +1886,7 @@ msgstr ""
18771886
msgid "Read-only filesystem"
18781887
msgstr ""
18791888

1880-
#: shared-module/bitmaptools/__init__.c shared-module/displayio/Bitmap.c
1889+
#: shared-module/displayio/Bitmap.c
18811890
msgid "Read-only object"
18821891
msgstr ""
18831892

@@ -2488,10 +2497,6 @@ msgstr ""
24882497
msgid "buffer is smaller than requested size"
24892498
msgstr ""
24902499

2491-
#: shared-bindings/audiocore/RawSample.c
2492-
msgid "buffer must be a bytes-like object"
2493-
msgstr ""
2494-
24952500
#: extmod/ulab/code/ulab_create.c
24962501
msgid "buffer size must be a multiple of element size"
24972502
msgstr ""
@@ -2933,7 +2938,7 @@ msgid "f-string: single '}' is not allowed"
29332938
msgstr ""
29342939

29352940
#: shared-bindings/audiocore/WaveFile.c shared-bindings/audiomp3/MP3Decoder.c
2936-
#: shared-bindings/displayio/OnDiskBitmap.c
2941+
#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/synthio/__init__.c
29372942
msgid "file must be a file opened in byte mode"
29382943
msgstr ""
29392944

ports/atmel-samd/mpconfigport.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ CIRCUITPY_BUILTINS_POW3 ?= 0
3939
CIRCUITPY_COMPUTED_GOTO_SAVE_SPACE ?= 1
4040
CIRCUITPY_FREQUENCYIO ?= 0
4141
CIRCUITPY_JSON ?= 0
42+
CIRCUITPY_SYNTHIO ?= 0
4243
CIRCUITPY_TOUCHIO_USE_NATIVE ?= 1
4344

4445
# No room for HCI _bleio on SAMD21.

py/circuitpy_defns.mk

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ endif
280280
ifeq ($(CIRCUITPY_SUPERVISOR),1)
281281
SRC_PATTERNS += supervisor/%
282282
endif
283+
ifeq ($(CIRCUITPY_SYNTHIO),1)
284+
SRC_PATTERNS += synthio/%
285+
endif
283286
ifeq ($(CIRCUITPY_TERMINALIO),1)
284287
SRC_PATTERNS += terminalio/% fontio/%
285288
endif
@@ -523,6 +526,8 @@ SRC_SHARED_MODULE_ALL = \
523526
socket/__init__.c \
524527
storage/__init__.c \
525528
struct/__init__.c \
529+
synthio/MidiTrack.c \
530+
synthio/__init__.c \
526531
terminalio/Terminal.c \
527532
terminalio/__init__.c \
528533
time/__init__.c \

py/circuitpy_mpconfig.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,13 @@ extern const struct _mp_obj_module_t supervisor_module;
728728
#define SUPERVISOR_MODULE
729729
#endif
730730

731+
#if CIRCUITPY_SYNTHIO
732+
#define SYNTHIO_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_synthio), (mp_obj_t)&synthio_module },
733+
extern const struct _mp_obj_module_t synthio_module;
734+
#else
735+
#define SYNTHIO_MODULE
736+
#endif
737+
731738
#if CIRCUITPY_TIME
732739
extern const struct _mp_obj_module_t time_module;
733740
#define TIME_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_time), (mp_obj_t)&time_module },
@@ -897,6 +904,7 @@ extern const struct _mp_obj_module_t msgpack_module;
897904
STORAGE_MODULE \
898905
STRUCT_MODULE \
899906
SUPERVISOR_MODULE \
907+
SYNTHIO_MODULE \
900908
TOUCHIO_MODULE \
901909
UHEAP_MODULE \
902910
USB_CDC_MODULE \

py/circuitpy_mpconfig.mk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,9 @@ CFLAGS += -DCIRCUITPY_STRUCT=$(CIRCUITPY_STRUCT)
305305
CIRCUITPY_SUPERVISOR ?= 1
306306
CFLAGS += -DCIRCUITPY_SUPERVISOR=$(CIRCUITPY_SUPERVISOR)
307307

308+
CIRCUITPY_SYNTHIO ?= $(CIRCUITPY_AUDIOCORE)
309+
CFLAGS += -DCIRCUITPY_SYNTHIO=$(CIRCUITPY_SYNTHIO)
310+
308311
CIRCUITPY_TERMINALIO ?= $(CIRCUITPY_DISPLAYIO)
309312
CFLAGS += -DCIRCUITPY_TERMINALIO=$(CIRCUITPY_TERMINALIO)
310313

shared-bindings/_typing/__init__.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@ WriteableBuffer = Union[
3838
"""
3939

4040
AudioSample = Union[
41-
audiocore.WaveFile, audiocore.RawSample, audiomixer.Mixer, audiomp3.MP3Decoder
41+
audiocore.WaveFile, audiocore.RawSample, audiomixer.Mixer, audiomp3.MP3Decoder, synthio.MidiTrack
4242
]
4343
"""Classes that implement the audiosample protocol
4444
4545
- `audiocore.WaveFile`
4646
- `audiocore.RawSample`
4747
- `audiomixer.Mixer`
4848
- `audiomp3.MP3Decoder`
49+
- `synthio.MidiTrack`
4950
5051
You can play these back with `audioio.AudioOut`, `audiobusio.I2SOut` or `audiopwmio.PWMAudioOut`.
5152
"""

shared-bindings/audiocore/RawSample.c

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
#include "py/binary.h"
3131
#include "py/objproperty.h"
3232
#include "py/runtime.h"
33-
#include "shared-bindings/microcontroller/Pin.h"
3433
#include "shared-bindings/util.h"
3534
#include "shared-bindings/audiocore/RawSample.h"
3635
#include "supervisor/shared/translate.h"
@@ -83,26 +82,23 @@ STATIC mp_obj_t audioio_rawsample_make_new(const mp_obj_type_t *type, size_t n_a
8382
audioio_rawsample_obj_t *self = m_new_obj(audioio_rawsample_obj_t);
8483
self->base.type = &audioio_rawsample_type;
8584
mp_buffer_info_t bufinfo;
86-
if (mp_get_buffer(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ)) {
87-
uint8_t bytes_per_sample = 1;
88-
bool signed_samples = bufinfo.typecode == 'b' || bufinfo.typecode == 'h';
89-
if (bufinfo.typecode == 'h' || bufinfo.typecode == 'H') {
90-
bytes_per_sample = 2;
91-
} else if (bufinfo.typecode != 'b' && bufinfo.typecode != 'B' && bufinfo.typecode != BYTEARRAY_TYPECODE) {
92-
mp_raise_ValueError(translate("sample_source buffer must be a bytearray or array of type 'h', 'H', 'b' or 'B'"));
93-
}
94-
common_hal_audioio_rawsample_construct(self, ((uint8_t *)bufinfo.buf), bufinfo.len,
95-
bytes_per_sample, signed_samples, args[ARG_channel_count].u_int,
96-
args[ARG_sample_rate].u_int);
97-
} else {
98-
mp_raise_TypeError(translate("buffer must be a bytes-like object"));
85+
mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ);
86+
uint8_t bytes_per_sample = 1;
87+
bool signed_samples = bufinfo.typecode == 'b' || bufinfo.typecode == 'h';
88+
if (bufinfo.typecode == 'h' || bufinfo.typecode == 'H') {
89+
bytes_per_sample = 2;
90+
} else if (bufinfo.typecode != 'b' && bufinfo.typecode != 'B' && bufinfo.typecode != BYTEARRAY_TYPECODE) {
91+
mp_raise_ValueError(translate("sample_source buffer must be a bytearray or array of type 'h', 'H', 'b' or 'B'"));
9992
}
93+
common_hal_audioio_rawsample_construct(self, ((uint8_t *)bufinfo.buf), bufinfo.len,
94+
bytes_per_sample, signed_samples, args[ARG_channel_count].u_int,
95+
args[ARG_sample_rate].u_int);
10096

10197
return MP_OBJ_FROM_PTR(self);
10298
}
10399

104100
//| def deinit(self) -> None:
105-
//| """Deinitialises the AudioOut and releases any hardware resources for reuse."""
101+
//| """Deinitialises the RawSample and releases any hardware resources for reuse."""
106102
//| ...
107103
//|
108104
STATIC mp_obj_t audioio_rawsample_deinit(mp_obj_t self_in) {

shared-bindings/audiocore/RawSample.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_RAWSAMPLE_H
2828
#define MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_RAWSAMPLE_H
2929

30-
#include "common-hal/microcontroller/Pin.h"
3130
#include "shared-module/audiocore/RawSample.h"
3231

3332
extern const mp_obj_type_t audioio_rawsample_type;

shared-bindings/audiocore/WaveFile.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
//| """Load a .wav file for playback with `audioio.AudioOut` or `audiobusio.I2SOut`.
4545
//|
4646
//| :param typing.BinaryIO file: Already opened wave file
47-
//| :param ~_typing.WriteableBuffer buffer: Optional pre-allocated buffer, that will be split in half and used for double-buffering of the data. If not provided, two 512 byte buffers are allocated internally.
47+
//| :param ~_typing.WriteableBuffer buffer: Optional pre-allocated buffer, that will be split in half and used for double-buffering of the data. If not provided, two 256 byte buffers are allocated internally.
4848
//|
4949
//|
5050
//| Playing a wave file from flash::

shared-bindings/synthio/MidiTrack.c

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
* This file is part of the Micro Python project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2021 Artyom Skrobov
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#include <stdint.h>
28+
29+
#include "lib/utils/context_manager_helpers.h"
30+
#include "py/binary.h"
31+
#include "py/objproperty.h"
32+
#include "py/runtime.h"
33+
#include "shared-bindings/util.h"
34+
#include "shared-bindings/synthio/MidiTrack.h"
35+
#include "supervisor/shared/translate.h"
36+
37+
//| class MidiTrack:
38+
//| """Simple square-wave MIDI synth"""
39+
//|
40+
//| def __init__(self, buffer: ReadableBuffer, tempo: int, *, sample_rate: int = 11025) -> None:
41+
//| """Create a MidiTrack from the given stream of MIDI events. Only "Note On" and "Note Off" events
42+
//| are supported; channel numbers and key velocities are ignored. Up to two notes may be on at the
43+
//| same time.
44+
//|
45+
//| :param ~_typing.ReadableBuffer buffer: Stream of MIDI events, as stored in a MIDI file track chunk
46+
//| :param int tempo: Tempo of the streamed events, in MIDI ticks per second
47+
//| :param int sample_rate: The desired playback sample rate; higher sample rate requires more memory
48+
//|
49+
//| Simple melody::
50+
//|
51+
//| import audioio
52+
//| import board
53+
//| import synthio
54+
//|
55+
//| dac = audioio.AudioOut(board.SPEAKER)
56+
//| melody = synthio.MidiTrack(b"\\0\\x90H\\0*\\x80H\\0\\6\\x90J\\0*\\x80J\\0\\6\\x90L\\0*\\x80L\\0\\6\\x90J\\0" +
57+
//| b"*\\x80J\\0\\6\\x90H\\0*\\x80H\\0\\6\\x90J\\0*\\x80J\\0\\6\\x90L\\0T\\x80L\\0" +
58+
//| b"\\x0c\\x90H\\0T\\x80H\\0\\x0c\\x90H\\0T\\x80H\\0", tempo=640)
59+
//| dac.play(melody)
60+
//| print("playing")
61+
//| while dac.playing:
62+
//| pass
63+
//| print("stopped")"""
64+
//| ...
65+
//|
66+
STATIC mp_obj_t synthio_miditrack_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
67+
enum { ARG_buffer, ARG_tempo, ARG_sample_rate };
68+
static const mp_arg_t allowed_args[] = {
69+
{ MP_QSTR_buffer, MP_ARG_OBJ | MP_ARG_REQUIRED },
70+
{ MP_QSTR_tempo, MP_ARG_INT | MP_ARG_REQUIRED },
71+
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 11025} },
72+
};
73+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
74+
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
75+
76+
mp_buffer_info_t bufinfo;
77+
mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ);
78+
79+
synthio_miditrack_obj_t *self = m_new_obj(synthio_miditrack_obj_t);
80+
self->base.type = &synthio_miditrack_type;
81+
82+
common_hal_synthio_miditrack_construct(self,
83+
(uint8_t *)bufinfo.buf, bufinfo.len,
84+
args[ARG_tempo].u_int,
85+
args[ARG_sample_rate].u_int);
86+
87+
return MP_OBJ_FROM_PTR(self);
88+
}
89+
90+
//| def deinit(self) -> None:
91+
//| """Deinitialises the MidiTrack and releases any hardware resources for reuse."""
92+
//| ...
93+
//|
94+
STATIC mp_obj_t synthio_miditrack_deinit(mp_obj_t self_in) {
95+
synthio_miditrack_obj_t *self = MP_OBJ_TO_PTR(self_in);
96+
common_hal_synthio_miditrack_deinit(self);
97+
return mp_const_none;
98+
}
99+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(synthio_miditrack_deinit_obj, synthio_miditrack_deinit);
100+
101+
STATIC void check_for_deinit(synthio_miditrack_obj_t *self) {
102+
if (common_hal_synthio_miditrack_deinited(self)) {
103+
raise_deinited_error();
104+
}
105+
}
106+
107+
//| def __enter__(self) -> MidiTrack:
108+
//| """No-op used by Context Managers."""
109+
//| ...
110+
//|
111+
// Provided by context manager helper.
112+
113+
//| def __exit__(self) -> None:
114+
//| """Automatically deinitializes the hardware when exiting a context. See
115+
//| :ref:`lifetime-and-contextmanagers` for more info."""
116+
//| ...
117+
//|
118+
STATIC mp_obj_t synthio_miditrack_obj___exit__(size_t n_args, const mp_obj_t *args) {
119+
(void)n_args;
120+
common_hal_synthio_miditrack_deinit(args[0]);
121+
return mp_const_none;
122+
}
123+
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(synthio_miditrack___exit___obj, 4, 4, synthio_miditrack_obj___exit__);
124+
125+
//| sample_rate: Optional[int]
126+
//| """32 bit value that tells how quickly samples are played in Hertz (cycles per second)."""
127+
//|
128+
STATIC mp_obj_t synthio_miditrack_obj_get_sample_rate(mp_obj_t self_in) {
129+
synthio_miditrack_obj_t *self = MP_OBJ_TO_PTR(self_in);
130+
check_for_deinit(self);
131+
return MP_OBJ_NEW_SMALL_INT(common_hal_synthio_miditrack_get_sample_rate(self));
132+
}
133+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_miditrack_get_sample_rate_obj, synthio_miditrack_obj_get_sample_rate);
134+
135+
const mp_obj_property_t synthio_miditrack_sample_rate_obj = {
136+
.base.type = &mp_type_property,
137+
.proxy = {(mp_obj_t)&synthio_miditrack_get_sample_rate_obj,
138+
(mp_obj_t)&mp_const_none_obj,
139+
(mp_obj_t)&mp_const_none_obj},
140+
};
141+
142+
STATIC const mp_rom_map_elem_t synthio_miditrack_locals_dict_table[] = {
143+
// Methods
144+
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&synthio_miditrack_deinit_obj) },
145+
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
146+
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&synthio_miditrack___exit___obj) },
147+
148+
// Properties
149+
{ MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&synthio_miditrack_sample_rate_obj) },
150+
};
151+
STATIC MP_DEFINE_CONST_DICT(synthio_miditrack_locals_dict, synthio_miditrack_locals_dict_table);
152+
153+
STATIC const audiosample_p_t synthio_miditrack_proto = {
154+
MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample)
155+
.sample_rate = (audiosample_sample_rate_fun)common_hal_synthio_miditrack_get_sample_rate,
156+
.bits_per_sample = (audiosample_bits_per_sample_fun)common_hal_synthio_miditrack_get_bits_per_sample,
157+
.channel_count = (audiosample_channel_count_fun)common_hal_synthio_miditrack_get_channel_count,
158+
.reset_buffer = (audiosample_reset_buffer_fun)synthio_miditrack_reset_buffer,
159+
.get_buffer = (audiosample_get_buffer_fun)synthio_miditrack_get_buffer,
160+
.get_buffer_structure = (audiosample_get_buffer_structure_fun)synthio_miditrack_get_buffer_structure,
161+
};
162+
163+
const mp_obj_type_t synthio_miditrack_type = {
164+
{ &mp_type_type },
165+
.name = MP_QSTR_MidiTrack,
166+
.make_new = synthio_miditrack_make_new,
167+
.locals_dict = (mp_obj_dict_t *)&synthio_miditrack_locals_dict,
168+
.protocol = &synthio_miditrack_proto,
169+
};

0 commit comments

Comments
 (0)