From cbc037e055c285573a11359d28e84f7b5bc050a8 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 13 Apr 2020 00:33:16 +0200 Subject: [PATCH 1/3] Add RTSemaphore and tests --- src/CMakeLists.txt | 1 + src/sfizz/RTSemaphore.cpp | 254 ++++++++++++++++++++++++++++++++++++++ src/sfizz/RTSemaphore.h | 50 ++++++++ tests/CMakeLists.txt | 1 + tests/SemaphoreT.cpp | 68 ++++++++++ 5 files changed, 374 insertions(+) create mode 100644 src/sfizz/RTSemaphore.cpp create mode 100644 src/sfizz/RTSemaphore.h create mode 100644 tests/SemaphoreT.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b3d472df..aceb1165 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,7 @@ set (SFIZZ_SOURCES sfizz/SfzFilter.cpp sfizz/Curve.cpp sfizz/Wavetables.cpp + sfizz/RTSemaphore.cpp sfizz/Effects.cpp sfizz/effects/Nothing.cpp sfizz/effects/Filter.cpp diff --git a/src/sfizz/RTSemaphore.cpp b/src/sfizz/RTSemaphore.cpp new file mode 100644 index 00000000..7a3340d5 --- /dev/null +++ b/src/sfizz/RTSemaphore.cpp @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "RTSemaphore.h" +#include +#include +#include + +RTSemaphore::RTSemaphore(unsigned value) +{ + std::error_code ec; + init(ec, value); + if (ec) + throw std::system_error(ec); + good_ = true; +} + +RTSemaphore::RTSemaphore(std::error_code &ec, unsigned value) noexcept +{ + init(ec, value); + good_ = ec ? false : true; +} + +RTSemaphore::~RTSemaphore() noexcept +{ + if (good_) { + std::error_code ec; + destroy(ec); + } +} + +void RTSemaphore::post() +{ + std::error_code ec; + post(ec); + if (ec) + throw std::system_error(ec); +} + +void RTSemaphore::wait() +{ + std::error_code ec; + wait(ec); + if (ec) + throw std::system_error(ec); +} + +bool RTSemaphore::try_wait() +{ + std::error_code ec; + bool b = try_wait(ec); + if (ec) + throw std::system_error(ec); + return b; +} + +#if defined(__APPLE__) +void RTSemaphore::init(std::error_code &ec, unsigned value) +{ + ec.clear(); + kern_return_t ret = semaphore_create(mach_task_self(), &sem_, SYNC_POLICY_FIFO, value); + if (ret != KERN_SUCCESS) + ec = std::error_code(ret, mach_category()); +} + +void RTSemaphore::destroy(std::error_code &ec) +{ + ec.clear(); + kern_return_t ret = semaphore_destroy(mach_task_self(), sem_); + if (ret != KERN_SUCCESS) + ec = std::error_code(ret, mach_category()); +} + +void RTSemaphore::post(std::error_code &ec) noexcept +{ + ec.clear(); + kern_return_t ret = semaphore_signal(sem_); + if (ret != KERN_SUCCESS) + ec = std::error_code(ret, mach_category()); +} + +void RTSemaphore::wait(std::error_code &ec) noexcept +{ + ec.clear(); + do { + kern_return_t ret = semaphore_wait(sem_); + switch (ret) { + case KERN_SUCCESS: + return; + case KERN_ABORTED: + break; + default: + ec = std::error_code(ret, mach_category()); + return; + } + } while (1); +} + +bool RTSemaphore::try_wait(std::error_code &ec) noexcept +{ + ec.clear(); + do { + const mach_timespec_t timeout = {0, 0}; + kern_return_t ret = semaphore_timedwait(sem_, timeout); + switch (ret) { + case KERN_SUCCESS: + return true; + case KERN_OPERATION_TIMED_OUT: + return false; + case KERN_ABORTED: + break; + default: + ec = std::error_code(ret, mach_category()); + return false; + } + } while (1); +} + +const std::error_category &RTSemaphore::mach_category() +{ + class mach_category : public std::error_category { + public: + const char *name() const noexcept override + { + return "kern_return_t"; + } + + std::string message(int condition) const override + { + const char *str = mach_error_string(condition); + return str ? str : ""; + } + }; + + static const mach_category cat; + return cat; +} +#elif defined(_WIN32) +void RTSemaphore::init(std::error_code &ec, unsigned value) +{ + ec.clear(); + sem_ = CreateSemaphore(nullptr, value, LONG_MAX, nullptr); + if (!sem_) + ec = std::error_code(GetLastError(), std::system_category()); +} + +void RTSemaphore::destroy(std::error_code &ec) +{ + ec.clear(); + if (CloseHandle(sem_) == 0) + ec = std::error_code(GetLastError(), std::system_category()); +} + +void RTSemaphore::post(std::error_code &ec) noexcept +{ + ec.clear(); + if (ReleaseSemaphore(sem_, 1, nullptr) == 0) + ec = std::error_code(GetLastError(), std::system_category()); +} + +void RTSemaphore::wait(std::error_code &ec) noexcept +{ + ec.clear(); + DWORD ret = WaitForSingleObject(sem_, INFINITE); + switch (ret) { + case WAIT_OBJECT_0: + return; + case WAIT_FAILED: + ec = std::error_code(GetLastError(), std::system_category()); + return; + default: + ec = std::error_code(ret, std::system_category()); + return; + } +} + +bool RTSemaphore::try_wait(std::error_code &ec) noexcept +{ + ec.clear(); + DWORD ret = WaitForSingleObject(sem_, 0); + switch (ret) { + case WAIT_OBJECT_0: + return true; + case WAIT_TIMEOUT: + return false; + case WAIT_FAILED: + ec = std::error_code(GetLastError(), std::system_category()); + return false; + default: + ec = std::error_code(ret, std::system_category()); + return false; + } +} +#else +void RTSemaphore::init(std::error_code &ec, unsigned value) +{ + ec.clear(); + if (sem_init(&sem_, 0, value) != 0) + ec = std::error_code(errno, std::generic_category()); +} + +void RTSemaphore::destroy(std::error_code &ec) +{ + ec.clear(); + if (sem_destroy(&sem_) != 0) + ec = std::error_code(errno, std::generic_category()); +} + +void RTSemaphore::post(std::error_code &ec) noexcept +{ + ec.clear(); + while (sem_post(&sem_) != 0) { + int e = errno; + if (e != EINTR) { + ec = std::error_code(e, std::generic_category()); + return; + } + } +} + +void RTSemaphore::wait(std::error_code &ec) noexcept +{ + ec.clear(); + while (sem_wait(&sem_) != 0) { + int e = errno; + if (e != EINTR) { + ec = std::error_code(e, std::generic_category()); + return; + } + } +} + +bool RTSemaphore::try_wait(std::error_code &ec) noexcept +{ + ec.clear(); + do { + if (sem_trywait(&sem_) == 0) + return true; + int e = errno; + switch (e) { + case EINTR: + break; + case EAGAIN: + return false; + default: + ec = std::error_code(e, std::generic_category()); + return false; + } + } while (1); +} +#endif diff --git a/src/sfizz/RTSemaphore.h b/src/sfizz/RTSemaphore.h new file mode 100644 index 00000000..4ce01112 --- /dev/null +++ b/src/sfizz/RTSemaphore.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#if defined(__APPLE__) +#include +#elif defined(_WIN32) +#include +#else +#include +#endif +#include + +class RTSemaphore { +public: + explicit RTSemaphore(unsigned value = 0); + explicit RTSemaphore(std::error_code &ec, unsigned value = 0) noexcept; + ~RTSemaphore() noexcept; + + RTSemaphore(const RTSemaphore &) = delete; + RTSemaphore &operator=(const RTSemaphore &) = delete; + + explicit operator bool() const noexcept { return good_; } + + void post(); + void wait(); + bool try_wait(); + + void post(std::error_code &ec) noexcept; + void wait(std::error_code &ec) noexcept; + bool try_wait(std::error_code &ec) noexcept; + +private: + void init(std::error_code &ec, unsigned value); + void destroy(std::error_code &ec); + +private: +#if defined(__APPLE__) + semaphore_t sem_ {}; + static const std::error_category &mach_category(); +#elif defined(_WIN32) + HANDLE sem_ {}; +#else + sem_t sem_ {}; +#endif + bool good_ {}; +}; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 423c867e..345a052b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -29,6 +29,7 @@ set(SFIZZ_TEST_SOURCES RegionTriggersT.cpp FloatHelpersT.cpp WavetablesT.cpp + SemaphoreT.cpp ) add_executable(sfizz_tests ${SFIZZ_TEST_SOURCES}) diff --git a/tests/SemaphoreT.cpp b/tests/SemaphoreT.cpp new file mode 100644 index 00000000..cc4b22c2 --- /dev/null +++ b/tests/SemaphoreT.cpp @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "sfizz/RTSemaphore.h" +#include "catch2/catch.hpp" +#include + +TEST_CASE("[Semaphore] Basic operations") +{ + RTSemaphore sem; + + REQUIRE(sem.try_wait() == false); + + sem.post(); + REQUIRE(sem.try_wait() == true); + REQUIRE(sem.try_wait() == false); + + sem.post(); + sem.post(); + REQUIRE(sem.try_wait() == true); + REQUIRE(sem.try_wait() == true); + REQUIRE(sem.try_wait() == false); + + sem.post(); + sem.post(); + sem.wait(); + sem.wait(); + + REQUIRE(sem.try_wait() == false); +} + +TEST_CASE("[Semaphore] Counter initialization") +{ + RTSemaphore sem(3); + + REQUIRE(sem.try_wait() == true); + REQUIRE(sem.try_wait() == true); + REQUIRE(sem.try_wait() == true); + REQUIRE(sem.try_wait() == false); +} + +TEST_CASE("[Semaphore] Thread synchronization") +{ + RTSemaphore sem1; + RTSemaphore sem2; + constexpr int n = 1000; + + std::thread t1([&]() { + for (int i = 0; i < n; ++i) { + sem1.post(); + sem2.wait(); + } + }); + std::thread t2([&]() { + for (int i = 0; i < n; ++i) { + sem2.post(); + sem1.wait(); + } + }); + + t1.join(); + t2.join(); + REQUIRE(sem1.try_wait() == false); + REQUIRE(sem2.try_wait() == false); +} From 54b92f9f9d575b0368cc5409e84feee49c9798c3 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 13 Apr 2020 17:14:04 +0200 Subject: [PATCH 2/3] Try fixing the case-sensitivity test on Mac --- tests/FilesT.cpp | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 51f5e40f..75941298 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -8,6 +8,9 @@ #include "sfizz/SfzHelpers.h" #include "catch2/catch.hpp" #include "ghc/fs_std.hpp" +#if defined(__APPLE__) +#include // pathconf +#endif using namespace Catch::literals; using namespace sfz::literals; @@ -529,15 +532,26 @@ TEST_CASE("[Files] Looped regions taken from files and possibly overriden") TEST_CASE("[Files] Case sentitiveness") { -#ifndef WIN32 - sfz::Synth synth; - synth.loadSfzFile(fs::current_path() / "tests/TestFiles/case_insensitive.sfz"); - REQUIRE(synth.getNumRegions() == 4); - REQUIRE(synth.getRegionView(0)->sample == "dummy1.wav"); - REQUIRE(synth.getRegionView(1)->sample == "Regions/dummy.wav"); - REQUIRE(synth.getRegionView(2)->sample == "Regions/dummy.wav"); - REQUIRE(synth.getRegionView(3)->sample == "Regions/dummy.wav"); + const fs::path sfzFilePath = fs::current_path() / + "tests/TestFiles/case_insensitive.sfz"; + +#if defined(_WIN32) + const bool caseSensitiveFs = false; +#elif defined(__APPLE__) + const bool caseSensitiveFs = pathconf(sfzFilePath.string().c_str(), _PC_CASE_SENSITIVE) != 0; +#else + const bool caseSensitiveFs = true; #endif + + if (caseSensitiveFs) { + sfz::Synth synth; + synth.loadSfzFile(sfzFilePath); + REQUIRE(synth.getNumRegions() == 4); + REQUIRE(synth.getRegionView(0)->sample == "dummy1.wav"); + REQUIRE(synth.getRegionView(1)->sample == "Regions/dummy.wav"); + REQUIRE(synth.getRegionView(2)->sample == "Regions/dummy.wav"); + REQUIRE(synth.getRegionView(3)->sample == "Regions/dummy.wav"); + } } TEST_CASE("[Files] Empty file") From 99cb921b2c04b177a54aa764a993594a653ef8f2 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 13 Apr 2020 17:35:37 +0200 Subject: [PATCH 3/3] Make VST use the RTSemaphore from sfizz --- vst/RTSemaphore.h | 167 ---------------------------------------- vst/SfizzVstProcessor.h | 2 +- 2 files changed, 1 insertion(+), 168 deletions(-) delete mode 100644 vst/RTSemaphore.h diff --git a/vst/RTSemaphore.h b/vst/RTSemaphore.h deleted file mode 100644 index 5ec3c765..00000000 --- a/vst/RTSemaphore.h +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright Jean Pierre Cimalando 2018-2020. -// 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 -#if defined(__APPLE__) -#include -#elif defined(_WIN32) -#include -#include -#else -#include -#include -#endif -#include - -class RTSemaphore { -public: - explicit RTSemaphore(unsigned value = 0); - ~RTSemaphore(); - - RTSemaphore(const RTSemaphore &) = delete; - RTSemaphore &operator=(const RTSemaphore &) = delete; - - void post(); - void wait(); - bool try_wait(); - -private: -#if defined(__APPLE__) - semaphore_t sem_; -#elif defined(_WIN32) - HANDLE sem_; -#else - sem_t sem_; -#endif -}; - -#if defined(__APPLE__) -inline RTSemaphore::RTSemaphore(unsigned value) -{ - if (semaphore_create(mach_task_self(), &sem_, SYNC_POLICY_FIFO, value) != 0) - throw std::runtime_error("RTSemaphore::RTSemaphore"); -} - -inline RTSemaphore::~RTSemaphore() -{ - semaphore_destroy(mach_task_self(), sem_); -} - -inline void RTSemaphore::post() -{ - if (semaphore_signal(sem_) != KERN_SUCCESS) - throw std::runtime_error("RTSemaphore::post"); -} - -inline void RTSemaphore::wait() -{ - do { - switch (semaphore_wait(sem_)) { - case KERN_SUCCESS: - return; - case KERN_ABORTED: - break; - default: - throw std::runtime_error("RTSemaphore::wait"); - } - } while (1); -} - -inline bool RTSemaphore::try_wait() -{ - do { - const mach_timespec_t timeout = {0, 0}; - switch (semaphore_timedwait(sem_, timeout)) { - case KERN_SUCCESS: - return true; - case KERN_OPERATION_TIMED_OUT: - return false; - case KERN_ABORTED: - break; - default: - throw std::runtime_error("RTSemaphore::try_wait"); - } - } while (1); -} -#elif defined(_WIN32) -inline RTSemaphore::RTSemaphore(unsigned value) -{ - sem_ = CreateSemaphore(nullptr, value, LONG_MAX, nullptr); - if (!sem_) - throw std::runtime_error("RTSemaphore::RTSemaphore"); -} - -inline RTSemaphore::~RTSemaphore() -{ - CloseHandle(sem_); -} - -inline void RTSemaphore::post() -{ - if (!ReleaseSemaphore(sem_, 1, nullptr)) - throw std::runtime_error("RTSemaphore::post"); -} - -inline void RTSemaphore::wait() -{ - if (WaitForSingleObject(sem_, INFINITE) != WAIT_OBJECT_0) - throw std::runtime_error("RTSemaphore::wait"); -} - -inline bool RTSemaphore::try_wait() -{ - switch (WaitForSingleObject(sem_, 0)) { - case WAIT_OBJECT_0: - return true; - case WAIT_TIMEOUT: - return false; - default: - throw std::runtime_error("RTSemaphore::try_wait"); - } -} -#else -inline RTSemaphore::RTSemaphore(unsigned value) -{ - if (sem_init(&sem_, 0, value) != 0) - throw std::runtime_error("RTSemaphore::RTSemaphore"); -} - -inline RTSemaphore::~RTSemaphore() -{ - sem_destroy(&sem_); -} - -inline void RTSemaphore::post() -{ - while (sem_post(&sem_) != 0) { - if (errno != EINTR) - throw std::runtime_error("RTSemaphore::post"); - } -} - -inline void RTSemaphore::wait() -{ - while (sem_wait(&sem_) != 0) { - if (errno != EINTR) - throw std::runtime_error("RTSemaphore::wait"); - } -} - -inline bool RTSemaphore::try_wait() -{ - do { - if (sem_trywait(&sem_) == 0) - return true; - switch (errno) { - case EINTR: - break; - case EAGAIN: - return false; - default: - throw std::runtime_error("RTSemaphore::try_wait"); - } - } while (1); -} -#endif diff --git a/vst/SfizzVstProcessor.h b/vst/SfizzVstProcessor.h index 1ac71827..1ed7fb37 100644 --- a/vst/SfizzVstProcessor.h +++ b/vst/SfizzVstProcessor.h @@ -6,7 +6,7 @@ #pragma once #include "SfizzVstState.h" -#include "RTSemaphore.h" +#include "sfizz/RTSemaphore.h" #include "ring_buffer/ring_buffer.h" #include "public.sdk/source/vst/vstaudioeffect.h" #include