From 34ca6d69e39bc9759d90e250fdb060d436a2398a Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Tue, 23 Feb 2021 09:27:20 +0100 Subject: [PATCH] Display key events on VK, with VST --- plugins/editor/src/editor/EditIds.h | 16 ++++++++ plugins/editor/src/editor/Editor.cpp | 8 ++++ plugins/editor/src/editor/GUIPiano.cpp | 14 +++++++ plugins/editor/src/editor/GUIPiano.h | 3 +- plugins/vst/SfizzVstController.cpp | 16 ++++++++ plugins/vst/SfizzVstController.h | 1 + plugins/vst/SfizzVstEditor.cpp | 43 ++++++++++++++++++++- plugins/vst/SfizzVstEditor.h | 4 ++ plugins/vst/SfizzVstProcessor.cpp | 52 ++++++++++++++++++++++---- plugins/vst/SfizzVstProcessor.h | 4 ++ plugins/vst/SfizzVstUpdates.cpp | 30 +++++++++++++++ plugins/vst/SfizzVstUpdates.h | 27 +++++++++++++ 12 files changed, 207 insertions(+), 11 deletions(-) diff --git a/plugins/editor/src/editor/EditIds.h b/plugins/editor/src/editor/EditIds.h index 869d1c44..fd1556bc 100644 --- a/plugins/editor/src/editor/EditIds.h +++ b/plugins/editor/src/editor/EditIds.h @@ -22,6 +22,9 @@ enum class EditId : int { UserFilesDir, FallbackFilesDir, // + Key0, + KeyLast = Key0 + 128 - 1, + // Controller0, ControllerLast = Controller0 + sfz::config::numCCs - 1, // @@ -58,3 +61,16 @@ inline bool editIdIsCC(EditId id) return int(id) >= int(EditId::Controller0) && int(id) <= int(EditId::ControllerLast); } + +inline EditId editIdForKey(int key) +{ + return EditId(int(EditId::Key0) + key); +} +inline int keyForEditId(EditId id) +{ + return int(id) - int(EditId::Key0); +} +inline bool editIdIsKey(EditId id) +{ + return int(id) >= int(EditId::Key0) && int(id) <= int(EditId::KeyLast); +} diff --git a/plugins/editor/src/editor/Editor.cpp b/plugins/editor/src/editor/Editor.cpp index 173d7166..83507bd6 100644 --- a/plugins/editor/src/editor/Editor.cpp +++ b/plugins/editor/src/editor/Editor.cpp @@ -387,6 +387,14 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v) setActivePanel(value); } break; + default: + if (editIdIsKey(id)) { + const int key = keyForEditId(id); + const float value = v.to_float(); + if (SPiano* piano = piano_) + piano->setKeyValue(key, value); + } + break; } } diff --git a/plugins/editor/src/editor/GUIPiano.cpp b/plugins/editor/src/editor/GUIPiano.cpp index 22ec4681..6dc990f1 100644 --- a/plugins/editor/src/editor/GUIPiano.cpp +++ b/plugins/editor/src/editor/GUIPiano.cpp @@ -50,6 +50,20 @@ void SPiano::setKeyUsed(unsigned key, bool used) invalid(); } +void SPiano::setKeyValue(unsigned key, float value) +{ + if (key >= 128) + return; + + value = std::max(0.0f, std::min(1.0f, value)); + + if (keyval_[key] == value) + return; + + keyval_[key] = value; + invalid(); +} + void SPiano::draw(CDrawContext* dc) { const Dimensions dim = getDimensions(false); diff --git a/plugins/editor/src/editor/GUIPiano.h b/plugins/editor/src/editor/GUIPiano.h index c35f8741..eafe115c 100644 --- a/plugins/editor/src/editor/GUIPiano.h +++ b/plugins/editor/src/editor/GUIPiano.h @@ -26,6 +26,7 @@ public: void setNumOctaves(unsigned octs); void setKeyUsed(unsigned key, bool used); + void setKeyValue(unsigned key, float value); std::function onKeyPressed; std::function onKeyReleased; @@ -54,7 +55,7 @@ private: private: unsigned octs_ {}; - std::vector keyval_; + std::vector keyval_; std::bitset<128> keyUsed_; unsigned mousePressedKey_ = ~0u; diff --git a/plugins/vst/SfizzVstController.cpp b/plugins/vst/SfizzVstController.cpp index 6717f685..2c3cb320 100644 --- a/plugins/vst/SfizzVstController.cpp +++ b/plugins/vst/SfizzVstController.cpp @@ -21,6 +21,7 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) // create update objects oscUpdate_ = Steinberg::owned(new OSCUpdate); + noteUpdate_ = Steinberg::owned(new NoteUpdate); sfzPathUpdate_ = Steinberg::owned(new FilePathUpdate(kFilePathUpdateSfz)); scalaPathUpdate_ = Steinberg::owned(new FilePathUpdate(kFilePathUpdateScala)); processorStateUpdate_ = Steinberg::owned(new ProcessorStateUpdate); @@ -283,6 +284,20 @@ tresult SfizzVstControllerNoUi::notify(Vst::IMessage* message) oscUpdate_->changed(); oscUpdate_->clear(); } + else if (!strcmp(id, "NoteEvents")) { + const void* data = nullptr; + uint32 size = 0; + result = attr->getBinary("Events", data, size); + + const auto* events = reinterpret_cast< + const std::pair*>(data); + uint32 numEvents = size / sizeof(events[0]); + + // this is a synchronous send, because the update object gets reused + noteUpdate_->setEvents(events, numEvents, false); + noteUpdate_->changed(); + noteUpdate_->clear(); + } return result; } @@ -307,6 +322,7 @@ IPlugView* PLUGIN_API SfizzVstController::createView(FIDString _name) std::vector triggerUpdates; triggerUpdates.push_back(oscUpdate_); + triggerUpdates.push_back(noteUpdate_); IPtr editor = Steinberg::owned( new SfizzVstEditor(this, absl::MakeSpan(continuousUpdates), absl::MakeSpan(triggerUpdates))); diff --git a/plugins/vst/SfizzVstController.h b/plugins/vst/SfizzVstController.h index 2c207e23..83e94eff 100644 --- a/plugins/vst/SfizzVstController.h +++ b/plugins/vst/SfizzVstController.h @@ -46,6 +46,7 @@ public: protected: Steinberg::IPtr oscUpdate_; + Steinberg::IPtr noteUpdate_; Steinberg::IPtr sfzPathUpdate_; Steinberg::IPtr scalaPathUpdate_; Steinberg::IPtr processorStateUpdate_; diff --git a/plugins/vst/SfizzVstEditor.cpp b/plugins/vst/SfizzVstEditor.cpp index 8345c274..e42721ef 100644 --- a/plugins/vst/SfizzVstEditor.cpp +++ b/plugins/vst/SfizzVstEditor.cpp @@ -23,6 +23,7 @@ static ViewRect sfizzUiViewRect { 0, 0, Editor::viewWidth, Editor::viewHeight }; enum { kOscTempSize = 8192, kOscQueueSize = 65536, + kNoteEventQueueSize = 8192, }; SfizzVstEditor::SfizzVstEditor( @@ -70,6 +71,12 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p oscQueue_.reset(queue); queue->reserve(kOscQueueSize); } + { + std::lock_guard lock(noteEventQueueMutex_); + NoteEventsVec* queue = new NoteEventsVec; + noteEventQueue_.reset(queue); + queue->reserve(kNoteEventQueueSize); + } if (!frame->open(parent, platformType, config)) { fprintf(stderr, "[sfizz] error opening frame\n"); @@ -116,8 +123,14 @@ void PLUGIN_API SfizzVstEditor::close() this->frame = nullptr; } - std::lock_guard lock(oscQueueMutex_); - oscQueue_.reset(); + { + std::lock_guard lock(oscQueueMutex_); + oscQueue_.reset(); + } + { + std::lock_guard lock(noteEventQueueMutex_); + noteEventQueue_.reset(); + } } /// @@ -144,6 +157,7 @@ CMessageResult SfizzVstEditor::notify(CBaseObject* sender, const char* message) if (message == CVSTGUITimer::kMsgTimer) { processOscQueue(); + processNoteEventQueue(); } return result; @@ -163,6 +177,17 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message) return; } + if (NoteUpdate* update = FCast(changedUnknown)) { + // this update is synchronous: may happen from non-UI thread + uint32 count = update->count(); + if (count > 0) { + const auto* events = update->events(); + std::lock_guard lock(noteEventQueueMutex_); + if (NoteEventsVec* queue = noteEventQueue_.get()) + std::copy(events, events + count, std::back_inserter(*queue)); + } + } + if (FilePathUpdate* update = FCast(changedUnknown)) { const std::string path = update->getPath(); switch (update->getType()) { @@ -259,6 +284,20 @@ void SfizzVstEditor::processOscQueue() queue->clear(); } +void SfizzVstEditor::processNoteEventQueue() +{ + std::lock_guard lock(noteEventQueueMutex_); + + NoteEventsVec* queue = noteEventQueue_.get(); + if (!queue) + return; + + for (std::pair event : *queue) + uiReceiveValue(editIdForKey(event.first), event.second); + + queue->clear(); +} + /// void SfizzVstEditor::uiSendValue(EditId id, const EditValue& v) { diff --git a/plugins/vst/SfizzVstEditor.h b/plugins/vst/SfizzVstEditor.h index a03302a5..79cbb0ee 100644 --- a/plugins/vst/SfizzVstEditor.h +++ b/plugins/vst/SfizzVstEditor.h @@ -48,6 +48,7 @@ public: private: void processOscQueue(); + void processNoteEventQueue(); protected: // EditorController @@ -77,6 +78,9 @@ private: typedef std::vector OscByteVec; std::unique_ptr oscQueue_; std::mutex oscQueueMutex_; + typedef std::vector> NoteEventsVec; + std::unique_ptr noteEventQueue_; + std::mutex noteEventQueueMutex_; // subscribed updates std::vector> continuousUpdates_; diff --git a/plugins/vst/SfizzVstProcessor.cpp b/plugins/vst/SfizzVstProcessor.cpp index 39b021f3..609de2f8 100644 --- a/plugins/vst/SfizzVstProcessor.cpp +++ b/plugins/vst/SfizzVstProcessor.cpp @@ -95,6 +95,8 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context) _synth->timePosition(0, 0, 0); _synth->playbackState(0, 0); + _noteEventsCurrentCycle.fill(-1.0f); + return result; } @@ -281,6 +283,21 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data) _semaToWorker.post(); } + // + std::pair noteEvents[128]; + size_t numNoteEvents = 0; + for (uint32 key = 0; key < 128; ++key) { + float value = _noteEventsCurrentCycle[key]; + if (value < 0.0f) + continue; + noteEvents[numNoteEvents++] = std::make_pair(key, value); + _noteEventsCurrentCycle[key] = -1.0f; + } + if (numNoteEvents > 0) { + if (writeWorkerMessage("NoteEvents", noteEvents, numNoteEvents * sizeof(noteEvents[0]))) + _semaToWorker.post(); + } + return kResultTrue; } @@ -422,15 +439,28 @@ void SfizzVstProcessor::processEvents(Vst::IEventList& events) continue; switch (e.type) { - case Vst::Event::kNoteOnEvent: - if (e.noteOn.velocity == 0.0f) - synth.noteOff(e.sampleOffset, e.noteOn.pitch, 0); - else - synth.noteOn(e.sampleOffset, e.noteOn.pitch, convertVelocityFromFloat(e.noteOn.velocity)); + case Vst::Event::kNoteOnEvent: { + int pitch = e.noteOn.pitch; + if (pitch < 0 || pitch >= 128) + break; + if (e.noteOn.velocity <= 0.0f) { + synth.noteOff(e.sampleOffset, pitch, 0); + _noteEventsCurrentCycle[pitch] = 0.0f; + } + else { + synth.noteOn(e.sampleOffset, pitch, convertVelocityFromFloat(e.noteOn.velocity)); + _noteEventsCurrentCycle[pitch] = e.noteOn.velocity; + } break; - case Vst::Event::kNoteOffEvent: - synth.noteOff(e.sampleOffset, e.noteOff.pitch, convertVelocityFromFloat(e.noteOff.velocity)); + } + case Vst::Event::kNoteOffEvent: { + int pitch = e.noteOn.pitch; + if (pitch < 0 || pitch >= 128) + break; + synth.noteOff(e.sampleOffset, pitch, convertVelocityFromFloat(e.noteOff.velocity)); + _noteEventsCurrentCycle[pitch] = 0.0f; break; + } // case Vst::Event::kPolyPressureEvent: // synth.aftertouch(e.sampleOffset, convertVelocityFromFloat(e.polyPressure.pressure)); // break; @@ -574,7 +604,7 @@ FUnknown* SfizzVstProcessor::createInstance(void*) void SfizzVstProcessor::receiveMessage(int delay, const char* path, const char* sig, const sfizz_arg_t* args) { uint8_t* oscTemp = _oscTemp.get(); - uint32_t oscSize = sfizz_prepare_message(oscTemp, kOscTempSize, path, sig, args); + uint32 oscSize = sfizz_prepare_message(oscTemp, kOscTempSize, path, sig, args); if (oscSize <= kOscTempSize) { if (writeWorkerMessage("ReceiveMessage", oscTemp, oscSize)) _semaToWorker.post(); @@ -650,6 +680,12 @@ void SfizzVstProcessor::doBackgroundWork() notification->getAttributes()->setBinary("Message", msg->payload(), msg->size); sendMessage(notification); } + else if (!std::strcmp(id, "NoteEvents")) { + Steinberg::OPtr notification { allocateMessage() }; + notification->setMessageID("NoteEvents"); + notification->getAttributes()->setBinary("Events", msg->payload(), msg->size); + sendMessage(notification); + } } } diff --git a/plugins/vst/SfizzVstProcessor.h b/plugins/vst/SfizzVstProcessor.h index 1401780b..2219ab6a 100644 --- a/plugins/vst/SfizzVstProcessor.h +++ b/plugins/vst/SfizzVstProcessor.h @@ -11,6 +11,7 @@ #include "public.sdk/source/vst/vstaudioeffect.h" #include #include +#include #include #include #include @@ -60,6 +61,9 @@ private: // misc static void loadSfzFileOrDefault(sfz::Sfizz& synth, const std::string& filePath); + // note event tracking + std::array _noteEventsCurrentCycle; // 0: off, >0: on, <0: no change + // worker and thread sync std::thread _worker; volatile bool _workRunning = false; diff --git a/plugins/vst/SfizzVstUpdates.cpp b/plugins/vst/SfizzVstUpdates.cpp index ab6c69f5..d9921bdb 100644 --- a/plugins/vst/SfizzVstUpdates.cpp +++ b/plugins/vst/SfizzVstUpdates.cpp @@ -35,3 +35,33 @@ void OSCUpdate::setMessage(const void* data, uint32_t size, bool copy) size_ = size; allocated_ = copy; } + +/// +NoteUpdate::~NoteUpdate() +{ + clear(); +} + +void NoteUpdate::clear() +{ + if (allocated_) + delete[] events_; + events_ = nullptr; + count_ = 0; + allocated_ = false; +} + +void NoteUpdate::setEvents(const std::pair* events, uint32_t count, bool copy) +{ + clear(); + + if (copy) { + auto *buffer = new std::pair[count]; + std::memcpy(buffer, events, count); + events = buffer; + } + + events_ = events; + count_ = count; + allocated_ = copy; +} diff --git a/plugins/vst/SfizzVstUpdates.h b/plugins/vst/SfizzVstUpdates.h index 0837a047..0fbd8d3a 100644 --- a/plugins/vst/SfizzVstUpdates.h +++ b/plugins/vst/SfizzVstUpdates.h @@ -39,6 +39,33 @@ private: OSCUpdate& operator=(const OSCUpdate&) = delete; }; +/** + * @brief Update which notifies one or more note on/off events + * Is is supposed to be used synchronously. + * (ie. FObject::changed or UpdateHandler::triggerUpdates) + */ +class NoteUpdate : public Steinberg::FObject { +public: + NoteUpdate() = default; + ~NoteUpdate(); + void clear(); + void setEvents(const std::pair* events, uint32_t count, bool copy); + + const std::pair* events() const noexcept { return events_; } + const uint32_t count() const noexcept { return count_; } + + OBJ_METHODS(NoteUpdate, FObject) + +private: + const std::pair* events_ = nullptr; + uint32_t count_ = 0; + bool allocated_ = false; + +private: + NoteUpdate(const NoteUpdate&) = delete; + NoteUpdate& operator=(const NoteUpdate&) = delete; +}; + /** * @brief Update which notifies a change of file path pseudo-parameter * The message ID is used to indicate which path it is.