From b2c8800b849798195c0d7738964aec01bd228aa6 Mon Sep 17 00:00:00 2001 From: paulfd Date: Sat, 28 Sep 2019 14:06:27 +0200 Subject: [PATCH] Added the atomic guards on other critical synth functions --- sfizz/AtomicGuard.h | 60 +++++++++++++++++++++++++++++++++++++++++++++ sfizz/Buffer.h | 1 + sfizz/Synth.cpp | 54 ++++++++++++++++++++++++++++++---------- 3 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 sfizz/AtomicGuard.h diff --git a/sfizz/AtomicGuard.h b/sfizz/AtomicGuard.h new file mode 100644 index 00000000..4577083d --- /dev/null +++ b/sfizz/AtomicGuard.h @@ -0,0 +1,60 @@ +// Copyright (c) 2019, Paul Ferrand +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +namespace sfz +{ +class AtomicGuard +{ +public: + AtomicGuard() = delete; + AtomicGuard(std::atomic& guard) + : guard(guard) + { + guard = true; + } + ~AtomicGuard() + { + guard = false; + } +private: + std::atomic& guard; +}; +class AtomicDisabler +{ +public: + AtomicDisabler() = delete; + AtomicDisabler(std::atomic& allowed) + : allowed(allowed) + { + allowed = false; + } + ~AtomicDisabler() + { + allowed = true; + } +private: + std::atomic& allowed; +}; +} \ No newline at end of file diff --git a/sfizz/Buffer.h b/sfizz/Buffer.h index 7f7b11ea..7f982bd4 100644 --- a/sfizz/Buffer.h +++ b/sfizz/Buffer.h @@ -160,4 +160,5 @@ private: pointer _alignedEnd { nullptr }; LEAK_DETECTOR(Buffer); }; + } \ No newline at end of file diff --git a/sfizz/Synth.cpp b/sfizz/Synth.cpp index 3cc03336..a2e61aba 100644 --- a/sfizz/Synth.cpp +++ b/sfizz/Synth.cpp @@ -22,9 +22,10 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Synth.h" -#include "MidiState.h" +#include "AtomicGuard.h" #include "Config.h" #include "Debug.h" +#include "MidiState.h" #include "ScopedFTZ.h" #include "StringViewHelpers.h" #include "absl/algorithm/container.h" @@ -139,17 +140,20 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) { for (auto& member : members) { switch (hash(member.opcode)) { - case hash("Set_cc"): [[fallthrough]]; + case hash("Set_cc"): + [[fallthrough]]; case hash("set_cc"): if (member.parameter && Default::ccRange.containsWithEnd(*member.parameter)) setValueFromOpcode(member, midiState.cc[*member.parameter], Default::ccRange); break; - case hash("Label_cc"): [[fallthrough]]; + case hash("Label_cc"): + [[fallthrough]]; case hash("label_cc"): if (member.parameter && Default::ccRange.containsWithEnd(*member.parameter)) ccNames.emplace_back(*member.parameter, member.value); break; - case hash("Default_path"): [[fallthrough]]; + case hash("Default_path"): + [[fallthrough]]; case hash("default_path"): { auto stringPath = std::string(member.value.begin(), member.value.end()); auto newPath = fs::path(stringPath); @@ -184,7 +188,7 @@ void addEndpointsToVelocityCurve(sfz::Region& region) bool sfz::Synth::loadSfzFile(const fs::path& filename) { - canEnterCallback = false; + AtomicDisabler callbackDisabler { canEnterCallback }; while (inCallback) { std::this_thread::sleep_for(1ms); } @@ -247,8 +251,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& filename) DBG("Removed " << regions.size() - std::distance(regions.begin(), lastRegion) - 1 << " out of " << regions.size() << " regions."); regions.resize(std::distance(regions.begin(), lastRegion) + 1); - - canEnterCallback = true; + return parserReturned; } @@ -261,12 +264,12 @@ sfz::Voice* sfz::Synth::findFreeVoice() noexcept // Find voices that can be stolen DBG("No free voice, trying to steal"); voiceViewArray.clear(); - for (auto& voice: voices) + for (auto& voice : voices) if (voice->canBeStolen()) voiceViewArray.push_back(voice.get()); absl::c_sort(voices, [](const auto& lhs, const auto& rhs) { return lhs->getSourcePosition() > rhs->getSourcePosition(); }); - for (auto* voice: voiceViewArray) { + for (auto* voice : voiceViewArray) { DBG("Average voice power: " << voice->getMeanSquaredAverage()); if (voice->getMeanSquaredAverage() < config::voiceStealingThreshold) { DBG("Stealing voice..."); @@ -297,6 +300,11 @@ void sfz::Synth::garbageCollect() noexcept void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept { + AtomicDisabler callbackDisabler { canEnterCallback }; + while (inCallback) { + std::this_thread::sleep_for(1ms); + } + this->samplesPerBlock = samplesPerBlock; this->tempBuffer.resize(samplesPerBlock); for (auto& voice : voices) @@ -305,6 +313,11 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept void sfz::Synth::setSampleRate(float sampleRate) noexcept { + AtomicDisabler callbackDisabler { canEnterCallback }; + while (inCallback) { + std::this_thread::sleep_for(1ms); + } + this->sampleRate = sampleRate; for (auto& voice : voices) voice->setSampleRate(sampleRate); @@ -314,18 +327,17 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept { ScopedFTZ ftz; buffer.fill(0.0f); + if (!canEnterCallback) return; - inCallback = true; + AtomicGuard callbackGuard { inCallback }; auto tempSpan = AudioSpan(tempBuffer).first(buffer.getNumFrames()); for (auto& voice : voices) { voice->renderBlock(tempSpan); buffer.add(tempSpan); } - - inCallback = false; } void sfz::Synth::noteOn(int delay, int channel, int noteNumber, uint8_t velocity) noexcept @@ -334,6 +346,12 @@ void sfz::Synth::noteOn(int delay, int channel, int noteNumber, uint8_t velocity ASSERT(noteNumber >= 0); midiState.noteOn(noteNumber, velocity); + + if (!canEnterCallback) + return; + + AtomicGuard callbackGuard { inCallback }; + auto randValue = randNoteDistribution(Random::randomGenerator); for (auto& region : noteActivationLists[noteNumber]) { @@ -361,7 +379,12 @@ void sfz::Synth::noteOff(int delay, int channel, int noteNumber, uint8_t velocit ASSERT(noteNumber < 128); ASSERT(noteNumber >= 0); - // FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a + if (!canEnterCallback) + return; + + AtomicGuard callbackGuard { inCallback }; + + // FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a // way in sfz to specify that a release trigger should NOT use the note-on velocity? // auto replacedVelocity = (velocity == 0 ? sfz::getNoteVelocity(noteNumber) : velocity); auto replacedVelocity = midiState.getNoteVelocity(noteNumber); @@ -389,6 +412,11 @@ void sfz::Synth::cc(int delay, int channel, int ccNumber, uint8_t ccValue) noexc ASSERT(ccNumber < 128); ASSERT(ccNumber >= 0); + if (!canEnterCallback) + return; + + AtomicGuard callbackGuard { inCallback }; + for (auto& voice : voices) voice->registerCC(delay, channel, ccNumber, ccValue);