Created
December 20, 2024 17:58
-
-
Save Xenakios/7d4ed7af81b1ad5e75f3f14943f92e05 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
If we were dealing with plain old MIDI, tracking active notes could be | |
done with something as simple as : bool activeNotes[16][128]; | |
Clap however complicates this as we want to also take into account the | |
note ports and note ids. | |
Theoretically we could use std::unordered_set<PCKI> or something | |
for this but then we couldn't control the number of buckets, | |
memory allocations etc. | |
*/ | |
class PlayingNotesTracker | |
{ | |
public: | |
using PCKI = std::tuple<int16_t, int16_t, int16_t, int32_t>; | |
static const size_t numBuckets = 16; | |
static const size_t initialBucketSize = 128; | |
PlayingNotesTracker() | |
{ | |
m_buckets.resize(numBuckets); | |
for (auto &b : m_buckets) | |
{ | |
b.reserve(initialBucketSize); | |
} | |
} | |
static uint64_t hashNote(PCKI note) | |
{ | |
choc::hash::xxHash64 h; | |
h.addInput(&std::get<0>(note), 2); | |
h.addInput(&std::get<1>(note), 2); | |
h.addInput(&std::get<2>(note), 2); | |
h.addInput(&std::get<3>(note), 4); | |
return h.getHash(); | |
} | |
size_t numTracked() const | |
{ | |
size_t result = 0; | |
for (const auto &b : m_buckets) | |
result += b.size(); | |
return result; | |
} | |
void dumpState() const | |
{ | |
for (size_t i = 0; i < m_buckets.size(); ++i) | |
{ | |
auto &b = m_buckets[i]; | |
std::print("{}\t", i); | |
for (auto &n : b) | |
{ | |
std::print("{} ", std::get<2>(n)); | |
} | |
std::print("\n"); | |
} | |
} | |
// call this for every note that is started | |
void noteOn(PCKI note) | |
{ | |
auto h = hashNote(note); | |
size_t bucket = h % numBuckets; | |
auto oldcapacity = m_buckets[bucket].capacity(); | |
m_buckets[bucket].push_back(note); | |
if (m_buckets[bucket].capacity() > oldcapacity) | |
++numAllocationsDone; | |
} | |
// call this for every note that was stopped from a sequence, user input etc | |
void noteOff(PCKI note) | |
{ | |
auto h = hashNote(note); | |
size_t bucket = h % numBuckets; | |
auto &b = m_buckets[bucket]; | |
std::erase(b, note); | |
} | |
// untrack all notes with channel>=0 && channel<16 and key>=0 && key<128 | |
void allNotesOffMidi() | |
{ | |
for (auto &b : m_buckets) | |
{ | |
std::erase_if(b, [](PCKI n) { | |
auto chan = std::get<1>(n); | |
auto key = std::get<2>(n); | |
return chan >= 0 && chan < 16 && key >= 0 && key < 128; | |
}); | |
} | |
} | |
// clear all tracked notes unconditionally | |
void allNotesOffTotal() | |
{ | |
for (auto &b : m_buckets) | |
{ | |
b.clear(); | |
} | |
} | |
// call at transport stop/seek etc with a callable that sends | |
// note offs to the destination synth etc | |
// callable should have signature (int16_t po, int16_t ch, int16_t key, int32_t id)->void | |
template <typename F> void forEach(F &&f) | |
{ | |
for (const auto &b : m_buckets) | |
{ | |
for (const auto &n : b) | |
{ | |
f(std::get<0>(n), std::get<1>(n), std::get<2>(n), std::get<3>(n)); | |
} | |
} | |
} | |
// for debugging | |
size_t numAllocationsDone = 0; | |
private: | |
std::vector<std::vector<PCKI>> m_buckets; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment