SpectrumAnalyzer.cpp 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. #include "SpectrumAnalyzer.h"
  2. #include <algorithm>
  3. #include <cmath>
  4. #include <cstdlib>
  5. using namespace WallpaperEngine::Audio;
  6. namespace {
  7. // wallpaper64.exe's capture thread settings (processor constructor sub_1400A9130)
  8. constexpr float BAND_EXPONENT = 0.25f;
  9. constexpr float TILT = 0.5f;
  10. constexpr float SIZE_FACTOR = 30.0f;
  11. constexpr float BIN_FACTOR = 10.0f;
  12. // "audioinputvolume" (default 50) * 0.02
  13. constexpr float GAIN = 1.0f;
  14. } // namespace
  15. SpectrumAnalyzer::SpectrumAnalyzer (int sampleRate) {
  16. const float rateScale = std::max (static_cast<float> (sampleRate) / 44100.0f, 1.0f);
  17. this->m_size = static_cast<int> (rateScale * 64.0f * SIZE_FACTOR);
  18. this->m_binCount = static_cast<int> (BIN_FACTOR * 64.0f);
  19. this->m_blockSize = static_cast<int> (
  20. static_cast<float> (this->m_size)
  21. - (BIN_FACTOR / SIZE_FACTOR) * static_cast<float> (this->m_size)
  22. );
  23. this->m_config = kiss_fft_alloc (this->m_size, 0, nullptr, nullptr);
  24. // samples are stored as s * 127 + 127 with 1 / that in the imaginary part, the part of the buffer past the
  25. // block is never written and keeps the silence value
  26. for (auto& input : this->m_input) {
  27. input.assign (this->m_size, kiss_fft_cpx { 127.0f, 1.0f / 127.0f });
  28. }
  29. this->m_output.resize (this->m_size);
  30. }
  31. SpectrumAnalyzer::~SpectrumAnalyzer () { kiss_fft_free (this->m_config); }
  32. void SpectrumAnalyzer::reset () { this->m_filled = 0; }
  33. bool SpectrumAnalyzer::feed (const float* samples, std::size_t frames, int channels, float* bands) {
  34. channels = std::clamp (channels, 1, 2);
  35. const int end = static_cast<int> (std::min<std::size_t> (this->m_blockSize, this->m_filled + frames));
  36. for (int i = this->m_filled; i < end; i++) {
  37. const float* frame = samples + static_cast<std::size_t> (i - this->m_filled) * channels;
  38. for (int c = 0; c < channels; c++) {
  39. const float value = frame[c] * 127.0f + 127.0f;
  40. this->m_input[c][i] = { value, 1.0f / value };
  41. }
  42. }
  43. this->m_filled = end;
  44. if (this->m_filled < this->m_blockSize) {
  45. return false;
  46. }
  47. this->m_filled = 0;
  48. this->analyze (channels, bands);
  49. return true;
  50. }
  51. void SpectrumAnalyzer::analyze (int channels, float* bands) {
  52. std::fill_n (bands, BANDS * 2, 0.0f);
  53. const float lastBin = static_cast<float> (this->m_binCount - 1);
  54. for (int c = 0; c < channels; c++) {
  55. kiss_fft (this->m_config, this->m_input[c].data (), this->m_output.data ());
  56. float* out = bands + c * BANDS;
  57. int band = 0;
  58. for (int bin = 1; bin < this->m_binCount; bin++) {
  59. const kiss_fft_cpx& value = this->m_output[bin];
  60. float power = value.r * value.r + value.i * value.i;
  61. if (!std::isfinite (power)) {
  62. power = 0.0f;
  63. }
  64. const float x = static_cast<float> (bin - 1);
  65. const float weight = TILT - std::cos (x * 3.1415927f / lastBin) * (1.0f - TILT);
  66. const float magnitude = std::sqrt (weight * power);
  67. const int target = static_cast<int> (std::pow (x / lastBin, BAND_EXPONENT) * 64.0f) % 64;
  68. // the low bins would all land in the first few bands, each takes the next band until the curve catches up
  69. band = std::min (band + 1, target);
  70. out[band] = std::max (out[band], magnitude);
  71. }
  72. }
  73. if (channels < 2) {
  74. std::copy_n (bands, BANDS, bands + BANDS);
  75. }
  76. const float scale = GAIN * 0.001f
  77. * (static_cast<float> (this->m_binCount) / (static_cast<float> (this->m_size) * 0.5f));
  78. for (int i = 0; i < BANDS * 2; i++) {
  79. bands[i] *= scale;
  80. }
  81. }