Skip to content

Instantly share code, notes, and snippets.

@Xenakios
Created December 20, 2024 17:58
Show Gist options
  • Save Xenakios/7d4ed7af81b1ad5e75f3f14943f92e05 to your computer and use it in GitHub Desktop.
Save Xenakios/7d4ed7af81b1ad5e75f3f14943f92e05 to your computer and use it in GitHub Desktop.
/*
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