Skip to content

Commit 3a1c505

Browse files
committed
LRU voice allocation
1 parent e15f505 commit 3a1c505

File tree

1 file changed

+43
-14
lines changed

1 file changed

+43
-14
lines changed

runtime/elem/builtins/MIDI.h

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ namespace elem
2424
// Maps incoming MidiEvents to AssignedMidiEvents with polyphonic
2525
// voice assignment.
2626
//
27-
// This uses MPE style voice allocation; where assigned voice is designated
27+
// This uses MPE style voice allocation, where assigned voice is designated
2828
// by channel number. That means that we clobber the incoming channel number
2929
// assigned to the original event and rewrite it with a new number corresponding
3030
// to the assigned voice.
@@ -55,42 +55,71 @@ namespace elem
5555
auto outEvent = MidiEvent((bytes[0] & 0xf0) | static_cast<uint8_t>(voiceIndex), bytes[1], bytes[2]);
5656

5757
ctx.outputEvents.addEvent(time, std::move(outEvent));
58-
voiceMap[voiceIndex] = bytes[1];
58+
voiceMap[voiceIndex].note = static_cast<int32_t>(bytes[1]);
59+
voiceMap[voiceIndex].lastModified = steadyClock + time;
5960
}
6061

6162
if (event.message.isNoteOff()) {
62-
auto assignedVoice = std::find(voiceMap.begin(), voiceMap.end(), bytes[1]);
63+
auto assignedVoice = std::find_if(voiceMap.begin(), voiceMap.end(),
64+
[&bytes](const Assignment& a) { return a.note == static_cast<int32_t>(bytes[1]); });
6365

6466
if (assignedVoice != voiceMap.end()) {
6567
auto voiceIndex = std::distance(voiceMap.begin(), assignedVoice);
6668
auto outEvent = MidiEvent((bytes[0] & 0xf0) | static_cast<uint8_t>(voiceIndex), bytes[1], bytes[2]);
6769

6870
ctx.outputEvents.addEvent(time, std::move(outEvent));
71+
6972
// Clear the mapping
70-
//
71-
// TODO: Technically 0 is a valid midi note; maybe just use an int
72-
// and let -1 be "unallocated"?
73-
voiceMap[voiceIndex] = 0;
73+
voiceMap[voiceIndex].note = -1;
74+
voiceMap[voiceIndex].lastModified = steadyClock + time;
7475
}
7576
}
7677
});
78+
79+
steadyClock += ctx.numSamples;
7780
}
7881

7982
size_t getFreeVoice() {
80-
auto out = nextFreeVoice;
83+
// The first free voice is the one in voiceMap that has note == -1
84+
// and whose lastModified timestamp is the oldest (i.e. smallest value).
85+
auto nv = numVoices.load();
86+
size_t bestIndex = 0;
87+
int64_t oldestTime = std::numeric_limits<int64_t>::max();
88+
89+
for (size_t i = 0; i < nv; ++i) {
90+
if (voiceMap[i].note == -1 && voiceMap[i].lastModified < oldestTime) {
91+
bestIndex = i;
92+
oldestTime = voiceMap[i].lastModified;
93+
}
94+
}
8195

82-
// TODO: This is round robin, need better
83-
if (++nextFreeVoice >= (numVoices.load() - 1))
84-
nextFreeVoice = 0;
96+
// If we found a free voice, return it
97+
if (voiceMap[bestIndex].note == -1) {
98+
return bestIndex;
99+
}
85100

86-
return out;
101+
// If there is no free voice, we want whichever voiceMap entry has the
102+
// oldest lastModified timestamp, regardless of note value.
103+
for (size_t i = 0; i < nv; ++i) {
104+
if (voiceMap[i].lastModified < oldestTime) {
105+
bestIndex = i;
106+
oldestTime = voiceMap[i].lastModified;
107+
}
108+
}
109+
110+
return bestIndex;
87111
}
88112

89113
// Maps the ith voice to the ith position in the array, where we capture
90114
// the note value the voice was last assigned
91-
std::array<uint8_t, 16> voiceMap;
115+
struct Assignment {
116+
int32_t note = -1;
117+
int64_t lastModified = 0;
118+
};
119+
120+
std::array<Assignment, 16> voiceMap;
92121
std::atomic<size_t> numVoices = 1;
93-
size_t nextFreeVoice = 0;
122+
int64_t steadyClock = 0;
94123
};
95124

96125
template <typename FloatType>

0 commit comments

Comments
 (0)