From 57a41fd0a752ce06ab6acea51f0444ab2c160f01 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 24 Jul 2021 19:09:26 +0200 Subject: [PATCH 01/10] Add a check for sustain and sostenuto in the LV2 If the midi message is a sustain or sostenuto, pass it on regardless of the plugin-side automation compile time switch. Also hide sustain/sostenuto CCs from the UI controls; most of the time they're for pedal noises and things like this. --- .../common/plugin/InstrumentDescription.cpp | 3 ++ plugins/common/plugin/InstrumentDescription.h | 1 + plugins/lv2/sfizz.cpp | 30 +++++++++++++++---- plugins/lv2/sfizz_lv2_plugin.h | 3 ++ plugins/lv2/sfizz_ui.cpp | 2 +- plugins/vst/SfizzVstEditor.cpp | 2 +- src/sfizz/Synth.cpp | 6 +++- src/sfizz/SynthMessaging.cpp | 7 +++++ src/sfizz/SynthPrivate.h | 1 + 9 files changed, 46 insertions(+), 9 deletions(-) diff --git a/plugins/common/plugin/InstrumentDescription.cpp b/plugins/common/plugin/InstrumentDescription.cpp index 7378fd03..071655b1 100644 --- a/plugins/common/plugin/InstrumentDescription.cpp +++ b/plugins/common/plugin/InstrumentDescription.cpp @@ -103,6 +103,7 @@ std::string getDescriptionBlob(sfizz_synth_t* handle) synth.sendMessage(*client, 0, "/key/slots", "", nullptr); synth.sendMessage(*client, 0, "/sw/last/slots", "", nullptr); synth.sendMessage(*client, 0, "/cc/slots", "", nullptr); + synth.sendMessage(*client, 0, "/sustain_or_sostenuto/slots", "", nullptr); blob.shrink_to_fit(); return blob; @@ -152,6 +153,8 @@ InstrumentDescription parseDescriptionBlob(absl::string_view blob) copyArgToBitSpan(args[0], desc.keyswitchUsed.span()); else if (Messages::matchOSC("/cc/slots", path, indices) && !strcmp(sig, "b")) copyArgToBitSpan(args[0], desc.ccUsed.span()); + else if (Messages::matchOSC("/sustain_or_sostenuto/slots", path, indices) && !strcmp(sig, "b")) + copyArgToBitSpan(args[0], desc.sustainOrSostenuto.span()); else if (Messages::matchOSC("/key&/label", path, indices) && !strcmp(sig, "s")) desc.keyLabel[indices[0]] = args[0].s; else if (Messages::matchOSC("/sw/last/&/label", path, indices) && !strcmp(sig, "s")) diff --git a/plugins/common/plugin/InstrumentDescription.h b/plugins/common/plugin/InstrumentDescription.h index b943dcba..094d06b3 100644 --- a/plugins/common/plugin/InstrumentDescription.h +++ b/plugins/common/plugin/InstrumentDescription.h @@ -26,6 +26,7 @@ struct InstrumentDescription { std::string image; BitArray<128> keyUsed {}; BitArray<128> keyswitchUsed {}; + BitArray<128> sustainOrSostenuto {}; BitArray ccUsed {}; std::array keyLabel {}; std::array keyswitchLabel {}; diff --git a/plugins/lv2/sfizz.cpp b/plugins/lv2/sfizz.cpp index 6f22f8fc..a3afccf6 100644 --- a/plugins/lv2/sfizz.cpp +++ b/plugins/lv2/sfizz.cpp @@ -683,6 +683,12 @@ sfizz_lv2_handle_atom_object(sfizz_plugin_t *self, int delay, const LV2_Atom_Obj } } +static bool +sfizz_is_sustain_or_sostenuto(sfizz_plugin_t* self, unsigned cc) +{ + return (self->sustain_or_sostenuto[cc / 8] & (1 << (cc % 8))); +} + static void sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev) { @@ -704,14 +710,23 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev) (int)msg[1], msg[2]); break; - // Note(jpc) CC must be mapped by host, not handled here. - // See LV2 midi:binding. -#if defined(SFIZZ_LV2_PSA) case LV2_MIDI_MSG_CONTROLLER: { unsigned cc = msg[1]; float value = float(msg[2]) * (1.0f / 127.0f); + // Send + if (sfizz_is_sustain_or_sostenuto(self, cc)) { + sfizz_automate_hdcc(self->synth, + (int)ev->time.frames, + (int)cc, + value); + break; + } + +// Note(jpc) CC must be mapped by host, not handled here. +// See LV2 midi:binding. +#if defined(SFIZZ_LV2_PSA) switch (cc) { default: @@ -733,9 +748,9 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev) sfizz_all_sound_off(self->synth); break; } +#endif } break; -#endif case LV2_MIDI_MSG_CHANNEL_PRESSURE: sfizz_send_channel_aftertouch(self->synth, (int)ev->time.frames, @@ -1218,7 +1233,7 @@ sfizz_lv2_update_sfz_info(sfizz_plugin_t *self) // const InstrumentDescription desc = parseDescriptionBlob(blob); for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) { - if (desc.ccUsed.test(cc)) { + if (desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc)) { // Mark all the used CCs for automation with default values self->ccauto[cc] = desc.ccDefault[cc]; self->have_ccauto = true; @@ -1226,6 +1241,9 @@ sfizz_lv2_update_sfz_info(sfizz_plugin_t *self) self->cc_current[cc] = desc.ccDefault[cc]; } } + + memcpy(self->sustain_or_sostenuto, desc.sustainOrSostenuto.data(), + sizeof(self->sustain_or_sostenuto)); } static bool @@ -1515,7 +1533,7 @@ save(LV2_Handle instance, self->sfz_blob_mutex->unlock(); for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) { - if (desc.ccUsed.test(cc)) { + if (desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc)) { LV2_URID urid = sfizz_lv2_ccmap_map(self->ccmap, int(cc)); store(handle, urid, diff --git a/plugins/lv2/sfizz_lv2_plugin.h b/plugins/lv2/sfizz_lv2_plugin.h index 90387684..9c1918f9 100644 --- a/plugins/lv2/sfizz_lv2_plugin.h +++ b/plugins/lv2/sfizz_lv2_plugin.h @@ -127,6 +127,9 @@ struct sfizz_plugin_t const uint8_t *volatile sfz_blob_data {}; volatile uint32_t sfz_blob_size {}; + // Sostenuto or sustain + char sustain_or_sostenuto[16] {}; + // Current CC values in the synth (synchronized by `synth_mutex`) // updated by hdcc or file load float *cc_current {}; diff --git a/plugins/lv2/sfizz_ui.cpp b/plugins/lv2/sfizz_ui.cpp index 3756c7f8..7e831989 100644 --- a/plugins/lv2/sfizz_ui.cpp +++ b/plugins/lv2/sfizz_ui.cpp @@ -447,7 +447,7 @@ sfizz_ui_update_description(sfizz_ui_t *self, const InstrumentDescription& desc) } for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) { - bool ccUsed = desc.ccUsed.test(cc); + bool ccUsed = desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc); self->uiReceiveValue(editIdForCCUsed(cc), float(ccUsed)); if (ccUsed) { self->uiReceiveValue(editIdForCCDefault(cc), desc.ccDefault[cc]); diff --git a/plugins/vst/SfizzVstEditor.cpp b/plugins/vst/SfizzVstEditor.cpp index 9f9e614c..5bb96725 100644 --- a/plugins/vst/SfizzVstEditor.cpp +++ b/plugins/vst/SfizzVstEditor.cpp @@ -246,7 +246,7 @@ bool SfizzVstEditor::processUpdate(FUnknown* changedUnknown, int32 message) } for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) { - bool ccUsed = desc.ccUsed.test(cc); + bool ccUsed = desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc); uiReceiveValue(editIdForCCUsed(int(cc)), float(ccUsed)); if (ccUsed) { uiReceiveValue(editIdForCCDefault(int(cc)), desc.ccDefault[cc]); diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index fcc11f51..dc76759e 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -271,6 +271,7 @@ void Synth::Impl::clear() filePool.setRamLoading(config::loadInRam); clearCCLabels(); currentUsedCCs_.clear(); + sustainOrSostenuto_.clear(); changedCCsThisCycle_.clear(); changedCCsLastCycle_.clear(); clearKeyLabels(); @@ -2113,8 +2114,11 @@ void Synth::Impl::collectUsedCCsFromModulations(BitArray& usedCC BitArray Synth::Impl::collectAllUsedCCs() { BitArray used; - for (const LayerPtr& layerPtr : layers_) + for (const LayerPtr& layerPtr : layers_) { collectUsedCCsFromRegion(used, layerPtr->getRegion()); + sustainOrSostenuto_.set(layerPtr->region_.sustainCC); + sustainOrSostenuto_.set(layerPtr->region_.sostenutoCC); + } collectUsedCCsFromModulations(used, resources_.getModMatrix()); return used; } diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index 36f4bc03..4d75451a 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -180,6 +180,13 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co client.receive<'b'>(delay, path, &blob); } break; + MATCH("/sustain_or_sostenuto/slots", "") { + const BitArray<128>& sustainOrSostenuto = impl.sustainOrSostenuto_; + sfizz_blob_t blob { sustainOrSostenuto.data(), + static_cast(sustainOrSostenuto.byte_size()) }; + client.receive<'b'>(delay, path, &blob); + } break; + //---------------------------------------------------------------------- MATCH("/mem/buffers", "") { diff --git a/src/sfizz/SynthPrivate.h b/src/sfizz/SynthPrivate.h index 2597f417..44fd3b54 100644 --- a/src/sfizz/SynthPrivate.h +++ b/src/sfizz/SynthPrivate.h @@ -248,6 +248,7 @@ struct Synth::Impl final: public Parser::Listener { std::map keyLabelsMap_; BitArray<128> keySlots_; BitArray<128> swLastSlots_; + BitArray<128> sustainOrSostenuto_; std::vector keyswitchLabels_; std::map keyswitchLabelsMap_; From c3df3d3b652f8bca0a8738749d3714ee2b74a492 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 22 Aug 2021 15:25:50 -0400 Subject: [PATCH 02/10] Fix build with clang & C++17 (bessel functions not defined) --- src/sfizz/MathHelpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/MathHelpers.h b/src/sfizz/MathHelpers.h index 0d2fa1b3..556526da 100644 --- a/src/sfizz/MathHelpers.h +++ b/src/sfizz/MathHelpers.h @@ -26,7 +26,7 @@ #include #endif -#if __cplusplus >= 201703L +#if __cplusplus >= 201703L && defined(__cpp_lib_math_special_functions) static double i0(double x) { return std::cyl_bessel_i(0.0, x); } #else // external Bessel function from cephes From c70f50197ba9d505a3f24dc29843ec60e45588d2 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 28 Aug 2021 13:28:46 +0200 Subject: [PATCH 03/10] Have the DS importer generate the correct loop mode --- src/sfizz/import/foreign_instruments/DecentSampler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/import/foreign_instruments/DecentSampler.cpp b/src/sfizz/import/foreign_instruments/DecentSampler.cpp index e20f3ab7..149ba86d 100644 --- a/src/sfizz/import/foreign_instruments/DecentSampler.cpp +++ b/src/sfizz/import/foreign_instruments/DecentSampler.cpp @@ -202,7 +202,7 @@ void DecentSamplerInstrumentImporter::emitRegionalOpcodes(std::ostream& os, pugi break; case hash("loopEnabled"): os << "loop_mode=" - << ((xmlOpcode.value == "true") ? "loop_continuous" : "one_shot") << "\n"; + << ((xmlOpcode.value == "true") ? "loop_continuous" : "no_loop") << "\n"; break; case hash("attack"): convertToReal("ampeg_attack"); From 174d58fd877eb3f060908811b49bd01685fa2fc7 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 20 Sep 2021 03:03:36 +0200 Subject: [PATCH 04/10] Fix the VST thread synchronization on activate/deactivate --- plugins/vst/SfizzVstProcessor.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/vst/SfizzVstProcessor.cpp b/plugins/vst/SfizzVstProcessor.cpp index cf01f04f..1e0a7134 100644 --- a/plugins/vst/SfizzVstProcessor.cpp +++ b/plugins/vst/SfizzVstProcessor.cpp @@ -740,8 +740,13 @@ void SfizzVstProcessor::doBackgroundWork() for (;;) { bool isNotified = _semaToWorker.timed_wait(kBackgroundIdleInterval.count()); - if (!_workRunning) + if (!_workRunning) { + // if the quit signal is sent, the semaphore is also signaled + // make sure the count is kept consistent + if (!isNotified) + _semaToWorker.wait(); break; + } const char* id = nullptr; RTMessagePtr msg; From e3dcaa7d9b63c9b422f60d2d43e7c86313cbaafa Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 20 Sep 2021 03:19:54 +0200 Subject: [PATCH 05/10] Unfork and update VST3 SDK --- .gitmodules | 2 +- plugins/vst/external/VST_SDK/VST3_SDK/pluginterfaces | 2 +- plugins/vst/external/VST_SDK/VST3_SDK/public.sdk | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index a1f99fd7..441fc807 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,7 +13,7 @@ shallow = true [submodule "vst/external/VST_SDK/VST3_SDK/public.sdk"] path = plugins/vst/external/VST_SDK/VST3_SDK/public.sdk - url = https://github.com/sfztools/vst3_public_sdk.git + url = https://github.com/steinbergmedia/vst3_public_sdk.git shallow = true [submodule "vst/external/VST_SDK/VST3_SDK/vstgui4"] path = plugins/editor/external/vstgui4 diff --git a/plugins/vst/external/VST_SDK/VST3_SDK/pluginterfaces b/plugins/vst/external/VST_SDK/VST3_SDK/pluginterfaces index b8566ef3..93cef1af 160000 --- a/plugins/vst/external/VST_SDK/VST3_SDK/pluginterfaces +++ b/plugins/vst/external/VST_SDK/VST3_SDK/pluginterfaces @@ -1 +1 @@ -Subproject commit b8566ef3b2a0cba60a96e3ef2001224c865c8b36 +Subproject commit 93cef1afb7061e488625045ba5a82abaa83d27fe diff --git a/plugins/vst/external/VST_SDK/VST3_SDK/public.sdk b/plugins/vst/external/VST_SDK/VST3_SDK/public.sdk index d69d011f..9589800e 160000 --- a/plugins/vst/external/VST_SDK/VST3_SDK/public.sdk +++ b/plugins/vst/external/VST_SDK/VST3_SDK/public.sdk @@ -1 +1 @@ -Subproject commit d69d011fa08c3977928f7da0010f8938f93fd370 +Subproject commit 9589800ed94573354bc29de45eec5744523fbfcb From 6735d77e637db26622d14681decd3a24d5eb4393 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 20 Sep 2021 03:21:24 +0200 Subject: [PATCH 06/10] vst: use the keyswitch change flag from 3.7.3 --- plugins/vst/SfizzVstController.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/vst/SfizzVstController.cpp b/plugins/vst/SfizzVstController.cpp index a3d38f07..dcf66b67 100644 --- a/plugins/vst/SfizzVstController.cpp +++ b/plugins/vst/SfizzVstController.cpp @@ -364,8 +364,7 @@ tresult SfizzVstControllerNoUi::notify(Vst::IMessage* message) } if (Vst::IComponentHandler* componentHandler = getComponentHandler()) - // NOTE(jpc) I think that's the right one, but it needs confirmation - componentHandler->restartComponent(Vst::kNoteExpressionChanged); + componentHandler->restartComponent(Vst::kKeyswitchChanged); // update the parameter titles and notify for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) { From 7c059ec75056a0beef56e0981187d004fcb6f953 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 20 Sep 2021 03:40:54 +0200 Subject: [PATCH 07/10] Add stringconvert.cpp to SDK sources --- plugins/vst/cmake/Vst3.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/vst/cmake/Vst3.cmake b/plugins/vst/cmake/Vst3.cmake index c05706dd..f461453f 100644 --- a/plugins/vst/cmake/Vst3.cmake +++ b/plugins/vst/cmake/Vst3.cmake @@ -30,7 +30,8 @@ add_library(vst3sdk STATIC EXCLUDE_FROM_ALL "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstnoteexpressiontypes.cpp" "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstparameters.cpp" "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstpresetfile.cpp" - "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstrepresentation.cpp") + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstrepresentation.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/utility/stringconvert.cpp") if(WIN32) target_sources(vst3sdk PRIVATE "${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_win32.cpp") From 0bc02f12cf64386ef0d1310c6875116bf4b9e45a Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 20 Sep 2021 07:02:09 +0200 Subject: [PATCH 08/10] Fix attempt for X11 runloop problems --- plugins/vst/SfizzVstEditor.cpp | 22 ++-- plugins/vst/X11RunLoop.cpp | 210 +++++++++++++++++++++++---------- plugins/vst/X11RunLoop.h | 46 ++++---- 3 files changed, 185 insertions(+), 93 deletions(-) diff --git a/plugins/vst/SfizzVstEditor.cpp b/plugins/vst/SfizzVstEditor.cpp index 5bb96725..2e1506ca 100644 --- a/plugins/vst/SfizzVstEditor.cpp +++ b/plugins/vst/SfizzVstEditor.cpp @@ -59,11 +59,8 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p config = &x11config; #endif - Editor* editor = editor_.get(); - if (!editor) { - editor = new Editor(*this); - editor_.reset(editor); - } + Editor* editor = new Editor(*this); + editor_.reset(editor); if (!frame->open(parent, platformType, config)) { fprintf(stderr, "[sfizz] error opening frame\n"); @@ -122,12 +119,21 @@ void PLUGIN_API SfizzVstEditor::close() for (FObject* update : updates_) update->removeDependent(this); - if (editor_) + if (editor_) { editor_->close(); + editor_ = nullptr; + } + if (frame->getNbReference() != 1) frame->forget(); - else + else { frame->close(); +#if !defined(__APPLE__) && !defined(_WIN32) + // if vstgui is done using the runloop, destroy it + if (!RunLoop::get()) + _runLoop = nullptr; +#endif + } this->frame = nullptr; } @@ -158,8 +164,6 @@ CMessageResult SfizzVstEditor::notify(CBaseObject* sender, const char* message) // notifier of X11 events is working. If there is, remove this and // avoid polluting Linux hosts which implement the loop correctly. runLoop->processSomeEvents(); - - runLoop->cleanupDeadHandlers(); } } #endif diff --git a/plugins/vst/X11RunLoop.cpp b/plugins/vst/X11RunLoop.cpp index 99077846..dec08074 100644 --- a/plugins/vst/X11RunLoop.cpp +++ b/plugins/vst/X11RunLoop.cpp @@ -1,33 +1,36 @@ -// SPDX-License-Identifier: GPL-3.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 #if !defined(__APPLE__) && !defined(_WIN32) #include "X11RunLoop.h" #include "vstgui/lib/platform/linux/x11platform.h" #include "base/source/fobject.h" +#include +#include namespace VSTGUI { -RunLoop::RunLoop(Steinberg::FUnknown* runLoop) - : runLoop(runLoop) -{ -} +struct RunLoop::Impl { + struct EventHandler; + struct TimerHandler; -RunLoop::~RunLoop() {} + using EventHandlers = std::vector>; + using TimerHandlers = std::vector>; -SharedPointer RunLoop::get() -{ - return X11::RunLoop::get().cast(); -} + EventHandlers eventHandlers; + TimerHandlers timerHandlers; + Steinberg::FUnknownPtr runLoop; +}; -struct RunLoop::EventHandler final : Steinberg::Linux::IEventHandler, public Steinberg::FObject { +//------------------------------------------------------------------------------ +struct RunLoop::Impl::EventHandler final : Steinberg::Linux::IEventHandler, public Steinberg::FObject { X11::IEventHandler* handler { nullptr }; bool alive { false }; - void PLUGIN_API onFDIsSet(Steinberg::Linux::FileDescriptor) override - { - if (alive && handler) - handler->onEvent(); - } + void PLUGIN_API onFDIsSet(Steinberg::Linux::FileDescriptor) override; DELEGATE_REFCOUNT(Steinberg::FObject) DEFINE_INTERFACES @@ -35,15 +38,11 @@ struct RunLoop::EventHandler final : Steinberg::Linux::IEventHandler, public Ste END_DEFINE_INTERFACES(Steinberg::FObject) }; -struct RunLoop::TimerHandler final : Steinberg::Linux::ITimerHandler, public Steinberg::FObject { +struct RunLoop::Impl::TimerHandler final : Steinberg::Linux::ITimerHandler, public Steinberg::FObject { X11::ITimerHandler* handler { nullptr }; bool alive { false }; - void PLUGIN_API onTimer() override - { - if (alive && handler) - handler->onTimer(); - } + void PLUGIN_API onTimer() override; DELEGATE_REFCOUNT(Steinberg::FObject) DEFINE_INTERFACES @@ -51,45 +50,126 @@ struct RunLoop::TimerHandler final : Steinberg::Linux::ITimerHandler, public Ste END_DEFINE_INTERFACES(Steinberg::FObject) }; +//------------------------------------------------------------------------------ +void PLUGIN_API RunLoop::Impl::EventHandler::onFDIsSet(Steinberg::Linux::FileDescriptor) +{ + SharedPointer runLoop = RunLoop::get(); + if (!runLoop) { + fprintf(stderr, "[x11] event has fired without active runloop\n"); + return; + } + + if (alive && handler) + handler->onEvent(); +} + +void PLUGIN_API RunLoop::Impl::TimerHandler::onTimer() +{ + SharedPointer runLoop = RunLoop::get(); + if (!runLoop) { + fprintf(stderr, "[x11] timer has fired without active runloop\n"); + return; + } + + if (alive && handler) + handler->onTimer(); +} + +//------------------------------------------------------------------------------ +RunLoop::RunLoop(Steinberg::FUnknown* runLoop) + : impl(new Impl) +{ + impl->runLoop = runLoop; +} + +RunLoop::~RunLoop() +{ + //dumpCurrentState(); + + if (0) { + // remove any leftover handlers + for (size_t i = 0; i < impl->eventHandlers.size(); ++i) { + const auto& eh = impl->eventHandlers[i]; + if (eh->alive && eh->handler) { + impl->runLoop->unregisterEventHandler(eh.get()); + } + } + for (size_t i = 0; i < impl->timerHandlers.size(); ++i) { + const auto& th = impl->timerHandlers[i]; + if (th->alive && th->handler) { + impl->runLoop->unregisterTimer(th.get()); + } + } + } +} + +SharedPointer RunLoop::get() +{ + return X11::RunLoop::get().cast(); +} void RunLoop::processSomeEvents() { - for (size_t i = 0; i < eventHandlers.size(); ++i) { - const auto& eh = eventHandlers[i]; + for (size_t i = 0; i < impl->eventHandlers.size(); ++i) { + const auto& eh = impl->eventHandlers[i]; if (eh->alive && eh->handler) { eh->handler->onEvent(); } } } -void RunLoop::cleanupDeadHandlers() +void RunLoop::dumpCurrentState() { - for (size_t i = 0; i < eventHandlers.size(); ++i) { - const auto& eh = eventHandlers[i]; - if (!eh->alive) { - runLoop->unregisterEventHandler(eh); - eventHandlers.erase(eventHandlers.begin() + i--); - } + fprintf(stderr, "=== X11 runloop ===\n"); + + fprintf(stderr, "\t" "Event slots:\n"); + for (size_t i = 0, n = impl->eventHandlers.size(); i < n; ++i) { + Impl::EventHandler *eh = impl->eventHandlers[i].get(); + fprintf(stderr, "\t\t" "(%lu) alive=%d handler=%p type=%s\n", i, eh->alive, eh->handler, (eh->alive && eh->handler) ? typeid(*eh->handler).name() : ""); } - for (size_t i = 0; i < timerHandlers.size(); ++i) { - const auto& th = timerHandlers[i]; - if (!th->alive) { - runLoop->unregisterTimer(th); - timerHandlers.erase(timerHandlers.begin() + i--); - } + + fprintf(stderr, "\t" "Timer slots:\n"); + for (size_t i = 0, n = impl->timerHandlers.size(); i < n; ++i) { + Impl::TimerHandler *th = impl->timerHandlers[i].get(); + fprintf(stderr, "\t\t" "(%lu) alive=%d handler=%p type=%s\n", i, th->alive, th->handler, (th->alive && th->handler) ? typeid(*th->handler).name() : ""); } + + fprintf(stderr, "===/X11 runloop ===\n"); +} + +template +static void insertHandler(std::vector>& list, Steinberg::IPtr handler) +{ + size_t i = 0; + size_t n = list.size(); + while (i < n && list[i]->alive) + ++i; + if (i < n) + list[i] = handler; + else + list.emplace_back(handler); +} + +template +static size_t findHandler(const std::vector>& list, U* handler) +{ + for (size_t i = 0, n = list.size(); i < n; ++i) { + if (list[i]->alive && list[i]->handler == handler) + return i; + } + return ~size_t(0); } bool RunLoop::registerEventHandler(int fd, X11::IEventHandler* handler) { - if (!runLoop) + if (!impl->runLoop) return false; - auto smtgHandler = Steinberg::owned(new EventHandler()); + auto smtgHandler = Steinberg::owned(new Impl::EventHandler); smtgHandler->handler = handler; smtgHandler->alive = true; - if (runLoop->registerEventHandler(smtgHandler, fd) == Steinberg::kResultTrue) { - eventHandlers.push_back(smtgHandler); + if (impl->runLoop->registerEventHandler(smtgHandler, fd) == Steinberg::kResultTrue) { + insertHandler(impl->eventHandlers, smtgHandler); return true; } return false; @@ -97,29 +177,31 @@ bool RunLoop::registerEventHandler(int fd, X11::IEventHandler* handler) bool RunLoop::unregisterEventHandler(X11::IEventHandler* handler) { - if (!runLoop) + if (!impl->runLoop) return false; - for (size_t i = 0; i < eventHandlers.size(); ++i) { - const auto& eh = eventHandlers[i]; - if (eh->alive && eh->handler == handler) { - eh->alive = false; - return true; - } - } - return false; + size_t index = findHandler(impl->eventHandlers, handler); + if (index == ~size_t(0)) + return false; + + Impl::EventHandler *eh = impl->eventHandlers[index].get(); + if (!impl->runLoop->unregisterEventHandler(eh)) + return false; + + eh->alive = false; + return true; } bool RunLoop::registerTimer(uint64_t interval, X11::ITimerHandler* handler) { - if (!runLoop) + if (!impl->runLoop) return false; - auto smtgHandler = Steinberg::owned(new TimerHandler()); + auto smtgHandler = Steinberg::owned(new Impl::TimerHandler); smtgHandler->handler = handler; smtgHandler->alive = true; - if (runLoop->registerTimer(smtgHandler, interval) == Steinberg::kResultTrue) { - timerHandlers.push_back(smtgHandler); + if (impl->runLoop->registerTimer(smtgHandler, interval) == Steinberg::kResultTrue) { + insertHandler(impl->timerHandlers, smtgHandler); return true; } return false; @@ -127,17 +209,19 @@ bool RunLoop::registerTimer(uint64_t interval, X11::ITimerHandler* handler) bool RunLoop::unregisterTimer(X11::ITimerHandler* handler) { - if (!runLoop) + if (!impl->runLoop) return false; - for (size_t i = 0; i < timerHandlers.size(); ++i) { - const auto& th = timerHandlers[i]; - if (th->alive && th->handler == handler) { - th->alive = false; - return true; - } - } - return false; + size_t index = findHandler(impl->timerHandlers, handler); + if (index == ~size_t(0)) + return false; + + Impl::TimerHandler *th = impl->timerHandlers[index].get(); + if (!impl->runLoop->unregisterTimer(th)) + return false; + + th->alive = false; + return true; } } // namespace VSTGUI diff --git a/plugins/vst/X11RunLoop.h b/plugins/vst/X11RunLoop.h index cb47b34c..5755a06b 100644 --- a/plugins/vst/X11RunLoop.h +++ b/plugins/vst/X11RunLoop.h @@ -1,16 +1,28 @@ -// SPDX-License-Identifier: GPL-3.0 -/* - This is a modified version the X11 run loop from vst3editor.cpp. +// SPDX-License-Identifier: BSD-2-Clause - This version is edited to add more safeguards to protect against host bugs. - It also permits to call event processing externally in case the host has a - defective X11 event loop notifier. +// 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 + +/* + This runloop connects to X11 VSTGUI, it connects VST3 and VSTGUI together. + The Windows and macOS runloops do not need this, the OS-provided + functionality is used instead. + + Previously, this was based on VSTGUI code provided by Steinberg. + This is replaced with a rewrite, because the original code has too many + issues. For example, it has no robustness in case handlers get added or + removed within the execution of the handler. + + This version allows to call event processing externally, in case the host + has a defective X11 event loop notifier. (some versions of Bitwig do) */ #pragma once #if !defined(__APPLE__) && !defined(_WIN32) #include "vstgui/lib/platform/linux/x11frame.h" #include "pluginterfaces/gui/iplugview.h" +#include namespace VSTGUI { @@ -22,24 +34,16 @@ public: static SharedPointer get(); void processSomeEvents(); - void cleanupDeadHandlers(); + void dumpCurrentState(); // X11::IRunLoop - bool registerEventHandler(int fd, X11::IEventHandler* handler); - bool unregisterEventHandler(X11::IEventHandler* handler); - bool registerTimer(uint64_t interval, X11::ITimerHandler* handler); - bool unregisterTimer(X11::ITimerHandler* handler); + bool registerEventHandler(int fd, X11::IEventHandler* handler) override; + bool unregisterEventHandler(X11::IEventHandler* handler) override; + bool registerTimer(uint64_t interval, X11::ITimerHandler* handler) override; + bool unregisterTimer(X11::ITimerHandler* handler) override; -private: - struct EventHandler; - struct TimerHandler; - -private: - using EventHandlers = std::vector>; - using TimerHandlers = std::vector>; - EventHandlers eventHandlers; - TimerHandlers timerHandlers; - Steinberg::FUnknownPtr runLoop; + struct Impl; + std::unique_ptr impl; }; } // namespace VSTGUI From b2368d6e80592d54a8e65c28a8257b94a9a1db8e Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 20 Sep 2021 07:59:38 +0200 Subject: [PATCH 09/10] Add a couple of includes used in the file [ci skip] --- plugins/vst/X11RunLoop.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/vst/X11RunLoop.cpp b/plugins/vst/X11RunLoop.cpp index dec08074..04725c5a 100644 --- a/plugins/vst/X11RunLoop.cpp +++ b/plugins/vst/X11RunLoop.cpp @@ -9,6 +9,8 @@ #include "vstgui/lib/platform/linux/x11platform.h" #include "base/source/fobject.h" #include +#include +#include #include namespace VSTGUI { From 99d907e5c7090580a99ca130ae9651cb848dc733 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 24 Jul 2021 16:59:22 +0200 Subject: [PATCH 10/10] Remember the last keyswitch used in the states --- plugins/lv2/sfizz.cpp | 31 +++++++++++++++++++++++++++++++ plugins/lv2/sfizz_lv2.h | 1 + plugins/lv2/sfizz_lv2_plugin.h | 2 ++ plugins/vst/SfizzVstProcessor.cpp | 12 ++++++++++++ plugins/vst/SfizzVstState.cpp | 11 +++++++++++ plugins/vst/SfizzVstState.h | 3 ++- 6 files changed, 59 insertions(+), 1 deletion(-) diff --git a/plugins/lv2/sfizz.cpp b/plugins/lv2/sfizz.cpp index 9bc3c4a1..407ed804 100644 --- a/plugins/lv2/sfizz.cpp +++ b/plugins/lv2/sfizz.cpp @@ -120,6 +120,7 @@ sfizz_lv2_map_required_uris(sfizz_plugin_t *self) self->sfizz_num_voices_uri = map->map(map->handle, SFIZZ__numVoices); self->sfizz_preload_size_uri = map->map(map->handle, SFIZZ__preloadSize); self->sfizz_oversampling_uri = map->map(map->handle, SFIZZ__oversampling); + self->sfizz_last_keyswitch_uri = map->map(map->handle, SFIZZ__lastKeyswitch); self->sfizz_log_status_uri = map->map(map->handle, SFIZZ__logStatus); self->sfizz_check_modification_uri = map->map(map->handle, SFIZZ__checkModification); self->sfizz_osc_blob_uri = map->map(map->handle, SFIZZ__OSCBlob); @@ -323,6 +324,14 @@ sfizz_lv2_receive_message(void* data, int delay, const char* path, const char* s sfizz_plugin_t *self = (sfizz_plugin_t *)data; + if (!strcmp(path, "/sw/last/current") && sig) + { + if (sig[0] == 'i') + self->last_keyswitch = args[0].i; + else if (sig[0] == 'N') + self->last_keyswitch = -1; + } + // transmit to UI as OSC blob uint8_t *osc_temp = self->osc_temp; uint32_t osc_size = sfizz_prepare_message(osc_temp, OSC_TEMP_SIZE, path, sig, args); @@ -1301,6 +1310,7 @@ restore(LV2_Handle instance, } // Set default values + self->last_keyswitch = -1; sfizz_lv2_get_default_sfz_path(self, self->sfz_file_path, MAX_PATH_SIZE); sfizz_lv2_get_default_scala_path(self, self->scala_file_path, MAX_PATH_SIZE); @@ -1351,6 +1361,13 @@ restore(LV2_Handle instance, } } + value = retrieve(handle, self->sfizz_last_keyswitch_uri, &size, &type, &val_flags); + if (value) + { + int last_keyswitch = *(const int*)value; + self->last_keyswitch = last_keyswitch; + } + // Collect all CC values present in the state std::unique_ptr[]> cc_values( new absl::optional[sfz::config::numCCs]); @@ -1405,6 +1422,11 @@ restore(LV2_Handle instance, } } + if (self->last_keyswitch >= 0 && self->last_keyswitch <= 127) { + sfizz_send_hd_note_on(self->synth, 0, self->last_keyswitch, 1.0f); + sfizz_send_hd_note_off(self->synth, 1, self->last_keyswitch, 0.0f); + } + spin_mutex_unlock(self->synth_mutex); return status; @@ -1474,6 +1496,15 @@ save(LV2_Handle instance, absl::string_view((const char*)self->sfz_blob_data, self->sfz_blob_size)); self->sfz_blob_mutex->unlock(); + if (self->last_keyswitch >= 0 && self->last_keyswitch <= 127) { + store(handle, + self->sfizz_last_keyswitch_uri, + &self->last_keyswitch, + sizeof(int), + self->atom_int_uri, + LV2_STATE_IS_POD); + } + for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) { if (desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc)) { LV2_URID urid = sfizz_lv2_ccmap_map(self->ccmap, int(cc)); diff --git a/plugins/lv2/sfizz_lv2.h b/plugins/lv2/sfizz_lv2.h index f48f2606..c9935ec0 100644 --- a/plugins/lv2/sfizz_lv2.h +++ b/plugins/lv2/sfizz_lv2.h @@ -39,6 +39,7 @@ #define SFIZZ__numVoices SFIZZ_URI ":" "numvoices" #define SFIZZ__preloadSize SFIZZ_URI ":" "preload_size" #define SFIZZ__oversampling SFIZZ_URI ":" "oversampling" +#define SFIZZ__lastKeyswitch SFIZZ_URI ":" "last_keyswitch" // These ones are just for the worker #define SFIZZ__logStatus SFIZZ_URI ":" "log_status" #define SFIZZ__checkModification SFIZZ_URI ":" "check_modification" diff --git a/plugins/lv2/sfizz_lv2_plugin.h b/plugins/lv2/sfizz_lv2_plugin.h index 9c1918f9..fad8342a 100644 --- a/plugins/lv2/sfizz_lv2_plugin.h +++ b/plugins/lv2/sfizz_lv2_plugin.h @@ -86,6 +86,7 @@ struct sfizz_plugin_t LV2_URID sfizz_num_voices_uri {}; LV2_URID sfizz_preload_size_uri {}; LV2_URID sfizz_oversampling_uri {}; + LV2_URID sfizz_last_keyswitch_uri {}; LV2_URID sfizz_log_status_uri {}; LV2_URID sfizz_check_modification_uri {}; LV2_URID sfizz_active_voices_uri {}; @@ -120,6 +121,7 @@ struct sfizz_plugin_t float sample_rate {}; std::atomic must_update_midnam {}; volatile bool must_automate_cc {}; + int last_keyswitch { -1 }; // Current instrument description std::mutex *sfz_blob_mutex {}; diff --git a/plugins/vst/SfizzVstProcessor.cpp b/plugins/vst/SfizzVstProcessor.cpp index 1e0a7134..e50801fa 100644 --- a/plugins/vst/SfizzVstProcessor.cpp +++ b/plugins/vst/SfizzVstProcessor.cpp @@ -227,6 +227,10 @@ void SfizzVstProcessor::syncStateToSynth() synth->setScalaRootKey(_state.scalaRootKey); synth->setTuningFrequency(_state.tuningFrequency); synth->loadStretchTuningByRatio(_state.stretchedTuning); + if (_state.lastKeyswitch >= 0 && _state.lastKeyswitch <= 127) { + synth->hdNoteOn(0, _state.lastKeyswitch, 1.0f); + synth->hdNoteOff(1, _state.lastKeyswitch, 0.0f); + } } tresult PLUGIN_API SfizzVstProcessor::canProcessSampleSize(int32 symbolicSampleSize) @@ -669,6 +673,14 @@ bool SfizzVstProcessor::processUpdate(FUnknown* changedUnknown, int32 message) void SfizzVstProcessor::receiveOSC(int delay, const char* path, const char* sig, const sfizz_arg_t* args) { + if (!strcmp(path, "/sw/last/current") && sig) + { + if (sig[0] == 'i') + _state.lastKeyswitch = args[0].i; + else if (sig[0] == 'N') + _state.lastKeyswitch = -1; + } + uint8_t* oscTemp = _oscTemp.get(); uint32 oscSize = sfizz_prepare_message(oscTemp, kOscTempSize, path, sig, args); if (oscSize <= kOscTempSize) { diff --git a/plugins/vst/SfizzVstState.cpp b/plugins/vst/SfizzVstState.cpp index 1783f817..160d7abb 100644 --- a/plugins/vst/SfizzVstState.cpp +++ b/plugins/vst/SfizzVstState.cpp @@ -73,6 +73,14 @@ tresult SfizzVstState::load(IBStream* state) oscillatorQuality = defaults.oscillatorQuality; } + if (version >= 4) { + if (!s.readInt32(lastKeyswitch)) + return kResultFalse; + } + else { + lastKeyswitch = -1; + } + controllers.clear(); if (version >= 2) { uint32 count; @@ -135,6 +143,9 @@ tresult SfizzVstState::store(IBStream* state) const if (!s.writeInt32(oscillatorQuality)) return kResultFalse; + if (!s.writeInt32(lastKeyswitch)) + return kResultFalse; + { uint32 ccCount = 0; uint32 ccLimit = uint32(std::min(controllers.size(), size_t(0x10000))); diff --git a/plugins/vst/SfizzVstState.h b/plugins/vst/SfizzVstState.h index 7f50f4c5..93a7a4ec 100644 --- a/plugins/vst/SfizzVstState.h +++ b/plugins/vst/SfizzVstState.h @@ -27,9 +27,10 @@ public: float stretchedTuning = 0.0; int32 sampleQuality = 2; int32 oscillatorQuality = 1; + int32 lastKeyswitch = -1; std::vector> controllers; - static constexpr uint64 currentStateVersion = 3; + static constexpr uint64 currentStateVersion = 4; tresult load(IBStream* state); tresult store(IBStream* state) const;