Skip to content

Commit

Permalink
Implement ring buffers for real-time synthesis
Browse files Browse the repository at this point in the history
  • Loading branch information
Sleepwalking committed Nov 24, 2017
1 parent 091fb63 commit 4bfc06a
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 1 deletion.
171 changes: 171 additions & 0 deletions buffer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/** @file */

#ifndef LLSM_BUFFER_H
#define LLSM_BUFFER_H

#include <assert.h>
#include <stdlib.h>

/** @defgroup group_ringbuffer llsm_ringbuffer
* @{ */
/** @brief A circular array structure for storing audio samples streaming in
* and streaming out in real time. */
typedef struct {
FP_TYPE* data; /**< the audio samples */
int capacity; /**< the size of the ring buffer */
int curr; /**< the current access position */
} llsm_ringbuffer;

/** @brief Create an empty ring buffer with a given size. */
static inline llsm_ringbuffer* llsm_create_ringbuffer(int capacity) {
assert(capacity > 0);
llsm_ringbuffer* ret = (llsm_ringbuffer*)malloc(sizeof(llsm_ringbuffer));
ret -> capacity = capacity;
ret -> data = (FP_TYPE*)calloc(capacity, sizeof(FP_TYPE));
ret -> curr = 0;
return ret;
}

/** @brief Delete and free a ring buffer. */
static inline void llsm_delete_ringbuffer(llsm_ringbuffer* dst) {
if(dst == NULL) return;
free(dst -> data);
free(dst);
}

/** @brief Access the sample at an index relative to the current position;
* the index is expected to be negative. */
static inline FP_TYPE llsm_ringbuffer_read(llsm_ringbuffer* src, int idx) {
assert(idx < 0 && idx >= -src -> capacity);
return src -> data[(src -> curr + idx + src -> capacity) % src -> capacity];
}

/** @brief Modify the sample at an index relative to the current position;
* the index is expected to be negative. */
static inline void llsm_ringbuffer_write(llsm_ringbuffer* dst, int idx, FP_TYPE x) {
assert(idx < 0 && idx >= -dst -> capacity);
dst -> data[(dst -> curr + idx + dst -> capacity) % dst -> capacity] = x;
}

/** @brief Append a sample to the ring buffer and push the current position
* forward by one sample. */
static inline void llsm_ringbuffer_append(llsm_ringbuffer* dst, FP_TYPE x) {
dst -> data[dst -> curr] = x;
dst -> curr = (dst -> curr + 1) % dst -> capacity;
}

/** @brief Push the current position forward by several samples without
* modifying the content. */
static inline void llsm_ringbuffer_forward(llsm_ringbuffer* dst, int size) {
dst -> curr = (dst -> curr + size) % dst -> capacity;
}

/** @brief Load a contiguous subset of samples into a destination pointer;
* the subset starts from a negative index (lag). */
static inline void llsm_ringbuffer_readchunk(llsm_ringbuffer* src,
int lag, int size, FP_TYPE* dst) {
assert(size > 0);
assert(lag + size <= 0);
assert(lag > -src -> capacity);
int base = src -> curr + src -> capacity;
for(int i = 0; i < size; i ++)
dst[i] = src -> data[(base + lag + i) % src -> capacity];
}

/** @brief Write a contiguous subset of samples from a source pointer;
* the subset starts from a negative index (lag). */
static inline void llsm_ringbuffer_writechunk(llsm_ringbuffer* dst,
int lag, int size, FP_TYPE* src) {
assert(size > 0);
assert(lag + size <= 0);
assert(lag >= -dst -> capacity);
int base = dst -> curr + dst -> capacity;
for(int i = 0; i < size; i ++)
dst -> data[(base + lag + i) % dst -> capacity] = src[i];
}

/** @brief Append a contiguous subset of samples from a source pointer.
* This will move the current position forward by the size of the data. */
static inline void llsm_ringbuffer_appendchunk(llsm_ringbuffer* dst,
int size, FP_TYPE* src) {
assert(size > 0);
assert(size <= dst -> capacity);
llsm_ringbuffer_forward(dst, size);
llsm_ringbuffer_writechunk(dst, -size, size, src);
}

/** @brief Append a chunk of zeros. This will move the current position forward
* by the number of zeros. */
static inline void llsm_ringbuffer_appendblank(llsm_ringbuffer* dst, int size) {
assert(size > 0);
assert(size <= dst -> capacity);
llsm_ringbuffer_forward(dst, size);
int base = dst -> curr + dst -> capacity;
for(int i = 0; i < size; i ++)
dst -> data[(base - size + i) % dst -> capacity] = 0;
}
/** @} */

/** @defgroup group_vringbuffer llsm_vringbuffer
* @{ */
/** @brief A circular array structure for storing structural objects streaming
* int and streaming out in real time. A destructor function has to be
* specified (and hence the name virtual ring buffer). */
typedef struct {
void** data;
int capacity;
int curr;
llsm_fdestructor destructor;
} llsm_vringbuffer;

/** @brief Create an empty virtual ring buffer with a given size. */
static inline llsm_vringbuffer* llsm_create_vringbuffer(int capacity,
llsm_fdestructor destructor) {
llsm_vringbuffer* ret = (llsm_vringbuffer*)malloc(sizeof(llsm_vringbuffer));
ret -> capacity = capacity;
ret -> curr = 0;
ret -> data = (void**)malloc(sizeof(void*) * capacity);
ret -> destructor = destructor;
for(int i = 0; i < capacity; i ++) ret -> data[i] = NULL;
return ret;
}

/** @brief Delete and free a virtual ring buffer, including all its stored
* objects. */
static inline void llsm_delete_vringbuffer(llsm_vringbuffer* dst) {
if(dst == NULL) return;
for(int i = 0; i < dst -> capacity; i ++)
if(dst -> data[i] != NULL)
dst -> destructor(dst -> data[i]);
free(dst -> data);
free(dst);
}

/** @brief Access the object at an index relative to the current position;
* the index is expected to be negative. */
static inline void* llsm_vringbuffer_read(llsm_vringbuffer* src, int idx) {
assert(idx < 0 && idx >= -src -> capacity);
return src -> data[(src -> curr + idx + src -> capacity) % src -> capacity];
}

/** @brief Replace the object at an index relative to the current position by
* a given pointer; no actual deep copy is performed; the index is expected
* to be negative. */
static inline void llsm_vringbuffer_write(llsm_vringbuffer* dst, int idx, void* x) {
assert(idx < 0 && idx >= -dst -> capacity);
void** currptr = & dst -> data[(dst -> curr + idx + dst -> capacity) % dst -> capacity];
if(*currptr != NULL) dst -> destructor(*currptr);
*currptr = x;
}

/** @brief Append an object to the virtual ring buffer and push the current
* position forward by one sample; no actual deep copy is performed. */
static inline void llsm_vringbuffer_append(llsm_vringbuffer* dst, void* x) {
if(dst -> data[dst -> curr] != NULL)
dst -> destructor(dst -> data[dst -> curr]);
dst -> data[dst -> curr] = x;
dst -> curr = (dst -> curr + 1) % dst -> capacity;
}
/** @} */

#endif
4 changes: 3 additions & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ AR = ar
ARFLAGS = -rv
CFLAGS_DBG = -DFP_TYPE=float -Og -g -std=c99 -Wall -fPIC
CFLAGS_REL = -DFP_TYPE=float -Ofast -std=c99 -Wall -fPIC
CFLAGS = CFLAGS_DBG
CFLAGS = $(CFLAGS_DBG)

OUT_DIR = ./build
OBJS = $(OUT_DIR)/container.o \
Expand Down Expand Up @@ -55,6 +55,8 @@ test-dsputils: $(OUT_DIR)/test-structs \
$(OUT_DIR)/test-dsputils
$(OUT_DIR)/test-harmonic

$(OUT_DIR)/test-structs: buffer.h

$(OUT_DIR)/test-%: test/test-%.c $(TARGET_A) \
$(LIBPYIN_A) $(LIBGVPS_A) $(CIGLET_O) test/verify-utils.h
$(CC) $(CFLAGS) -o $(OUT_DIR)/test-$* test/test-$*.c \
Expand Down
50 changes: 50 additions & 0 deletions test/test-structs.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "../llsm.h"
#include "../buffer.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
Expand Down Expand Up @@ -125,10 +126,59 @@ void test_chunk() {
llsm_delete_aoptions(opt);
}

void test_buffer() {
llsm_ringbuffer* rb1 = llsm_create_ringbuffer(4096);
FP_TYPE x[100];
FP_TYPE y[200];
for(int i = 0; i < 100; i ++)
x[i] = (FP_TYPE)rand() / RAND_MAX - 0.5;
for(int i = 0; i < 100; i ++) {
// The following two realizations of appending 100 samples should
// give equivalent results.
if(i % 2 == 0) {
llsm_ringbuffer_appendchunk(rb1, 100, x);
} else {
llsm_ringbuffer_forward(rb1, 100);
llsm_ringbuffer_writechunk(rb1, -100, 100, x);
}
// The following two realizations of appending 100 zeros should
// give equivalent results.
if(i % 3 == 0) {
llsm_ringbuffer_appendblank(rb1, 100);
} else {
for(int j = 0; j < 100; j ++)
llsm_ringbuffer_append(rb1, 0);
}
// The following two realizations of reading 200 samples should
// give equivalent results.
if(i % 4 == 0) {
for(int j = 0; j < 200; j ++)
y[j] = llsm_ringbuffer_read(rb1, j - 200);
} else {
llsm_ringbuffer_readchunk(rb1, -200, 200, y);
}

for(int j = 0; j < 100; j ++)
assert(y[j] == x[j]);
for(int j = 100; j < 200; j ++)
assert(y[j] == 0);

if(i <= 5) continue;

llsm_ringbuffer_readchunk(rb1, -1300, 200, y);
for(int j = 0; j < 100; j ++)
assert(y[j] == 0);
for(int j = 100; j < 200; j ++)
assert(y[j] == x[j - 100]);
}
llsm_delete_ringbuffer(rb1);
}

int main() {
test_container();
test_hmframe();
test_nmframe();
test_chunk();
test_buffer();
return 0;
}

0 comments on commit 4bfc06a

Please sign in to comment.