From 6ca7a8571ae099c981caa9bbfab9e0d0dced7260 Mon Sep 17 00:00:00 2001 From: Daniel Jones Date: Thu, 24 Oct 2024 18:20:28 +0100 Subject: [PATCH] Add Python bindings and tests for RingBuffer; fix read/write head positions --- source/include/signalflow/buffer/ringbuffer.h | 20 ++++++++--- source/src/node/io/input/miniaudio.cpp | 5 ++- source/src/node/processors/delays/comb.cpp | 2 +- source/src/node/processors/delays/onetap.cpp | 2 +- source/src/python/buffer.cpp | 19 ++++++++++ tests/test_buffer.py | 36 ++++++++++++++++++- 6 files changed, 75 insertions(+), 9 deletions(-) diff --git a/source/include/signalflow/buffer/ringbuffer.h b/source/include/signalflow/buffer/ringbuffer.h index 5697426b..7220da67 100644 --- a/source/include/signalflow/buffer/ringbuffer.h +++ b/source/include/signalflow/buffer/ringbuffer.h @@ -7,11 +7,11 @@ *--------------------------------------------------------------------------------*/ #include +#include #include #include #include #include -#include enum signalflow_interpolation_mode_t : unsigned int; @@ -27,8 +27,11 @@ class RingBuffer void append(T value); void extend(T *ptr, unsigned int count); + void extend(std::vector vec); T get(double index); T operator[](double index) { return this->get(index); } + unsigned int get_capacity() { return this->capacity; } + unsigned int get_write_position() { return this->write_position; } protected: T *data = nullptr; @@ -44,7 +47,7 @@ class RingQueue : public RingBuffer RingQueue(unsigned int capacity) : RingBuffer(capacity) { - this->read_position = this->capacity - 826; + this->read_position = this->capacity - 1; this->filled_count = 0; } T pop(); @@ -66,7 +69,7 @@ RingBuffer::RingBuffer(unsigned int capacity) throw std::runtime_error("RingBuffer must have a capacity greater than zero"); } this->data = new T[capacity](); - this->write_position = 0; + this->write_position = capacity - 1; this->capacity = capacity; } @@ -79,8 +82,8 @@ RingBuffer::~RingBuffer() template void RingBuffer::append(T value) { - this->data[this->write_position] = value; this->write_position = (this->write_position + 1) % this->capacity; + this->data[this->write_position] = value; } template @@ -90,6 +93,13 @@ void RingBuffer::extend(T *ptr, unsigned int count) this->append(ptr[i]); } +template +void RingBuffer::extend(std::vector vec) +{ + for (auto item : vec) + this->append(item); +} + template T RingBuffer::get(double index) { @@ -113,9 +123,9 @@ template T RingQueue::pop() { mutex.lock(); - T rv = this->data[this->read_position]; this->read_position = (this->read_position + 1) % this->capacity; this->filled_count--; + T rv = this->data[this->read_position]; mutex.unlock(); return rv; } diff --git a/source/src/node/io/input/miniaudio.cpp b/source/src/node/io/input/miniaudio.cpp index f8828527..cfdc22be 100644 --- a/source/src/node/io/input/miniaudio.cpp +++ b/source/src/node/io/input/miniaudio.cpp @@ -127,7 +127,10 @@ void AudioIn::init() for (int channel = 0; channel < device.capture.internalChannels; channel++) { - input_queue.push_back(new SampleRingQueue(device.capture.internalPeriodSizeInFrames * 8)); + SampleRingQueue *queue = new SampleRingQueue(device.capture.internalPeriodSizeInFrames * 8); + std::vector silence(device.capture.internalPeriodSizeInFrames, 0); + queue->extend(silence); + input_queue.push_back(queue); } this->start(); diff --git a/source/src/node/processors/delays/comb.cpp b/source/src/node/processors/delays/comb.cpp index 1f7cde05..91f4d639 100644 --- a/source/src/node/processors/delays/comb.cpp +++ b/source/src/node/processors/delays/comb.cpp @@ -46,7 +46,7 @@ void CombDelay::process(Buffer &out, int num_frames) signalflow_audio_thread_error("CombDelay: Delay time exceeds maximum. Reduce the delay_time, or increase max_delay_time."); } - sample rv = input->out[channel][frame] + (feedback * buffers[channel]->get(-offset)); + sample rv = input->out[channel][frame] + (feedback * buffers[channel]->get(-offset + 1)); out[channel][frame] = rv; buffers[channel]->append(rv); } diff --git a/source/src/node/processors/delays/onetap.cpp b/source/src/node/processors/delays/onetap.cpp index b7826853..a9108824 100644 --- a/source/src/node/processors/delays/onetap.cpp +++ b/source/src/node/processors/delays/onetap.cpp @@ -42,7 +42,7 @@ void OneTapDelay::process(Buffer &out, int num_frames) * through the current frame immediately *-------------------------------------------------------------------------------*/ buffers[channel]->append(this->input->out[channel][frame]); - out[channel][frame] = buffers[channel]->get(-offset - 1); + out[channel][frame] = buffers[channel]->get(-offset); } } } diff --git a/source/src/python/buffer.cpp b/source/src/python/buffer.cpp index e2ae6c54..f887294f 100644 --- a/source/src/python/buffer.cpp +++ b/source/src/python/buffer.cpp @@ -2,6 +2,25 @@ void init_python_buffer(py::module &m) { + py::class_(m, "SampleRingBuffer", "A circular buffer of audio samples with a single read/write head") + .def(py::init(), "capacity"_a, R"pbdoc(Create a new ring buffer)pbdoc") + .def("append", &SampleRingBuffer::append, R"pbdoc(Append an item to the ring buffer.)pbdoc") + .def( + "extend", [](SampleRingBuffer &buf, std::vector vec) { buf.extend(vec); }, + R"pbdoc(Extend the ring buffer.)pbdoc") + .def("get", &SampleRingBuffer::get, R"pbdoc(Retrieve an item from the ring buffer, with offset relative to the read head.)pbdoc") + .def("get_capacity", &SampleRingBuffer::get_capacity, R"pbdoc(Returns the capacity of the ring buffer.)pbdoc"); + + py::class_(m, "SampleRingQueue", "A circular queue of audio samples with separate read/write heads") + .def(py::init(), "capacity"_a, R"pbdoc(Create a new ring queue)pbdoc") + .def("append", &SampleRingQueue::append, R"pbdoc(Append an item to the ring queue.)pbdoc") + .def( + "extend", [](SampleRingQueue &buf, std::vector vec) { buf.extend(vec); }, + R"pbdoc(Extend the ring queue.)pbdoc") + .def("pop", &SampleRingQueue::pop, R"pbdoc(Pop an item from the ring queue.)pbdoc") + .def("get_capacity", &SampleRingQueue::get_capacity, R"pbdoc(Returns the capacity of the ring queue.)pbdoc") + .def("get_filled_count", &SampleRingQueue::get_filled_count, R"pbdoc(Returns the number of items filled in the ring queue.)pbdoc"); + /*-------------------------------------------------------------------------------- * Buffer *-------------------------------------------------------------------------------*/ diff --git a/tests/test_buffer.py b/tests/test_buffer.py index f3795142..01cdc491 100644 --- a/tests/test_buffer.py +++ b/tests/test_buffer.py @@ -1,4 +1,4 @@ -from signalflow import Buffer, Buffer2D +from signalflow import Buffer, Buffer2D, SampleRingBuffer, SampleRingQueue from signalflow import SIGNALFLOW_INTERPOLATION_MODE_NONE, SIGNALFLOW_INTERPOLATION_MODE_LINEAR from signalflow import GraphNotCreatedException import numpy as np @@ -202,3 +202,37 @@ def test_buffer_2d(graph): assert b2d.get2D(1.5, 1.00) == 5 # TODO: Test with no interpolation + + +def test_ring_buffer(): + buf = SampleRingBuffer(128) + assert buf.get_capacity() == 128 + + assert buf.get(0) == 0.0 + buf.append(7) + buf.append(9) + buf.append(8) + assert buf.get(0) == 8 + assert buf.get(-1) == 9 + assert buf.get(-2) == 7 + + buf.extend([1, 2, 3]) + assert buf.get(0) == 3 + assert buf.get(-1) == 2 + assert buf.get(-2) == 1 + +def test_ring_queue(): + queue = SampleRingQueue(128) + assert queue.get_capacity() == 128 + assert queue.get_filled_count() == 0 + queue.append(7) + queue.append(8) + assert queue.get_filled_count() == 2 + assert queue.pop() == 7 + assert queue.pop() == 8 + assert queue.get_filled_count() == 0 + + queue.extend([1, 2, 3]) + assert queue.pop() == 1 + assert queue.pop() == 2 + assert queue.pop() == 3