Audacity 3.2.0
MirDsp.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/*!********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 MirDsp.cpp
7
8 Matthieu Hodgkinson
9
10**********************************************************************/
11#include "MirDsp.h"
12#include "IteratorX.h"
13#include "MathApprox.h"
14#include "MemoryX.h"
15#include "MirTypes.h"
16#include "MirUtils.h"
17#include "PowerSpectrumGetter.h"
18#include "StftFrameProvider.h"
19#include <cassert>
20#include <cmath>
21#include <numeric>
22#include <pffft.h>
23
24namespace MIR
25{
26namespace
27{
29 const PffftFloatVector& prevPowSpec, const PffftFloatVector& powSpec)
30{
31 auto k = 0;
32 return std::accumulate(
33 powSpec.begin(), powSpec.end(), 0.f, [&](float a, float mag) {
34 // Half-wave-rectified stuff
35 return a + std::max(0.f, mag - prevPowSpec[k++]);
36 });
37}
38
39std::vector<float> GetMovingAverage(const std::vector<float>& x, double hopRate)
40{
41 constexpr auto smoothingWindowDuration = 0.2;
42 // An odd number.
43 const int M = std::round(smoothingWindowDuration * hopRate / 4) * 2 + 1;
44 const auto window = GetNormalizedHann(2 * M + 1);
45 auto n = 0;
46 std::vector<float> movingAverage(x.size());
47 std::transform(x.begin(), x.end(), movingAverage.begin(), [&](float) {
48 const auto m = IotaRange(-M, M + 1);
49 const auto y =
50 std::accumulate(m.begin(), m.end(), 0.f, [&](float y, int i) {
51 auto k = n + i;
52 while (k < 0)
53 k += x.size();
54 while (k >= x.size())
55 k -= x.size();
56 return y + x[k] * window[i + M];
57 });
58 ++n;
59 // The moving average of the raw ODF will be subtracted from it to yield
60 // the final ODF, negative results being set to 0. (This is to remove
61 // noise of small ODF peaks before the method's quantization step.) The
62 // larger this multiplier, the less peaks will remain. This value was
63 // found by trial and error, using the benchmarking framework
64 // (see TatumQuantizationFitBenchmarking.cpp)
65 constexpr auto thresholdRaiser = 1.5f;
66 return y * thresholdRaiser;
67 });
68 return movingAverage;
69}
70} // namespace
71
72std::vector<float>
73GetNormalizedCircularAutocorr(const std::vector<float>& ux /* unaligned x*/)
74{
75 if (std::all_of(ux.begin(), ux.end(), [](float x) { return x == 0.f; }))
76 return ux;
77 const auto N = ux.size();
78 assert(IsPowOfTwo(N));
79 PffftSetupHolder setup { pffft_new_setup(N, PFFFT_REAL) };
80 PffftFloatVector x { ux.begin(), ux.end() };
81 PffftFloatVector work(N);
82 pffft_transform_ordered(
83 setup.get(), x.data(), x.data(), work.data(), PFFFT_FORWARD);
84
85 // Transform to a power spectrum, but preserving the layout expected by PFFFT
86 // in preparation for the inverse transform.
87 x[0] *= x[0];
88 x[1] *= x[1];
89 for (auto n = 2; n < N; n += 2)
90 {
91 x[n] = x[n] * x[n] + x[n + 1] * x[n + 1];
92 x[n + 1] = 0.f;
93 }
94
95 pffft_transform_ordered(
96 setup.get(), x.data(), x.data(), work.data(), PFFFT_BACKWARD);
97
98 // The second half of the circular autocorrelation is the mirror of the first
99 // half. We are economic and only keep the first half.
100 x.erase(x.begin() + N / 2 + 1, x.end());
101
102 const auto normalizer = 1 / x[0];
103 std::transform(x.begin(), x.end(), x.begin(), [normalizer](float x) {
104 return x * normalizer;
105 });
106 return { x.begin(), x.end() };
107}
108
109std::vector<float> GetOnsetDetectionFunction(
110 const MirAudioReader& audio,
111 const std::function<void(double)>& progressCallback,
112 QuantizationFitDebugOutput* debugOutput)
113{
114 StftFrameProvider frameProvider { audio };
115 const auto sampleRate = frameProvider.GetSampleRate();
116 const auto numFrames = frameProvider.GetNumFrames();
117 const auto frameSize = frameProvider.GetFftSize();
118 PffftFloatVector buffer(frameSize);
119 std::vector<float> odf;
120 odf.reserve(numFrames);
121 const auto powSpecSize = frameSize / 2 + 1;
122 PffftFloatVector powSpec(powSpecSize);
123 PffftFloatVector prevPowSpec(powSpecSize);
124 PffftFloatVector firstPowSpec;
125 std::fill(prevPowSpec.begin(), prevPowSpec.end(), 0.f);
126
127 PowerSpectrumGetter getPowerSpectrum { frameSize };
128
129 auto frameCounter = 0;
130 while (frameProvider.GetNextFrame(buffer))
131 {
132 getPowerSpectrum(buffer.aligned(), powSpec.aligned());
133
134 // Compress the frame as per section (6.5) in Müller, Meinard.
135 // Fundamentals of music processing: Audio, analysis, algorithms,
136 // applications. Vol. 5. Cham: Springer, 2015.
137 constexpr auto gamma = 100.f;
138 std::transform(
139 powSpec.begin(), powSpec.end(), powSpec.begin(),
140 [gamma](float x) { return FastLog2(1 + gamma * std::sqrt(x)); });
141
142 if (firstPowSpec.empty())
143 firstPowSpec = powSpec;
144 else
145 odf.push_back(GetNoveltyMeasure(prevPowSpec, powSpec));
146
147 if (debugOutput)
148 debugOutput->postProcessedStft.push_back(powSpec);
149
150 std::swap(prevPowSpec, powSpec);
151
152 if (progressCallback)
153 progressCallback(1. * ++frameCounter / numFrames);
154 }
155
156 // Close the loop.
157 odf.push_back(GetNoveltyMeasure(prevPowSpec, firstPowSpec));
158 assert(IsPowOfTwo(odf.size()));
159
160 const auto movingAverage =
161 GetMovingAverage(odf, frameProvider.GetFrameRate());
162
163 if (debugOutput)
164 {
165 debugOutput->rawOdf = odf;
166 debugOutput->movingAverage = movingAverage;
167 }
168
169 // Subtract moving average from ODF.
170 std::transform(
171 odf.begin(), odf.end(), movingAverage.begin(), odf.begin(),
172 [](float a, float b) { return std::max<float>(a - b, 0.f); });
173
174 return odf;
175}
176} // namespace MIR
std::unique_ptr< PFFFT_Setup, PffftSetupDeleter > PffftSetupHolder
MockedAudio audio
Much faster that FFT.h's PowerSpectrum, at least in Short-Time Fourier Transform-like situations,...
float GetNoveltyMeasure(const PffftFloatVector &prevPowSpec, const PffftFloatVector &powSpec)
Definition: MirDsp.cpp:28
std::vector< float > GetMovingAverage(const std::vector< float > &x, double hopRate)
Definition: MirDsp.cpp:39
std::vector< float > GetOnsetDetectionFunction(const MirAudioReader &audio, const std::function< void(double)> &progressCallback, QuantizationFitDebugOutput *debugOutput)
Definition: MirDsp.cpp:109
std::vector< float > GetNormalizedCircularAutocorr(const std::vector< float > &ux)
Get the normalized, circular auto-correlation for a signal x whose length already is a power of two....
Definition: MirDsp.cpp:73
std::vector< float > GetNormalizedHann(int size)
Definition: MirUtils.cpp:80
constexpr auto IsPowOfTwo(int x)
Definition: MirUtils.h:28
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:634
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
Definition: fast_float.h:2512
std::vector< float > rawOdf
Definition: MirTypes.h:143
std::vector< float > movingAverage
Definition: MirTypes.h:144
std::vector< PffftFloatVector > postProcessedStft
Definition: MirTypes.h:142
A vector of floats guaranteeing alignment as demanded by pffft.
PffftFloats aligned(PffftAlignedCount c={})