Merge pull request #656 from jpcima/show-key-press
Display key events on VK, with VST
This commit is contained in:
commit
86e7f703a1
12 changed files with 207 additions and 11 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue