Skip to content

Commit

Permalink
Merge pull request #9421 from timchinowsky/rawsample-doublebuffer
Browse files Browse the repository at this point in the history
Add double buffering to RawSample
  • Loading branch information
tannewt authored Jul 22, 2024
2 parents f3b9b35 + ce67e2a commit 612bf20
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 20 deletions.
5 changes: 5 additions & 0 deletions locale/circuitpython.pot
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,7 @@ msgid "Interrupted by output function"
msgstr ""

#: ports/espressif/common-hal/espulp/ULP.c
#: ports/espressif/common-hal/microcontroller/Processor.c
#: ports/mimxrt10xx/common-hal/audiobusio/__init__.c
#: ports/mimxrt10xx/common-hal/pwmio/PWMOut.c
#: ports/raspberrypi/bindings/picodvi/Framebuffer.c
Expand Down Expand Up @@ -1284,6 +1285,10 @@ msgstr ""
msgid "Layer must be a Group or TileGrid subclass"
msgstr ""

#: shared-bindings/audiocore/RawSample.c
msgid "Length of %q must be an even multiple of channel_count * type_size"
msgstr ""

#: ports/espressif/common-hal/espidf/__init__.c
msgid "MAC address was invalid"
msgstr ""
Expand Down
56 changes: 44 additions & 12 deletions shared-bindings/audiocore/RawSample.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
//| """A raw audio sample buffer in memory"""
//|
//| def __init__(
//| self, buffer: ReadableBuffer, *, channel_count: int = 1, sample_rate: int = 8000
//| self,
//| buffer: ReadableBuffer,
//| *,
//| channel_count: int = 1,
//| sample_rate: int = 8000,
//| single_buffer: bool = True
//| ) -> None:
//| """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
Expand All @@ -27,34 +32,58 @@
//| :param ~circuitpython_typing.ReadableBuffer buffer: A buffer with samples
//| :param int channel_count: The number of channels in the buffer
//| :param int sample_rate: The desired playback sample rate
//| :param bool single_buffer: Selects single buffered or double buffered transfer mode. This affects
//| what happens if the sample buffer is changed while the sample is playing.
//| In single buffered transfers, a change in buffer contents will not affect active playback.
//| In double buffered transfers, changed buffer contents will
//| be played back when the transfer reaches the next half-buffer point.
//|
//| Simple 8ksps 440 Hz sin wave::
//| Playing 8ksps 440 Hz and 880 Hz sine waves::
//|
//| import analogbufio
//| import array
//| import audiocore
//| import audioio
//| import audiopwmio
//| import board
//| import array
//| import time
//| import math
//| import time
//|
//| # Generate one period of sine wav.
//| # Generate one period of sine wave.
//| length = 8000 // 440
//| sine_wave = array.array("h", [0] * length)
//| for i in range(length):
//| sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15))
//| pwm = audiopwmio.PWMAudioOut(left_channel=board.D12, right_channel=board.D13)
//|
//| dac = audioio.AudioOut(board.SPEAKER)
//| sine_wave = audiocore.RawSample(sine_wave)
//| dac.play(sine_wave, loop=True)
//| # Play single-buffered
//| sample = audiocore.RawSample(sine_wave)
//| pwm.play(sample, loop=True)
//| time.sleep(3)
//| # changing the wave has no effect
//| for i in range(length):
//| sine_wave[i] = int(math.sin(math.pi * 4 * i / length) * (2 ** 15))
//| time.sleep(3)
//| pwm.stop()
//| time.sleep(1)
//| dac.stop()"""
//|
//| # Play double-buffered
//| sample = audiocore.RawSample(sine_wave, single_buffer=False)
//| pwm.play(sample, loop=True)
//| time.sleep(3)
//| # changing the wave takes effect almost immediately
//| for i in range(length):
//| sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15))
//| time.sleep(3)
//| pwm.stop()
//| pwm.deinit()"""
//| ...
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) {
enum { ARG_buffer, ARG_channel_count, ARG_sample_rate };
enum { ARG_buffer, ARG_channel_count, ARG_sample_rate, ARG_single_buffer };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_buffer, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL } },
{ MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1 } },
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} },
{ MP_QSTR_single_buffer, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} },
};
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);
Expand All @@ -69,9 +98,12 @@ static mp_obj_t audioio_rawsample_make_new(const mp_obj_type_t *type, size_t n_a
} else if (bufinfo.typecode != 'b' && bufinfo.typecode != 'B' && bufinfo.typecode != BYTEARRAY_TYPECODE) {
mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be a bytearray or array of type 'h', 'H', 'b', or 'B'"), MP_QSTR_buffer);
}
if (!args[ARG_single_buffer].u_bool && bufinfo.len % (bytes_per_sample * args[ARG_channel_count].u_int * 2) != 0) {
mp_raise_ValueError_varg(MP_ERROR_TEXT("Length of %q must be an even multiple of channel_count * type_size"), MP_QSTR_buffer);
}
common_hal_audioio_rawsample_construct(self, ((uint8_t *)bufinfo.buf), bufinfo.len,
bytes_per_sample, signed_samples, args[ARG_channel_count].u_int,
args[ARG_sample_rate].u_int);
args[ARG_sample_rate].u_int, args[ARG_single_buffer].u_bool);

return MP_OBJ_FROM_PTR(self);
}
Expand Down
2 changes: 1 addition & 1 deletion shared-bindings/audiocore/RawSample.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ extern const mp_obj_type_t audioio_rawsample_type;

void common_hal_audioio_rawsample_construct(audioio_rawsample_obj_t *self,
uint8_t *buffer, uint32_t len, uint8_t bytes_per_sample, bool samples_signed,
uint8_t channel_count, uint32_t sample_rate);
uint8_t channel_count, uint32_t sample_rate, bool single_buffer);

void common_hal_audioio_rawsample_deinit(audioio_rawsample_obj_t *self);
bool common_hal_audioio_rawsample_deinited(audioio_rawsample_obj_t *self);
Expand Down
34 changes: 27 additions & 7 deletions shared-module/audiocore/RawSample.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
//
// SPDX-FileCopyrightText: Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
//
// SPDX-FileCopyrightText: Copyright (c) 2024 Tim Chinowsky
//
// SPDX-License-Identifier: MIT

#include "shared-bindings/audiocore/RawSample.h"
Expand All @@ -16,13 +18,17 @@ void common_hal_audioio_rawsample_construct(audioio_rawsample_obj_t *self,
uint8_t bytes_per_sample,
bool samples_signed,
uint8_t channel_count,
uint32_t sample_rate) {
uint32_t sample_rate,
bool single_buffer) {

self->buffer = buffer;
self->bits_per_sample = bytes_per_sample * 8;
self->samples_signed = samples_signed;
self->len = len;
self->channel_count = channel_count;
self->sample_rate = sample_rate;
self->single_buffer = single_buffer;
self->buffer_index = 0;
}

void common_hal_audioio_rawsample_deinit(audioio_rawsample_obj_t *self) {
Expand Down Expand Up @@ -56,19 +62,33 @@ audioio_get_buffer_result_t audioio_rawsample_get_buffer(audioio_rawsample_obj_t
uint8_t channel,
uint8_t **buffer,
uint32_t *buffer_length) {
*buffer_length = self->len;
if (single_channel_output) {
*buffer = self->buffer + (channel % self->channel_count) * (self->bits_per_sample / 8);

if (self->single_buffer) {
*buffer_length = self->len;
if (single_channel_output) {
*buffer = self->buffer + (channel % self->channel_count) * (self->bits_per_sample / 8);
} else {
*buffer = self->buffer;
}
return GET_BUFFER_DONE;
} else {
*buffer = self->buffer;
*buffer_length = self->len / 2;
if (single_channel_output) {
*buffer = self->buffer + (channel % self->channel_count) * (self->bits_per_sample / 8) + \
self->len / 2 * self->buffer_index;
} else {
*buffer = self->buffer + self->len / 2 * self->buffer_index;
}
self->buffer_index = 1 - self->buffer_index;
return GET_BUFFER_DONE;
}
return GET_BUFFER_DONE;
}

void audioio_rawsample_get_buffer_structure(audioio_rawsample_obj_t *self, bool single_channel_output,
bool *single_buffer, bool *samples_signed,
uint32_t *max_buffer_length, uint8_t *spacing) {
*single_buffer = true;

*single_buffer = self->single_buffer;
*samples_signed = self->samples_signed;
*max_buffer_length = self->len;
if (single_channel_output) {
Expand Down
2 changes: 2 additions & 0 deletions shared-module/audiocore/RawSample.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ typedef struct {
bool samples_signed;
uint8_t channel_count;
uint32_t sample_rate;
bool single_buffer;
uint8_t buffer_index;
} audioio_rawsample_obj_t;


Expand Down
67 changes: 67 additions & 0 deletions tests/circuitpython-manual/live_audio/mix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import audiocore
import audiopwmio
import audiomixer
import board
import array
import time
import math

CHANNELS = 2
RATE = 8000
SAMPLE_TYPE = "H"
OFFSET = 2**15 - 1
BUFFER_SIZE = 640
SINGLE_BUFFER = True
LOOP = True

# (frequency, amp_left, amp_right)
VOICES = ((200, 1, 0), (400, 0, 1), (100, 1, 1))


def play(
voices=VOICES,
channels=CHANNELS,
rate=RATE,
sample_type=SAMPLE_TYPE,
offset=OFFSET,
buffer_size=BUFFER_SIZE,
single_buffer=SINGLE_BUFFER,
loop=LOOP,
):
waves = []
samples = []
for v in voices:
print(v)
sample_length = int(rate // v[0])
wave = array.array(sample_type, [offset] * sample_length * channels)
for i in range(0, sample_length):
if channels == 1:
wave[i] = int(
math.sin(math.pi * 2 * i / sample_length) * v[1] * (2**15 - 1) + offset
)
else:
wave[2 * i] = int(
math.sin(math.pi * 2 * i / sample_length) * v[1] * (2**15 - 1) + offset
)
wave[2 * i + 1] = int(
math.sin(math.pi * 2 * i / sample_length) * v[2] * (2**15 - 1) + offset
)
waves.append(wave)
samples.append(
audiocore.RawSample(
wave, sample_rate=rate, channel_count=channels, single_buffer=single_buffer
)
)
mixer = audiomixer.Mixer(
voice_count=len(voices),
sample_rate=rate,
channel_count=channels,
bits_per_sample=16,
samples_signed=False,
buffer_size=buffer_size,
)
pwm = audiopwmio.PWMAudioOut(left_channel=board.D12, right_channel=board.D13)
pwm.play(mixer)
for i in range(len(samples)):
mixer.voice[i].play(samples[i], loop=loop)
mixer.voice[i].level = 0.5

0 comments on commit 612bf20

Please sign in to comment.