Merge pull request #9421 from timchinowsky/rawsample-doublebuffer

Add double buffering to RawSample
This commit is contained in:
Scott Shawcroft 2024-07-22 13:24:30 -07:00 committed by GitHub
commit 612bf209b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 147 additions and 21 deletions

View file

@ -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
@ -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 ""

View file

@ -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
@ -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 audiocore
//| import audioio
//| import board
//| import analogbufio
//| import array
//| import time
//| import audiocore
//| import audiopwmio
//| import board
//| 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);
@ -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);
}

View file

@ -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);

View file

@ -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"
@ -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) {
@ -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) {

View file

@ -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;

View file

@ -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