Merge pull request #656 from jpcima/show-key-press

Display key events on VK, with VST
This commit is contained in:
JP Cimalando 2021-02-23 16:17:01 +01:00 committed by GitHub
commit 86e7f703a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 207 additions and 11 deletions

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);

View file

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

View file

@ -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<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;
}
@ -307,6 +322,7 @@ IPlugView* PLUGIN_API SfizzVstController::createView(FIDString _name)
std::vector<FObject*> triggerUpdates;
triggerUpdates.push_back(oscUpdate_);
triggerUpdates.push_back(noteUpdate_);
IPtr<SfizzVstEditor> editor = Steinberg::owned(
new SfizzVstEditor(this, absl::MakeSpan(continuousUpdates), absl::MakeSpan(triggerUpdates)));

View file

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

View file

@ -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<std::mutex> 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<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) {
processOscQueue();
processNoteEventQueue();
}
return result;
@ -163,6 +177,17 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message)
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)) {
const std::string path = update->getPath();
switch (update->getType()) {
@ -259,6 +284,20 @@ void SfizzVstEditor::processOscQueue()
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)
{

View file

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

View file

@ -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<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;
}
@ -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<uint8_t>(), msg->size);
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 <sfizz.hpp>
#include <SpinMutex.h>
#include <array>
#include <thread>
#include <memory>
#include <cstdlib>
@ -60,6 +61,9 @@ private:
// misc
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
std::thread _worker;
volatile bool _workRunning = false;

View file

@ -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<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;
};
/**
* @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
* The message ID is used to indicate which path it is.