Skip to content

Commit 612bf20

Browse files
authored
Merge pull request #9421 from timchinowsky/rawsample-doublebuffer
Add double buffering to RawSample
2 parents f3b9b35 + ce67e2a commit 612bf20

File tree

6 files changed

+146
-20
lines changed

6 files changed

+146
-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: 44 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,58 @@
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
36+
//| what happens if the sample buffer is changed while the sample is playing.
37+
//| In single buffered transfers, a change in buffer contents will not affect active playback.
38+
//| In double buffered transfers, changed buffer contents will
39+
//| be played back when the transfer reaches the next half-buffer point.
3040
//|
31-
//| Simple 8ksps 440 Hz sin wave::
41+
//| Playing 8ksps 440 Hz and 880 Hz sine waves::
3242
//|
43+
//| import analogbufio
44+
//| import array
3345
//| import audiocore
34-
//| import audioio
46+
//| import audiopwmio
3547
//| import board
36-
//| import array
37-
//| import time
3848
//| import math
49+
//| import time
3950
//|
40-
//| # Generate one period of sine wav.
51+
//| # Generate one period of sine wave.
4152
//| length = 8000 // 440
4253
//| sine_wave = array.array("h", [0] * length)
4354
//| for i in range(length):
4455
//| sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15))
56+
//| pwm = audiopwmio.PWMAudioOut(left_channel=board.D12, right_channel=board.D13)
4557
//|
46-
//| dac = audioio.AudioOut(board.SPEAKER)
47-
//| sine_wave = audiocore.RawSample(sine_wave)
48-
//| dac.play(sine_wave, loop=True)
58+
//| # Play single-buffered
59+
//| sample = audiocore.RawSample(sine_wave)
60+
//| pwm.play(sample, loop=True)
61+
//| time.sleep(3)
62+
//| # changing the wave has no effect
63+
//| for i in range(length):
64+
//| sine_wave[i] = int(math.sin(math.pi * 4 * i / length) * (2 ** 15))
65+
//| time.sleep(3)
66+
//| pwm.stop()
4967
//| time.sleep(1)
50-
//| dac.stop()"""
68+
//|
69+
//| # Play double-buffered
70+
//| sample = audiocore.RawSample(sine_wave, single_buffer=False)
71+
//| pwm.play(sample, loop=True)
72+
//| time.sleep(3)
73+
//| # changing the wave takes effect almost immediately
74+
//| for i in range(length):
75+
//| sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15))
76+
//| time.sleep(3)
77+
//| pwm.stop()
78+
//| pwm.deinit()"""
5179
//| ...
5280
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 };
81+
enum { ARG_buffer, ARG_channel_count, ARG_sample_rate, ARG_single_buffer };
5482
static const mp_arg_t allowed_args[] = {
5583
{ MP_QSTR_buffer, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL } },
5684
{ MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1 } },
5785
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} },
86+
{ MP_QSTR_single_buffer, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} },
5887
};
5988
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
6089
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
@@ -69,9 +98,12 @@ static mp_obj_t audioio_rawsample_make_new(const mp_obj_type_t *type, size_t n_a
6998
} else if (bufinfo.typecode != 'b' && bufinfo.typecode != 'B' && bufinfo.typecode != BYTEARRAY_TYPECODE) {
7099
mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be a bytearray or array of type 'h', 'H', 'b', or 'B'"), MP_QSTR_buffer);
71100
}
101+
if (!args[ARG_single_buffer].u_bool && bufinfo.len % (bytes_per_sample * args[ARG_channel_count].u_int * 2) != 0) {
102+
mp_raise_ValueError_varg(MP_ERROR_TEXT("Length of %q must be an even multiple of channel_count * type_size"), MP_QSTR_buffer);
103+
}
72104
common_hal_audioio_rawsample_construct(self, ((uint8_t *)bufinfo.buf), bufinfo.len,
73105
bytes_per_sample, signed_samples, args[ARG_channel_count].u_int,
74-
args[ARG_sample_rate].u_int);
106+
args[ARG_sample_rate].u_int, args[ARG_single_buffer].u_bool);
75107

76108
return MP_OBJ_FROM_PTR(self);
77109
}

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: 27 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) {
@@ -56,19 +62,33 @@ audioio_get_buffer_result_t audioio_rawsample_get_buffer(audioio_rawsample_obj_t
5662
uint8_t channel,
5763
uint8_t **buffer,
5864
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);
65+
66+
if (self->single_buffer) {
67+
*buffer_length = self->len;
68+
if (single_channel_output) {
69+
*buffer = self->buffer + (channel % self->channel_count) * (self->bits_per_sample / 8);
70+
} else {
71+
*buffer = self->buffer;
72+
}
73+
return GET_BUFFER_DONE;
6274
} else {
63-
*buffer = self->buffer;
75+
*buffer_length = self->len / 2;
76+
if (single_channel_output) {
77+
*buffer = self->buffer + (channel % self->channel_count) * (self->bits_per_sample / 8) + \
78+
self->len / 2 * self->buffer_index;
79+
} else {
80+
*buffer = self->buffer + self->len / 2 * self->buffer_index;
81+
}
82+
self->buffer_index = 1 - self->buffer_index;
83+
return GET_BUFFER_DONE;
6484
}
65-
return GET_BUFFER_DONE;
6685
}
6786

6887
void audioio_rawsample_get_buffer_structure(audioio_rawsample_obj_t *self, bool single_channel_output,
6988
bool *single_buffer, bool *samples_signed,
7089
uint32_t *max_buffer_length, uint8_t *spacing) {
71-
*single_buffer = true;
90+
91+
*single_buffer = self->single_buffer;
7292
*samples_signed = self->samples_signed;
7393
*max_buffer_length = self->len;
7494
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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import audiocore
2+
import audiopwmio
3+
import audiomixer
4+
import board
5+
import array
6+
import time
7+
import math
8+
9+
CHANNELS = 2
10+
RATE = 8000
11+
SAMPLE_TYPE = "H"
12+
OFFSET = 2**15 - 1
13+
BUFFER_SIZE = 640
14+
SINGLE_BUFFER = True
15+
LOOP = True
16+
17+
# (frequency, amp_left, amp_right)
18+
VOICES = ((200, 1, 0), (400, 0, 1), (100, 1, 1))
19+
20+
21+
def play(
22+
voices=VOICES,
23+
channels=CHANNELS,
24+
rate=RATE,
25+
sample_type=SAMPLE_TYPE,
26+
offset=OFFSET,
27+
buffer_size=BUFFER_SIZE,
28+
single_buffer=SINGLE_BUFFER,
29+
loop=LOOP,
30+
):
31+
waves = []
32+
samples = []
33+
for v in voices:
34+
print(v)
35+
sample_length = int(rate // v[0])
36+
wave = array.array(sample_type, [offset] * sample_length * channels)
37+
for i in range(0, sample_length):
38+
if channels == 1:
39+
wave[i] = int(
40+
math.sin(math.pi * 2 * i / sample_length) * v[1] * (2**15 - 1) + offset
41+
)
42+
else:
43+
wave[2 * i] = int(
44+
math.sin(math.pi * 2 * i / sample_length) * v[1] * (2**15 - 1) + offset
45+
)
46+
wave[2 * i + 1] = int(
47+
math.sin(math.pi * 2 * i / sample_length) * v[2] * (2**15 - 1) + offset
48+
)
49+
waves.append(wave)
50+
samples.append(
51+
audiocore.RawSample(
52+
wave, sample_rate=rate, channel_count=channels, single_buffer=single_buffer
53+
)
54+
)
55+
mixer = audiomixer.Mixer(
56+
voice_count=len(voices),
57+
sample_rate=rate,
58+
channel_count=channels,
59+
bits_per_sample=16,
60+
samples_signed=False,
61+
buffer_size=buffer_size,
62+
)
63+
pwm = audiopwmio.PWMAudioOut(left_channel=board.D12, right_channel=board.D13)
64+
pwm.play(mixer)
65+
for i in range(len(samples)):
66+
mixer.voice[i].play(samples[i], loop=loop)
67+
mixer.voice[i].level = 0.5

0 commit comments

Comments
 (0)