Rewrite the unordered player to accomodate multiple queues per parameters
Now, there is a hard limit on the number of queues treated (as before), but there may be duplicates, and overflowing queues will be ignored
This commit is contained in:
parent
d11142639a
commit
0eada20587
3 changed files with 79 additions and 251 deletions
|
|
@ -21,45 +21,17 @@ public:
|
||||||
void processUnorderedEvents(int32 numSamples, Vst::IParameterChanges* pcs, Vst::IEventList* evs);
|
void processUnorderedEvents(int32 numSamples, Vst::IParameterChanges* pcs, Vst::IEventList* evs);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startProcessing(Vst::IEventList* evs, Vst::IParameterChanges* pcs);
|
int32 paramCount_ { 0 };
|
||||||
void processSubdiv(Vst::IEventList* evs, int32 firstOffset, int32 lastOffset);
|
int32 subdivSize_ { 0 };
|
||||||
|
struct SubdivChange {
|
||||||
void sortSubdiv(int32 firstOffset, int32 lastOffset);
|
SubdivChange(int32 offset, Vst::ParamID id, Vst::ParamValue value)
|
||||||
void playSubdiv(Vst::IEventList* evs, int32 firstOffset, int32 lastOffset);
|
: offset(offset), id(std::move(id)), value(std::move(value)) {}
|
||||||
|
int32 offset;
|
||||||
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::ParamID id {};
|
||||||
Vst::ParamValue value {};
|
Vst::ParamValue value {};
|
||||||
};
|
};
|
||||||
|
std::vector<SubdivChange> subdivChanges_;
|
||||||
std::unique_ptr<ParameterAndValue[]> pointsBySample_;
|
std::vector<int32> queuePositions_;
|
||||||
std::unique_ptr<uint32[]> numPointsBySample_;
|
|
||||||
|
|
||||||
int32 eventIndex_ = 0;
|
|
||||||
int32 eventCount_ = 0;
|
|
||||||
bool haveCurrentEvent_ = false;
|
|
||||||
Vst::Event currentEvent_ {};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#include "OrderedEventProcessor.hpp"
|
#include "OrderedEventProcessor.hpp"
|
||||||
|
|
|
||||||
|
|
@ -14,234 +14,90 @@ void OrderedEventProcessor<R>::initializeEventProcessor(const Vst::ProcessSetup&
|
||||||
{
|
{
|
||||||
paramCount_ = paramCount;
|
paramCount_ = paramCount;
|
||||||
subdivSize_ = subdivSize;
|
subdivSize_ = subdivSize;
|
||||||
queues_.reset(new Cell<QueueStatus>[paramCount]);
|
subdivChanges_.reserve(subdivSize * paramCount);
|
||||||
pointsBySample_.reset(new ParameterAndValue[paramCount * subdivSize]);
|
queuePositions_.reserve(paramCount);
|
||||||
numPointsBySample_.reset(new uint32[subdivSize]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class R>
|
template <class R>
|
||||||
void OrderedEventProcessor<R>::processUnorderedEvents(int32 numSamples, Vst::IParameterChanges* pcs, Vst::IEventList* evs)
|
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 sampleIndex = 0;
|
||||||
int32 subdivNumber = 0;
|
int32 subdivNumber = 0;
|
||||||
const int32 subdivSize = subdivSize_;
|
const int32 subdivSize = subdivSize_;
|
||||||
|
if (subdivSize == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
startProcessing(evs, pcs);
|
int32 eventIdx = 0;
|
||||||
|
int32 eventCount = evs->getEventCount();
|
||||||
|
Vst::Event event;
|
||||||
|
bool hasEvent = false;
|
||||||
|
if (eventCount > 0) {
|
||||||
|
evs->getEvent(eventIdx++, event);
|
||||||
|
hasEvent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
while (sampleIndex < numSamples || subdivNumber == 0) {
|
while (sampleIndex < numSamples || subdivNumber == 0) {
|
||||||
int32 subdivCurrentSize = std::min(numSamples - sampleIndex, subdivSize);
|
const int32 subdivCurrentSize = std::min(numSamples - sampleIndex, subdivSize);
|
||||||
processSubdiv(evs, sampleIndex, sampleIndex + subdivCurrentSize - 1);
|
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;
|
||||||
|
|
||||||
|
int32 offset;
|
||||||
|
Vst::ParamID id = vq->getParameterId();
|
||||||
|
Vst::ParamValue value;
|
||||||
|
|
||||||
|
int32& queuePosition = queuePositions_[qIdx];
|
||||||
|
while (queuePosition < vq->getPointCount()) {
|
||||||
|
vq->getPoint(queuePosition, offset, value);
|
||||||
|
if (offset > lastOffset)
|
||||||
|
break;
|
||||||
|
|
||||||
|
subdivChanges_.emplace_back(offset, id, value);
|
||||||
|
queuePosition++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
sampleIndex += subdivCurrentSize;
|
sampleIndex += subdivCurrentSize;
|
||||||
++subdivNumber;
|
++subdivNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
playRemainder(std::max(int32(0), numSamples - 1), evs);
|
// Play the remaining events
|
||||||
}
|
while (hasEvent) {
|
||||||
|
receiver.playOrderedEvent(event);
|
||||||
template <class R>
|
hasEvent = (eventIdx < eventCount);
|
||||||
void OrderedEventProcessor<R>::startProcessing(Vst::IEventList* evs, Vst::IParameterChanges* pcs)
|
evs->getEvent(eventIdx++, event);
|
||||||
{
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,7 @@ tresult SfizzVstControllerNoUi::notify(Vst::IMessage* message)
|
||||||
///
|
///
|
||||||
if (!strcmp(id, SfzUpdate::getFClassID())) {
|
if (!strcmp(id, SfzUpdate::getFClassID())) {
|
||||||
if (!sfzUpdate_->convertFromMessage(*message)) {
|
if (!sfzUpdate_->convertFromMessage(*message)) {
|
||||||
assert(false);
|
// assert(false);
|
||||||
return kResultFalse;
|
return kResultFalse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue