Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions src/audiograph/Graph.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
#include "py/runtime.h"
#include "shared/audiodsp_pump_lock.h"

// A table page's size: small enough to fit a fragmented heap.
#define AUDIOGRAPH_PAGE_BYTES 4096u

// A live graph, or a raise.
static audiograph_graph_obj_t *graph_self(mp_obj_t self_in) {
audiograph_graph_obj_t *self = MP_OBJ_TO_PTR(self_in);
Expand Down Expand Up @@ -75,14 +78,35 @@ static mp_obj_t audiograph_graph_make_new(const mp_obj_type_t *type,
mp_raise_ValueError(MP_ERROR_TEXT("queue must be from 1 to 65536"));
}
audiograph_graph_obj_t *self = mp_obj_malloc(audiograph_graph_obj_t, type);
int16_t *bufs = m_new0(int16_t, (size_t)buffers * (size_t)frames);
audiodsp_graph_node_t *table = m_new0(audiodsp_graph_node_t, (size_t)nodes);
// Every table goes in pages of a few KB, so a large patch still fits a
// fragmented heap. The GC keeps a block alive only through a pointer to
// its start, so the first pointer into each page is that start.
size_t per = AUDIOGRAPH_PAGE_BYTES / ((size_t)frames * sizeof(int16_t));
per = per ? per : 1;
int16_t **bufs = m_new(int16_t *, (size_t)buffers);
for (mp_int_t i = 0; i < buffers; i += (mp_int_t)per) {
size_t n = (size_t)(buffers - i) < per ? (size_t)(buffers - i) : per;
int16_t *page = m_new0(int16_t, n * (size_t)frames);
for (size_t k = 0; k < n; ++k) {
bufs[i + (mp_int_t)k] = page + k * (size_t)frames;
}
}
uint32_t node_pages = audiodsp_graph_pages((uint32_t)nodes, AUDIODSP_GRAPH_NODE_PAGE);
audiodsp_graph_node_t **table = m_new(audiodsp_graph_node_t *, node_pages);
float **shadow = m_new(float *, node_pages);
for (uint32_t i = 0; i < node_pages; ++i) {
table[i] = m_new0(audiodsp_graph_node_t, AUDIODSP_GRAPH_NODE_PAGE);
shadow[i] = m_new0(float, AUDIODSP_GRAPH_SHADOW_PAGE);
}
uint32_t record_pages = audiodsp_graph_pages((uint32_t)queue, AUDIODSP_GRAPH_RECORD_PAGE);
audiodsp_graph_record_t **records = m_new(audiodsp_graph_record_t *, record_pages);
for (uint32_t i = 0; i < record_pages; ++i) {
records[i] = m_new0(audiodsp_graph_record_t, AUDIODSP_GRAPH_RECORD_PAGE);
}
audiodsp_graph_init(&self->graph, (uint32_t)args[ARG_sample_rate].u_int,
(uint32_t)frames, (uint32_t)channels, bufs, (uint32_t)buffers, table,
(uint32_t)nodes);
audiodsp_graph_queue_init(&self->graph,
m_new0(audiodsp_graph_record_t, (size_t)queue), (uint32_t)queue,
m_new0(float, (size_t)nodes * AUDIODSP_GRAPH_MAX_PARAMS));
audiodsp_graph_queue_init(&self->graph, records, (uint32_t)queue, shadow);
self->out = m_new0(int16_t, 2u * (size_t)frames * (size_t)channels);
self->which = 0;
self->pumped = false;
Expand Down Expand Up @@ -345,7 +369,7 @@ static int16_t *block_of(audiograph_graph_obj_t *self, mp_obj_t buffer) {
if (id < 0 || (uint32_t)id >= self->graph.nbufs) {
mp_raise_ValueError(MP_ERROR_TEXT("bad buffer id"));
}
return self->graph.bufs + (size_t)id * self->graph.frames;
return self->graph.bufs[id];
}

// read(buffer, dst): copy a block out into an int16 buffer of `frames`.
Expand Down
2 changes: 1 addition & 1 deletion src/circuitpython_spike/shared-bindings/audiograph/Graph.c
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ static int16_t *block_of(audiograph_graph_obj_t *self, mp_obj_t buffer) {
if (id < 0 || (uint32_t)id >= self->graph.nbufs) {
mp_raise_ValueError(MP_ERROR_TEXT("bad buffer id"));
}
return self->graph.bufs + (size_t)id * self->graph.frames;
return self->graph.bufs[id];
}

// read(buffer, dst): copy a block out into an int16 buffer of `frames`.
Expand Down
34 changes: 29 additions & 5 deletions src/circuitpython_spike/shared-module/audiograph/Graph.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,40 @@

#include "py/runtime.h"

// A table page's size: small enough to fit a fragmented heap.
#define AUDIOGRAPH_PAGE_BYTES 4096u

void common_hal_audiograph_graph_construct(audiograph_graph_obj_t *self,
uint32_t sample_rate, uint32_t frames, uint32_t channel_count,
uint32_t buffers, uint32_t nodes, uint32_t queue) {
int16_t *bufs = m_new0(int16_t, (size_t)buffers * (size_t)frames);
audiodsp_graph_node_t *table = m_new0(audiodsp_graph_node_t, (size_t)nodes);
// Every table goes in pages of a few KB, so a large patch still fits a
// fragmented heap. The GC keeps a block alive only through a pointer to
// its start, so the first pointer into each page is that start.
size_t per = AUDIOGRAPH_PAGE_BYTES / ((size_t)frames * sizeof(int16_t));
per = per ? per : 1;
int16_t **bufs = m_new(int16_t *, (size_t)buffers);
for (uint32_t i = 0; i < buffers; i += (uint32_t)per) {
size_t n = (size_t)(buffers - i) < per ? (size_t)(buffers - i) : per;
int16_t *page = m_new0(int16_t, n * (size_t)frames);
for (size_t k = 0; k < n; ++k) {
bufs[i + k] = page + k * (size_t)frames;
}
}
uint32_t node_pages = audiodsp_graph_pages(nodes, AUDIODSP_GRAPH_NODE_PAGE);
audiodsp_graph_node_t **table = m_new(audiodsp_graph_node_t *, node_pages);
float **shadow = m_new(float *, node_pages);
for (uint32_t i = 0; i < node_pages; ++i) {
table[i] = m_new0(audiodsp_graph_node_t, AUDIODSP_GRAPH_NODE_PAGE);
shadow[i] = m_new0(float, AUDIODSP_GRAPH_SHADOW_PAGE);
}
uint32_t record_pages = audiodsp_graph_pages(queue, AUDIODSP_GRAPH_RECORD_PAGE);
audiodsp_graph_record_t **records = m_new(audiodsp_graph_record_t *, record_pages);
for (uint32_t i = 0; i < record_pages; ++i) {
records[i] = m_new0(audiodsp_graph_record_t, AUDIODSP_GRAPH_RECORD_PAGE);
}
audiodsp_graph_init(&self->graph, sample_rate, frames, channel_count,
bufs, buffers, table, nodes);
audiodsp_graph_queue_init(&self->graph,
m_new0(audiodsp_graph_record_t, (size_t)queue), queue,
m_new0(float, (size_t)nodes * AUDIODSP_GRAPH_MAX_PARAMS));
audiodsp_graph_queue_init(&self->graph, records, queue, shadow);
self->out = m_new0(int16_t, 2u * (size_t)frames * channel_count);
self->which = 0;
self->base.sample_rate = sample_rate;
Expand Down
47 changes: 41 additions & 6 deletions src/cpython/_audiodsp.c
Original file line number Diff line number Diff line change
Expand Up @@ -3416,7 +3416,10 @@ typedef struct {
// Memoryviews of the wow tables the delay nodes borrow, kept alive (and
// their arrays kept from resizing) for as long as the graph is.
PyObject *keep;
// The queue's records and get()'s shadow (shared/audiodsp_graph.h).
// The tables as single blocks; the graph sees them through page
// pointers into these (shared/audiodsp_graph.h).
int16_t *bufs;
audiodsp_graph_node_t *table;
audiodsp_graph_record_t *records;
float *shadow;
// Two output blocks for _get_buffer, used in turn.
Expand Down Expand Up @@ -3474,23 +3477,47 @@ static int graph_state_init(audiodsp_graph_object_t *self, PyObject *args,
sizeof(float));
int16_t *out = PyMem_Calloc(2u * (size_t)frames * (size_t)channels,
sizeof(int16_t));
uint32_t node_pages = audiodsp_graph_pages((uint32_t)nodes, AUDIODSP_GRAPH_NODE_PAGE);
uint32_t record_pages = audiodsp_graph_pages((uint32_t)queue, AUDIODSP_GRAPH_RECORD_PAGE);
int16_t **buf_at = PyMem_Calloc((size_t)buffers, sizeof(int16_t *));
audiodsp_graph_node_t **node_at = PyMem_Calloc(node_pages, sizeof(audiodsp_graph_node_t *));
audiodsp_graph_record_t **record_at = PyMem_Calloc(record_pages,
sizeof(audiodsp_graph_record_t *));
float **shadow_at = PyMem_Calloc(node_pages, sizeof(float *));
PyObject *keep = PyList_New(0);
if (bufs == NULL || table == NULL || records == NULL || shadow == NULL ||
out == NULL || keep == NULL) {
out == NULL || buf_at == NULL || node_at == NULL || record_at == NULL ||
shadow_at == NULL || keep == NULL) {
PyMem_Free(bufs);
PyMem_Free(table);
PyMem_Free(records);
PyMem_Free(shadow);
PyMem_Free(out);
PyMem_Free(buf_at);
PyMem_Free(node_at);
PyMem_Free(record_at);
PyMem_Free(shadow_at);
Py_XDECREF(keep);
if (!PyErr_Occurred()) {
PyErr_NoMemory();
}
return -1;
}
for (long i = 0; i < buffers; ++i) {
buf_at[i] = bufs + (size_t)i * (size_t)frames;
}
for (uint32_t i = 0; i < node_pages; ++i) {
node_at[i] = table + (size_t)i * AUDIODSP_GRAPH_NODE_PAGE;
shadow_at[i] = shadow + (size_t)i * AUDIODSP_GRAPH_SHADOW_PAGE;
}
for (uint32_t i = 0; i < record_pages; ++i) {
record_at[i] = records + (size_t)i * AUDIODSP_GRAPH_RECORD_PAGE;
}
audiodsp_graph_init(&self->graph, (uint32_t)sample_rate, (uint32_t)frames,
(uint32_t)channels, bufs, (uint32_t)buffers, table, (uint32_t)nodes);
audiodsp_graph_queue_init(&self->graph, records, (uint32_t)queue, shadow);
(uint32_t)channels, buf_at, (uint32_t)buffers, node_at, (uint32_t)nodes);
audiodsp_graph_queue_init(&self->graph, record_at, (uint32_t)queue, shadow_at);
self->bufs = bufs;
self->table = table;
self->records = records;
self->shadow = shadow;
self->out = out;
Expand All @@ -3505,16 +3532,24 @@ static void graph_state_dealloc(audiodsp_graph_object_t *self) {
PyTypeObject *type = Py_TYPE(self);
if (self->graph.nodes != NULL) {
for (uint32_t i = 0; i < self->graph.nnodes; ++i) {
PyMem_Free(self->graph.nodes[i].data);
PyMem_Free(audiodsp_graph_node(&self->graph, i)->data);
}
}
PyMem_Free(self->graph.nodes);
PyMem_Free(self->graph.bufs);
PyMem_Free(self->graph.records);
PyMem_Free(self->graph.shadow);
PyMem_Free(self->table);
PyMem_Free(self->bufs);
PyMem_Free(self->records);
PyMem_Free(self->shadow);
PyMem_Free(self->out);
self->graph.nodes = NULL;
self->graph.bufs = NULL;
self->graph.records = NULL;
self->graph.shadow = NULL;
self->table = NULL;
self->bufs = NULL;
self->records = NULL;
self->shadow = NULL;
self->out = NULL;
Expand Down Expand Up @@ -3999,7 +4034,7 @@ static int16_t *graph_block_of(audiodsp_graph_object_t *self, long id) {
PyErr_SetString(PyExc_ValueError, "bad buffer id");
return NULL;
}
return self->graph.bufs + (size_t)id * self->graph.frames;
return self->graph.bufs[id];
}

// read(buffer, dst): copy a block out into an int16 buffer of `frames`.
Expand Down
Loading
Loading