Display key events on VK, with VST

This commit is contained in:
Jean Pierre Cimalando 2021-02-23 09:27:20 +01:00
parent c5c7e2bb63
commit 34ca6d69e3
12 changed files with 207 additions and 11 deletions

View file

@ -22,6 +22,9 @@ enum class EditId : int {
UserFilesDir, UserFilesDir,
FallbackFilesDir, FallbackFilesDir,
// //
Key0,
KeyLast = Key0 + 128 - 1,
//
Controller0, Controller0,
ControllerLast = Controller0 + sfz::config::numCCs - 1, ControllerLast = Controller0 + sfz::config::numCCs - 1,
// //
@ -58,3 +61,16 @@ inline bool editIdIsCC(EditId id)
return int(id) >= int(EditId::Controller0) && return int(id) >= int(EditId::Controller0) &&
int(id) <= int(EditId::ControllerLast); 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);
}

View file

@ -387,6 +387,14 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
setActivePanel(value); setActivePanel(value);
} }
break; 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;
} }
} }

View file

@ -50,6 +50,20 @@ void SPiano::setKeyUsed(unsigned key, bool used)
invalid(); 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) void SPiano::draw(CDrawContext* dc)
{ {
const Dimensions dim = getDimensions(false); const Dimensions dim = getDimensions(false);

View file

@ -26,6 +26,7 @@ public:
void setNumOctaves(unsigned octs); void setNumOctaves(unsigned octs);
void setKeyUsed(unsigned key, bool used); void setKeyUsed(unsigned key, bool used);
void setKeyValue(unsigned key, float value);
std::function<void(unsigned, float)> onKeyPressed; std::function<void(unsigned, float)> onKeyPressed;
std::function<void(unsigned, float)> onKeyReleased; std::function<void(unsigned, float)> onKeyReleased;
@ -54,7 +55,7 @@ private:
private: private:
unsigned octs_ {}; unsigned octs_ {};
std::vector<unsigned> keyval_; std::vector<float> keyval_;
std::bitset<128> keyUsed_; std::bitset<128> keyUsed_;
unsigned mousePressedKey_ = ~0u; unsigned mousePressedKey_ = ~0u;

View file

@ -21,6 +21,7 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context)
// create update objects // create update objects
oscUpdate_ = Steinberg::owned(new OSCUpdate); oscUpdate_ = Steinberg::owned(new OSCUpdate);
noteUpdate_ = Steinberg::owned(new NoteUpdate);
sfzPathUpdate_ = Steinberg::owned(new FilePathUpdate(kFilePathUpdateSfz)); sfzPathUpdate_ = Steinberg::owned(new FilePathUpdate(kFilePathUpdateSfz));
scalaPathUpdate_ = Steinberg::owned(new FilePathUpdate(kFilePathUpdateScala)); scalaPathUpdate_ = Steinberg::owned(new FilePathUpdate(kFilePathUpdateScala));
processorStateUpdate_ = Steinberg::owned(new ProcessorStateUpdate); processorStateUpdate_ = Steinberg::owned(new ProcessorStateUpdate);
@ -283,6 +284,20 @@ tresult SfizzVstControllerNoUi::notify(Vst::IMessage* message)
oscUpdate_->changed(); oscUpdate_->changed();
oscUpdate_->clear(); 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<uint32_t, float>*>(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; return result;
} }
@ -307,6 +322,7 @@ IPlugView* PLUGIN_API SfizzVstController::createView(FIDString _name)
std::vector<FObject*> triggerUpdates; std::vector<FObject*> triggerUpdates;
triggerUpdates.push_back(oscUpdate_); triggerUpdates.push_back(oscUpdate_);
triggerUpdates.push_back(noteUpdate_);
IPtr<SfizzVstEditor> editor = Steinberg::owned( IPtr<SfizzVstEditor> editor = Steinberg::owned(
new SfizzVstEditor(this, absl::MakeSpan(continuousUpdates), absl::MakeSpan(triggerUpdates))); new SfizzVstEditor(this, absl::MakeSpan(continuousUpdates), absl::MakeSpan(triggerUpdates)));

View file

@ -46,6 +46,7 @@ public:
protected: protected:
Steinberg::IPtr<OSCUpdate> oscUpdate_; Steinberg::IPtr<OSCUpdate> oscUpdate_;
Steinberg::IPtr<NoteUpdate> noteUpdate_;
Steinberg::IPtr<FilePathUpdate> sfzPathUpdate_; Steinberg::IPtr<FilePathUpdate> sfzPathUpdate_;
Steinberg::IPtr<FilePathUpdate> scalaPathUpdate_; Steinberg::IPtr<FilePathUpdate> scalaPathUpdate_;
Steinberg::IPtr<ProcessorStateUpdate> processorStateUpdate_; Steinberg::IPtr<ProcessorStateUpdate> processorStateUpdate_;

View file

@ -23,6 +23,7 @@ static ViewRect sfizzUiViewRect { 0, 0, Editor::viewWidth, Editor::viewHeight };
enum { enum {
kOscTempSize = 8192, kOscTempSize = 8192,
kOscQueueSize = 65536, kOscQueueSize = 65536,
kNoteEventQueueSize = 8192,
}; };
SfizzVstEditor::SfizzVstEditor( SfizzVstEditor::SfizzVstEditor(
@ -70,6 +71,12 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p
oscQueue_.reset(queue); oscQueue_.reset(queue);
queue->reserve(kOscQueueSize); queue->reserve(kOscQueueSize);
} }
{
std::lock_guard<std::mutex> lock(noteEventQueueMutex_);
NoteEventsVec* queue = new NoteEventsVec;
noteEventQueue_.reset(queue);
queue->reserve(kNoteEventQueueSize);
}
if (!frame->open(parent, platformType, config)) { if (!frame->open(parent, platformType, config)) {
fprintf(stderr, "[sfizz] error opening frame\n"); fprintf(stderr, "[sfizz] error opening frame\n");
@ -116,8 +123,14 @@ void PLUGIN_API SfizzVstEditor::close()
this->frame = nullptr; this->frame = nullptr;
} }
std::lock_guard<std::mutex> lock(oscQueueMutex_); {
oscQueue_.reset(); std::lock_guard<std::mutex> lock(oscQueueMutex_);
oscQueue_.reset();
}
{
std::lock_guard<std::mutex> lock(noteEventQueueMutex_);
noteEventQueue_.reset();
}
} }
/// ///
@ -144,6 +157,7 @@ CMessageResult SfizzVstEditor::notify(CBaseObject* sender, const char* message)
if (message == CVSTGUITimer::kMsgTimer) { if (message == CVSTGUITimer::kMsgTimer) {
processOscQueue(); processOscQueue();
processNoteEventQueue();
} }
return result; return result;
@ -163,6 +177,17 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message)
return; return;
} }
if (NoteUpdate* update = FCast<NoteUpdate>(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<std::mutex> lock(noteEventQueueMutex_);
if (NoteEventsVec* queue = noteEventQueue_.get())
std::copy(events, events + count, std::back_inserter(*queue));
}
}
if (FilePathUpdate* update = FCast<FilePathUpdate>(changedUnknown)) { if (FilePathUpdate* update = FCast<FilePathUpdate>(changedUnknown)) {
const std::string path = update->getPath(); const std::string path = update->getPath();
switch (update->getType()) { switch (update->getType()) {
@ -259,6 +284,20 @@ void SfizzVstEditor::processOscQueue()
queue->clear(); queue->clear();
} }
void SfizzVstEditor::processNoteEventQueue()
{
std::lock_guard<std::mutex> lock(noteEventQueueMutex_);
NoteEventsVec* queue = noteEventQueue_.get();
if (!queue)
return;
for (std::pair<uint32, float> event : *queue)
uiReceiveValue(editIdForKey(event.first), event.second);
queue->clear();
}
/// ///
void SfizzVstEditor::uiSendValue(EditId id, const EditValue& v) void SfizzVstEditor::uiSendValue(EditId id, const EditValue& v)
{ {

View file

@ -48,6 +48,7 @@ public:
private: private:
void processOscQueue(); void processOscQueue();
void processNoteEventQueue();
protected: protected:
// EditorController // EditorController
@ -77,6 +78,9 @@ private:
typedef std::vector<uint8_t> OscByteVec; typedef std::vector<uint8_t> OscByteVec;
std::unique_ptr<OscByteVec> oscQueue_; std::unique_ptr<OscByteVec> oscQueue_;
std::mutex oscQueueMutex_; std::mutex oscQueueMutex_;
typedef std::vector<std::pair<uint32, float>> NoteEventsVec;
std::unique_ptr<NoteEventsVec> noteEventQueue_;
std::mutex noteEventQueueMutex_;
// subscribed updates // subscribed updates
std::vector<IPtr<FObject>> continuousUpdates_; std::vector<IPtr<FObject>> continuousUpdates_;

View file

@ -95,6 +95,8 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context)
_synth->timePosition(0, 0, 0); _synth->timePosition(0, 0, 0);
_synth->playbackState(0, 0); _synth->playbackState(0, 0);
_noteEventsCurrentCycle.fill(-1.0f);
return result; return result;
} }
@ -281,6 +283,21 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data)
_semaToWorker.post(); _semaToWorker.post();
} }
//
std::pair<uint32, float> 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; return kResultTrue;
} }
@ -422,15 +439,28 @@ void SfizzVstProcessor::processEvents(Vst::IEventList& events)
continue; continue;
switch (e.type) { switch (e.type) {
case Vst::Event::kNoteOnEvent: case Vst::Event::kNoteOnEvent: {
if (e.noteOn.velocity == 0.0f) int pitch = e.noteOn.pitch;
synth.noteOff(e.sampleOffset, e.noteOn.pitch, 0); if (pitch < 0 || pitch >= 128)
else break;
synth.noteOn(e.sampleOffset, e.noteOn.pitch, convertVelocityFromFloat(e.noteOn.velocity)); 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; 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; break;
}
// case Vst::Event::kPolyPressureEvent: // case Vst::Event::kPolyPressureEvent:
// synth.aftertouch(e.sampleOffset, convertVelocityFromFloat(e.polyPressure.pressure)); // synth.aftertouch(e.sampleOffset, convertVelocityFromFloat(e.polyPressure.pressure));
// break; // 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) void SfizzVstProcessor::receiveMessage(int delay, const char* path, const char* sig, const sfizz_arg_t* args)
{ {
uint8_t* oscTemp = _oscTemp.get(); 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 (oscSize <= kOscTempSize) {
if (writeWorkerMessage("ReceiveMessage", oscTemp, oscSize)) if (writeWorkerMessage("ReceiveMessage", oscTemp, oscSize))
_semaToWorker.post(); _semaToWorker.post();
@ -650,6 +680,12 @@ void SfizzVstProcessor::doBackgroundWork()
notification->getAttributes()->setBinary("Message", msg->payload<uint8_t>(), msg->size); notification->getAttributes()->setBinary("Message", msg->payload<uint8_t>(), msg->size);
sendMessage(notification); sendMessage(notification);
} }
else if (!std::strcmp(id, "NoteEvents")) {
Steinberg::OPtr<Vst::IMessage> notification { allocateMessage() };
notification->setMessageID("NoteEvents");
notification->getAttributes()->setBinary("Events", msg->payload<uint8_t>(), msg->size);
sendMessage(notification);
}
} }
} }

View file

@ -11,6 +11,7 @@
#include "public.sdk/source/vst/vstaudioeffect.h" #include "public.sdk/source/vst/vstaudioeffect.h"
#include <sfizz.hpp> #include <sfizz.hpp>
#include <SpinMutex.h> #include <SpinMutex.h>
#include <array>
#include <thread> #include <thread>
#include <memory> #include <memory>
#include <cstdlib> #include <cstdlib>
@ -60,6 +61,9 @@ private:
// misc // misc
static void loadSfzFileOrDefault(sfz::Sfizz& synth, const std::string& filePath); static void loadSfzFileOrDefault(sfz::Sfizz& synth, const std::string& filePath);
// note event tracking
std::array<float, 128> _noteEventsCurrentCycle; // 0: off, >0: on, <0: no change
// worker and thread sync // worker and thread sync
std::thread _worker; std::thread _worker;
volatile bool _workRunning = false; volatile bool _workRunning = false;

View file

@ -35,3 +35,33 @@ void OSCUpdate::setMessage(const void* data, uint32_t size, bool copy)
size_ = size; size_ = size;
allocated_ = copy; allocated_ = copy;
} }
///
NoteUpdate::~NoteUpdate()
{
clear();
}
void NoteUpdate::clear()
{
if (allocated_)
delete[] events_;
events_ = nullptr;
count_ = 0;
allocated_ = false;
}
void NoteUpdate::setEvents(const std::pair<uint32_t, float>* events, uint32_t count, bool copy)
{
clear();
if (copy) {
auto *buffer = new std::pair<uint32_t, float>[count];
std::memcpy(buffer, events, count);
events = buffer;
}
events_ = events;
count_ = count;
allocated_ = copy;
}

View file

@ -39,6 +39,33 @@ private:
OSCUpdate& operator=(const OSCUpdate&) = delete; 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<uint32_t, float>* events, uint32_t count, bool copy);
const std::pair<uint32_t, float>* events() const noexcept { return events_; }
const uint32_t count() const noexcept { return count_; }
OBJ_METHODS(NoteUpdate, FObject)
private:
const std::pair<uint32_t, float>* 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 * @brief Update which notifies a change of file path pseudo-parameter
* The message ID is used to indicate which path it is. * The message ID is used to indicate which path it is.