From 23a3508d819aff48671a53d04e3fe24ca24bb731 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 28 Mar 2020 14:34:55 +0100 Subject: [PATCH 1/3] Add the spsc ring buffer --- vst/CMakeLists.txt | 4 + vst/external/ring_buffer/LICENSE | 23 ++++ .../ring_buffer/ring_buffer/ring_buffer.cpp | 105 ++++++++++++++++++ .../ring_buffer/ring_buffer/ring_buffer.h | 69 ++++++++++++ .../ring_buffer/ring_buffer/ring_buffer.tcc | 61 ++++++++++ 5 files changed, 262 insertions(+) create mode 100644 vst/external/ring_buffer/LICENSE create mode 100644 vst/external/ring_buffer/ring_buffer/ring_buffer.cpp create mode 100644 vst/external/ring_buffer/ring_buffer/ring_buffer.h create mode 100644 vst/external/ring_buffer/ring_buffer/ring_buffer.tcc diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index 92228d45..377b7dd0 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -60,6 +60,10 @@ set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES plugin_add_vst3sdk(${VSTPLUGIN_PRJ_NAME}) plugin_add_vstgui(${VSTPLUGIN_PRJ_NAME}) +# Add the ring buffer +target_include_directories(${VSTPLUGIN_PRJ_NAME} PRIVATE "external/ring_buffer") +target_sources(${VSTPLUGIN_PRJ_NAME} PRIVATE "external/ring_buffer/ring_buffer/ring_buffer.cpp") + if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") target_link_libraries(${VSTPLUGIN_PRJ_NAME} PRIVATE "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/vst3.version") diff --git a/vst/external/ring_buffer/LICENSE b/vst/external/ring_buffer/LICENSE new file mode 100644 index 00000000..36b7cd93 --- /dev/null +++ b/vst/external/ring_buffer/LICENSE @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/vst/external/ring_buffer/ring_buffer/ring_buffer.cpp b/vst/external/ring_buffer/ring_buffer/ring_buffer.cpp new file mode 100644 index 00000000..bf0fcb9c --- /dev/null +++ b/vst/external/ring_buffer/ring_buffer/ring_buffer.cpp @@ -0,0 +1,105 @@ +// Copyright Jean Pierre Cimalando 2018. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "ring_buffer.h" +#include +#include + +#if defined(__cpp_if_constexpr) +# define if_constexpr if constexpr +#else +# define if_constexpr if +#endif + +template +Ring_Buffer_Ex::Ring_Buffer_Ex(size_t capacity) + : cap_(capacity + 1), + rbdata_(new uint8_t[capacity + 1]) +{ +} + +template +Ring_Buffer_Ex::~Ring_Buffer_Ex() +{ +} + +template +size_t Ring_Buffer_Ex::size_used() const +{ + const size_t rp = rp_, wp = wp_, cap = cap_; + return wp + ((wp < rp) ? cap : 0) - rp; +} + +template +bool Ring_Buffer_Ex::discard(size_t len) +{ + return getbytes_ex_(nullptr, len, true); +} + +template +size_t Ring_Buffer_Ex::size_free() const +{ + const size_t rp = rp_, wp = wp_, cap = cap_; + return rp + ((rp <= wp) ? cap : 0) - wp - 1; +} + +template +bool Ring_Buffer_Ex::getbytes_(void *data, size_t len) +{ + return getbytes_ex_(data, len, true); +} + +template +bool Ring_Buffer_Ex::peekbytes_(void *data, size_t len) const +{ + auto *ncthis = const_cast *>(this); + return ncthis->getbytes_ex_(data, len, false); +} + +template +bool Ring_Buffer_Ex::getbytes_ex_(void *data, size_t len, bool advp) +{ + if (size_used() < len) + return false; + + const size_t rp = rp_, cap = cap_; + const uint8_t *src = rbdata_.get(); + uint8_t *dst = (uint8_t *)data; + + if (data) { + const size_t taillen = std::min(len, cap - rp); + if_constexpr (Atomic) + std::atomic_thread_fence(std::memory_order_acquire); + std::copy_n(&src[rp], taillen, dst); + std::copy_n(src, len - taillen, dst + taillen); + } + + if (advp) + rp_ = (rp + len < cap) ? (rp + len) : (rp + len - cap); + return true; +} + +template +bool Ring_Buffer_Ex::putbytes_(const void *data, size_t len) +{ + if (size_free() < len) + return false; + + const size_t wp = wp_, cap = cap_; + const uint8_t *src = (const uint8_t *)data; + uint8_t *dst = rbdata_.get(); + + const size_t taillen = std::min(len, cap - wp); + std::copy_n(src, taillen, &dst[wp]); + std::copy_n(src + taillen, len - taillen, dst); + if_constexpr (Atomic) + std::atomic_thread_fence(std::memory_order_release); + + wp_ = (wp + len < cap) ? (wp + len) : (wp + len - cap); + return true; +} + +template class Ring_Buffer_Ex; +template class Ring_Buffer_Ex; diff --git a/vst/external/ring_buffer/ring_buffer/ring_buffer.h b/vst/external/ring_buffer/ring_buffer/ring_buffer.h new file mode 100644 index 00000000..395ba9e0 --- /dev/null +++ b/vst/external/ring_buffer/ring_buffer/ring_buffer.h @@ -0,0 +1,69 @@ +// Copyright Jean Pierre Cimalando 2018. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#pragma once +#include +#include +#include +#include +#include + +#if defined(__cpp_lib_atomic_is_always_lock_free) +static_assert( + std::atomic::is_always_lock_free, "atomic must be lock free"); +#endif + +//------------------------------------------------------------------------------ +template class Ring_Buffer_Ex; +typedef Ring_Buffer_Ex Ring_Buffer; + +//------------------------------------------------------------------------------ +template +class Basic_Ring_Buffer { +public: + // read operations + template bool get(T &x); + template bool get(T *x, size_t n); + template bool peek(T &x); + template bool peek(T *x, size_t n); + // write operations + template bool put(const T &x); + template bool put(const T *x, size_t n); +}; + +//------------------------------------------------------------------------------ +template +class Ring_Buffer_Ex final : + private Basic_Ring_Buffer> { +private: + typedef Basic_Ring_Buffer> Base; +public: + // initialization and cleanup + explicit Ring_Buffer_Ex(size_t capacity); + ~Ring_Buffer_Ex(); + // attributes + size_t capacity() const; + // read operations + size_t size_used() const; + bool discard(size_t len); + using Base::get; + using Base::peek; + // write operations + size_t size_free() const; + using Base::put; + +private: + size_t cap_{0}; + typename std::conditional, size_t>::type rp_{0}, wp_{0}; + std::unique_ptr rbdata_ {}; + friend Base; + bool getbytes_(void *data, size_t len); + bool peekbytes_(void *data, size_t len) const; + bool getbytes_ex_(void *data, size_t len, bool advp); + bool putbytes_(const void *data, size_t len); +}; + +//------------------------------------------------------------------------------ +#include "ring_buffer.tcc" diff --git a/vst/external/ring_buffer/ring_buffer/ring_buffer.tcc b/vst/external/ring_buffer/ring_buffer/ring_buffer.tcc new file mode 100644 index 00000000..b53b17f4 --- /dev/null +++ b/vst/external/ring_buffer/ring_buffer/ring_buffer.tcc @@ -0,0 +1,61 @@ +// Copyright Jean Pierre Cimalando 2018. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "ring_buffer.h" + +template +inline size_t Ring_Buffer_Ex::capacity() const +{ + return cap_ - 1; +} + +//------------------------------------------------------------------------------ +template +template +inline bool Basic_Ring_Buffer::get(T &x) +{ + return get(&x, 1); +} + +template +template +inline bool Basic_Ring_Buffer::get(T *x, size_t n) +{ + // static_assert(std::is_trivially_copyable::value, "ring_buffer: T must be trivially copyable"); + RB *self = static_cast(this); + return self->getbytes_(x, n * sizeof(T)); +} + +template +template +inline bool Basic_Ring_Buffer::peek(T &x) +{ + return peek(&x, 1); +} + +template +template +inline bool Basic_Ring_Buffer::peek(T *x, size_t n) +{ + // static_assert(std::is_trivially_copyable::value, "ring_buffer: T must be trivially copyable"); + RB *self = static_cast(this); + return self->peekbytes_(x, n * sizeof(T)); +} + +template +template +inline bool Basic_Ring_Buffer::put(const T &x) +{ + return put(&x, 1); +} + +template +template +inline bool Basic_Ring_Buffer::put(const T *x, size_t n) +{ + // static_assert(std::is_trivially_copyable::value, "ring_buffer: T must be trivially copyable"); + RB *self = static_cast(this); + return self->putbytes_(x, n * sizeof(T)); +} From 07668c7bd62c2da1bc7dc4591bcdff7ef3f68eda Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 28 Mar 2020 17:00:07 +0100 Subject: [PATCH 2/3] VST hard realtime fix and improvement --- vst/SfizzVstEditor.cpp | 2 +- vst/SfizzVstProcessor.cpp | 177 +++++++++++++++++++------------------- vst/SfizzVstProcessor.h | 31 ++++++- vst/SfizzVstState.h | 2 + 4 files changed, 121 insertions(+), 91 deletions(-) diff --git a/vst/SfizzVstEditor.cpp b/vst/SfizzVstEditor.cpp index 4205c4f6..8448b218 100644 --- a/vst/SfizzVstEditor.cpp +++ b/vst/SfizzVstEditor.cpp @@ -199,7 +199,7 @@ void SfizzVstEditor::loadSfzFile(const std::string& filePath) msg->setMessageID("LoadSfz"); Vst::IAttributeList* attr = msg->getAttributes(); - attr->setString("File", Steinberg::String(filePath.c_str()).text()); + attr->setBinary("File", filePath.data(), filePath.size()); ctl->sendMessage(msg); msg->release(); diff --git a/vst/SfizzVstProcessor.cpp b/vst/SfizzVstProcessor.cpp index daf141d1..46fab205 100644 --- a/vst/SfizzVstProcessor.cpp +++ b/vst/SfizzVstProcessor.cpp @@ -21,7 +21,7 @@ constexpr int fastRound(T x) } SfizzVstProcessor::SfizzVstProcessor() - : _fifoToWorker(1024) + : _fifoToWorker(64 * 1024) { setControllerClass(SfizzVstController::cid); } @@ -119,14 +119,6 @@ tresult PLUGIN_API SfizzVstProcessor::setActive(TBool state) _fileChangePeriod = static_cast(processSetup.sampleRate); - if (!_msgCheckShouldReload) { - auto msg = IPtr::adopt(allocateMessage()); - if (!msg) - return kResultFalse; - msg->setMessageID("CheckShouldReload"); - _msgCheckShouldReload = msg; - } - _workRunning = true; _worker = std::thread([this]() { doBackgroundWork(); }); } else { @@ -183,11 +175,8 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data) _fileChangeCounter += numFrames; if (_fileChangeCounter > _fileChangePeriod) { _fileChangeCounter %= _fileChangePeriod; - Vst::IMessage* msg = _msgCheckShouldReload.get(); - if (_fifoToWorker.push(msg)) { - msg->addRef(); + if (writeWorkerMessage("CheckShouldReload", nullptr, 0)) _semaToWorker.post(); - } } return kResultTrue; @@ -214,47 +203,26 @@ void SfizzVstProcessor::processParameterChanges(Vst::IParameterChanges& pc) break; case kPidNumVoices: if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue) { - Vst::IMessage* msg = allocateMessage(); - if (!msg) - break; - msg->setMessageID("SetNumVoices"); - Vst::IAttributeList* attr = msg->getAttributes(); - attr->setInt("NumVoices", static_cast(kParamNumVoicesRange.denormalize(value))); - if (!_fifoToWorker.push(msg)) { - msg->release(); - break; - } - _semaToWorker.post(); + int32 data = static_cast(kParamNumVoicesRange.denormalize(value)); + _state.numVoices = data; + if (writeWorkerMessage("SetNumVoices", &data, sizeof(data))) + _semaToWorker.post(); } break; case kPidOversampling: if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue) { - Vst::IMessage* msg = allocateMessage(); - if (!msg) - break; - msg->setMessageID("SetOversampling"); - Vst::IAttributeList* attr = msg->getAttributes(); - attr->setInt("Oversampling", static_cast(kParamOversamplingRange.denormalize(value))); - if (!_fifoToWorker.push(msg)) { - msg->release(); - break; - } - _semaToWorker.post(); + int32 data = static_cast(kParamOversamplingRange.denormalize(value)); + _state.oversamplingLog2 = data; + if (writeWorkerMessage("SetOversampling", &data, sizeof(data))) + _semaToWorker.post(); } break; case kPidPreloadSize: if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue) { - Vst::IMessage* msg = allocateMessage(); - if (!msg) - break; - msg->setMessageID("SetPreloadSize"); - Vst::IAttributeList* attr = msg->getAttributes(); - attr->setInt("PreloadSize", static_cast(kParamPreloadSizeRange.denormalize(value))); - if (!_fifoToWorker.push(msg)) { - msg->release(); - break; - } - _semaToWorker.post(); + int32 data = static_cast(kParamPreloadSizeRange.denormalize(value)); + _state.preloadSize = data; + if (writeWorkerMessage("SetPreloadSize", &data, sizeof(data))) + _semaToWorker.post(); } break; } @@ -335,17 +303,29 @@ int SfizzVstProcessor::convertVelocityFromFloat(float x) tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message) { + // Note(jpc) this notification is not necessarily handled by the RT thread + tresult result = AudioEffect::notify(message); if (result != kResultFalse) return result; - if (!_fifoToWorker.push(message)) - return kOutOfMemory; + const char* id = message->getMessageID(); + Vst::IAttributeList* attr = message->getAttributes(); - message->addRef(); - _semaToWorker.post(); + if (!std::strcmp(id, "LoadSfz")) { + const void* data = nullptr; + uint32 size = 0; + result = attr->getBinary("File", data, size); - return kResultTrue; + if (result != kResultTrue) + return result; + + std::lock_guard lock(_processMutex); + _state.sfzFile.assign(static_cast(data), size); + _synth->loadSfzFile(_state.sfzFile); + } + + return result; } FUnknown* SfizzVstProcessor::createInstance(void*) @@ -355,61 +335,38 @@ FUnknown* SfizzVstProcessor::createInstance(void*) void SfizzVstProcessor::doBackgroundWork() { - constexpr uint32 maxPathLen = 32768; - for (;;) { _semaToWorker.wait(); if (!_workRunning) break; - Vst::IMessage* msg; - if (!_fifoToWorker.pop(msg)) { + RTMessagePtr msg = readWorkerMessage(); + if (!msg) { fprintf(stderr, "[Sfizz] message synchronization error in worker\n"); std::abort(); } - const char* id = msg->getMessageID(); - Vst::IAttributeList* attr = msg->getAttributes(); + const char* id = msg->type; - if (!std::strcmp(id, "LoadSfz")) { - std::vector path(maxPathLen + 1); - if (attr->getString("File", path.data(), maxPathLen) == kResultTrue) { - std::lock_guard lock(_processMutex); - _state.sfzFile = Steinberg::String(path.data()).text8(); - _synth->loadSfzFile(_state.sfzFile); - } - } - else if (!std::strcmp(id, "SetNumVoices")) { - int64 value; - if (attr->getInt("NumVoices", value) == kResultTrue) { - _state.numVoices = value; - _synth->setNumVoices(value); - } + if (!std::strcmp(id, "SetNumVoices")) { + int32 value = *msg->payload(); + _synth->setNumVoices(value); } else if (!std::strcmp(id, "SetOversampling")) { - int64 value; - if (attr->getInt("Oversampling", value) == kResultTrue) { - _state.oversamplingLog2 = value; - _synth->setOversamplingFactor(1 << value); - } + int32 value = *msg->payload(); + _synth->setOversamplingFactor(1 << value); } else if (!std::strcmp(id, "SetPreloadSize")) { - int64 value; - if (attr->getInt("PreloadSize", value) == kResultTrue) { - _state.preloadSize = value; - _synth->setPreloadSize(value); - } + int32 value = *msg->payload(); + _synth->setPreloadSize(value); } else if (!std::strcmp(id, "CheckShouldReload")) { if (_synth->shouldReloadFile()) { fprintf(stderr, "[Sfizz] file has changed, reloading\n"); - std::lock_guard lock(_processMutex); _synth->loadSfzFile(_state.sfzFile); } } - - msg->release(); } } @@ -423,15 +380,61 @@ void SfizzVstProcessor::stopBackgroundWork() _worker.join(); while (_semaToWorker.try_wait()) { - Vst::IMessage* msg; - if (!_fifoToWorker.pop(msg)) { + if (!discardWorkerMessage()) { fprintf(stderr, "[Sfizz] message synchronization error in processor\n"); std::abort(); } - msg->release(); } } +bool SfizzVstProcessor::writeWorkerMessage(const char* type, const void* data, uintptr_t size) +{ + RTMessage header; + header.type = type; + header.size = size; + + if (_fifoToWorker.size_free() < sizeof(header) + size) + return false; + + _fifoToWorker.put(header); + _fifoToWorker.put(static_cast(data), size); + return true; +} + +SfizzVstProcessor::RTMessagePtr SfizzVstProcessor::readWorkerMessage() +{ + RTMessage header; + + if (!_fifoToWorker.peek(header)) + return nullptr; + if (_fifoToWorker.size_used() < sizeof(header) + header.size) + return nullptr; + + RTMessagePtr msg { reinterpret_cast(std::malloc(sizeof(header) + header.size)) }; + if (!msg) + throw std::bad_alloc(); + + msg->type = header.type; + msg->size = header.size; + _fifoToWorker.discard(sizeof(header)); + _fifoToWorker.get(const_cast(msg->payload()), header.size); + + return msg; +} + +bool SfizzVstProcessor::discardWorkerMessage() +{ + RTMessage header; + + if (!_fifoToWorker.peek(header)) + return false; + if (_fifoToWorker.size_used() < sizeof(header) + header.size) + return false; + + _fifoToWorker.discard(sizeof(header) + header.size); + return true; +} + /* Note(jpc) Generated at random with uuidgen. Can't find docs on it... maybe it's to register somewhere? diff --git a/vst/SfizzVstProcessor.h b/vst/SfizzVstProcessor.h index a8ed43e1..1ac71827 100644 --- a/vst/SfizzVstProcessor.h +++ b/vst/SfizzVstProcessor.h @@ -7,12 +7,13 @@ #pragma once #include "SfizzVstState.h" #include "RTSemaphore.h" +#include "ring_buffer/ring_buffer.h" #include "public.sdk/source/vst/vstaudioeffect.h" -#include "public.sdk/source/vst/utility/ringbuffer.h" #include #include #include #include +#include using namespace Steinberg; @@ -51,16 +52,40 @@ private: // worker and thread sync std::thread _worker; volatile bool _workRunning = false; - Steinberg::OneReaderOneWriter::RingBuffer _fifoToWorker; + Ring_Buffer _fifoToWorker; RTSemaphore _semaToWorker; std::mutex _processMutex; // file modification periodic checker uint32 _fileChangeCounter = 0; uint32 _fileChangePeriod = 0; - IPtr _msgCheckShouldReload; + + // messaging + struct RTMessage { + const char* type; + uintptr_t size; + // 32-bit aligned data after header + template const T* payload() const; + }; + struct RTMessageDelete { + void operator()(RTMessage* x) const noexcept { std::free(x); } + }; + typedef std::unique_ptr RTMessagePtr; // worker void doBackgroundWork(); void stopBackgroundWork(); + // writer + bool writeWorkerMessage(const char* type, const void* data, uintptr_t size); + // reader + RTMessagePtr readWorkerMessage(); + bool discardWorkerMessage(); }; + +//------------------------------------------------------------------------------ + +template const T* SfizzVstProcessor::RTMessage::payload() const +{ + return reinterpret_cast( + reinterpret_cast(this) + sizeof(*this)); +} diff --git a/vst/SfizzVstState.h b/vst/SfizzVstState.h index 5f3a0fb0..eed212e8 100644 --- a/vst/SfizzVstState.h +++ b/vst/SfizzVstState.h @@ -31,6 +31,8 @@ enum { class SfizzVstState { public: + SfizzVstState() { sfzFile.reserve(8192); } + std::string sfzFile; float volume = 0; int32 numVoices = 64; From 7a7dc43e516ee6e4bcc9cb1dfabeedaafc1c4cdd Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 28 Mar 2020 17:55:38 +0100 Subject: [PATCH 3/3] Update the header search path of clang-tidy --- scripts/run_clang_tidy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run_clang_tidy.sh b/scripts/run_clang_tidy.sh index 782c1e0f..bcb695f2 100755 --- a/scripts/run_clang_tidy.sh +++ b/scripts/run_clang_tidy.sh @@ -30,4 +30,4 @@ clang-tidy \ vst/SfizzVstState.cpp \ -- -Iexternal/abseil-cpp -Isrc/external -Isrc/external/pugixml/src \ -Isrc/sfizz -Isrc -Isrc/external/spline \ - -Ivst -Ivst/external/VST_SDK/VST3_SDK -Ivst/external/VST_SDK/VST3_SDK/vstgui4 -DNDEBUG + -Ivst -Ivst/external/VST_SDK/VST3_SDK -Ivst/external/VST_SDK/VST3_SDK/vstgui4 -Ivst/external/ring_buffer -DNDEBUG