sfizz/src/sfizz/SisterVoiceRing.h

169 lines
4.7 KiB
C++

// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#pragma once
#include "Voice.h"
#include "absl/meta/type_traits.h"
namespace sfz
{
struct SisterVoiceRing {
/**
* @brief Apply a lambda function to all sisters in a ring.
* This function should be robust enough to be able to kill the voice
* in the lambda.
*
* @param voice
* @param lambda
*/
template<class F, class T,
absl::enable_if_t<std::is_same<Voice, absl::remove_const_t<T>>::value, int> = 0>
static void applyToRing(T* voice, F&& lambda) noexcept
{
auto v = voice->getNextSisterVoice();
while (v != voice) {
const auto next = v->getNextSisterVoice();
lambda(v);
v = next;
}
lambda(voice);
}
/**
* @brief Count the number of sister voices in a ring
*
* @param start
* @return unsigned
*/
static unsigned countSisterVoices(const Voice* start) noexcept
{
if (!start)
return 0;
unsigned count = 0;
auto next = start;
do
{
count++;
next = next->getNextSisterVoice();
} while (next != start && count < config::maxVoices);
ASSERT(count < config::maxVoices);
return count;
}
/**
* @brief Off all sisters in a ring
*
* @param voice
* @param delay
*/
template<class T,
absl::enable_if_t<std::is_same<Voice, absl::remove_const_t<T>>::value, int> = 0>
static void offAllSisters(T* voice, int delay) {
if (voice != nullptr) {
SisterVoiceRing::applyToRing(voice, [&] (Voice* v) {
v->off(delay);
});
}
}
/**
* @brief Check if a sister voice ring is well formed
*
* @param start
* @return true
* @return false
*/
static bool checkRingValidity(const Voice* start) noexcept
{
if (start == nullptr)
return true;
unsigned idx { 0 };
const Voice* ring[config::maxVoices];
ring[idx] = start;
while (idx < config::maxVoices) {
const auto* newVoice = ring[idx]->getNextSisterVoice();
if (newVoice == nullptr) {
DBG("Error in ring: " << static_cast<const void*>(ring[idx])
<< " next sister is null");
return false;
}
if (newVoice->getPreviousSisterVoice() != ring[idx]) {
DBG("Error in ring: " << static_cast<const void*>(newVoice)
<< " refers " << static_cast<const void*>(newVoice->getPreviousSisterVoice())
<< " as previous sister voice instead of "
<< static_cast<const void*>(ring[idx]));
return false;
}
if (newVoice == start)
break;
for (unsigned i = 1; i < idx; ++i) {
if (ring[i] == newVoice) {
DBG("Error in ring: " << static_cast<const void*>(newVoice)
<< " already present in ring at index " << i);
return false;
}
}
ring[++idx] = newVoice;
}
return true;
}
};
/**
* @brief RAII helper to build sister voice rings.
* Closes the doubly-linked list on destruction.
*
*/
class SisterVoiceRingBuilder {
public:
~SisterVoiceRingBuilder() noexcept {
if (lastStartedVoice != nullptr) {
ASSERT(firstStartedVoice);
lastStartedVoice->setNextSisterVoice(firstStartedVoice);
firstStartedVoice->setPreviousSisterVoice(lastStartedVoice);
}
}
/**
* @brief Add a voice to the sister ring
*
* @param voice
*/
void addVoiceToRing(Voice* voice) noexcept {
if (firstStartedVoice == nullptr)
firstStartedVoice = voice;
if (lastStartedVoice != nullptr) {
voice->setPreviousSisterVoice(lastStartedVoice);
lastStartedVoice->setNextSisterVoice(voice);
}
lastStartedVoice = voice;
}
/**
* @brief Apply a function to the sister ring, including the current voice.
* This function should be safe enough to even reset the sister voices, but
* if you mutate the ring significantly you should probably roll your own
* iterator.
*
* @param lambda the function to apply.
* @param voice the starting voice
*/
private:
Voice* firstStartedVoice { nullptr };
Voice* lastStartedVoice { nullptr };
};
}