Merge pull request #169 from paulfd/try-locks
Replace AtomicGuards by mutexes and use RTSemaphore in the background loader
This commit is contained in:
commit
ec2373e678
12 changed files with 100 additions and 241 deletions
|
|
@ -1,114 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief This file contains a pair of RAII helpers that handle some form
|
|
||||||
* of lock-free mutex-type protection adapter to audio applications where you have 1 priority thread
|
|
||||||
* that should never block and would rather return silence than wait, and another low-priority
|
|
||||||
* thread that handles long computations.
|
|
||||||
*
|
|
||||||
* @code{.cpp}
|
|
||||||
*
|
|
||||||
* // Somewhere in a class...
|
|
||||||
* std::atomic<bool> canEnterCallback;
|
|
||||||
* std::atomic<bool> inCallback;
|
|
||||||
*
|
|
||||||
* void functionThatSuspendsCallback()
|
|
||||||
* {
|
|
||||||
* AtomicDisabler callbackDisabler { canEnterCallback };
|
|
||||||
*
|
|
||||||
* while (inCallback) {
|
|
||||||
* std::this_thread::sleep_for(1ms);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* // Do your thing.
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* void callback(int samplesPerBlock) noexcept
|
|
||||||
* {
|
|
||||||
* AtomicGuard callbackGuard { inCallback };
|
|
||||||
* if (!canEnterCallback)
|
|
||||||
* return;
|
|
||||||
*
|
|
||||||
* // Do your thing.
|
|
||||||
* }
|
|
||||||
* @endcode
|
|
||||||
* There are probably many ways to improve these and probably even debug them.
|
|
||||||
* The spinlocking itself could be integrated in the constructor, although the
|
|
||||||
* check for return in the callback could not.
|
|
||||||
*/
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
namespace sfz
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @brief Simple class to set an atomic to true and automatically set it back to false on
|
|
||||||
* destruction.
|
|
||||||
*
|
|
||||||
* You call it like this assuming you need indicate that you are in e.g. a callback
|
|
||||||
* @code{.cpp}
|
|
||||||
* void functionToProtect()
|
|
||||||
* {
|
|
||||||
* AtomicGuard { guard };
|
|
||||||
*
|
|
||||||
* // Do stuff, the atomic will be set back to false as soon as you're back
|
|
||||||
* }
|
|
||||||
* @endcode
|
|
||||||
* Note that this is not thread-safe at all, in the sense that it is only meant to be
|
|
||||||
* used with 2 threads along with the AtomicDisabler. One thread uses AtomicGuards, the other
|
|
||||||
* AtomicDisablers, and no other contending thread can share this pair of atomics.
|
|
||||||
*/
|
|
||||||
class AtomicGuard
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AtomicGuard() = delete;
|
|
||||||
AtomicGuard(std::atomic<bool>& guard)
|
|
||||||
: guard(guard)
|
|
||||||
{
|
|
||||||
guard = true;
|
|
||||||
}
|
|
||||||
~AtomicGuard()
|
|
||||||
{
|
|
||||||
guard = false;
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
std::atomic<bool>& guard;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Simple class to set an atomic to false and automatically set it back to true on
|
|
||||||
* destruction.
|
|
||||||
*
|
|
||||||
* You call it like this assuming you need to disable e.g. a callback
|
|
||||||
* @code{.cpp}
|
|
||||||
* void functionThatDisableAnotherFunction()
|
|
||||||
* {
|
|
||||||
* AtomicDisabler { disabler };
|
|
||||||
*
|
|
||||||
* // Do stuff, the atomic will be set back to true as soon as you're back
|
|
||||||
* }
|
|
||||||
* @endcode
|
|
||||||
* Note that this is not thread-safe at all, in the sense that it is only meant to be
|
|
||||||
* used with 2 threads along with the AtomicGuard. One thread uses AtomicGuards, the other
|
|
||||||
* AtomicDisabler, and no other contending thread can share this pair of atomics.
|
|
||||||
*/
|
|
||||||
class AtomicDisabler
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AtomicDisabler() = delete;
|
|
||||||
AtomicDisabler(std::atomic<bool>& allowed)
|
|
||||||
: allowed(allowed)
|
|
||||||
{
|
|
||||||
allowed = false;
|
|
||||||
}
|
|
||||||
~AtomicDisabler()
|
|
||||||
{
|
|
||||||
allowed = true;
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
std::atomic<bool>& allowed;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
#include "EQPool.h"
|
#include "EQPool.h"
|
||||||
#include "AtomicGuard.h"
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include "absl/algorithm/container.h"
|
#include "absl/algorithm/container.h"
|
||||||
#include "SIMDHelpers.h"
|
#include "SIMDHelpers.h"
|
||||||
|
|
@ -108,8 +107,8 @@ sfz::EQPool::EQPool(const MidiState& state, int numEQs)
|
||||||
|
|
||||||
sfz::EQHolderPtr sfz::EQPool::getEQ(const EQDescription& description, unsigned numChannels, float velocity)
|
sfz::EQHolderPtr sfz::EQPool::getEQ(const EQDescription& description, unsigned numChannels, float velocity)
|
||||||
{
|
{
|
||||||
AtomicGuard guard { givingOutEQs };
|
const std::unique_lock<std::mutex> lock { eqGuard, std::try_to_lock };
|
||||||
if (!canGiveOutEQs)
|
if (!lock.owns_lock())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
auto eq = absl::c_find_if(eqs, [](const EQHolderPtr& holder) {
|
auto eq = absl::c_find_if(eqs, [](const EQHolderPtr& holder) {
|
||||||
|
|
@ -132,10 +131,7 @@ size_t sfz::EQPool::getActiveEQs() const
|
||||||
|
|
||||||
size_t sfz::EQPool::setnumEQs(size_t numEQs)
|
size_t sfz::EQPool::setnumEQs(size_t numEQs)
|
||||||
{
|
{
|
||||||
AtomicDisabler disabler { canGiveOutEQs };
|
const std::lock_guard<std::mutex> eqLock { eqGuard };
|
||||||
|
|
||||||
while(givingOutEQs)
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
|
|
||||||
auto eqIterator = eqs.begin();
|
auto eqIterator = eqs.begin();
|
||||||
auto eqSentinel = eqs.rbegin();
|
auto eqSentinel = eqs.rbegin();
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include "MidiState.h"
|
#include "MidiState.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
namespace sfz
|
namespace sfz
|
||||||
{
|
{
|
||||||
|
|
@ -114,8 +115,7 @@ public:
|
||||||
*/
|
*/
|
||||||
void setSampleRate(float sampleRate);
|
void setSampleRate(float sampleRate);
|
||||||
private:
|
private:
|
||||||
std::atomic<bool> givingOutEQs { false };
|
std::mutex eqGuard;
|
||||||
std::atomic<bool> canGiveOutEQs { true };
|
|
||||||
float sampleRate { config::defaultSampleRate };
|
float sampleRate { config::defaultSampleRate };
|
||||||
const MidiState& midiState;
|
const MidiState& midiState;
|
||||||
std::vector<EQHolderPtr> eqs;
|
std::vector<EQHolderPtr> eqs;
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Debug.h"
|
#include "Debug.h"
|
||||||
#include "Oversampler.h"
|
#include "Oversampler.h"
|
||||||
#include "AtomicGuard.h"
|
|
||||||
#include "absl/types/span.h"
|
#include "absl/types/span.h"
|
||||||
#include "absl/strings/match.h"
|
#include "absl/strings/match.h"
|
||||||
#include "absl/memory/memory.h"
|
#include "absl/memory/memory.h"
|
||||||
|
|
@ -106,6 +105,12 @@ sfz::FilePool::FilePool(sfz::Logger& logger)
|
||||||
sfz::FilePool::~FilePool()
|
sfz::FilePool::~FilePool()
|
||||||
{
|
{
|
||||||
quitThread = true;
|
quitThread = true;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < threadPool.size(); ++i) {
|
||||||
|
std::error_code ec;
|
||||||
|
workerBarrier.post(ec);
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& thread: threadPool)
|
for (auto& thread: threadPool)
|
||||||
thread.join();
|
thread.join();
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +287,12 @@ sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) n
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
workerBarrier.post(ec);
|
||||||
|
ASSERT(!ec);
|
||||||
|
|
||||||
emptyPromises.pop_back();
|
emptyPromises.pop_back();
|
||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -301,10 +311,7 @@ void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept
|
||||||
|
|
||||||
void sfz::FilePool::tryToClearPromises()
|
void sfz::FilePool::tryToClearPromises()
|
||||||
{
|
{
|
||||||
AtomicDisabler disabler { canAddPromisesToClear };
|
const std::lock_guard<std::mutex> promiseLock { promiseGuard };
|
||||||
|
|
||||||
while (addingPromisesToClear)
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
|
|
||||||
for (auto& promise: promisesToClear) {
|
for (auto& promise: promisesToClear) {
|
||||||
if (promise->dataStatus != FilePromise::DataStatus::Wait)
|
if (promise->dataStatus != FilePromise::DataStatus::Wait)
|
||||||
|
|
@ -333,8 +340,11 @@ void sfz::FilePool::loadingThread() noexcept
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
workerBarrier.wait(ec);
|
||||||
|
ASSERT(!ec);
|
||||||
|
|
||||||
if (!promiseQueue.try_pop(promise)) {
|
if (!promiseQueue.try_pop(promise)) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -376,9 +386,8 @@ void sfz::FilePool::clear()
|
||||||
|
|
||||||
void sfz::FilePool::cleanupPromises() noexcept
|
void sfz::FilePool::cleanupPromises() noexcept
|
||||||
{
|
{
|
||||||
AtomicGuard guard { addingPromisesToClear };
|
const std::unique_lock<std::mutex> lock { promiseGuard, std::try_to_lock };
|
||||||
|
if (!lock.owns_lock())
|
||||||
if (!canAddPromisesToClear)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// The garbage collection cleared the data from these so we can move them
|
// The garbage collection cleared the data from these so we can move them
|
||||||
|
|
@ -444,6 +453,10 @@ uint32_t sfz::FilePool::getPreloadSize() const noexcept
|
||||||
void sfz::FilePool::emptyFileLoadingQueues() noexcept
|
void sfz::FilePool::emptyFileLoadingQueues() noexcept
|
||||||
{
|
{
|
||||||
emptyQueue = true;
|
emptyQueue = true;
|
||||||
|
std::error_code ec;
|
||||||
|
workerBarrier.post(ec);
|
||||||
|
ASSERT(!ec);
|
||||||
|
|
||||||
while (emptyQueue)
|
while (emptyQueue)
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Defaults.h"
|
#include "Defaults.h"
|
||||||
#include "LeakDetector.h"
|
#include "LeakDetector.h"
|
||||||
|
#include "RTSemaphore.h"
|
||||||
#include "AudioBuffer.h"
|
#include "AudioBuffer.h"
|
||||||
#include "AudioSpan.h"
|
#include "AudioSpan.h"
|
||||||
#include "SIMDHelpers.h"
|
#include "SIMDHelpers.h"
|
||||||
|
|
@ -38,6 +39,7 @@
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
using AudioBufferPtr = std::shared_ptr<AudioBuffer<float>>;
|
using AudioBufferPtr = std::shared_ptr<AudioBuffer<float>>;
|
||||||
|
|
@ -256,16 +258,16 @@ private:
|
||||||
uint32_t preloadSize { config::preloadSize };
|
uint32_t preloadSize { config::preloadSize };
|
||||||
Oversampling oversamplingFactor { config::defaultOversamplingFactor };
|
Oversampling oversamplingFactor { config::defaultOversamplingFactor };
|
||||||
// Signals
|
// Signals
|
||||||
bool quitThread { false };
|
volatile bool quitThread { false };
|
||||||
bool emptyQueue { false };
|
volatile bool emptyQueue { false };
|
||||||
std::atomic<int> threadsLoading { 0 };
|
std::atomic<int> threadsLoading { 0 };
|
||||||
|
RTSemaphore workerBarrier;
|
||||||
|
|
||||||
// File promises data structures along with their guards.
|
// File promises data structures along with their guards.
|
||||||
std::vector<FilePromisePtr> emptyPromises;
|
std::vector<FilePromisePtr> emptyPromises;
|
||||||
std::vector<FilePromisePtr> temporaryFilePromises;
|
std::vector<FilePromisePtr> temporaryFilePromises;
|
||||||
std::vector<FilePromisePtr> promisesToClear;
|
std::vector<FilePromisePtr> promisesToClear;
|
||||||
std::atomic<bool> addingPromisesToClear { false };
|
std::mutex promiseGuard;
|
||||||
std::atomic<bool> canAddPromisesToClear { true };
|
|
||||||
|
|
||||||
// Preloaded data
|
// Preloaded data
|
||||||
absl::flat_hash_map<absl::string_view, FileDataHandle> preloadedFiles;
|
absl::flat_hash_map<absl::string_view, FileDataHandle> preloadedFiles;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#include "FilterPool.h"
|
#include "FilterPool.h"
|
||||||
#include "SIMDHelpers.h"
|
#include "SIMDHelpers.h"
|
||||||
#include "absl/algorithm/container.h"
|
#include "absl/algorithm/container.h"
|
||||||
#include "AtomicGuard.h"
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
|
|
@ -109,8 +108,8 @@ sfz::FilterPool::FilterPool(const MidiState& state, int numFilters)
|
||||||
|
|
||||||
sfz::FilterHolderPtr sfz::FilterPool::getFilter(const FilterDescription& description, unsigned numChannels, int noteNumber, float velocity)
|
sfz::FilterHolderPtr sfz::FilterPool::getFilter(const FilterDescription& description, unsigned numChannels, int noteNumber, float velocity)
|
||||||
{
|
{
|
||||||
AtomicGuard guard { givingOutFilters };
|
const std::unique_lock<std::mutex> lock { filterGuard, std::try_to_lock };
|
||||||
if (!canGiveOutFilters)
|
if (!lock.owns_lock())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
auto filter = absl::c_find_if(filters, [](const FilterHolderPtr& holder) {
|
auto filter = absl::c_find_if(filters, [](const FilterHolderPtr& holder) {
|
||||||
|
|
@ -133,10 +132,7 @@ size_t sfz::FilterPool::getActiveFilters() const
|
||||||
|
|
||||||
size_t sfz::FilterPool::setNumFilters(size_t numFilters)
|
size_t sfz::FilterPool::setNumFilters(size_t numFilters)
|
||||||
{
|
{
|
||||||
AtomicDisabler disabler { canGiveOutFilters };
|
const std::lock_guard<std::mutex> filterLock { filterGuard };
|
||||||
|
|
||||||
while(givingOutFilters)
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
|
|
||||||
auto filterIterator = filters.begin();
|
auto filterIterator = filters.begin();
|
||||||
auto filterSentinel = filters.rbegin();
|
auto filterSentinel = filters.rbegin();
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include "MidiState.h"
|
#include "MidiState.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
namespace sfz
|
namespace sfz
|
||||||
{
|
{
|
||||||
|
|
@ -118,8 +119,7 @@ public:
|
||||||
*/
|
*/
|
||||||
void setSampleRate(float sampleRate);
|
void setSampleRate(float sampleRate);
|
||||||
private:
|
private:
|
||||||
std::atomic<bool> givingOutFilters { false };
|
std::mutex filterGuard;
|
||||||
std::atomic<bool> canGiveOutFilters { true };
|
|
||||||
float sampleRate { config::defaultSampleRate };
|
float sampleRate { config::defaultSampleRate };
|
||||||
const MidiState& midiState;
|
const MidiState& midiState;
|
||||||
std::vector<FilterHolderPtr> filters;
|
std::vector<FilterHolderPtr> filters;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ RTSemaphore::RTSemaphore(unsigned value)
|
||||||
good_ = true;
|
good_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
RTSemaphore::RTSemaphore(std::error_code &ec, unsigned value) noexcept
|
RTSemaphore::RTSemaphore(std::error_code& ec, unsigned value) noexcept
|
||||||
{
|
{
|
||||||
init(ec, value);
|
init(ec, value);
|
||||||
good_ = ec ? false : true;
|
good_ = ec ? false : true;
|
||||||
|
|
@ -58,7 +58,7 @@ bool RTSemaphore::try_wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
void RTSemaphore::init(std::error_code &ec, unsigned value)
|
void RTSemaphore::init(std::error_code& ec, unsigned value)
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
kern_return_t ret = semaphore_create(mach_task_self(), &sem_, SYNC_POLICY_FIFO, value);
|
kern_return_t ret = semaphore_create(mach_task_self(), &sem_, SYNC_POLICY_FIFO, value);
|
||||||
|
|
@ -66,7 +66,7 @@ void RTSemaphore::init(std::error_code &ec, unsigned value)
|
||||||
ec = std::error_code(ret, mach_category());
|
ec = std::error_code(ret, mach_category());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RTSemaphore::destroy(std::error_code &ec)
|
void RTSemaphore::destroy(std::error_code& ec)
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
kern_return_t ret = semaphore_destroy(mach_task_self(), sem_);
|
kern_return_t ret = semaphore_destroy(mach_task_self(), sem_);
|
||||||
|
|
@ -74,7 +74,7 @@ void RTSemaphore::destroy(std::error_code &ec)
|
||||||
ec = std::error_code(ret, mach_category());
|
ec = std::error_code(ret, mach_category());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RTSemaphore::post(std::error_code &ec) noexcept
|
void RTSemaphore::post(std::error_code& ec) noexcept
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
kern_return_t ret = semaphore_signal(sem_);
|
kern_return_t ret = semaphore_signal(sem_);
|
||||||
|
|
@ -82,7 +82,7 @@ void RTSemaphore::post(std::error_code &ec) noexcept
|
||||||
ec = std::error_code(ret, mach_category());
|
ec = std::error_code(ret, mach_category());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RTSemaphore::wait(std::error_code &ec) noexcept
|
void RTSemaphore::wait(std::error_code& ec) noexcept
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
do {
|
do {
|
||||||
|
|
@ -99,11 +99,11 @@ void RTSemaphore::wait(std::error_code &ec) noexcept
|
||||||
} while (1);
|
} while (1);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RTSemaphore::try_wait(std::error_code &ec) noexcept
|
bool RTSemaphore::try_wait(std::error_code& ec) noexcept
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
do {
|
do {
|
||||||
const mach_timespec_t timeout = {0, 0};
|
const mach_timespec_t timeout = { 0, 0 };
|
||||||
kern_return_t ret = semaphore_timedwait(sem_, timeout);
|
kern_return_t ret = semaphore_timedwait(sem_, timeout);
|
||||||
switch (ret) {
|
switch (ret) {
|
||||||
case KERN_SUCCESS:
|
case KERN_SUCCESS:
|
||||||
|
|
@ -119,18 +119,18 @@ bool RTSemaphore::try_wait(std::error_code &ec) noexcept
|
||||||
} while (1);
|
} while (1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::error_category &RTSemaphore::mach_category()
|
const std::error_category& RTSemaphore::mach_category()
|
||||||
{
|
{
|
||||||
class mach_category : public std::error_category {
|
class mach_category : public std::error_category {
|
||||||
public:
|
public:
|
||||||
const char *name() const noexcept override
|
const char* name() const noexcept override
|
||||||
{
|
{
|
||||||
return "kern_return_t";
|
return "kern_return_t";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string message(int condition) const override
|
std::string message(int condition) const override
|
||||||
{
|
{
|
||||||
const char *str = mach_error_string(condition);
|
const char* str = mach_error_string(condition);
|
||||||
return str ? str : "";
|
return str ? str : "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -139,7 +139,7 @@ const std::error_category &RTSemaphore::mach_category()
|
||||||
return cat;
|
return cat;
|
||||||
}
|
}
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
void RTSemaphore::init(std::error_code &ec, unsigned value)
|
void RTSemaphore::init(std::error_code& ec, unsigned value)
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
sem_ = CreateSemaphore(nullptr, value, LONG_MAX, nullptr);
|
sem_ = CreateSemaphore(nullptr, value, LONG_MAX, nullptr);
|
||||||
|
|
@ -147,21 +147,21 @@ void RTSemaphore::init(std::error_code &ec, unsigned value)
|
||||||
ec = std::error_code(GetLastError(), std::system_category());
|
ec = std::error_code(GetLastError(), std::system_category());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RTSemaphore::destroy(std::error_code &ec)
|
void RTSemaphore::destroy(std::error_code& ec)
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
if (CloseHandle(sem_) == 0)
|
if (CloseHandle(sem_) == 0)
|
||||||
ec = std::error_code(GetLastError(), std::system_category());
|
ec = std::error_code(GetLastError(), std::system_category());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RTSemaphore::post(std::error_code &ec) noexcept
|
void RTSemaphore::post(std::error_code& ec) noexcept
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
if (ReleaseSemaphore(sem_, 1, nullptr) == 0)
|
if (ReleaseSemaphore(sem_, 1, nullptr) == 0)
|
||||||
ec = std::error_code(GetLastError(), std::system_category());
|
ec = std::error_code(GetLastError(), std::system_category());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RTSemaphore::wait(std::error_code &ec) noexcept
|
void RTSemaphore::wait(std::error_code& ec) noexcept
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
DWORD ret = WaitForSingleObject(sem_, INFINITE);
|
DWORD ret = WaitForSingleObject(sem_, INFINITE);
|
||||||
|
|
@ -177,7 +177,7 @@ void RTSemaphore::wait(std::error_code &ec) noexcept
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RTSemaphore::try_wait(std::error_code &ec) noexcept
|
bool RTSemaphore::try_wait(std::error_code& ec) noexcept
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
DWORD ret = WaitForSingleObject(sem_, 0);
|
DWORD ret = WaitForSingleObject(sem_, 0);
|
||||||
|
|
@ -195,21 +195,21 @@ bool RTSemaphore::try_wait(std::error_code &ec) noexcept
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
void RTSemaphore::init(std::error_code &ec, unsigned value)
|
void RTSemaphore::init(std::error_code& ec, unsigned value)
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
if (sem_init(&sem_, 0, value) != 0)
|
if (sem_init(&sem_, 0, value) != 0)
|
||||||
ec = std::error_code(errno, std::generic_category());
|
ec = std::error_code(errno, std::generic_category());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RTSemaphore::destroy(std::error_code &ec)
|
void RTSemaphore::destroy(std::error_code& ec)
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
if (sem_destroy(&sem_) != 0)
|
if (sem_destroy(&sem_) != 0)
|
||||||
ec = std::error_code(errno, std::generic_category());
|
ec = std::error_code(errno, std::generic_category());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RTSemaphore::post(std::error_code &ec) noexcept
|
void RTSemaphore::post(std::error_code& ec) noexcept
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
while (sem_post(&sem_) != 0) {
|
while (sem_post(&sem_) != 0) {
|
||||||
|
|
@ -221,7 +221,7 @@ void RTSemaphore::post(std::error_code &ec) noexcept
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RTSemaphore::wait(std::error_code &ec) noexcept
|
void RTSemaphore::wait(std::error_code& ec) noexcept
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
while (sem_wait(&sem_) != 0) {
|
while (sem_wait(&sem_) != 0) {
|
||||||
|
|
@ -233,7 +233,7 @@ void RTSemaphore::wait(std::error_code &ec) noexcept
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RTSemaphore::try_wait(std::error_code &ec) noexcept
|
bool RTSemaphore::try_wait(std::error_code& ec) noexcept
|
||||||
{
|
{
|
||||||
ec.clear();
|
ec.clear();
|
||||||
do {
|
do {
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@
|
||||||
class RTSemaphore {
|
class RTSemaphore {
|
||||||
public:
|
public:
|
||||||
explicit RTSemaphore(unsigned value = 0);
|
explicit RTSemaphore(unsigned value = 0);
|
||||||
explicit RTSemaphore(std::error_code &ec, unsigned value = 0) noexcept;
|
explicit RTSemaphore(std::error_code& ec, unsigned value = 0) noexcept;
|
||||||
~RTSemaphore() noexcept;
|
~RTSemaphore() noexcept;
|
||||||
|
|
||||||
RTSemaphore(const RTSemaphore &) = delete;
|
RTSemaphore(const RTSemaphore&) = delete;
|
||||||
RTSemaphore &operator=(const RTSemaphore &) = delete;
|
RTSemaphore& operator=(const RTSemaphore&) = delete;
|
||||||
|
|
||||||
explicit operator bool() const noexcept { return good_; }
|
explicit operator bool() const noexcept { return good_; }
|
||||||
|
|
||||||
|
|
@ -29,18 +29,18 @@ public:
|
||||||
void wait();
|
void wait();
|
||||||
bool try_wait();
|
bool try_wait();
|
||||||
|
|
||||||
void post(std::error_code &ec) noexcept;
|
void post(std::error_code& ec) noexcept;
|
||||||
void wait(std::error_code &ec) noexcept;
|
void wait(std::error_code& ec) noexcept;
|
||||||
bool try_wait(std::error_code &ec) noexcept;
|
bool try_wait(std::error_code& ec) noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void init(std::error_code &ec, unsigned value);
|
void init(std::error_code& ec, unsigned value);
|
||||||
void destroy(std::error_code &ec);
|
void destroy(std::error_code& ec);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
semaphore_t sem_ {};
|
semaphore_t sem_ {};
|
||||||
static const std::error_category &mach_category();
|
static const std::error_category& mach_category();
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
HANDLE sem_ {};
|
HANDLE sem_ {};
|
||||||
#else
|
#else
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
#include "Synth.h"
|
#include "Synth.h"
|
||||||
#include "AtomicGuard.h"
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Debug.h"
|
#include "Debug.h"
|
||||||
#include "Macros.h"
|
#include "Macros.h"
|
||||||
|
|
@ -28,21 +27,16 @@ sfz::Synth::Synth()
|
||||||
|
|
||||||
sfz::Synth::Synth(int numVoices)
|
sfz::Synth::Synth(int numVoices)
|
||||||
{
|
{
|
||||||
|
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
|
||||||
parser.setListener(this);
|
parser.setListener(this);
|
||||||
|
|
||||||
effectFactory.registerStandardEffectTypes();
|
effectFactory.registerStandardEffectTypes();
|
||||||
|
|
||||||
effectBuses.reserve(5); // sufficient room for main and fx1-4
|
effectBuses.reserve(5); // sufficient room for main and fx1-4
|
||||||
|
|
||||||
resetVoices(numVoices);
|
resetVoices(numVoices);
|
||||||
}
|
}
|
||||||
|
|
||||||
sfz::Synth::~Synth()
|
sfz::Synth::~Synth()
|
||||||
{
|
{
|
||||||
AtomicDisabler callbackDisabler { canEnterCallback };
|
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
|
||||||
while (inCallback) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& voice : voices)
|
for (auto& voice : voices)
|
||||||
voice->reset();
|
voice->reset();
|
||||||
|
|
@ -128,10 +122,7 @@ void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
|
||||||
|
|
||||||
void sfz::Synth::clear()
|
void sfz::Synth::clear()
|
||||||
{
|
{
|
||||||
AtomicDisabler callbackDisabler { canEnterCallback };
|
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
|
||||||
while (inCallback) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& voice : voices)
|
for (auto& voice : voices)
|
||||||
voice->reset();
|
voice->reset();
|
||||||
|
|
@ -327,13 +318,9 @@ void addEndpointsToVelocityCurve(sfz::Region& region)
|
||||||
|
|
||||||
bool sfz::Synth::loadSfzFile(const fs::path& file)
|
bool sfz::Synth::loadSfzFile(const fs::path& file)
|
||||||
{
|
{
|
||||||
AtomicDisabler callbackDisabler { canEnterCallback };
|
|
||||||
while (inCallback) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
|
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
|
||||||
parser.parseFile(file);
|
parser.parseFile(file);
|
||||||
if (parser.getErrorCount() > 0)
|
if (parser.getErrorCount() > 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -508,11 +495,7 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||||
{
|
{
|
||||||
ASSERT(samplesPerBlock < config::maxBlockSize);
|
ASSERT(samplesPerBlock < config::maxBlockSize);
|
||||||
|
|
||||||
AtomicDisabler callbackDisabler { canEnterCallback };
|
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
|
||||||
|
|
||||||
while (inCallback) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
this->samplesPerBlock = samplesPerBlock;
|
this->samplesPerBlock = samplesPerBlock;
|
||||||
for (auto& voice : voices)
|
for (auto& voice : voices)
|
||||||
|
|
@ -528,10 +511,7 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||||
|
|
||||||
void sfz::Synth::setSampleRate(float sampleRate) noexcept
|
void sfz::Synth::setSampleRate(float sampleRate) noexcept
|
||||||
{
|
{
|
||||||
AtomicDisabler callbackDisabler { canEnterCallback };
|
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
|
||||||
while (inCallback) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
this->sampleRate = sampleRate;
|
this->sampleRate = sampleRate;
|
||||||
for (auto& voice : voices)
|
for (auto& voice : voices)
|
||||||
|
|
@ -558,9 +538,8 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
if (freeWheeling)
|
if (freeWheeling)
|
||||||
resources.filePool.waitForBackgroundLoading();
|
resources.filePool.waitForBackgroundLoading();
|
||||||
|
|
||||||
|
const std::unique_lock<std::mutex> lock { callbackGuard, std::try_to_lock };
|
||||||
AtomicGuard callbackGuard { inCallback };
|
if (!lock.owns_lock())
|
||||||
if (!canEnterCallback)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -655,8 +634,8 @@ void sfz::Synth::noteOn(int delay, int noteNumber, uint8_t velocity) noexcept
|
||||||
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
||||||
resources.midiState.noteOnEvent(delay, noteNumber, normalizedVelocity);
|
resources.midiState.noteOnEvent(delay, noteNumber, normalizedVelocity);
|
||||||
|
|
||||||
AtomicGuard callbackGuard { inCallback };
|
const std::unique_lock<std::mutex> lock { callbackGuard, std::try_to_lock };
|
||||||
if (!canEnterCallback)
|
if (!lock.owns_lock())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
noteOnDispatch(delay, noteNumber, normalizedVelocity);
|
noteOnDispatch(delay, noteNumber, normalizedVelocity);
|
||||||
|
|
@ -671,8 +650,8 @@ void sfz::Synth::noteOff(int delay, int noteNumber, uint8_t velocity) noexcept
|
||||||
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
||||||
resources.midiState.noteOffEvent(delay, noteNumber, normalizedVelocity);
|
resources.midiState.noteOffEvent(delay, noteNumber, normalizedVelocity);
|
||||||
|
|
||||||
AtomicGuard callbackGuard { inCallback };
|
const std::unique_lock<std::mutex> lock { callbackGuard, std::try_to_lock };
|
||||||
if (!canEnterCallback)
|
if (!lock.owns_lock())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a
|
// FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a
|
||||||
|
|
@ -767,8 +746,8 @@ void sfz::Synth::cc(int delay, int ccNumber, uint8_t ccValue) noexcept
|
||||||
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
||||||
resources.midiState.ccEvent(delay, ccNumber, normalizedCC);
|
resources.midiState.ccEvent(delay, ccNumber, normalizedCC);
|
||||||
|
|
||||||
AtomicGuard callbackGuard { inCallback };
|
const std::unique_lock<std::mutex> lock { callbackGuard, std::try_to_lock };
|
||||||
if (!canEnterCallback)
|
if (!lock.owns_lock())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (ccNumber == config::resetCC) {
|
if (ccNumber == config::resetCC) {
|
||||||
|
|
@ -961,16 +940,12 @@ int sfz::Synth::getNumVoices() const noexcept
|
||||||
void sfz::Synth::setNumVoices(int numVoices) noexcept
|
void sfz::Synth::setNumVoices(int numVoices) noexcept
|
||||||
{
|
{
|
||||||
ASSERT(numVoices > 0);
|
ASSERT(numVoices > 0);
|
||||||
|
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
|
||||||
resetVoices(numVoices);
|
resetVoices(numVoices);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Synth::resetVoices(int numVoices)
|
void sfz::Synth::resetVoices(int numVoices)
|
||||||
{
|
{
|
||||||
AtomicDisabler callbackDisabler { canEnterCallback };
|
|
||||||
while (inCallback) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
voices.clear();
|
voices.clear();
|
||||||
for (int i = 0; i < numVoices; ++i)
|
for (int i = 0; i < numVoices; ++i)
|
||||||
voices.push_back(absl::make_unique<Voice>(resources));
|
voices.push_back(absl::make_unique<Voice>(resources));
|
||||||
|
|
@ -986,10 +961,7 @@ void sfz::Synth::resetVoices(int numVoices)
|
||||||
|
|
||||||
void sfz::Synth::setOversamplingFactor(sfz::Oversampling factor) noexcept
|
void sfz::Synth::setOversamplingFactor(sfz::Oversampling factor) noexcept
|
||||||
{
|
{
|
||||||
AtomicDisabler callbackDisabler { canEnterCallback };
|
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
|
||||||
while (inCallback) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& voice : voices)
|
for (auto& voice : voices)
|
||||||
voice->reset();
|
voice->reset();
|
||||||
|
|
@ -1006,10 +978,7 @@ sfz::Oversampling sfz::Synth::getOversamplingFactor() const noexcept
|
||||||
|
|
||||||
void sfz::Synth::setPreloadSize(uint32_t preloadSize) noexcept
|
void sfz::Synth::setPreloadSize(uint32_t preloadSize) noexcept
|
||||||
{
|
{
|
||||||
AtomicDisabler callbackDisabler { canEnterCallback };
|
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
|
||||||
while (inCallback) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
resources.filePool.setPreloadSize(preloadSize);
|
resources.filePool.setPreloadSize(preloadSize);
|
||||||
}
|
}
|
||||||
|
|
@ -1036,11 +1005,12 @@ void sfz::Synth::disableFreeWheeling() noexcept
|
||||||
|
|
||||||
void sfz::Synth::resetAllControllers(int delay) noexcept
|
void sfz::Synth::resetAllControllers(int delay) noexcept
|
||||||
{
|
{
|
||||||
AtomicGuard callbackGuard { inCallback };
|
resources.midiState.resetAllControllers(delay);
|
||||||
if (!canEnterCallback)
|
|
||||||
|
const std::unique_lock<std::mutex> lock { callbackGuard, std::try_to_lock };
|
||||||
|
if (!lock.owns_lock())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
resources.midiState.resetAllControllers(delay);
|
|
||||||
for (auto& voice : voices) {
|
for (auto& voice : voices) {
|
||||||
voice->registerPitchWheel(delay, 0);
|
voice->registerPitchWheel(delay, 0);
|
||||||
for (int cc = 0; cc < config::numCCs; ++cc)
|
for (int cc = 0; cc < config::numCCs; ++cc)
|
||||||
|
|
@ -1086,10 +1056,7 @@ void sfz::Synth::disableLogging() noexcept
|
||||||
|
|
||||||
void sfz::Synth::allSoundOff() noexcept
|
void sfz::Synth::allSoundOff() noexcept
|
||||||
{
|
{
|
||||||
AtomicDisabler callbackDisabler { canEnterCallback };
|
const std::lock_guard<std::mutex> disableCallback { callbackGuard };
|
||||||
while (inCallback) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& voice : voices)
|
for (auto& voice : voices)
|
||||||
voice->reset();
|
voice->reset();
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
#include "absl/types/span.h"
|
#include "absl/types/span.h"
|
||||||
#include <absl/types/optional.h>
|
#include <absl/types/optional.h>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <mutex>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
@ -436,13 +437,7 @@ private:
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
/**
|
|
||||||
* @brief Resets and possibly changes the number of voices (polyphony) in
|
|
||||||
* the synth.
|
|
||||||
*
|
|
||||||
* @param numVoices
|
|
||||||
*/
|
|
||||||
void resetVoices(int numVoices);
|
|
||||||
/**
|
/**
|
||||||
* @brief Helper function to dispatch <global> opcodes
|
* @brief Helper function to dispatch <global> opcodes
|
||||||
*
|
*
|
||||||
|
|
@ -475,6 +470,13 @@ private:
|
||||||
* @param regionOpcodes the opcodes that are specific to the region
|
* @param regionOpcodes the opcodes that are specific to the region
|
||||||
*/
|
*/
|
||||||
void buildRegion(const std::vector<Opcode>& regionOpcodes);
|
void buildRegion(const std::vector<Opcode>& regionOpcodes);
|
||||||
|
/**
|
||||||
|
* @brief Resets and possibly changes the number of voices (polyphony) in
|
||||||
|
* the synth.
|
||||||
|
*
|
||||||
|
* @param numVoices
|
||||||
|
*/
|
||||||
|
void resetVoices(int numVoices);
|
||||||
|
|
||||||
fs::file_time_type checkModificationTime();
|
fs::file_time_type checkModificationTime();
|
||||||
|
|
||||||
|
|
@ -526,9 +528,7 @@ private:
|
||||||
std::uniform_real_distribution<float> randNoteDistribution { 0, 1 };
|
std::uniform_real_distribution<float> randNoteDistribution { 0, 1 };
|
||||||
unsigned fileTicket { 1 };
|
unsigned fileTicket { 1 };
|
||||||
|
|
||||||
// Atomic guards; must be used with AtomicGuard and AtomicDisabler
|
std::mutex callbackGuard;
|
||||||
std::atomic<bool> canEnterCallback { true };
|
|
||||||
std::atomic<bool> inCallback { false };
|
|
||||||
bool freeWheeling { false };
|
bool freeWheeling { false };
|
||||||
|
|
||||||
// Singletons passed as references to the voices
|
// Singletons passed as references to the voices
|
||||||
|
|
|
||||||
|
|
@ -532,8 +532,7 @@ TEST_CASE("[Files] Looped regions taken from files and possibly overriden")
|
||||||
|
|
||||||
TEST_CASE("[Files] Case sentitiveness")
|
TEST_CASE("[Files] Case sentitiveness")
|
||||||
{
|
{
|
||||||
const fs::path sfzFilePath = fs::current_path() /
|
const fs::path sfzFilePath = fs::current_path() / "tests/TestFiles/case_insensitive.sfz";
|
||||||
"tests/TestFiles/case_insensitive.sfz";
|
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
const bool caseSensitiveFs = false;
|
const bool caseSensitiveFs = false;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue