#include "ringbuffercache.h" #include "remotebuffer.h" #include RingBufferCache::RingBufferCache( std::shared_ptr underlyingReadable, int pageSize, int pageCount) : RingBufferCache(underlyingReadable->getChannelCount(), pageSize, pageCount) { assert(underlyingReadable != nullptr); assert(pageSize > 0); assert(pageCount > 0); this->underlyingReadable = std::move(underlyingReadable); } RingBufferCache::RingBufferCache(int channelCount, int pageSize, int pageCount) : pageSize(pageSize), pageCount(pageCount) { for (int i = 0; i < pageCount; i++) { pages.emplace_back(channelCount, pageSize); } } RingBufferCache::~RingBufferCache() {} void RingBufferCache::setUnderlyingReadable( std::shared_ptr underlyingReadable) { assert(underlyingReadable != nullptr); this->underlyingReadable = std::move(underlyingReadable); for (auto &page : pages) { page.startFrame = -1; } } void RingBufferCache::read(int frameOffset, BufferF32 output) { if (underlyingReadable == nullptr) { output.fill(0); return; } requestSerial += 1; if (requestSerial == 0) { for (auto &page : pages) { page.requestedSerial = 0; } requestSerial = 1; } const int outputFrameCount = output.getFrameCount(); assert(frameOffset >= 0 && frameOffset + outputFrameCount <= underlyingReadable->getFrameCount()); assert(output.getChannelCount() == underlyingReadable->getChannelCount()); int framesRead = 0; while (framesRead < outputFrameCount) { auto &page = getPageForFrame(frameOffset + framesRead); page.requestedSerial = requestSerial; int offsetInPage = frameOffset + framesRead - page.startFrame; int framesToReadFromPage = std::min(outputFrameCount - framesRead, pageSize - offsetInPage); auto outBuf = output.slice(framesRead, framesRead + framesToReadFromPage); outBuf.set(0, page.buffer.slice(offsetInPage, offsetInPage + framesToReadFromPage)); framesRead += framesToReadFromPage; } } RingBufferCache::Page &RingBufferCache::getPageForFrame(int frame) { for (int i = 0; i < pageCount; i++) { auto &page = pages[i]; if (page.startFrame >= 0 && frame >= page.startFrame && frame < page.startFrame + pageSize) { return page; } } auto &newPage = pages[findEmptyPageIndex()]; newPage.startFrame = frame - (frame % pageSize); int framesToRead = std::min(pageSize, underlyingReadable->getFrameCount() - newPage.startFrame); underlyingReadable->read(newPage.startFrame, newPage.buffer.slice(0, framesToRead)); if (framesToRead < pageSize) { newPage.buffer.slice(framesToRead).fill(0); } return newPage; } int RingBufferCache::findEmptyPageIndex() { // find an empty page (startFrame == -1) or the page with the oldest serial int oldestPageIndex = 0; for (int i = 1; i < pageCount; i++) { if (pages[i].startFrame == -1) { return i; } if (pages[i].requestedSerial < pages[oldestPageIndex].requestedSerial) { oldestPageIndex = i; } } return oldestPageIndex; } TEST_CASE("ring buffer cache tests", "[ringbuffercache]") { const int totalFrames = 44100; auto buf = std::make_shared(1, totalFrames); buf->noise(); auto rab = std::make_shared(44100, buf); for (auto &sizeCombo : std::vector>{{128, 1024}, {1024, 1024}, {2048, 1024}, {1234, 1024}, {1024, 128}, {1025, 127}}) { auto [outBufferSize, pageSize] = sizeCombo; auto cache = std::make_shared(rab, pageSize, 10); BufferF32 out(1, outBufferSize); // sequential for (int i = 0; i < totalFrames / outBufferSize; i++) { cache->read(i * outBufferSize, out); bool match = true; auto refData = buf->getChannelData(0) + i * outBufferSize; auto outData = out.getChannelData(0); for (int j = 0; j < out.getFrameCount(); j++) { if (refData[j] != outData[j]) { match = false; break; } } REQUIRE(match); } // random for (int i = 0; i < 100; i++) { int offset = (286473 + i * 123471) % (totalFrames - outBufferSize); cache->read(offset, out); bool match = true; auto refData = buf->getChannelData(0) + offset; auto outData = out.getChannelData(0); for (int j = 0; j < out.getFrameCount(); j++) { if (refData[j] != outData[j]) { match = false; break; } } REQUIRE(match); } } }