SpectrumNormalizer.cpp 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. #include "SpectrumNormalizer.h"
  2. #include <algorithm>
  3. #include <array>
  4. #include <cmath>
  5. #include <functional>
  6. using namespace WallpaperEngine::Audio;
  7. namespace {
  8. // levels are 0.35 * log10(power) scaled, so one unit is a bit under 30dB
  9. constexpr float WINDOW = 1.2f;
  10. // below this a band is never shown, capture noise and dither sit around here
  11. constexpr float GATE = 0.2f;
  12. // the smallest span that still gets stretched to fill the range, so faint sounds fill it without noise blowing up
  13. constexpr float MIN_SPAN = 0.35f;
  14. // how quickly the reference rises to a louder peak, per second (as an exponential rate)
  15. constexpr float ATTACK_RATE = 25.0f;
  16. constexpr float RELEASE_SECONDS = 3.0f;
  17. } // namespace
  18. void SpectrumNormalizer::update (const float* levels, std::size_t count, float dt) {
  19. dt = std::clamp (dt, 0.0f, 0.25f);
  20. count = std::min<std::size_t> (count, 64);
  21. if (count == 0) {
  22. return;
  23. }
  24. std::array<float, 64> sorted {};
  25. std::copy_n (levels, count, sorted.begin ());
  26. // average of the loudest fifth of the bands
  27. const std::size_t top = std::max<std::size_t> (1, count / 5);
  28. std::partial_sort (sorted.begin (), sorted.begin () + top, sorted.begin () + count, std::greater<> ());
  29. float peak = 0.0f;
  30. for (std::size_t i = 0; i < top; i++) {
  31. peak += sorted[i];
  32. }
  33. peak /= static_cast<float> (top);
  34. if (peak > this->m_reference) {
  35. this->m_reference += (peak - this->m_reference) * (1.0f - std::exp (-ATTACK_RATE * dt));
  36. } else {
  37. // falls back to the current peak rather than to zero, so a steady level settles right at it
  38. this->m_reference = peak + (this->m_reference - peak) * std::exp (-dt / RELEASE_SECONDS);
  39. }
  40. }
  41. float SpectrumNormalizer::apply (float level) const {
  42. const float high = std::max (this->m_reference, GATE + MIN_SPAN);
  43. const float low = std::max (high - WINDOW, GATE);
  44. return std::clamp ((level - low) / (high - low), 0.0f, 1.0f);
  45. }