audiocore: Integrate mp3 decoder

This commit is contained in:
2025-03-10 21:06:02 +01:00
parent ff1ddfb639
commit cc2bf8a84b
13 changed files with 623 additions and 234 deletions

2
.gitignore vendored
View File

@@ -3,3 +3,5 @@ hardware/tonberry-pico/tonberry-pico-backups/
*.kicad_sch-bak
*.kicad_sch.lck
software/build
compile_commands.json
.dir-locals.el

View File

@@ -1,27 +1,88 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
// Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
#include "audiocore.h"
#include "i2s.h"
#include "mp3.h"
#include "py/mperrno.h"
void core1_main(void)
void __time_critical_func(volume_adjust)(int16_t *buf, size_t samples, uint16_t scalef)
{
if (!i2s_init(shared_context.out_pin, shared_context.sideset_base, shared_context.samplerate)) {
multicore_fifo_push_blocking(MP_EIO);
return;
for (size_t pos = 0; pos < samples; ++pos) {
buf[pos] = ((int32_t)buf[pos] * scalef) >> 15;
}
multicore_fifo_push_blocking(0);
uint32_t cmd;
while ((cmd = multicore_fifo_pop_blocking()) != AUDIOCORE_CMD_SHUTDOWN) {
switch (cmd) {
default:
break;
}
}
i2s_deinit();
multicore_fifo_push_blocking(0);
}
void __time_critical_func(core1_main)(void)
{
uint32_t ret = 0;
bool running = true, playing = false;
if (!i2s_init(shared_context.out_pin, shared_context.sideset_base)) {
ret = MP_EIO;
goto out;
}
if (!mp3_init()) {
ret = MP_ENOMEM;
goto out_i2s;
}
multicore_fifo_push_blocking(0);
uint32_t current_volume = AUDIOCORE_MAX_VOLUME >> 4;
bool flushing = false;
while (running) {
uint32_t cmd;
uint32_t *buf;
if (multicore_fifo_rvalid()) {
cmd = multicore_fifo_pop_blocking();
switch (cmd) {
case AUDIOCORE_CMD_SHUTDOWN:
running = false;
break;
case AUDIOCORE_CMD_SET_VOLUME: {
const uint32_t new_volume = multicore_fifo_pop_blocking();
if (new_volume > AUDIOCORE_MAX_VOLUME) {
multicore_fifo_push_blocking(1);
} else {
current_volume = new_volume;
multicore_fifo_push_blocking(0);
}
} break;
case AUDIOCORE_CMD_FLUSH:
flushing = true;
break;
default:
break;
}
}
if ((buf = i2s_next_buf()) != NULL) {
unsigned samplerate;
// decode one frame
if (mp3_decode(buf, &samplerate)) {
if (!playing) {
i2s_play(samplerate);
playing = true;
}
volume_adjust((int16_t *)buf, 2304, current_volume);
i2s_commit_buf(buf);
continue;
}
/* mp3_decode returned false: not enough data in buffer */
if (flushing) {
mp3_reset();
i2s_stop();
playing = false;
flushing = false;
multicore_fifo_push_blocking(0);
}
}
__wfe();
}
mp3_deinit();
out_i2s:
i2s_deinit();
out:
multicore_fifo_push_blocking(ret);
}

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
// Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
#pragma once
@@ -17,7 +17,12 @@
* communication between the cores.
*/
#define AUDIO_BUFFER_SIZE 2048
#define MP3_BUFFER_ALIGN (sizeof(uintptr_t))
#define MP3_BUFFER_SIZE 4096
#define MP3_BUFFER_PREAREA 1048
_Static_assert(MP3_BUFFER_PREAREA % MP3_BUFFER_ALIGN == 0,
"Prearea must be a multiple of machine word size for alignment");
// Context shared between the micropython runtime on core0 and the audio task on
// core1 All access must hold "lock" unless otherwise noted
@@ -27,59 +32,59 @@ struct audiocore_shared_context {
// Set by module.c before core1 is launched and then never changed, can be read without lock
int out_pin, sideset_base, samplerate;
// Must hold lock
uint32_t audio_buffer[AUDIO_BUFFER_SIZE];
int audio_buffer_write, audio_buffer_read;
// Must hold lock. The indices 0..MP3_BUFFER_PREAREA-1 may only be read and written on core1 (no
// lock needed) The buffer is aligned to, and MP3_BUFFER_PREAREA is a multiple of, the machine
// word size to prevent these accesses from crossing word boundaries.
char mp3_buffer[MP3_BUFFER_PREAREA + MP3_BUFFER_SIZE] __attribute__((aligned(MP3_BUFFER_ALIGN)));
int mp3_buffer_write, mp3_buffer_read;
int underruns;
};
extern struct audiocore_shared_context shared_context;
static inline unsigned audiocore_get_audio_buffer_space(void)
/* Must hold audiocore_shared_context.lock */
static inline unsigned audiocore_get_buffer_space(void)
{
if (shared_context.audio_buffer_write >= shared_context.audio_buffer_read)
return AUDIO_BUFFER_SIZE - 1 - (shared_context.audio_buffer_write - shared_context.audio_buffer_read);
if (shared_context.mp3_buffer_write >= shared_context.mp3_buffer_read)
return MP3_BUFFER_SIZE - 1 - (shared_context.mp3_buffer_write - shared_context.mp3_buffer_read);
else
return shared_context.audio_buffer_read - shared_context.audio_buffer_write - 1;
return shared_context.mp3_buffer_read - shared_context.mp3_buffer_write - 1;
}
static inline unsigned audiocore_get_audio_buffer_avail(void)
/* Must hold audiocore_shared_context.lock */
static inline unsigned audiocore_get_buffer_avail(void)
{
if (shared_context.audio_buffer_write >= shared_context.audio_buffer_read)
return shared_context.audio_buffer_write - shared_context.audio_buffer_read;
if (shared_context.mp3_buffer_write >= shared_context.mp3_buffer_read)
return shared_context.mp3_buffer_write - shared_context.mp3_buffer_read;
else
return AUDIO_BUFFER_SIZE - (shared_context.audio_buffer_read - shared_context.audio_buffer_write);
return MP3_BUFFER_SIZE - (shared_context.mp3_buffer_read - shared_context.mp3_buffer_write);
}
static inline void audiocore_audio_buffer_put(const uint32_t *restrict src, const size_t len)
/* Must hold audiocore_shared_context.lock */
static inline void audiocore_buffer_put(const char *restrict src, const size_t len)
{
const unsigned end_samples = AUDIO_BUFFER_SIZE - shared_context.audio_buffer_write;
memcpy(shared_context.audio_buffer + shared_context.audio_buffer_write, src,
4 * ((end_samples >= len) ? len : end_samples));
if (end_samples < len) {
memcpy(shared_context.audio_buffer, src + end_samples, 4 * (len - end_samples));
shared_context.audio_buffer_write = len - end_samples;
const unsigned end_bytes = MP3_BUFFER_SIZE - shared_context.mp3_buffer_write;
memcpy(shared_context.mp3_buffer + MP3_BUFFER_PREAREA + shared_context.mp3_buffer_write, src,
((end_bytes >= len) ? len : end_bytes));
if (end_bytes < len) {
memcpy(shared_context.mp3_buffer + MP3_BUFFER_PREAREA, src + end_bytes, len - end_bytes);
shared_context.mp3_buffer_write = len - end_bytes;
} else {
shared_context.audio_buffer_write += len;
shared_context.mp3_buffer_write += len;
}
shared_context.audio_buffer_write %= AUDIO_BUFFER_SIZE;
shared_context.mp3_buffer_write %= MP3_BUFFER_SIZE;
}
static inline void audiocore_audio_buffer_get(uint32_t *restrict dst, const size_t len)
{
const unsigned end_samples = AUDIO_BUFFER_SIZE - shared_context.audio_buffer_read;
memcpy(dst, shared_context.audio_buffer + shared_context.audio_buffer_read,
4 * ((end_samples >= len) ? len : end_samples));
if (end_samples < len) {
memcpy(dst + end_samples, shared_context.audio_buffer, 4 * (len - end_samples));
shared_context.audio_buffer_read = len - end_samples;
} else {
shared_context.audio_buffer_read += len;
}
shared_context.audio_buffer_read %= AUDIO_BUFFER_SIZE;
}
void __time_critical_func(volume_adjust)(int16_t *buf, size_t samples, uint16_t scalef);
void core1_main(void);
// SHUTDOWN - no arguments - return 0
#define AUDIOCORE_CMD_SHUTDOWN 0xdeadc0de
// FLUSH - signals end of file and stop decoding when buffer empty - no arguments - return 0 when decoding is finished
#define AUDIOCORE_CMD_FLUSH 0xdeadc0dd
#define AUDIOCORE_MAX_VOLUME 0x8000u
// SET VOLUME - one argument: uint32_t range 0 to AUDIOCORE_MAX_VOLUME - return 0 if volume changed, 1 on error
#define AUDIOCORE_CMD_SET_VOLUME 0xdead0001

View File

@@ -1,43 +1,53 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
// Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
#include "i2s.h"
#include "i2s_max98357.pio.h"
#include "audiocore.h"
#include "i2s_max98357.pio.h"
#include <hardware/dma.h>
#include <hardware/sync.h>
#include <stdio.h>
#include <string.h>
#define audiocore_pio pio1
#define I2S_DMA_BUF_SIZE 256
// Must be at least 2
#define AUDIO_BUFS 2
struct i2s_context {
unsigned pio_program_offset;
int pio_sm;
int dma_ch;
dma_channel_config dma_config;
uint32_t dma_buf[I2S_DMA_BUF_SIZE];
uint32_t dma_buf[AUDIO_BUFS][I2S_DMA_BUF_SIZE];
int cur_playing;
int out_pin, sideset_base;
bool has_data[AUDIO_BUFS];
bool playback_active;
};
static struct i2s_context i2s_context;
static void dma_isr(void)
static void __time_critical_func(dma_isr)(void)
{
if (!dma_channel_get_irq1_status(i2s_context.dma_ch))
if (get_core_num() != 1 || !dma_channel_get_irq1_status(i2s_context.dma_ch))
return;
dma_channel_acknowledge_irq1(i2s_context.dma_ch);
const uint32_t flags = spin_lock_blocking(shared_context.lock);
if (audiocore_get_audio_buffer_avail() >= I2S_DMA_BUF_SIZE) {
audiocore_audio_buffer_get(i2s_context.dma_buf, I2S_DMA_BUF_SIZE);
spin_unlock(shared_context.lock, flags);
const int next_buf = (i2s_context.cur_playing + 1) % AUDIO_BUFS;
if (i2s_context.playback_active && i2s_context.has_data[next_buf]) {
i2s_context.cur_playing = next_buf;
i2s_context.has_data[next_buf] = false;
} else {
++shared_context.underruns;
spin_unlock(shared_context.lock, flags);
memset(i2s_context.dma_buf, 0, sizeof(uint32_t) * I2S_DMA_BUF_SIZE);
memset(i2s_context.dma_buf[i2s_context.cur_playing], 0, sizeof(uint32_t) * I2S_DMA_BUF_SIZE);
if (i2s_context.playback_active) {
++shared_context.underruns;
}
}
dma_channel_transfer_from_buffer_now(i2s_context.dma_ch, i2s_context.dma_buf, I2S_DMA_BUF_SIZE);
dma_channel_transfer_from_buffer_now(i2s_context.dma_ch, i2s_context.dma_buf[i2s_context.cur_playing],
I2S_DMA_BUF_SIZE);
}
static void setup_dma_config(void)
@@ -46,41 +56,108 @@ static void setup_dma_config(void)
channel_config_set_dreq(&i2s_context.dma_config, pio_get_dreq(pio1, i2s_context.pio_sm, true));
}
bool i2s_init(int out_pin, int sideset_base, int samplerate)
uint32_t *__time_critical_func(i2s_next_buf)(void)
{
memset(i2s_context.dma_buf, 0, sizeof(i2s_context.dma_buf[0]) * I2S_DMA_BUF_SIZE);
if (!pio_can_add_program(audiocore_pio, &i2s_max98357_program))
uint32_t *ret = NULL;
const long flags = save_and_disable_interrupts();
for (int i = 1; i < AUDIO_BUFS; ++i) {
const int next_buf = (i2s_context.cur_playing + i) % AUDIO_BUFS;
if (!i2s_context.has_data[next_buf]) {
ret = i2s_context.dma_buf[next_buf];
break;
}
}
restore_interrupts(flags);
return ret;
}
void __time_critical_func(i2s_commit_buf)(uint32_t *buf)
{
const long flags = save_and_disable_interrupts();
for (int i = 1; i < AUDIO_BUFS; ++i) {
const int next_buf = (i2s_context.cur_playing + i) % AUDIO_BUFS;
if (i2s_context.dma_buf[next_buf] == buf) {
i2s_context.has_data[next_buf] = true;
if (i == AUDIO_BUFS - 1) {
i2s_context.playback_active = true;
}
break;
}
}
restore_interrupts(flags);
}
void i2s_stop(void)
{
if (!i2s_context.playback_active)
return;
bool have_data = false;
do {
const long flags = save_and_disable_interrupts();
const int next_buf = (i2s_context.cur_playing + 1) % AUDIO_BUFS;
have_data = i2s_context.has_data[next_buf];
restore_interrupts(flags);
if (have_data)
__wfi();
} while (have_data);
const long flags = save_and_disable_interrupts();
i2s_context.playback_active = false;
restore_interrupts(flags);
// Workaround rp2040 E13
dma_channel_set_irq1_enabled(i2s_context.dma_ch, false);
dma_channel_abort(i2s_context.dma_ch);
dma_channel_acknowledge_irq1(i2s_context.dma_ch);
dma_channel_set_irq1_enabled(i2s_context.dma_ch, true);
pio_sm_set_enabled(audiocore_pio, i2s_context.pio_sm, false);
pio_sm_clear_fifos(audiocore_pio, i2s_context.pio_sm);
}
bool i2s_init(int out_pin, int sideset_base)
{
memset(i2s_context.dma_buf, 0, sizeof(i2s_context.dma_buf[0][0]) * AUDIO_BUFS * I2S_DMA_BUF_SIZE);
if (!pio_can_add_program(audiocore_pio, (const pio_program_t *)&i2s_max98357_program))
return false;
i2s_context.pio_sm = pio_claim_unused_sm(audiocore_pio, false);
i2s_context.out_pin = out_pin;
i2s_context.sideset_base = sideset_base;
if (i2s_context.pio_sm == -1)
return false;
i2s_context.pio_program_offset = pio_add_program(audiocore_pio, &i2s_max98357_program);
i2s_max98357_program_init(audiocore_pio, i2s_context.pio_sm, i2s_context.pio_program_offset, out_pin, sideset_base,
samplerate);
i2s_context.pio_program_offset = pio_add_program(audiocore_pio, (const pio_program_t *)&i2s_max98357_program);
i2s_context.dma_ch = dma_claim_unused_channel(false);
if (i2s_context.dma_ch == -1)
goto out_dma_claim;
i2s_context.playback_active = false;
setup_dma_config();
irq_set_exclusive_handler(DMA_IRQ_1, &dma_isr);
irq_add_shared_handler(DMA_IRQ_1, &dma_isr, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
dma_channel_set_irq1_enabled(i2s_context.dma_ch, true);
irq_set_enabled(DMA_IRQ_1, true);
dma_channel_configure(i2s_context.dma_ch, &i2s_context.dma_config, &audiocore_pio->txf[i2s_context.pio_sm],
i2s_context.dma_buf, I2S_DMA_BUF_SIZE, true);
pio_sm_set_enabled(audiocore_pio, i2s_context.pio_sm, true);
return true;
out_dma_claim:
pio_remove_program(audiocore_pio, &i2s_max98357_program, i2s_context.pio_program_offset);
pio_remove_program(audiocore_pio, (const pio_program_t *)&i2s_max98357_program, i2s_context.pio_program_offset);
pio_sm_unclaim(audiocore_pio, i2s_context.pio_sm);
return false;
}
void i2s_play(int samplerate)
{
i2s_context.playback_active = false;
i2s_context.cur_playing = 0;
memset(i2s_context.dma_buf, 0, sizeof(i2s_context.dma_buf[0][0]) * AUDIO_BUFS * I2S_DMA_BUF_SIZE);
i2s_max98357_program_init(audiocore_pio, i2s_context.pio_sm, i2s_context.pio_program_offset, i2s_context.out_pin,
i2s_context.sideset_base, samplerate);
dma_channel_configure(i2s_context.dma_ch, &i2s_context.dma_config, &audiocore_pio->txf[i2s_context.pio_sm],
i2s_context.dma_buf, I2S_DMA_BUF_SIZE, true);
pio_sm_set_enabled(audiocore_pio, i2s_context.pio_sm, true);
}
void i2s_deinit(void)
{
pio_sm_set_enabled(audiocore_pio, i2s_context.pio_sm, false);
dma_channel_set_irq1_enabled(i2s_context.dma_ch, false);
dma_channel_abort(i2s_context.dma_ch);
irq_remove_handler(DMA_IRQ_1, &dma_isr);
dma_channel_unclaim(i2s_context.dma_ch);
pio_remove_program(audiocore_pio, &i2s_max98357_program, i2s_context.pio_program_offset);
pio_sm_unclaim(audiocore_pio, i2s_context.pio_sm);

View File

@@ -1,10 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
// Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
#pragma once
#include <stdbool.h>
#include <stdint.h>
bool i2s_init(int out_pin, int sideset_base, int samplerate);
#define I2S_DMA_BUF_SIZE (1152)
bool i2s_init(int out_pin, int sideset_base);
void i2s_deinit(void);
void i2s_play(int samplerate);
void i2s_stop(void);
uint32_t *i2s_next_buf(void);
void i2s_commit_buf(uint32_t *buf);

View File

@@ -8,8 +8,9 @@ pico_generate_pio_header(usermod_audiocore ${CMAKE_CURRENT_LIST_DIR}/i2s_max9835
target_sources(usermod_audiocore INTERFACE
${CMAKE_CURRENT_LIST_DIR}/audiocore.c
${CMAKE_CURRENT_LIST_DIR}/module.c
${CMAKE_CURRENT_LIST_DIR}/i2s.c
${CMAKE_CURRENT_LIST_DIR}/module.c
${CMAKE_CURRENT_LIST_DIR}/mp3.c
${CMAKE_CURRENT_BINARY_DIR}/i2s_max98357.pio.h
)

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
// Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
#include "audiocore.h"
@@ -16,52 +16,54 @@
struct audiocore_shared_context shared_context = {.lock = NULL};
static bool initialized = false;
struct audiocore_Context_obj {
struct audiocore_obj {
mp_obj_base_t base;
};
const mp_obj_type_t audiocore_type;
/*
* audiocore.Context.deinit(self)
*
* Deinitializes the audiocore.Context and shuts down processing on core1
*/
static mp_obj_t audiocore_Context_deinit(mp_obj_t self_in)
static mp_obj_t audiocore_deinit(mp_obj_t self_in)
{
struct audiocore_Context_obj *self = MP_OBJ_TO_PTR(self_in);
struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in);
multicore_fifo_push_blocking(AUDIOCORE_CMD_SHUTDOWN);
multicore_fifo_pop_blocking();
(void)self;
initialized = false;
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_1(audiocore_Context_deinit_obj, audiocore_Context_deinit);
static MP_DEFINE_CONST_FUN_OBJ_1(audiocore_deinit_obj, audiocore_deinit);
/*
* (copied, buf_space, undderuns) = audiocore.Context.put(self, buffer)
* (copied, buf_space, undderuns) = audiocore.put(self, buffer)
*
* Copies as many integers as possible from the buffer to the audiocore ring buffer for playback.
* 'buffer' must be any object supporting the buffer protocol with data in unsigned int (array typecode 'I')
* format. The actual number of elements copied is returned in 'copied', the remaining free ring buffer space
* is in 'buf_space', and the total number of buffer underruns since initialization of the audiocore Context
* Copies as many bytes as possible from the buffer to the audiocore ring buffer for playback.
* 'buffer' must be any object supporting the buffer protocol with data in byte (array typecode 'b' or 'B')
* format. The actual number of elements copied is returned in 'copied', the remaining free buffer space
* is in 'buf_space', and the total number of audio underruns since initialization of the audiocore Context
* is in 'underruns'.
*/
static mp_obj_t audiocore_Context_put(mp_obj_t self_in, mp_obj_t buffer)
static mp_obj_t audiocore_put(mp_obj_t self_in, mp_obj_t buffer)
{
struct audiocore_Context_obj *self = MP_OBJ_TO_PTR(self_in);
struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in);
(void)self;
mp_buffer_info_t bufinfo;
if (!mp_get_buffer(buffer, &bufinfo, MP_BUFFER_READ))
mp_raise_ValueError("not a read buffer");
if (bufinfo.typecode != 'I')
if (bufinfo.typecode != 'b' && bufinfo.typecode != 'B')
mp_raise_ValueError("unsupported buffer type");
unsigned to_copy = bufinfo.len / 4;
unsigned to_copy = bufinfo.len;
const uint32_t flags = spin_lock_blocking(shared_context.lock);
const unsigned buf_space = audiocore_get_audio_buffer_space();
const unsigned buf_space = audiocore_get_buffer_space();
if (to_copy > buf_space)
to_copy = buf_space;
if (to_copy > 0) {
audiocore_audio_buffer_put(bufinfo.buf, to_copy);
audiocore_buffer_put(bufinfo.buf, to_copy);
__sev();
}
const unsigned underruns = shared_context.underruns;
spin_unlock(shared_context.lock, flags);
@@ -73,33 +75,68 @@ static mp_obj_t audiocore_Context_put(mp_obj_t self_in, mp_obj_t buffer)
};
return mp_obj_new_tuple(3, items);
}
static MP_DEFINE_CONST_FUN_OBJ_2(audiocore_Context_put_obj, audiocore_Context_put);
static uint32_t __scratch_y("core1_stack") core1_stack[1024];
static const mp_rom_map_elem_t audiocore_Context_locals_dict_table[] = {
{MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiocore_Context_deinit_obj)},
{MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiocore_Context_deinit_obj)},
{MP_ROM_QSTR(MP_QSTR_put), MP_ROM_PTR(&audiocore_Context_put_obj)},
};
static MP_DEFINE_CONST_DICT(audiocore_Context_locals_dict, audiocore_Context_locals_dict_table);
const mp_obj_type_t audiocore_Context_type;
MP_DEFINE_CONST_OBJ_TYPE(audiocore_Context_type, MP_QSTR_Context, MP_TYPE_FLAG_NONE, locals_dict,
&audiocore_Context_locals_dict);
static MP_DEFINE_CONST_FUN_OBJ_2(audiocore_put_obj, audiocore_put);
/*
* audiocore.init(pin, sideset, samplerate)
* Returns: audiocore.Context
* audiocore.flush(self)
*
* Initialize the audiocore module, starting the audio processing on core1 and initializing the I2S driver.
* Tells the audiocore to stop playback as soon as all MP3 frames still in the buffer have been decoded.
* This function blocks until playback has ended.
*/
static mp_obj_t audiocore_flush(mp_obj_t self_in)
{
struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in);
(void)self;
multicore_fifo_push_blocking(AUDIOCORE_CMD_FLUSH);
__sev();
multicore_fifo_pop_blocking();
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_1(audiocore_flush_obj, audiocore_flush);
/*
* audiocore.set_volume(self, volume)
*
* Set a volume between 0 (silence) and 255 (PCM values are output as they are decoded from the mp3 stream). Bewteen
* 0 and 255 the volume is scaled linearly.
* Raises a ValueError of the volume is not in the range [0, 255].
* Raises an OSError if the audiocore rejects the volume setting.
*/
static mp_obj_t audiocore_set_volume(mp_obj_t self_in, mp_obj_t volume_obj)
{
struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in);
(void)self;
const int volume = mp_obj_get_int(volume_obj);
if (volume < 0 || volume > 255)
mp_raise_ValueError("volume out of range");
multicore_fifo_push_blocking(AUDIOCORE_CMD_SET_VOLUME);
multicore_fifo_push_blocking(AUDIOCORE_MAX_VOLUME * volume / 255);
__sev();
const uint32_t ret = multicore_fifo_pop_blocking();
if (ret != 0)
mp_raise_OSError(MP_EINVAL);
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_2(audiocore_set_volume_obj, audiocore_set_volume);
static uint32_t __scratch_y("core1_stack") core1_stack[1024];
/*
* Audiocore(pin, sideset)
*
* Initialize the audiocore module, starting the audio processing on core1.
* The pin parameter specifies the I2S data pin, the sideset parameter specifies the I2S LRCLK pin, the
* BCLK must be on pin sideset+1.
* Samplerate should be 44100 or 48000.
* Only a single audiocore.Context may exist at a time.
* Return a audiocore.Context object on success, raises a OSError or ValueError on failure.
* Only a single Audiocore may exist at a time.
* Constructs an Audiocore object on success, raises a OSError or ValueError on failure.
*/
static mp_obj_t audiocore_init(mp_obj_t pin_obj, mp_obj_t sideset_obj, mp_obj_t samplerate_obj)
static void audiocore_init(struct audiocore_obj *obj, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
{
enum { ARG_pin, ARG_sideset };
static const mp_arg_t allowed_args[] = {
{MP_QSTR_pin, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
{MP_QSTR_sideset, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
};
if (initialized)
mp_raise_OSError(MP_EBUSY);
if (!shared_context.lock) {
@@ -109,21 +146,15 @@ static mp_obj_t audiocore_init(mp_obj_t pin_obj, mp_obj_t sideset_obj, mp_obj_t
mp_raise_OSError(MP_ENOMEM);
shared_context.lock = spin_lock_init(lock);
}
shared_context.audio_buffer_write = shared_context.audio_buffer_read = shared_context.underruns = 0;
memset(shared_context.audio_buffer, 0, AUDIO_BUFFER_SIZE * 4);
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
const mp_hal_pin_obj_t pin = mp_hal_get_pin_obj(args[ARG_pin].u_obj);
const mp_hal_pin_obj_t sideset_pin = mp_hal_get_pin_obj(args[ARG_sideset].u_obj);
shared_context.mp3_buffer_write = shared_context.mp3_buffer_read = shared_context.underruns = 0;
memset(shared_context.mp3_buffer, 0, MP3_BUFFER_PREAREA + MP3_BUFFER_SIZE);
multicore_reset_core1();
struct audiocore_Context_obj *context = m_malloc_with_finaliser(sizeof(struct audiocore_Context_obj));
context->base.type = &audiocore_Context_type;
mp_hal_pin_obj_t pin = pin_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(pin_obj);
if (pin == -1)
mp_raise_ValueError("Invalid out pin");
mp_hal_pin_obj_t sideset_pin = sideset_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(sideset_obj);
if (sideset_pin == -1)
mp_raise_ValueError("Invalid sideset base pin");
int samplerate = mp_obj_get_int(samplerate_obj);
shared_context.out_pin = pin;
shared_context.sideset_base = sideset_pin;
shared_context.samplerate = samplerate;
initialized = true;
multicore_launch_core1_with_stack(&core1_main, core1_stack, sizeof(core1_stack));
uint32_t result = multicore_fifo_pop_blocking();
@@ -132,15 +163,32 @@ static mp_obj_t audiocore_init(mp_obj_t pin_obj, mp_obj_t sideset_obj, mp_obj_t
initialized = false;
mp_raise_OSError(result);
}
return MP_OBJ_FROM_PTR(context);
}
static MP_DEFINE_CONST_FUN_OBJ_3(audiocore_init_obj, audiocore_init);
static mp_obj_t audiocore_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args)
{
struct audiocore_obj *audiocore = mp_obj_malloc(struct audiocore_obj, &audiocore_type);
mp_map_t kw_args;
mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
audiocore_init(audiocore, n_args, args, &kw_args);
return MP_OBJ_FROM_PTR(audiocore);
}
static const mp_rom_map_elem_t audiocore_locals_dict_table[] = {
{MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiocore_deinit_obj)},
{MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiocore_deinit_obj)},
{MP_ROM_QSTR(MP_QSTR_put), MP_ROM_PTR(&audiocore_put_obj)},
{MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&audiocore_flush_obj)},
{MP_ROM_QSTR(MP_QSTR_set_volume), MP_ROM_PTR(&audiocore_set_volume_obj)},
};
static MP_DEFINE_CONST_DICT(audiocore_locals_dict, audiocore_locals_dict_table);
MP_DEFINE_CONST_OBJ_TYPE(audiocore_type, MP_QSTR_Audiocore, MP_TYPE_FLAG_NONE, locals_dict, &audiocore_locals_dict,
make_new, &audiocore_make_new);
static const mp_rom_map_elem_t audiocore_module_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiocore)},
{MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&audiocore_init_obj)},
{MP_ROM_QSTR(MP_QSTR_Context), MP_ROM_PTR(&audiocore_Context_type)},
{MP_ROM_QSTR(MP_QSTR_Audiocore), MP_ROM_PTR(&audiocore_type)},
};
static MP_DEFINE_CONST_DICT(audiocore_module_globals, audiocore_module_globals_table);

View File

@@ -0,0 +1,146 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
#include "mp3.h"
#include "audiocore.h"
#include "mp3dec.h"
#include <stdio.h>
#include <string.h>
static HMP3Decoder mp3dec;
static bool synced = false;
static size_t prearea_fill = 0;
#ifdef MP3_DEBUG
static first = true;
#endif
bool mp3_init(void)
{
mp3dec = MP3InitDecoder();
return (mp3dec != 0);
}
void mp3_deinit(void)
{
if (mp3dec) {
MP3FreeDecoder(mp3dec);
mp3dec = NULL;
}
}
static size_t __time_critical_func(mp3_get_continuous_data)(unsigned char **readptr)
{
size_t bytes_left;
const uint32_t flags = spin_lock_blocking(shared_context.lock);
/* Check if remaining bytes at end of buffer should be moved to prearea */
const unsigned end_bytes = MP3_BUFFER_SIZE - shared_context.mp3_buffer_read;
if (shared_context.mp3_buffer_write < shared_context.mp3_buffer_read && end_bytes <= MP3_BUFFER_PREAREA) {
assert(prearea_fill == 0);
memcpy(shared_context.mp3_buffer + MP3_BUFFER_PREAREA - end_bytes,
shared_context.mp3_buffer + MP3_BUFFER_PREAREA + shared_context.mp3_buffer_read, end_bytes);
prearea_fill = end_bytes;
shared_context.mp3_buffer_read = 0;
}
/* Get start and length of valid continuous data */
if (shared_context.mp3_buffer_read == 0) {
*readptr = (unsigned char *)(shared_context.mp3_buffer + MP3_BUFFER_PREAREA - prearea_fill);
bytes_left = prearea_fill + shared_context.mp3_buffer_write;
} else if (shared_context.mp3_buffer_write < shared_context.mp3_buffer_read) {
*readptr = (unsigned char *)(shared_context.mp3_buffer + MP3_BUFFER_PREAREA + shared_context.mp3_buffer_read);
bytes_left = end_bytes;
} else {
*readptr = (unsigned char *)(shared_context.mp3_buffer + MP3_BUFFER_PREAREA + shared_context.mp3_buffer_read);
bytes_left = shared_context.mp3_buffer_write - shared_context.mp3_buffer_read;
}
spin_unlock(shared_context.lock, flags);
return bytes_left;
}
static void __time_critical_func(mp3_consume_data)(size_t bytes_used)
{
if (prearea_fill >= bytes_used) {
prearea_fill -= bytes_used;
return;
} else {
bytes_used -= prearea_fill;
prearea_fill = 0;
}
const uint32_t flags = spin_lock_blocking(shared_context.lock);
shared_context.mp3_buffer_read += bytes_used;
shared_context.mp3_buffer_read %= MP3_BUFFER_SIZE;
spin_unlock(shared_context.lock, flags);
}
bool __time_critical_func(mp3_decode)(uint32_t pcm_buf[static 1152], unsigned *samplerate)
{
unsigned char *readptr;
size_t bytes_avail = mp3_get_continuous_data(&readptr);
if (!bytes_avail)
return false;
if (!synced) {
const int ofs = MP3FindSyncWord((unsigned char *)readptr, bytes_avail);
if (ofs == -1) {
#ifdef MP3_DEBUG
printf("MP3 sync word not found\n");
#endif
mp3_consume_data(bytes_avail);
return false;
}
readptr += ofs;
bytes_avail -= ofs;
mp3_consume_data(ofs);
#ifdef MP3_DEBUG
printf("MP3 sync word found after %zu bytes\n", ofs);
#endif
synced = true;
}
if (!bytes_avail)
return false;
// Decode one frame
int int_bytes_avail = bytes_avail;
const int status = MP3Decode(mp3dec, &readptr, &int_bytes_avail, (short *)pcm_buf, 0);
if (status) {
if (status == ERR_MP3_INDATA_UNDERFLOW) {
return false;
} else /*if (status== ERR_MP3_MAINDATA_UNDERFLOW)*/ {
mp3_consume_data(1);
synced = false;
return false;
}
}
mp3_consume_data(bytes_avail - int_bytes_avail);
MP3FrameInfo info;
MP3GetLastFrameInfo(mp3dec, &info);
#ifdef MP3_DEBUG
if (first) {
printf("bitrate %d, nChans %d, samprate %d, bitsPerSample %d, outputSamps %d, layer %d, version %d\n",
info.bitrate, info.nChans, info.samprate, info.bitsPerSample, info.outputSamps, info.layer,
info.version);
first = false;
}
#endif
*samplerate = info.samprate;
if (info.outputSamps != 2304) {
#ifdef MP3_DEBUG
printf("Unexpected number of output samples: %d\n", info.outputSamps);
#endif
return false;
}
return true;
}
void mp3_reset(void)
{
synced = false;
prearea_fill = 0;
#ifdef MP3_DEBUG
first = true;
#endif
const uint32_t flags = spin_lock_blocking(shared_context.lock);
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = 0;
spin_unlock(shared_context.lock, flags);
}

View File

@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
#pragma once
#include <stdbool.h>
#include <stdint.h>
bool mp3_init(void);
void mp3_deinit(void);
bool mp3_decode(uint32_t pcm_buf[static 1152], unsigned *samplerate);
void mp3_reset(void);

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
// Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
#include "audiocore.h"
#include "i2s.h"
@@ -15,7 +15,7 @@ static unsigned multicore_fifo_push_last;
static unsigned (*multicore_fifo_pop_blocking_cb)(void);
bool i2s_init(int out_pin, int sideset_base, int samplerate)
bool i2s_init(int out_pin, int sideset_base)
{
TEST_ASSERT_FALSE(i2s_initialized);
if (i2s_init_return)
@@ -23,12 +23,28 @@ bool i2s_init(int out_pin, int sideset_base, int samplerate)
return i2s_init_return;
}
uint32_t *i2s_next_buf(void) { return NULL; }
void i2s_commit_buf(uint32_t *buf) {}
void i2s_play(int samplerate) {}
void i2s_stop(void) {}
void i2s_deinit(void)
{
TEST_ASSERT_TRUE(i2s_initialized);
i2s_initialized = false;
}
bool mp3_init(void) { return true; }
void mp3_deinit(void) {}
bool mp3_decode(uint32_t pcm_buf[static 1152], unsigned *samplerate) { return false; }
void mp3_reset(void) {}
void multicore_fifo_push_blocking(unsigned val) { multicore_fifo_push_last = val; }
unsigned multicore_fifo_pop_blocking(void)
@@ -38,6 +54,8 @@ unsigned multicore_fifo_pop_blocking(void)
return 0;
}
bool multicore_fifo_rvalid(void) { return multicore_fifo_pop_blocking_cb; }
void test_audiocore_handles_i2sinit_failure(void)
{
i2s_init_return = false;
@@ -65,78 +83,78 @@ void test_audiocore_init_deinit(void)
void test_audiocore_buffer_space(void)
{
// empty ring buffer
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 0;
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_space());
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = 0;
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1, audiocore_get_buffer_space());
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 23;
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_space());
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = 23;
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1, audiocore_get_buffer_space());
shared_context.audio_buffer_read = shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_space());
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = MP3_BUFFER_SIZE - 1;
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1, audiocore_get_buffer_space());
// full ring buffer
shared_context.audio_buffer_write = 0;
shared_context.audio_buffer_read = 1;
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_space());
shared_context.mp3_buffer_write = 0;
shared_context.mp3_buffer_read = 1;
TEST_ASSERT_EQUAL(0, audiocore_get_buffer_space());
shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
shared_context.audio_buffer_read = 0;
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_space());
shared_context.mp3_buffer_write = MP3_BUFFER_SIZE - 1;
shared_context.mp3_buffer_read = 0;
TEST_ASSERT_EQUAL(0, audiocore_get_buffer_space());
// write > read
shared_context.audio_buffer_write = 10;
shared_context.audio_buffer_read = 0;
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1 - 10, audiocore_get_audio_buffer_space());
shared_context.mp3_buffer_write = 10;
shared_context.mp3_buffer_read = 0;
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1 - 10, audiocore_get_buffer_space());
// write < read
shared_context.audio_buffer_write = 0;
shared_context.audio_buffer_read = 10;
TEST_ASSERT_EQUAL(9, audiocore_get_audio_buffer_space());
shared_context.mp3_buffer_write = 0;
shared_context.mp3_buffer_read = 10;
TEST_ASSERT_EQUAL(9, audiocore_get_buffer_space());
}
void test_audiocore_buffer_avail(void)
{
// empty ring buffer
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 0;
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_avail());
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = 0;
TEST_ASSERT_EQUAL(0, audiocore_get_buffer_avail());
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 23;
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_avail());
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = 23;
TEST_ASSERT_EQUAL(0, audiocore_get_buffer_avail());
shared_context.audio_buffer_read = shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_avail());
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = MP3_BUFFER_SIZE - 1;
TEST_ASSERT_EQUAL(0, audiocore_get_buffer_avail());
// full ring buffer
shared_context.audio_buffer_write = 0;
shared_context.audio_buffer_read = 1;
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_avail());
shared_context.mp3_buffer_write = 0;
shared_context.mp3_buffer_read = 1;
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1, audiocore_get_buffer_avail());
shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
shared_context.audio_buffer_read = 0;
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_avail());
shared_context.mp3_buffer_write = MP3_BUFFER_SIZE - 1;
shared_context.mp3_buffer_read = 0;
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1, audiocore_get_buffer_avail());
// write > read
shared_context.audio_buffer_write = 10;
shared_context.audio_buffer_read = 0;
TEST_ASSERT_EQUAL(10, audiocore_get_audio_buffer_avail());
shared_context.mp3_buffer_write = 10;
shared_context.mp3_buffer_read = 0;
TEST_ASSERT_EQUAL(10, audiocore_get_buffer_avail());
// write < read
shared_context.audio_buffer_write = 0;
shared_context.audio_buffer_read = 10;
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 10, audiocore_get_audio_buffer_avail());
shared_context.mp3_buffer_write = 0;
shared_context.mp3_buffer_read = 10;
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 10, audiocore_get_buffer_avail());
}
static unsigned fill_buffer_helper(void)
{
uint32_t test_data[100];
char test_data[100];
uint32_t ctr = 0;
unsigned avail, filled = 0;
while ((avail = audiocore_get_audio_buffer_space()) > 0) {
while ((avail = audiocore_get_buffer_space()) > 0) {
const unsigned todo = avail > 100 ? 100 : avail;
for (unsigned i = 0; i < todo; ++i)
test_data[i] = ctr++;
audiocore_audio_buffer_put(test_data, todo);
audiocore_buffer_put(test_data, todo);
filled += todo;
}
return filled;
@@ -144,49 +162,39 @@ static unsigned fill_buffer_helper(void)
void test_audiocore_buffer_put(void)
{
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 0;
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = 0;
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, fill_buffer_helper());
for (unsigned i = 0; i < AUDIO_BUFFER_SIZE - 1; ++i) {
TEST_ASSERT_EQUAL(i, shared_context.audio_buffer[i]);
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1, fill_buffer_helper());
for (unsigned i = 0; i < MP3_BUFFER_SIZE - 1; ++i) {
TEST_ASSERT_EQUAL((char)(i & 0xFF), shared_context.mp3_buffer[MP3_BUFFER_PREAREA + i]);
}
// test wraparound fill
shared_context.audio_buffer_read = shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 10;
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, fill_buffer_helper());
for (unsigned i = 0; i < AUDIO_BUFFER_SIZE - 1; ++i) {
TEST_ASSERT_EQUAL(i, shared_context.audio_buffer[(i + AUDIO_BUFFER_SIZE - 10) % AUDIO_BUFFER_SIZE]);
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = MP3_BUFFER_SIZE - 10;
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1, fill_buffer_helper());
for (unsigned i = 0; i < MP3_BUFFER_SIZE - 1; ++i) {
TEST_ASSERT_EQUAL(
(char)(i & 0xFF),
shared_context.mp3_buffer[MP3_BUFFER_PREAREA + ((i + MP3_BUFFER_SIZE - 10) % MP3_BUFFER_SIZE)]);
}
}
static unsigned get_buffer_helper(void)
void test_audiocore_volume_adjust(void)
{
uint32_t test_data[100];
uint32_t ctr = 0;
const int16_t samples[] = {0, 1, -1, INT16_MIN, INT16_MAX, 100, -100, 99, -99};
#define SAMPLE_COUNT (sizeof(samples) / sizeof(samples[0]))
const struct {
uint16_t scalefactor;
int16_t expected[SAMPLE_COUNT];
} expected[] = {
{.scalefactor = AUDIOCORE_MAX_VOLUME, .expected = {0, 1, -1, INT16_MIN, INT16_MAX, 100, -100, 99, -99}},
{.scalefactor = 0, .expected = {0}},
{.scalefactor = AUDIOCORE_MAX_VOLUME / 2, .expected = {0, 0, -1, -16384, 16383, 50, -50, 49, -50}}};
unsigned avail, gotten = 0;
while ((avail = audiocore_get_audio_buffer_avail()) > 0) {
const unsigned todo = avail > 100 ? 100 : avail;
audiocore_audio_buffer_get(test_data, todo);
for (unsigned i = 0; i < todo; ++i)
TEST_ASSERT_EQUAL(ctr++, test_data[i]);
gotten += todo;
for (int test = 0; test < sizeof(expected) / sizeof(expected[0]); ++test) {
int16_t buf[sizeof(samples) / sizeof(samples[0])];
memcpy(buf, samples, sizeof(samples));
volume_adjust(buf, SAMPLE_COUNT, expected[test].scalefactor);
TEST_ASSERT_EQUAL_HEX16_ARRAY(expected[test].expected, buf, SAMPLE_COUNT);
}
return gotten;
}
void test_audiocore_buffer_get(void)
{
shared_context.audio_buffer_read = 0;
shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
for (unsigned i = 0; i < AUDIO_BUFFER_SIZE - 1; ++i)
shared_context.audio_buffer[i] = i;
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, get_buffer_helper());
// test wraparound read
shared_context.audio_buffer_read = 10;
shared_context.audio_buffer_write = 9;
for (unsigned i = 0; i < AUDIO_BUFFER_SIZE - 1; ++i)
shared_context.audio_buffer[(i + 10) % AUDIO_BUFFER_SIZE] = i;
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, get_buffer_helper());
}

View File

@@ -3,22 +3,21 @@
import aiorepl
import asyncio
import audiocore
import machine
import micropython
import os
import time
from array import array
from machine import Pin
from math import pi, sin, pow
from micropython import const
# Own modules
from audiocore import Audiocore
from rp2_neopixel import NeoPixel
from rp2_sd import SDCard
micropython.alloc_emergency_exception_buf(100)
asyncio.create_task(aiorepl.task())
leds = const(10)
brightness = 0.5
@@ -43,27 +42,35 @@ async def rainbow(np, period=10):
await asyncio.sleep_ms(20 - (now - before))
samplerate = 44100
hz = 441
count = 100
amplitude = 0x1fff
buf = array('I', range(count))
for i in range(len(buf)):
val = int(sin(i * hz / samplerate * 2 * pi)*amplitude) & 0xffff
buf[i] = (val << 16) | val
async def output_sound(audioctx):
pos = 0
async def play_mp3(audiocore, mp3file):
_, avail, _ = audioctx.put(b'')
known_underruns = 0
while True:
pushed, avail, underruns = audioctx.put(buf[pos:])
pos = (pos + pushed) % len(buf)
# print(f"pushed {pushed}, pos {pos}, avail {avail}")
data = mp3file.read(avail)
if avail > 0 and len(data) == 0:
# End of file
break
pos = 0
while pos < len(data):
pushed, avail, underruns = audioctx.put(data[pos:])
if pushed == 0:
await asyncio.sleep_ms(0)
else:
await asyncio.sleep_ms(0)
pos += pushed
if underruns > known_underruns:
print(f"{underruns:x}")
known_underruns = underruns
await asyncio.sleep(0)
audioctx.flush()
print("Decoding ended")
async def play_mp3s(audiocore, mp3files):
for name in mp3files:
print(b'Playing ' + name)
with open(name, "rb") as testfile:
await play_mp3(audiocore, testfile)
await asyncio.sleep_ms(1000)
# Set 8 mA drive strength and fast slew rate
@@ -120,9 +127,14 @@ list_sd()
asyncio.create_task(rainbow(np))
# Test audio
audioctx = audiocore.init(Pin(8), Pin(6), samplerate)
asyncio.create_task(output_sound(audioctx))
audioctx = Audiocore(Pin(8), Pin(6))
asyncio.create_task(latency_test())
# high prio for proc 1
machine.mem32[0x40030000 + 0x00] = 0x10
testfiles = [b'/sd/' + name for name in os.listdir(b'/sd') if name.endswith(b'mp3')]
asyncio.create_task(play_mp3s(audioctx, testfiles))
asyncio.create_task(aiorepl.task())
asyncio.get_event_loop().run_forever()

View File

@@ -1,10 +1,16 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
// Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
#pragma once
#include <stdbool.h>
struct spin_lock;
typedef struct spin_lock spin_lock_t;
void multicore_fifo_push_blocking(unsigned val);
unsigned multicore_fifo_pop_blocking(void);
bool multicore_fifo_rvalid(void);
#define __time_critical_func(x) x
#define __wfe()

View File

@@ -3,3 +3,4 @@
#include <errno.h>
#define MP_EIO EIO
#define MP_ENOMEM ENOMEM