Merge pull request #803 from jpcima/sorted-events
Ordered event processing for VST
This commit is contained in:
commit
4ecfac1dce
6 changed files with 408 additions and 148 deletions
|
|
@ -38,6 +38,8 @@ add_library(sfizz-vst3-core STATIC
|
||||||
SfizzVstUpdates.h
|
SfizzVstUpdates.h
|
||||||
SfizzVstUpdates.cpp
|
SfizzVstUpdates.cpp
|
||||||
SfizzVstIDs.h
|
SfizzVstIDs.h
|
||||||
|
OrderedEventProcessor.h
|
||||||
|
OrderedEventProcessor.cpp
|
||||||
IdleUpdateHandler.h
|
IdleUpdateHandler.h
|
||||||
X11RunLoop.h
|
X11RunLoop.h
|
||||||
X11RunLoop.cpp)
|
X11RunLoop.cpp)
|
||||||
|
|
|
||||||
233
plugins/vst/OrderedEventProcessor.cpp
Normal file
233
plugins/vst/OrderedEventProcessor.cpp
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
#include "OrderedEventProcessor.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
void OrderedEventProcessor::initializeEventProcessor(const Vst::ProcessSetup& setup, int32 paramCount, int32 subdivSize)
|
||||||
|
{
|
||||||
|
paramCount_ = paramCount;
|
||||||
|
subdivSize_ = subdivSize;
|
||||||
|
queues_.reset(new Cell<QueueStatus>[paramCount]);
|
||||||
|
pointsBySample_.reset(new ParameterAndValue[paramCount * subdivSize]);
|
||||||
|
numPointsBySample_.reset(new uint32[subdivSize]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OrderedEventProcessor::processUnorderedEvents(int32 numSamples, Vst::IParameterChanges* pcs, Vst::IEventList* evs)
|
||||||
|
{
|
||||||
|
int32 sampleIndex = 0;
|
||||||
|
int32 subdivNumber = 0;
|
||||||
|
const int32 subdivSize = subdivSize_;
|
||||||
|
|
||||||
|
startProcessing(evs, pcs);
|
||||||
|
|
||||||
|
while (sampleIndex < numSamples || subdivNumber == 0) {
|
||||||
|
int32 subdivCurrentSize = std::min(numSamples - sampleIndex, subdivSize);
|
||||||
|
processSubdiv(evs, sampleIndex, sampleIndex + subdivCurrentSize - 1);
|
||||||
|
sampleIndex += subdivCurrentSize;
|
||||||
|
++subdivNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
playRemainder(std::max(int32(0), numSamples - 1), evs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OrderedEventProcessor::startProcessing(Vst::IEventList* evs, Vst::IParameterChanges* pcs)
|
||||||
|
{
|
||||||
|
// collect the parameter queues which have values on them
|
||||||
|
// push them onto a work list
|
||||||
|
Cell<QueueStatus>* head = nullptr;
|
||||||
|
Cell<QueueStatus>* queues = queues_.get();
|
||||||
|
|
||||||
|
if (pcs) {
|
||||||
|
const int32 count = pcs->getParameterCount();
|
||||||
|
for (int32 index = count; index-- > 0; ) {
|
||||||
|
Vst::IParamValueQueue* vq = pcs->getParameterData(index);
|
||||||
|
if (vq) {
|
||||||
|
// Note: expectation that hosts does not send more than one
|
||||||
|
// queue for one parameter
|
||||||
|
Vst::ParamID id = vq->getParameterId();
|
||||||
|
Cell<QueueStatus>* cell = &queues[id];
|
||||||
|
cell->next = head;
|
||||||
|
cell->value.queue = vq;
|
||||||
|
cell->value.pointIndex = 0;
|
||||||
|
cell->value.pointCount = vq->getPointCount();
|
||||||
|
head = cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queueList_ = head;
|
||||||
|
|
||||||
|
// position ourselves to the start of the event list
|
||||||
|
eventIndex_ = 0;
|
||||||
|
eventCount_ = evs ? evs->getEventCount() : 0;
|
||||||
|
haveCurrentEvent_ = eventCount_ > 0 &&
|
||||||
|
evs->getEvent(eventIndex_, currentEvent_) == kResultTrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OrderedEventProcessor::processSubdiv(Vst::IEventList* evs, int32 firstOffset, int32 lastOffset)
|
||||||
|
{
|
||||||
|
sortSubdiv(firstOffset, lastOffset);
|
||||||
|
playSubdiv(evs, firstOffset, lastOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OrderedEventProcessor::sortSubdiv(int32 firstOffset, int32 lastOffset)
|
||||||
|
{
|
||||||
|
// this collects parameter changes from the subdivision that goes from
|
||||||
|
// first offset to last offset, included.
|
||||||
|
|
||||||
|
// these parameter changes are on a array of lists.
|
||||||
|
// this 2D structure is backed by a contiguous array dimensioned for
|
||||||
|
// the worst case.
|
||||||
|
|
||||||
|
// Example:
|
||||||
|
// pointsBySample (column-major →) | numPointsBySample[sample]
|
||||||
|
// ====================================================================
|
||||||
|
// sample 0 [P1] [P2] [ ] [ ] | 2
|
||||||
|
// sample 1 [P3] [ ] [ ] [ ] | 1
|
||||||
|
// sample 2 [ ] [ ] [ ] [ ] | 0
|
||||||
|
// sample 3 [P1] [P2] [P3] [P4] | 4
|
||||||
|
|
||||||
|
Cell<QueueStatus>* head = queueList_;
|
||||||
|
Cell<QueueStatus>* queues = queues_.get();
|
||||||
|
const int32 paramCount = paramCount_;
|
||||||
|
ParameterAndValue* pointsBySample = pointsBySample_.get();
|
||||||
|
uint32* numPointsBySample = numPointsBySample_.get();
|
||||||
|
|
||||||
|
std::fill_n(numPointsBySample, lastOffset - firstOffset + 1, 0);
|
||||||
|
|
||||||
|
Cell<QueueStatus>* cur = head;
|
||||||
|
Cell<QueueStatus>* prev = nullptr;
|
||||||
|
|
||||||
|
while (cur) {
|
||||||
|
Vst::IParamValueQueue* vq = cur->value.queue;
|
||||||
|
const Vst::ParamID id = Vst::ParamID(std::distance(queues, cur));
|
||||||
|
|
||||||
|
int32 pointIndex = cur->value.pointIndex;
|
||||||
|
int32 pointCount = cur->value.pointCount;
|
||||||
|
|
||||||
|
int32 previousOffset = firstOffset;
|
||||||
|
while (pointIndex < pointCount) {
|
||||||
|
int32 sampleOffset;
|
||||||
|
Vst::ParamValue value;
|
||||||
|
|
||||||
|
if (vq->getPoint(pointIndex, sampleOffset, value) != kResultTrue) {
|
||||||
|
++pointIndex;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampleOffset > lastOffset)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ensure that offsets never go back
|
||||||
|
if (sampleOffset < previousOffset)
|
||||||
|
sampleOffset = previousOffset;
|
||||||
|
|
||||||
|
int32 listSize = numPointsBySample[sampleOffset - firstOffset];
|
||||||
|
ParameterAndValue* listItems = &pointsBySample[paramCount * (sampleOffset - firstOffset)];
|
||||||
|
|
||||||
|
bool isDuplicatePoint = listSize > 0 && listItems[listSize - 1].id == id;
|
||||||
|
if (isDuplicatePoint) {
|
||||||
|
// protect against duplicates, which might overflow the list
|
||||||
|
listItems[listSize - 1].value = value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(listSize < paramCount);
|
||||||
|
listItems[listSize].id = id;
|
||||||
|
listItems[listSize].value = value;
|
||||||
|
numPointsBySample[sampleOffset - firstOffset] = ++listSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
previousOffset = sampleOffset;
|
||||||
|
++pointIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pointIndex < pointCount) {
|
||||||
|
cur->value.pointIndex = pointIndex;
|
||||||
|
prev = cur;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// if no more points, take this queue off the list
|
||||||
|
if (prev)
|
||||||
|
prev->next = cur->next;
|
||||||
|
else {
|
||||||
|
head = cur->next;
|
||||||
|
prev = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cur = cur->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
queueList_ = head;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OrderedEventProcessor::playSubdiv(Vst::IEventList* evs, int32 firstOffset, int32 lastOffset)
|
||||||
|
{
|
||||||
|
// go over the points in sample order, and play them intermittently with the
|
||||||
|
// event queue according to the sample offsets.
|
||||||
|
|
||||||
|
const int32 paramCount = paramCount_;
|
||||||
|
const ParameterAndValue* pointsBySample = pointsBySample_.get();
|
||||||
|
const uint32* numPointsBySample = numPointsBySample_.get();
|
||||||
|
|
||||||
|
for (int32 sampleOffset = firstOffset; sampleOffset <= lastOffset; ++sampleOffset) {
|
||||||
|
const int32 listSize = numPointsBySample[sampleOffset - firstOffset];
|
||||||
|
const ParameterAndValue* listItems = &pointsBySample[paramCount * (sampleOffset - firstOffset)];
|
||||||
|
playEventsUpTo(evs, sampleOffset);
|
||||||
|
for (int32 i = 0; i < listSize; ++i) {
|
||||||
|
const ParameterAndValue item = listItems[i];
|
||||||
|
playOrderedParameter(sampleOffset, item.id, item.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OrderedEventProcessor::playRemainder(int32 sampleOffset, Vst::IEventList* evs)
|
||||||
|
{
|
||||||
|
// play any remaining events and parameters in arbitrary order,
|
||||||
|
// disregarding their respective offsets
|
||||||
|
|
||||||
|
int32 eventIndex = eventIndex_;
|
||||||
|
int32 eventCount = eventCount_;
|
||||||
|
bool haveCurrentEvent = haveCurrentEvent_;
|
||||||
|
while (haveCurrentEvent) {
|
||||||
|
currentEvent_.sampleOffset = std::min(sampleOffset, currentEvent_.sampleOffset);
|
||||||
|
playOrderedEvent(currentEvent_);
|
||||||
|
haveCurrentEvent = false;
|
||||||
|
while (!haveCurrentEvent && ++eventIndex < eventCount)
|
||||||
|
haveCurrentEvent = evs->getEvent(eventIndex, currentEvent_) == kResultTrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cell<QueueStatus>* queues = queues_.get();
|
||||||
|
for (Cell<QueueStatus>* cur = queueList_; cur; cur = cur->next) {
|
||||||
|
for (int32 i = cur->value.pointIndex, n = cur->value.pointCount; i < n; ++i) {
|
||||||
|
Vst::IParamValueQueue* vq = cur->value.queue;
|
||||||
|
const Vst::ParamID id = Vst::ParamID(std::distance(queues, cur));
|
||||||
|
int32 unusedSampleOffset;
|
||||||
|
Vst::ParamValue value;
|
||||||
|
if (vq->getPoint(i, unusedSampleOffset, value) == kResultTrue)
|
||||||
|
playOrderedParameter(sampleOffset, id, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OrderedEventProcessor::playEventsUpTo(Vst::IEventList* evs, int32 sampleOffset)
|
||||||
|
{
|
||||||
|
int32 index = eventIndex_;
|
||||||
|
int32 count = eventCount_;
|
||||||
|
bool haveCurrentEvent = haveCurrentEvent_;
|
||||||
|
|
||||||
|
while (haveCurrentEvent && currentEvent_.sampleOffset <= sampleOffset) {
|
||||||
|
playOrderedEvent(currentEvent_);
|
||||||
|
haveCurrentEvent = false;
|
||||||
|
while (!haveCurrentEvent && ++index < count)
|
||||||
|
haveCurrentEvent = evs->getEvent(index, currentEvent_) == kResultTrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
eventIndex_ = index;
|
||||||
|
haveCurrentEvent_ = haveCurrentEvent;
|
||||||
|
}
|
||||||
66
plugins/vst/OrderedEventProcessor.h
Normal file
66
plugins/vst/OrderedEventProcessor.h
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "pluginterfaces/vst/ivstevents.h"
|
||||||
|
#include "pluginterfaces/vst/ivstparameterchanges.h"
|
||||||
|
#include "pluginterfaces/vst/ivstaudioprocessor.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
using namespace Steinberg;
|
||||||
|
|
||||||
|
class OrderedEventProcessor {
|
||||||
|
public:
|
||||||
|
virtual ~OrderedEventProcessor() {}
|
||||||
|
|
||||||
|
void initializeEventProcessor(const Vst::ProcessSetup& setup, int32 paramCount, int32 subdivSize = 128);
|
||||||
|
void processUnorderedEvents(int32 numSamples, Vst::IParameterChanges* pcs, Vst::IEventList* evs);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void playOrderedParameter(int32 sampleOffset, Vst::ParamID id, Vst::ParamValue value) = 0;
|
||||||
|
virtual void playOrderedEvent(const Vst::Event& event) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void startProcessing(Vst::IEventList* evs, Vst::IParameterChanges* pcs);
|
||||||
|
void processSubdiv(Vst::IEventList* evs, int32 firstOffset, int32 lastOffset);
|
||||||
|
|
||||||
|
void sortSubdiv(int32 firstOffset, int32 lastOffset);
|
||||||
|
void playSubdiv(Vst::IEventList* evs, int32 firstOffset, int32 lastOffset);
|
||||||
|
|
||||||
|
void playRemainder(int32 sampleOffset, Vst::IEventList* evs);
|
||||||
|
|
||||||
|
void playEventsUpTo(Vst::IEventList* evs, int32 sampleOffset);
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <class T> struct Cell {
|
||||||
|
Cell<T>* next = nullptr;
|
||||||
|
T value {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct QueueStatus {
|
||||||
|
Vst::IParamValueQueue* queue = nullptr;
|
||||||
|
int32 pointIndex = 0;
|
||||||
|
int32 pointCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
int32 paramCount_ = 0;
|
||||||
|
int32 subdivSize_ = 0;
|
||||||
|
std::unique_ptr<Cell<QueueStatus>[]> queues_;
|
||||||
|
Cell<QueueStatus>* queueList_ = nullptr;
|
||||||
|
|
||||||
|
struct ParameterAndValue {
|
||||||
|
Vst::ParamID id {};
|
||||||
|
Vst::ParamValue value {};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<ParameterAndValue[]> pointsBySample_;
|
||||||
|
std::unique_ptr<uint32[]> numPointsBySample_;
|
||||||
|
|
||||||
|
int32 eventIndex_ = 0;
|
||||||
|
int32 eventCount_ = 0;
|
||||||
|
bool haveCurrentEvent_ = false;
|
||||||
|
Vst::Event currentEvent_ {};
|
||||||
|
};
|
||||||
|
|
@ -25,6 +25,7 @@ enum {
|
||||||
kPidCC0,
|
kPidCC0,
|
||||||
kPidCCLast = kPidCC0 + sfz::config::numCCs - 1,
|
kPidCCLast = kPidCC0 + sfz::config::numCCs - 1,
|
||||||
/* Reserved */
|
/* Reserved */
|
||||||
|
kNumParameters,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SfizzRange {
|
struct SfizzRange {
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,7 @@ tresult PLUGIN_API SfizzVstProcessor::setActive(TBool state)
|
||||||
if (state) {
|
if (state) {
|
||||||
synth->setSampleRate(processSetup.sampleRate);
|
synth->setSampleRate(processSetup.sampleRate);
|
||||||
synth->setSamplesPerBlock(processSetup.maxSamplesPerBlock);
|
synth->setSamplesPerBlock(processSetup.maxSamplesPerBlock);
|
||||||
|
initializeEventProcessor(processSetup, kNumParameters);
|
||||||
startBackgroundWork();
|
startBackgroundWork();
|
||||||
} else {
|
} else {
|
||||||
stopBackgroundWork();
|
stopBackgroundWork();
|
||||||
|
|
@ -241,16 +242,22 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data)
|
||||||
{
|
{
|
||||||
sfz::Sfizz& synth = *_synth;
|
sfz::Sfizz& synth = *_synth;
|
||||||
|
|
||||||
|
std::unique_lock<SpinMutex> lock(_processMutex, std::defer_lock);
|
||||||
|
if (data.processMode == Vst::kOffline)
|
||||||
|
lock.lock();
|
||||||
|
else
|
||||||
|
lock.try_lock();
|
||||||
|
|
||||||
if (data.processContext)
|
if (data.processContext)
|
||||||
updateTimeInfo(*data.processContext);
|
updateTimeInfo(*data.processContext);
|
||||||
|
|
||||||
if (Vst::IParameterChanges* pc = data.inputParameterChanges)
|
const uint32 numFrames = data.numSamples;
|
||||||
processParameterChanges(*pc);
|
_canPerformEventsAndParameters = lock.owns_lock();
|
||||||
|
processUnorderedEvents(numFrames, data.inputParameterChanges, data.inputEvents);
|
||||||
|
|
||||||
if (data.numOutputs < 1) // flush mode
|
if (data.numOutputs < 1) // flush mode
|
||||||
return kResultTrue;
|
return kResultTrue;
|
||||||
|
|
||||||
uint32 numFrames = data.numSamples;
|
|
||||||
constexpr uint32 numChannels = 2;
|
constexpr uint32 numChannels = 2;
|
||||||
float* outputs[numChannels];
|
float* outputs[numChannels];
|
||||||
|
|
||||||
|
|
@ -259,8 +266,6 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data)
|
||||||
for (unsigned c = 0; c < numChannels; ++c)
|
for (unsigned c = 0; c < numChannels; ++c)
|
||||||
outputs[c] = data.outputs[0].channelBuffers32[c];
|
outputs[c] = data.outputs[0].channelBuffers32[c];
|
||||||
|
|
||||||
std::unique_lock<SpinMutex> lock(_processMutex, std::try_to_lock);
|
|
||||||
|
|
||||||
if (!lock.owns_lock()) {
|
if (!lock.owns_lock()) {
|
||||||
for (unsigned c = 0; c < numChannels; ++c)
|
for (unsigned c = 0; c < numChannels; ++c)
|
||||||
std::memset(outputs[c], 0, numFrames * sizeof(float));
|
std::memset(outputs[c], 0, numFrames * sizeof(float));
|
||||||
|
|
@ -275,12 +280,6 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data)
|
||||||
|
|
||||||
processMessagesFromUi();
|
processMessagesFromUi();
|
||||||
|
|
||||||
if (Vst::IParameterChanges* pc = data.inputParameterChanges)
|
|
||||||
processControllerChanges(*pc);
|
|
||||||
|
|
||||||
if (Vst::IEventList* events = data.inputEvents)
|
|
||||||
processEvents(*events);
|
|
||||||
|
|
||||||
synth.setVolume(_state.volume);
|
synth.setVolume(_state.volume);
|
||||||
synth.setScalaRootKey(_state.scalaRootKey);
|
synth.setScalaRootKey(_state.scalaRootKey);
|
||||||
synth.setTuningFrequency(_state.tuningFrequency);
|
synth.setTuningFrequency(_state.tuningFrequency);
|
||||||
|
|
@ -336,153 +335,105 @@ void SfizzVstProcessor::updateTimeInfo(const Vst::ProcessContext& context)
|
||||||
synth.playbackState(0, (context.state & context.kPlaying) != 0);
|
synth.playbackState(0, (context.state & context.kPlaying) != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SfizzVstProcessor::processParameterChanges(Vst::IParameterChanges& pc)
|
void SfizzVstProcessor::playOrderedParameter(int32 sampleOffset, Vst::ParamID id, Vst::ParamValue value)
|
||||||
{
|
{
|
||||||
uint32 paramCount = pc.getParameterCount();
|
if (!_canPerformEventsAndParameters)
|
||||||
|
return;
|
||||||
|
|
||||||
for (uint32 paramIndex = 0; paramIndex < paramCount; ++paramIndex) {
|
sfz::Sfizz& synth = *_synth;
|
||||||
Vst::IParamValueQueue* vq = pc.getParameterData(paramIndex);
|
const SfizzRange range = SfizzRange::getForParameter(id);
|
||||||
if (!vq)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const Vst::ParamID id = vq->getParameterId();
|
switch (id) {
|
||||||
const SfizzRange range = SfizzRange::getForParameter(id);
|
case kPidVolume:
|
||||||
|
_state.volume = range.denormalize(value);
|
||||||
uint32 pointCount = vq->getPointCount();
|
break;
|
||||||
int32 sampleOffset;
|
case kPidNumVoices:
|
||||||
Vst::ParamValue value;
|
{
|
||||||
|
int32 data = static_cast<int32>(range.denormalize(value));
|
||||||
switch (id) {
|
_state.numVoices = data;
|
||||||
case kPidVolume:
|
if (writeWorkerMessage(kMsgIdSetNumVoices, &data, sizeof(data)))
|
||||||
if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue)
|
_semaToWorker.post();
|
||||||
_state.volume = range.denormalize(value);
|
|
||||||
break;
|
|
||||||
case kPidNumVoices:
|
|
||||||
if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue) {
|
|
||||||
int32 data = static_cast<int32>(range.denormalize(value));
|
|
||||||
_state.numVoices = data;
|
|
||||||
if (writeWorkerMessage(kMsgIdSetNumVoices, &data, sizeof(data)))
|
|
||||||
_semaToWorker.post();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case kPidOversampling:
|
|
||||||
if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue) {
|
|
||||||
int32 data = static_cast<int32>(range.denormalize(value));
|
|
||||||
_state.oversamplingLog2 = data;
|
|
||||||
if (writeWorkerMessage(kMsgIdSetOversampling, &data, sizeof(data)))
|
|
||||||
_semaToWorker.post();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case kPidPreloadSize:
|
|
||||||
if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue) {
|
|
||||||
int32 data = static_cast<int32>(range.denormalize(value));
|
|
||||||
_state.preloadSize = data;
|
|
||||||
if (writeWorkerMessage(kMsgIdSetPreloadSize, &data, sizeof(data)))
|
|
||||||
_semaToWorker.post();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case kPidScalaRootKey:
|
|
||||||
if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue)
|
|
||||||
_state.scalaRootKey = static_cast<int32>(range.denormalize(value));
|
|
||||||
break;
|
|
||||||
case kPidTuningFrequency:
|
|
||||||
if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue)
|
|
||||||
_state.tuningFrequency = range.denormalize(value);
|
|
||||||
break;
|
|
||||||
case kPidStretchedTuning:
|
|
||||||
if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue)
|
|
||||||
_state.stretchedTuning = range.denormalize(value);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case kPidOversampling:
|
||||||
|
{
|
||||||
|
int32 data = static_cast<int32>(range.denormalize(value));
|
||||||
|
_state.oversamplingLog2 = data;
|
||||||
|
if (writeWorkerMessage(kMsgIdSetOversampling, &data, sizeof(data)))
|
||||||
|
_semaToWorker.post();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case kPidPreloadSize:
|
||||||
|
{
|
||||||
|
int32 data = static_cast<int32>(range.denormalize(value));
|
||||||
|
_state.preloadSize = data;
|
||||||
|
if (writeWorkerMessage(kMsgIdSetPreloadSize, &data, sizeof(data)))
|
||||||
|
_semaToWorker.post();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case kPidScalaRootKey:
|
||||||
|
_state.scalaRootKey = static_cast<int32>(range.denormalize(value));
|
||||||
|
break;
|
||||||
|
case kPidTuningFrequency:
|
||||||
|
_state.tuningFrequency = range.denormalize(value);
|
||||||
|
break;
|
||||||
|
case kPidStretchedTuning:
|
||||||
|
_state.stretchedTuning = range.denormalize(value);
|
||||||
|
break;
|
||||||
|
case kPidAftertouch:
|
||||||
|
synth.aftertouch(sampleOffset, fastRound(value * 127.0));
|
||||||
|
break;
|
||||||
|
case kPidPitchBend:
|
||||||
|
synth.pitchWheel(sampleOffset, fastRound(value * 16383) - 8192);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (id >= kPidCC0 && id <= kPidCCLast) {
|
||||||
|
int32 ccNumber = static_cast<int32>(id - kPidCC0);
|
||||||
|
synth.hdcc(sampleOffset, ccNumber, value);
|
||||||
|
_state.controllers[ccNumber] = value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SfizzVstProcessor::processControllerChanges(Vst::IParameterChanges& pc)
|
void SfizzVstProcessor::playOrderedEvent(const Vst::Event& event)
|
||||||
{
|
{
|
||||||
|
if (!_canPerformEventsAndParameters)
|
||||||
|
return;
|
||||||
|
|
||||||
sfz::Sfizz& synth = *_synth;
|
sfz::Sfizz& synth = *_synth;
|
||||||
uint32 paramCount = pc.getParameterCount();
|
const int32 sampleOffset = event.sampleOffset;
|
||||||
|
|
||||||
for (uint32 paramIndex = 0; paramIndex < paramCount; ++paramIndex) {
|
switch (event.type) {
|
||||||
Vst::IParamValueQueue* vq = pc.getParameterData(paramIndex);
|
case Vst::Event::kNoteOnEvent: {
|
||||||
if (!vq)
|
int pitch = event.noteOn.pitch;
|
||||||
continue;
|
if (pitch < 0 || pitch >= 128)
|
||||||
|
|
||||||
Vst::ParamID id = vq->getParameterId();
|
|
||||||
uint32 pointCount = vq->getPointCount();
|
|
||||||
int32 sampleOffset;
|
|
||||||
Vst::ParamValue value;
|
|
||||||
|
|
||||||
switch (id) {
|
|
||||||
default:
|
|
||||||
if (id >= kPidCC0 && id <= kPidCCLast) {
|
|
||||||
auto ccNumber = static_cast<int>(id - kPidCC0);
|
|
||||||
for (uint32 pointIndex = 0; pointIndex < pointCount; ++pointIndex) {
|
|
||||||
if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue) {
|
|
||||||
synth.hdcc(sampleOffset, ccNumber, value);
|
|
||||||
_state.controllers[ccNumber] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
if (event.noteOn.velocity <= 0.0f) {
|
||||||
case kPidAftertouch:
|
synth.noteOff(sampleOffset, pitch, 0);
|
||||||
for (uint32 pointIndex = 0; pointIndex < pointCount; ++pointIndex) {
|
|
||||||
if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue)
|
|
||||||
synth.aftertouch(sampleOffset, fastRound(value * 127.0));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case kPidPitchBend:
|
|
||||||
for (uint32 pointIndex = 0; pointIndex < pointCount; ++pointIndex) {
|
|
||||||
if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue)
|
|
||||||
synth.pitchWheel(sampleOffset, fastRound(value * 16383) - 8192);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SfizzVstProcessor::processEvents(Vst::IEventList& events)
|
|
||||||
{
|
|
||||||
sfz::Sfizz& synth = *_synth;
|
|
||||||
uint32 numEvents = events.getEventCount();
|
|
||||||
|
|
||||||
for (uint32 i = 0; i < numEvents; i++) {
|
|
||||||
Vst::Event e;
|
|
||||||
if (events.getEvent(i, e) != kResultTrue)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
switch (e.type) {
|
|
||||||
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: {
|
|
||||||
int pitch = e.noteOn.pitch;
|
|
||||||
if (pitch < 0 || pitch >= 128)
|
|
||||||
break;
|
|
||||||
synth.noteOff(e.sampleOffset, pitch, convertVelocityFromFloat(e.noteOff.velocity));
|
|
||||||
_noteEventsCurrentCycle[pitch] = 0.0f;
|
_noteEventsCurrentCycle[pitch] = 0.0f;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
synth.noteOn(sampleOffset, pitch, convertVelocityFromFloat(event.noteOn.velocity));
|
||||||
|
_noteEventsCurrentCycle[pitch] = event.noteOn.velocity;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Vst::Event::kNoteOffEvent: {
|
||||||
|
int pitch = event.noteOn.pitch;
|
||||||
|
if (pitch < 0 || pitch >= 128)
|
||||||
break;
|
break;
|
||||||
}
|
synth.noteOff(sampleOffset, pitch, convertVelocityFromFloat(event.noteOff.velocity));
|
||||||
case Vst::Event::kPolyPressureEvent: {
|
_noteEventsCurrentCycle[pitch] = 0.0f;
|
||||||
int pitch = e.polyPressure.pitch;
|
break;
|
||||||
if (pitch < 0 || pitch >= 128)
|
}
|
||||||
break;
|
case Vst::Event::kPolyPressureEvent: {
|
||||||
synth.polyAftertouch(e.sampleOffset, pitch, convertVelocityFromFloat(e.polyPressure.pressure));
|
int pitch = event.polyPressure.pitch;
|
||||||
|
if (pitch < 0 || pitch >= 128)
|
||||||
break;
|
break;
|
||||||
}
|
synth.polyAftertouch(sampleOffset, pitch, convertVelocityFromFloat(event.polyPressure.pressure));
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "SfizzVstState.h"
|
#include "SfizzVstState.h"
|
||||||
|
#include "OrderedEventProcessor.h"
|
||||||
#include "sfizz/RTSemaphore.h"
|
#include "sfizz/RTSemaphore.h"
|
||||||
#include "ring_buffer/ring_buffer.h"
|
#include "ring_buffer/ring_buffer.h"
|
||||||
#include "public.sdk/source/vst/vstaudioeffect.h"
|
#include "public.sdk/source/vst/vstaudioeffect.h"
|
||||||
|
|
@ -18,7 +19,8 @@
|
||||||
|
|
||||||
using namespace Steinberg;
|
using namespace Steinberg;
|
||||||
|
|
||||||
class SfizzVstProcessor : public Vst::AudioEffect {
|
class SfizzVstProcessor : public Vst::AudioEffect,
|
||||||
|
public OrderedEventProcessor {
|
||||||
public:
|
public:
|
||||||
SfizzVstProcessor();
|
SfizzVstProcessor();
|
||||||
~SfizzVstProcessor();
|
~SfizzVstProcessor();
|
||||||
|
|
@ -35,9 +37,11 @@ public:
|
||||||
tresult PLUGIN_API canProcessSampleSize(int32 symbolicSampleSize) override;
|
tresult PLUGIN_API canProcessSampleSize(int32 symbolicSampleSize) override;
|
||||||
tresult PLUGIN_API setActive(TBool state) override;
|
tresult PLUGIN_API setActive(TBool state) override;
|
||||||
tresult PLUGIN_API process(Vst::ProcessData& data) override;
|
tresult PLUGIN_API process(Vst::ProcessData& data) override;
|
||||||
void processParameterChanges(Vst::IParameterChanges& pc);
|
|
||||||
void processControllerChanges(Vst::IParameterChanges& pc);
|
// OrderedEventProcessor
|
||||||
void processEvents(Vst::IEventList& events);
|
void playOrderedParameter(int32 sampleOffset, Vst::ParamID id, Vst::ParamValue value) override;
|
||||||
|
void playOrderedEvent(const Vst::Event& event) override;
|
||||||
|
|
||||||
void processMessagesFromUi();
|
void processMessagesFromUi();
|
||||||
static int convertVelocityFromFloat(float x);
|
static int convertVelocityFromFloat(float x);
|
||||||
|
|
||||||
|
|
@ -57,6 +61,9 @@ private:
|
||||||
SfizzVstState _state;
|
SfizzVstState _state;
|
||||||
float _currentStretchedTuning = 0;
|
float _currentStretchedTuning = 0;
|
||||||
|
|
||||||
|
// whether allowed to perform events (owns the processing lock)
|
||||||
|
bool _canPerformEventsAndParameters {};
|
||||||
|
|
||||||
// client
|
// client
|
||||||
sfz::ClientPtr _client;
|
sfz::ClientPtr _client;
|
||||||
std::unique_ptr<uint8_t[]> _oscTemp;
|
std::unique_ptr<uint8_t[]> _oscTemp;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue