Merge pull request #9421 from timchinowsky/rawsample-doublebuffer
Add double buffering to RawSample
This commit is contained in:
commit
612bf209b5
6 changed files with 147 additions and 21 deletions
|
|
@ -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 ""
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
|||
67
tests/circuitpython-manual/live_audio/mix.py
Normal file
67
tests/circuitpython-manual/live_audio/mix.py
Normal 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
|
||||
Loading…
Reference in a new issue