Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite the unordered player to accomodate multiple queues per parameters #1035

Merged
merged 1 commit into from
Nov 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 8 additions & 36 deletions plugins/vst/OrderedEventProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,45 +21,17 @@ class OrderedEventProcessor {
void processUnorderedEvents(int32 numSamples, Vst::IParameterChanges* pcs, Vst::IEventList* evs);

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 {
int32 paramCount_ { 0 };
int32 subdivSize_ { 0 };
struct SubdivChange {
SubdivChange(int32 offset, Vst::ParamID id, Vst::ParamValue value)
: offset(offset), id(std::move(id)), value(std::move(value)) {}
int32 offset;
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_ {};
std::vector<SubdivChange> subdivChanges_;
std::vector<int32> queuePositions_;
};

#include "OrderedEventProcessor.hpp"
268 changes: 62 additions & 206 deletions plugins/vst/OrderedEventProcessor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,234 +14,90 @@ void OrderedEventProcessor<R>::initializeEventProcessor(const Vst::ProcessSetup&
{
paramCount_ = paramCount;
subdivSize_ = subdivSize;
queues_.reset(new Cell<QueueStatus>[paramCount]);
pointsBySample_.reset(new ParameterAndValue[paramCount * subdivSize]);
numPointsBySample_.reset(new uint32[subdivSize]);
subdivChanges_.reserve(subdivSize * paramCount);
queuePositions_.reserve(paramCount);
}

template <class R>
void OrderedEventProcessor<R>::processUnorderedEvents(int32 numSamples, Vst::IParameterChanges* pcs, Vst::IEventList* evs)
{
if (!pcs || !evs)
return;

R& receiver = *static_cast<R*>(this);
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);
}

template <class R>
void OrderedEventProcessor<R>::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;
}
}
if (subdivSize == 0)
return;

int32 eventIdx = 0;
int32 eventCount = evs->getEventCount();
Vst::Event event;
bool hasEvent = false;
if (eventCount > 0) {
evs->getEvent(eventIdx++, event);
hasEvent = true;
}

queueList_ = head;
// Assume that there would be as many parameter changes as there are parameters, but some hosts are nice
// and send multiple queues per parameter. Clamp the number of considered parameter queues to the number of parameters.
queuePositions_.clear();
const int32 parameterCount = pcs->getParameterCount();
const int32 queueCapacity = static_cast<int32>(queuePositions_.capacity());
assert(queueCapacity >= parameterCount);
const int32 consideredQueueCount = std::min(queueCapacity, parameterCount);
queuePositions_.resize(consideredQueueCount, 0);

// position ourselves to the start of the event list
eventIndex_ = 0;
eventCount_ = evs ? evs->getEventCount() : 0;
haveCurrentEvent_ = eventCount_ > 0 &&
evs->getEvent(eventIndex_, currentEvent_) == kResultTrue;
}

template <class R>
void OrderedEventProcessor<R>::processSubdiv(Vst::IEventList* evs, int32 firstOffset, int32 lastOffset)
{
sortSubdiv(firstOffset, lastOffset);
playSubdiv(evs, firstOffset, lastOffset);
}

template <class R>
void OrderedEventProcessor<R>::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;
while (sampleIndex < numSamples || subdivNumber == 0) {
const int32 subdivCurrentSize = std::min(numSamples - sampleIndex, subdivSize);
const int32 lastOffset = sampleIndex + subdivSize;

// Queue all changes for the subdiv
subdivChanges_.clear();
for (int32 qIdx = 0; qIdx < consideredQueueCount; ++qIdx) {
auto* vq = pcs->getParameterData(qIdx);
if (!vq)
continue;
}

if (sampleOffset > lastOffset)
break;

// ensure that offsets never go back
if (sampleOffset < previousOffset)
sampleOffset = previousOffset;
int32 offset;
Vst::ParamID id = vq->getParameterId();
Vst::ParamValue value;

int32 listSize = numPointsBySample[sampleOffset - firstOffset];
ParameterAndValue* listItems = &pointsBySample[paramCount * (sampleOffset - firstOffset)];
int32& queuePosition = queuePositions_[qIdx];
while (queuePosition < vq->getPointCount()) {
vq->getPoint(queuePosition, offset, value);
if (offset > lastOffset)
break;

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;
subdivChanges_.emplace_back(offset, id, value);
queuePosition++;
}

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;
// Sort the changes
std::sort(subdivChanges_.begin(), subdivChanges_.end(), [] (const SubdivChange& lhs, const SubdivChange& rhs) {
return lhs.offset < rhs.offset;
});

// Play the parameter changes, interleaving events as needed in between
for (const auto& change: subdivChanges_) {
while (hasEvent && event.sampleOffset < change.offset) {
receiver.playOrderedEvent(event);
hasEvent = (eventIdx < eventCount);
evs->getEvent(eventIdx++, event);
}
receiver.playOrderedParameter(change.offset, change.id, change.value);
}
cur = cur->next;
}

queueList_ = head;
}

template <class R>
void OrderedEventProcessor<R>::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.

R& receiver = *static_cast<R*>(this);

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];
receiver.playOrderedParameter(sampleOffset, item.id, item.value);
}
}
}

template <class R>
void OrderedEventProcessor<R>::playRemainder(int32 sampleOffset, Vst::IEventList* evs)
{
// play any remaining events and parameters in arbitrary order,
// disregarding their respective offsets

R& receiver = *static_cast<R*>(this);

int32 eventIndex = eventIndex_;
int32 eventCount = eventCount_;
bool haveCurrentEvent = haveCurrentEvent_;
while (haveCurrentEvent) {
currentEvent_.sampleOffset = std::min(sampleOffset, currentEvent_.sampleOffset);
receiver.playOrderedEvent(currentEvent_);
haveCurrentEvent = false;
while (!haveCurrentEvent && ++eventIndex < eventCount)
haveCurrentEvent = evs->getEvent(eventIndex, currentEvent_) == kResultTrue;
sampleIndex += subdivCurrentSize;
++subdivNumber;
}

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)
receiver.playOrderedParameter(sampleOffset, id, value);
}
// Play the remaining events
while (hasEvent) {
receiver.playOrderedEvent(event);
hasEvent = (eventIdx < eventCount);
evs->getEvent(eventIdx++, event);
}
}

template <class R>
void OrderedEventProcessor<R>::playEventsUpTo(Vst::IEventList* evs, int32 sampleOffset)
{
R& receiver = *static_cast<R*>(this);

int32 index = eventIndex_;
int32 count = eventCount_;
bool haveCurrentEvent = haveCurrentEvent_;

while (haveCurrentEvent && currentEvent_.sampleOffset <= sampleOffset) {
receiver.playOrderedEvent(currentEvent_);
haveCurrentEvent = false;
while (!haveCurrentEvent && ++index < count)
haveCurrentEvent = evs->getEvent(index, currentEvent_) == kResultTrue;
}

eventIndex_ = index;
haveCurrentEvent_ = haveCurrentEvent;
}
2 changes: 1 addition & 1 deletion plugins/vst/SfizzVstController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ tresult SfizzVstControllerNoUi::notify(Vst::IMessage* message)
///
if (!strcmp(id, SfzUpdate::getFClassID())) {
if (!sfzUpdate_->convertFromMessage(*message)) {
assert(false);
// assert(false);
return kResultFalse;
}

Expand Down