Skip to content

Commit e8e63dc

Browse files
committed
fixed polyphonic voice stealing algo
1 parent 5bdf1f6 commit e8e63dc

File tree

2 files changed

+58
-23
lines changed

2 files changed

+58
-23
lines changed

clapeze/include/clapeze/voice.h

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,16 @@ class VoicePool {
106106
}
107107
}
108108

109+
size_t CountNumActiveVoices() {
110+
size_t count = 0;
111+
for (VoiceIndex idx = 0; idx < mVoices.size(); idx++) {
112+
if(mVoices[idx].activeNote) {
113+
count++;
114+
}
115+
}
116+
return count;
117+
}
118+
109119
private:
110120
using VoiceIndex = size_t;
111121
void StopVoice(VoiceIndex index) {
@@ -134,51 +144,76 @@ class VoicePool {
134144
struct VoiceData {
135145
explicit VoiceData(TProcessor& p) : voice(p) {}
136146
TVoice voice;
137-
std::optional<NoteTuple> activeNote;
147+
std::optional<NoteTuple> activeNote{};
148+
bool isPressed;
149+
int32_t age;
138150
};
139151

140152
TProcessor& mProcessor;
141153
etl::vector<VoiceData, TMaxVoices> mVoices;
142154
VoiceStrategy mStrategy{};
143155

144156
struct PolyVoiceStrategy {
145-
void Clear() { mVoicesByLastUsed.clear(); }
157+
void Clear() { mNextAge = 0; }
146158
void ProcessNoteOn(VoicePool& pool, const NoteTuple& note, float velocity) {
147-
// retrigger existing voice, if there is one
148-
VoiceIndex voiceIndex = SIZE_MAX;
159+
VoiceIndex nextVoiceIndex = SIZE_MAX;
160+
161+
auto compare = [&](const VoiceData& left, const VoiceData& right) {
162+
// priority #1: is note not active?
163+
if (left.activeNote == std::nullopt && right.activeNote != std::nullopt) {
164+
return -1;
165+
}
166+
if (right.activeNote == std::nullopt && left.activeNote != std::nullopt) {
167+
return 1;
168+
}
169+
// priority #2: is note not pressed?
170+
if (!left.isPressed && right.isPressed) {
171+
return -1;
172+
}
173+
if (!right.isPressed && left.isPressed) {
174+
return 1;
175+
}
176+
// tiebreaker: least recently used
177+
if(left.age != right.age) {
178+
return left.age < right.age ? -1 : 1;
179+
}
180+
return 0;
181+
};
182+
149183
for (VoiceIndex idx = 0; idx < pool.mVoices.size(); idx++) {
150-
auto& data = pool.mVoices[idx];
151-
if (data.activeNote && data.activeNote->Match(note)) {
152-
voiceIndex = idx;
153-
pool.SendNoteEnd(*(data.activeNote));
184+
auto& voiceNote = pool.mVoices[idx].activeNote;
185+
if (voiceNote && voiceNote->Match(note)) {
186+
// always win: does existing note match?
187+
nextVoiceIndex = idx;
154188
break;
189+
} else if (nextVoiceIndex == SIZE_MAX) {
190+
nextVoiceIndex = idx;
191+
} else if(compare(pool.mVoices[idx], pool.mVoices[nextVoiceIndex]) == -1) {
192+
nextVoiceIndex = idx;
155193
}
156-
if (data.activeNote == std::nullopt && voiceIndex == SIZE_MAX) {
157-
voiceIndex = idx;
158-
}
159-
}
160-
// no voice available, time to steal
161-
if (voiceIndex == SIZE_MAX) {
162-
voiceIndex = mVoicesByLastUsed.front();
163-
mVoicesByLastUsed.pop(); // from front
164-
auto& data = pool.mVoices[voiceIndex];
165-
pool.SendNoteEnd(*(data.activeNote));
166194
}
167195

168-
mVoicesByLastUsed.push(voiceIndex); // to back
169-
pool.mVoices[voiceIndex].activeNote = note;
170-
pool.mVoices[voiceIndex].voice.ProcessNoteOn(note, velocity);
196+
auto& nextVoiceData = pool.mVoices[nextVoiceIndex];
197+
if(nextVoiceData.activeNote) {
198+
pool.SendNoteEnd(*(nextVoiceData.activeNote));
199+
}
200+
nextVoiceData.activeNote = note;
201+
nextVoiceData.voice.ProcessNoteOn(note, velocity);
202+
nextVoiceData.isPressed = true;
203+
nextVoiceData.age = mNextAge++;
171204
}
172205
void ProcessNoteOff(VoicePool& pool, const NoteTuple& note) {
173206
auto& mVoices = pool.mVoices;
174207
for (VoiceIndex idx = 0; idx < mVoices.size(); idx++) {
175208
auto& data = mVoices[idx];
176209
if (data.activeNote && data.activeNote->Match(note)) {
177210
data.voice.ProcessNoteOff();
211+
data.isPressed = false;
178212
}
179213
}
180214
}
181-
etl::circular_buffer<VoiceIndex, TMaxVoices> mVoicesByLastUsed{};
215+
216+
int32_t mNextAge=0;
182217
} mPolyVoiceStrategy{};
183218

184219
struct MonoLastVoiceStrategy {

daw/src/keysynth/keysynth.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ struct ParamTraits<Params, Params::VcaLfoAmount> : public clapeze::NumericParam
160160

161161
template <>
162162
struct ParamTraits<Params, Params::PolyCount> : public clapeze::IntegerParam {
163-
ParamTraits() : clapeze::IntegerParam("Voice Count", 1, cMaxVoices, 8) {}
163+
ParamTraits() : clapeze::IntegerParam("Voice Count", 1, cMaxVoices, 4) {}
164164
};
165165

166166
template <>

0 commit comments

Comments
 (0)