Loading...
Searching...
No Matches
SpectrumAnalysis.hpp
1#pragma once
2#include <ossia/audio/fft.hpp>
3
4#include <Yin.h>
5
6#include <QString>
7
8#include <Ui/Monitor/MidiText.hpp>
9
10#include <algorithm>
11#include <array>
12#include <cmath>
13#include <numbers>
14#include <optional>
15#include <span>
16#include <vector>
17
23namespace Ui::Monitor
24{
25enum class Window
26{
27 Hann,
28 BlackmanHarris,
29 FlatTop
30};
31
33{
34 int size{};
35 double rate{48000.};
36 ossia::fft fft{16};
37 std::vector<double> window;
38 double coherent_gain{1.};
39 double sum_sq{1.};
40 Window kind{};
43 std::vector<double> power;
44
45 void prepare(int n, double sample_rate, Window w)
46 {
47 n = std::max(n, 64);
48 rate = sample_rate;
49 if(n == size && w == kind && !window.empty())
50 return;
51 if(n != size)
52 {
53 size = n;
54 fft.reset(n);
55 power.assign(n / 2 + 1, 0.);
56 }
57 kind = w;
58 window.resize(n);
59 constexpr double pi = std::numbers::pi;
60 for(int i = 0; i < n; i++)
61 {
62 const double t = double(i) / n;
63 switch(w)
64 {
65 case Window::Hann:
66 window[i] = 0.5 - 0.5 * std::cos(2 * pi * t);
67 break;
68 case Window::BlackmanHarris:
69 window[i] = 0.35875 - 0.48829 * std::cos(2 * pi * t)
70 + 0.14128 * std::cos(4 * pi * t) - 0.01168 * std::cos(6 * pi * t);
71 break;
72 case Window::FlatTop:
73 window[i] = 0.21557895 - 0.41663158 * std::cos(2 * pi * t)
74 + 0.277263158 * std::cos(4 * pi * t)
75 - 0.083578947 * std::cos(6 * pi * t)
76 + 0.006947368 * std::cos(8 * pi * t);
77 break;
78 }
79 }
80 double s = 0, s2 = 0;
81 for(double v : window)
82 {
83 s += v;
84 s2 += v * v;
85 }
86 coherent_gain = s;
87 sum_sq = s2;
88 }
89
91 template <typename T>
92 void analyse(const T* x) noexcept
93 {
94 auto* in = fft.input();
95 for(int i = 0; i < size; i++)
96 in[i] = x[i] * window[i];
97 const auto* out = fft.execute();
98 // |X| * 2 / sum(w) is the amplitude of a sine on that bin
99 const double k = 4. / (coherent_gain * coherent_gain);
100 const int bins = size / 2 + 1;
101 for(int b = 0; b < bins; b++)
102 power[b] = (out[b][0] * out[b][0] + out[b][1] * out[b][1]) * k;
103 }
104
105 double binFrequency(double bin) const noexcept { return bin * rate / size; }
106 double frequencyBin(double f) const noexcept { return f * size / rate; }
107
110 double enbw() const noexcept { return size * sum_sq / (coherent_gain * coherent_gain); }
111
115 double bandPower(std::span<const double> p, double f0, double f1) const noexcept
116 {
117 if(p.empty())
118 return 0.;
119 const int last = int(p.size()) - 1;
120 const int b0 = std::clamp(int(std::ceil(frequencyBin(f0))), 0, last);
121 const int b1 = std::clamp(int(std::floor(frequencyBin(f1))), 0, last);
122 if(b1 < b0)
123 {
124 // Band narrower than a bin: the share of the bin it falls in that its
125 // width covers, so that the level per Hz is the same as for wide bands
126 const int b = std::clamp(int(std::lround(frequencyBin((f0 + f1) / 2))), 0, last);
127 return p[b] * (frequencyBin(f1) - frequencyBin(f0)) / enbw();
128 }
129 double sum = 0;
130 for(int b = b0; b <= b1; b++)
131 sum += p[b];
132 return sum / enbw();
133 }
134
135 double bandPower(double f0, double f1) const noexcept { return bandPower(power, f0, f1); }
136};
137
141{
142 static constexpr int count = 31;
143 static double centre(int i) noexcept { return 1000. * std::pow(10., (i - 17) / 10.); }
144 static double lower(int i) noexcept { return centre(i) * std::pow(10., -1. / 20.); }
145 static double upper(int i) noexcept { return centre(i) * std::pow(10., 1. / 20.); }
147 static constexpr std::array<const char*, count> labels{
148 "20", "25", "31.5", "40", "50", "63", "80", "100", "125", "160", "200",
149 "250", "315", "400", "500", "630", "800", "1k", "1.25k", "1.6k", "2k", "2.5k",
150 "3.15k", "4k", "5k", "6.3k", "8k", "10k", "12.5k", "16k", "20k"};
151};
152
156{
157public:
158 double operator()(std::span<const float> frame, double rate, double fmax = 2000.)
159 {
160 if(rate <= 0 || frame.size() < 64)
161 return 0.;
162 if(!m_yin || m_rate != rate)
163 {
164 m_yin.emplace(int(rate));
165 m_yin->setMaxFrequency(float(fmax));
166 m_rate = rate;
167 }
168 m_frame.assign(frame.begin(), frame.end());
169 const float f = m_yin->pitchYin(m_frame);
170 return f > 0.f ? double(f) : 0.;
171 }
172
173private:
174 std::optional<Yin<float>> m_yin;
175 std::vector<float> m_frame;
176 double m_rate{};
177};
178
180inline QString noteName(double f, double* cents = nullptr)
181{
182 if(f <= 0)
183 return {};
184 const double midi = 69. + 12. * std::log2(f / 440.);
185 const int n = int(std::lround(midi));
186 if(cents)
187 *cents = (midi - n) * 100.;
188 return midiNoteName(n);
189}
190}
Definition SpectrumAnalysis.hpp:156
Definition BufferDecode.hpp:16
QString noteName(double f, double *cents=nullptr)
"A4", "C#3"
Definition SpectrumAnalysis.hpp:180
QString midiNoteName(int pitch)
The name of a MIDI note, middle C (60) being C4; "?" out of 0 to 127.
Definition MidiText.hpp:15
Definition SpectrumAnalysis.hpp:33
void analyse(const T *x) noexcept
Analyses size samples; x must hold at least that many.
Definition SpectrumAnalysis.hpp:92
double bandPower(std::span< const double > p, double f0, double f1) const noexcept
Definition SpectrumAnalysis.hpp:115
double enbw() const noexcept
Definition SpectrumAnalysis.hpp:110
std::vector< double > power
Definition SpectrumAnalysis.hpp:43
Definition SpectrumAnalysis.hpp:141
static constexpr std::array< const char *, count > labels
IEC 61260 nominal centre frequencies.
Definition SpectrumAnalysis.hpp:147