Skip to content

Commit b3b3577

Browse files
committed
add double buffering to RawSample
1 parent 685101f commit b3b3577

File tree

5 files changed

+82
-20
lines changed

5 files changed

+82
-20
lines changed

locale/circuitpython.pot

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,7 @@ msgid "Interrupted by output function"
11771177
msgstr ""
11781178

11791179
#: ports/espressif/common-hal/espulp/ULP.c
1180+
#: ports/espressif/common-hal/microcontroller/Processor.c
11801181
#: ports/mimxrt10xx/common-hal/audiobusio/__init__.c
11811182
#: ports/mimxrt10xx/common-hal/pwmio/PWMOut.c
11821183
#: ports/raspberrypi/bindings/picodvi/Framebuffer.c
@@ -1284,6 +1285,10 @@ msgstr ""
12841285
msgid "Layer must be a Group or TileGrid subclass"
12851286
msgstr ""
12861287

1288+
#: shared-bindings/audiocore/RawSample.c
1289+
msgid "Length of %q must be an even multiple of channel_count * type_size"
1290+
msgstr ""
1291+
12871292
#: ports/espressif/common-hal/espidf/__init__.c
12881293
msgid "MAC address was invalid"
12891294
msgstr ""

shared-bindings/audiocore/RawSample.c

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
//| """A raw audio sample buffer in memory"""
1818
//|
1919
//| def __init__(
20-
//| self, buffer: ReadableBuffer, *, channel_count: int = 1, sample_rate: int = 8000
20+
//| self,
21+
//| buffer: ReadableBuffer,
22+
//| *,
23+
//| channel_count: int = 1,
24+
//| sample_rate: int = 8000,
25+
//| single_buffer: bool = True
2126
//| ) -> None:
2227
//| """Create a RawSample based on the given buffer of values. If channel_count is more than
2328
//| 1 then each channel's samples should alternate. In other words, for a two channel buffer, the
@@ -27,34 +32,59 @@
2732
//| :param ~circuitpython_typing.ReadableBuffer buffer: A buffer with samples
2833
//| :param int channel_count: The number of channels in the buffer
2934
//| :param int sample_rate: The desired playback sample rate
35+
//| :param bool single_buffer: Selects single buffered or double buffered transfer mode. This affects sample looping
36+
//| and what happens if the sample buffer is changed while the sample is playing.
37+
//| In single buffered transfers, samples will play once unless the play method is invoked with
38+
//| loop=True. A change in buffer contents will not affect active playback.
39+
//| In double buffered transfers, samples are always looped, and changed buffer contents will
40+
//| be played back as soon as transfer reaches the next half-buffer point.
3041
//|
31-
//| Simple 8ksps 440 Hz sin wave::
42+
//| Playing 8ksps 440 Hz and 880 Hz sine waves::
3243
//|
44+
//| import analogbufio
45+
//| import array
3346
//| import audiocore
34-
//| import audioio
47+
//| import audiopwmio
3548
//| import board
36-
//| import array
37-
//| import time
3849
//| import math
50+
//| import time
3951
//|
40-
//| # Generate one period of sine wav.
52+
//| # Generate one period of sine wave.
4153
//| length = 8000 // 440
4254
//| sine_wave = array.array("h", [0] * length)
4355
//| for i in range(length):
4456
//| sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15))
57+
//| pwm = audiopwmio.PWMAudioOut(left_channel=board.D12, right_channel=board.D13)
4558
//|
46-
//| dac = audioio.AudioOut(board.SPEAKER)
47-
//| sine_wave = audiocore.RawSample(sine_wave)
48-
//| dac.play(sine_wave, loop=True)
59+
//| # Play single-buffered
60+
//| sample = audiocore.RawSample(sine_wave)
61+
//| pwm.play(sample, loop=True)
62+
//| time.sleep(3)
63+
//| # changing the wave has no effect
64+
//| for i in range(length):
65+
//| sine_wave[i] = int(math.sin(math.pi * 4 * i / length) * (2 ** 15))
66+
//| time.sleep(3)
67+
//| pwm.stop()
4968
//| time.sleep(1)
50-
//| dac.stop()"""
69+
//|
70+
//| # Play double-buffered
71+
//| sample = audiocore.RawSample(sine_wave, single_buffer=False)
72+
//| pwm.play(sample)
73+
//| time.sleep(3)
74+
//| # changing the wave takes effect almost immediately
75+
//| for i in range(length):
76+
//| sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15))
77+
//| time.sleep(3)
78+
//| pwm.stop()
79+
//| pwm.deinit()"""
5180
//| ...
5281
static mp_obj_t audioio_rawsample_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
53-
enum { ARG_buffer, ARG_channel_count, ARG_sample_rate };
82+
enum { ARG_buffer, ARG_channel_count, ARG_sample_rate, ARG_single_buffer };
5483
static const mp_arg_t allowed_args[] = {
5584
{ MP_QSTR_buffer, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL } },
5685
{ MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1 } },
5786
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} },
87+
{ MP_QSTR_single_buffer, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} },
5888
};
5989
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
6090
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
@@ -69,9 +99,12 @@ static mp_obj_t audioio_rawsample_make_new(const mp_obj_type_t *type, size_t n_a
6999
} else if (bufinfo.typecode != 'b' && bufinfo.typecode != 'B' && bufinfo.typecode != BYTEARRAY_TYPECODE) {
70100
mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be a bytearray or array of type 'h', 'H', 'b', or 'B'"), MP_QSTR_buffer);
71101
}
102+
if (!args[ARG_single_buffer].u_bool && bufinfo.len % (bytes_per_sample * args[ARG_channel_count].u_int * 2) != 0) {
103+
mp_raise_ValueError_varg(MP_ERROR_TEXT("Length of %q must be an even multiple of channel_count * type_size"), MP_QSTR_buffer);
104+
}
72105
common_hal_audioio_rawsample_construct(self, ((uint8_t *)bufinfo.buf), bufinfo.len,
73106
bytes_per_sample, signed_samples, args[ARG_channel_count].u_int,
74-
args[ARG_sample_rate].u_int);
107+
args[ARG_sample_rate].u_int, args[ARG_single_buffer].u_bool);
75108

76109
return MP_OBJ_FROM_PTR(self);
77110
}

shared-bindings/audiocore/RawSample.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ extern const mp_obj_type_t audioio_rawsample_type;
1212

1313
void common_hal_audioio_rawsample_construct(audioio_rawsample_obj_t *self,
1414
uint8_t *buffer, uint32_t len, uint8_t bytes_per_sample, bool samples_signed,
15-
uint8_t channel_count, uint32_t sample_rate);
15+
uint8_t channel_count, uint32_t sample_rate, bool single_buffer);
1616

1717
void common_hal_audioio_rawsample_deinit(audioio_rawsample_obj_t *self);
1818
bool common_hal_audioio_rawsample_deinited(audioio_rawsample_obj_t *self);

shared-module/audiocore/RawSample.c

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
//
33
// SPDX-FileCopyrightText: Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
44
//
5+
// SPDX-FileCopyrightText: Copyright (c) 2024 Tim Chinowsky
6+
//
57
// SPDX-License-Identifier: MIT
68

79
#include "shared-bindings/audiocore/RawSample.h"
@@ -16,13 +18,17 @@ void common_hal_audioio_rawsample_construct(audioio_rawsample_obj_t *self,
1618
uint8_t bytes_per_sample,
1719
bool samples_signed,
1820
uint8_t channel_count,
19-
uint32_t sample_rate) {
21+
uint32_t sample_rate,
22+
bool single_buffer) {
23+
2024
self->buffer = buffer;
2125
self->bits_per_sample = bytes_per_sample * 8;
2226
self->samples_signed = samples_signed;
2327
self->len = len;
2428
self->channel_count = channel_count;
2529
self->sample_rate = sample_rate;
30+
self->single_buffer = single_buffer;
31+
self->buffer_index = 0;
2632
}
2733

2834
void common_hal_audioio_rawsample_deinit(audioio_rawsample_obj_t *self) {
@@ -49,26 +55,42 @@ uint8_t common_hal_audioio_rawsample_get_channel_count(audioio_rawsample_obj_t *
4955
void audioio_rawsample_reset_buffer(audioio_rawsample_obj_t *self,
5056
bool single_channel_output,
5157
uint8_t channel) {
58+
59+
self->buffer_index = 0;
5260
}
5361

5462
audioio_get_buffer_result_t audioio_rawsample_get_buffer(audioio_rawsample_obj_t *self,
5563
bool single_channel_output,
5664
uint8_t channel,
5765
uint8_t **buffer,
5866
uint32_t *buffer_length) {
59-
*buffer_length = self->len;
60-
if (single_channel_output) {
61-
*buffer = self->buffer + (channel % self->channel_count) * (self->bits_per_sample / 8);
67+
68+
if (self->single_buffer) {
69+
*buffer_length = self->len;
70+
if (single_channel_output) {
71+
*buffer = self->buffer + (channel % self->channel_count) * (self->bits_per_sample / 8);
72+
} else {
73+
*buffer = self->buffer;
74+
}
75+
return GET_BUFFER_DONE;
6276
} else {
63-
*buffer = self->buffer;
77+
*buffer_length = self->len / 2;
78+
if (single_channel_output) {
79+
*buffer = self->buffer + (channel % self->channel_count) * (self->bits_per_sample / 8) + \
80+
self->len / 2 * self->buffer_index;
81+
} else {
82+
*buffer = self->buffer + self->len / 2 * self->buffer_index;
83+
}
84+
self->buffer_index = 1 - self->buffer_index;
85+
return GET_BUFFER_MORE_DATA;
6486
}
65-
return GET_BUFFER_DONE;
6687
}
6788

6889
void audioio_rawsample_get_buffer_structure(audioio_rawsample_obj_t *self, bool single_channel_output,
6990
bool *single_buffer, bool *samples_signed,
7091
uint32_t *max_buffer_length, uint8_t *spacing) {
71-
*single_buffer = true;
92+
93+
*single_buffer = self->single_buffer;
7294
*samples_signed = self->samples_signed;
7395
*max_buffer_length = self->len;
7496
if (single_channel_output) {

shared-module/audiocore/RawSample.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ typedef struct {
1818
bool samples_signed;
1919
uint8_t channel_count;
2020
uint32_t sample_rate;
21+
bool single_buffer;
22+
uint8_t buffer_index;
2123
} audioio_rawsample_obj_t;
2224

2325

0 commit comments

Comments
 (0)