Krotos Modules 3
Loading...
Searching...
No Matches
ShortTimeFourierTransform.cpp
Go to the documentation of this file.
1namespace krotos
2{
3 ShortTimeFourierTransform::ShortTimeFourierTransform(int winSizeInSamples, int fftSize, int hopSize,
4 WindowType winMethod, fftMode mode, int sampleRate)
5 : dftParams{winSizeInSamples, fftSize, hopSize, winSizeInSamples - hopSize, sampleRate}, m_mode(mode),
6 m_fft(static_cast<int>(log2f(static_cast<float>(fftSize)))), sigFrame(2 * fftSize),
7 // TODO: Remove after stl removal
8 m_sigFrame(2 * fftSize)
9 {
10 // Validate input parameters
11 jassert(dftParams.winSize > 0);
12 jassert(dftParams.fftSize > 0 &&
13 (dftParams.fftSize & (dftParams.fftSize - 1)) == 0); // Check if fftSize is a power of 2
15 jassert(dftParams.sampleRate > 0);
16 jassert(dftParams.winSize <= dftParams.fftSize); // Ensure window size does not exceed fft size
17
18 // Set here the fft binSize
19 binParams.binSize = static_cast<float>(dftParams.sampleRate) / static_cast<float>(dftParams.fftSize);
20
21 // generate window function
22 m_window = WindowFunctions::generateWindow(winSizeInSamples + 1, winMethod);
23 dftParams.window = WindowFunctions::generateWindowEigen(winSizeInSamples + 1, winMethod);
24 }
25
26 std::vector<std::vector<float>> ShortTimeFourierTransform::stft(const AudioSampleBuffer& buffer)
27 {
28 m_inputSize = buffer.getNumSamples();
29 auto inputData = buffer.getReadPointer(0);
30
31 std::vector<std::vector<float>> outputData;
32 // Pre allocate memory for outputData
33 outputData.reserve(m_inputSize / dftParams.hopSize + 1);
34
35 int posin = 0;
36
37 for (; posin < m_inputSize; posin += dftParams.hopSize)
38 {
39 // Initialize sigFrame with zeros for each frame
40 std::fill(m_sigFrame.begin(), m_sigFrame.end(), 0.0f);
41
42 // Extract a signal frame and apply window function
43 int frameEnd = std::min(posin + dftParams.winSize, m_inputSize);
44 for (int i = 0; i < frameEnd - posin; ++i)
45 {
46 m_sigFrame[i] = inputData[posin + i] * m_window[i];
47 }
48
49 // No explicit zero-padding needed here since sigFrame was initialized with zeros
50 // and it's already twice the size of FFT points as required.
51
52 // check which FFT we want to process the frame with
53 std::vector<float> frameOutput;
54 switch (m_mode)
55 {
57 // apply FFT on the frame - get back frequency magnitude
58 m_fft.performFrequencyOnlyForwardTransform(m_sigFrame.data(), false);
59 // store frequency magnitude in the outputData
60 frameOutput.assign(m_sigFrame.begin(), m_sigFrame.begin() + (dftParams.fftSize / 2) + 1);
61 break;
63 // apply FFT on the frame
64 // TODO: Decode the complex number encoding in here to retrieve correctly
65 // phase&mag info.
66 m_fft.performRealOnlyForwardTransform(m_sigFrame.data(), false);
67 // store the complex pairs in the outputData
68 frameOutput.assign(m_sigFrame.begin(), m_sigFrame.begin() + dftParams.winSize);
69 break;
70 }
71
72 outputData.push_back(std::move(frameOutput));
73
74 // increment counter of frames
76 }
77
78 return outputData;
79 }
80
81 Eigen::MatrixXf ShortTimeFourierTransform::processSignal(const Eigen::VectorXf inputSignal)
82 {
83 m_inputSize = inputSignal.size();
84
85 // Pre allocate memory for outputData
86 Eigen::MatrixXf outputData(dftParams.fftSize / 2 + 1, m_inputSize / dftParams.hopSize + 1);
87 outputData.setZero();
88
89 for (int posin = 0; posin < m_inputSize; posin += dftParams.hopSize)
90 {
91 // Initialize sigFrame with zeros for each frame
92 sigFrame.setZero();
93
94 // Extract a signal frame and apply window function
95 int frameEnd = std::min(posin + dftParams.winSize, m_inputSize);
96 for (int i = 0; i < frameEnd - posin; ++i)
97 {
98 sigFrame[i] = inputSignal[posin + i] * dftParams.window[i];
99 }
100
101 // Note: No explicit zero-padding needed here since sigFrame was initialized with zeros
102 // and it's already twice the size of FFT points as required.
103
104 // Check which FFT we want to process the frame with
105 switch (m_mode)
106 {
108 // apply FFT on the frame - get back frequency magnitude
109 m_fft.performFrequencyOnlyForwardTransform(sigFrame.data(), false);
110 // store frequency magnitude in the outputData
111 outputData.col(posin / dftParams.hopSize) =
112 Eigen::Map<Eigen::VectorXf>(sigFrame.data(), dftParams.fftSize / 2 + 1);
113 break;
114 }
116 // apply FFT on the frame
117 // TODO: Decode the complex number encoding in here to retrieve correctly
118 // phase&mag info.
119 m_fft.performRealOnlyForwardTransform(sigFrame.data(), false);
120 // store the complex pairs in the outputData
121 outputData.col(posin / dftParams.hopSize) =
122 Eigen::Map<Eigen::VectorXf>(sigFrame.data(), dftParams.fftSize);
123 break;
124 }
125 }
126
127 // increment counter of frames
128 m_numFrames++;
129 }
130
131 return outputData;
132 }
133
134 std::vector<float> ShortTimeFourierTransform::istft(std::vector<std::vector<float>> stftMatrix)
135 {
136 jassert(m_mode == fftMode::realOnly);
137
138 // Number of the spectral frames
139 int numOfFrames = static_cast<int>(stftMatrix.size());
140 jassert(numOfFrames > 0);
141
142 // Size of the reconstructed signal
143 int outputSize = numOfFrames * dftParams.hopSize;
144
145 std::vector<float> outputData(outputSize, 0.0f);
146
147 int posout = 0;
148
149 // int frameSize = static_cast<int>(m_sigFrame.size());
150
151 for (const auto& specFrame : stftMatrix)
152 {
153 // Inverse transform the spectral frame
154 m_fft.performRealOnlyInverseTransform(const_cast<float*>(specFrame.data()));
155
156 // Window and overlap-add the frame
157 for (size_t i = 0; i < dftParams.fftSize; i++)
158 {
159 if (posout + i < outputSize)
160 {
161 outputData[posout + i] += specFrame[i];
162 }
163 }
164
165 posout += dftParams.hopSize;
166 }
167
168 return outputData;
169 }
170
172 const std::vector<std::vector<float>>& stftMatrix)
173 {
174 jassert(m_mode == fftMode::freqMagOnly);
175 auto numOfFrames = stftMatrix.size();
176
177 auto imageWidth = image.getWidth();
178 auto imageHeight = image.getHeight();
179
180 jassert(imageWidth > 0 && imageHeight > 0);
181
182 float factor = (static_cast<float>(numOfFrames)) / (static_cast<float>(imageWidth));
183
184 for (int x = 0; x < imageWidth; x++)
185 {
186 int frameIndex = static_cast<int>(factor * static_cast<float>(x));
187
188 jassert(frameIndex <= numOfFrames);
189
190 for (auto y = 1; y < imageHeight; ++y)
191 {
192 auto maxLevel =
193 juce::FloatVectorOperations::findMinAndMax(stftMatrix.at(frameIndex).data(), dftParams.fftSize);
194
195 // proportion of the image that this y pixel corresponds to ?
196 auto skewedProportionY = 1.0f - std::exp(std::log((float)y / (float)imageHeight) * m_scalingConstant);
197 // proportion to fft Index
198 auto fftDataIndex = (size_t)jlimit(
199 /*lowerLimit*/ 0,
200 /*upperLimit*/ dftParams.fftSize,
201 /*value to constrain*/ (int)(skewedProportionY * dftParams.fftSize));
202
203 // maps from Magnitude of spectrum to 0...1
204 auto level = jmap(/*sourceValue*/ stftMatrix.at(frameIndex).at(fftDataIndex),
205 /*sourceRangeMin*/ 0.0f,
206 /*sourceRangeMax*/ jmax(maxLevel.getEnd(), 1e-5f),
207 /*targetRangeMin*/ 0.0f,
208 /*targetRangeMax*/ 1.0f);
209
210 image.setPixelAt(x, y, Colour::fromHSV(level, 1.0f, level, 1.0f));
211 }
212 }
213 }
214
216 {
217 float freqRes = dftParams.sampleRate / static_cast<float>(dftParams.fftSize);
218 Eigen::VectorXf w1 = freqRes * Eigen::VectorXf::LinSpaced(dftParams.fftSize, 0, dftParams.fftSize - 1).array();
219 float Nyq = dftParams.sampleRate / 2.0f;
220 float halfRes = freqRes / 2;
221 // NPT Info
223
225 {
226 // Adjust points on either side of Nyquist for odd NPTS.
227 w1(binParams.halfNPTS - 1) = Nyq - halfRes;
228 w1(binParams.halfNPTS) = Nyq + halfRes;
229 }
230 else
231 {
232 // Make sure we hit Nyquist exactly for even NPTS.
233 w1(binParams.halfNPTS - 1) = Nyq;
234 }
235
236 // Adjust the last point.
237 // Assuming NPTS is passed correctly and maps to w1.size() for the conversion
238 w1(w1.size() - 1) = dftParams.sampleRate - freqRes;
239
240 // Get the right grid based on range, centerdc, etc.
241 Eigen::VectorXf binsVector = finalGrid(w1, Nyq, false /*TODO: Check this*/, freqRange);
242 std::vector<float> binsVectorSTL(binsVector.data(), binsVector.data() + binsVector.size());
243
244 return binsVectorSTL;
245 }
246
248 {
249 float freqRes = dftParams.sampleRate / static_cast<float>(dftParams.fftSize);
250 Eigen::VectorXf w1 = freqRes * Eigen::VectorXf::LinSpaced(dftParams.fftSize, 0, dftParams.fftSize - 1).array();
251 float Nyq = dftParams.sampleRate / 2.0f;
252 float halfRes = freqRes / 2;
253 // NPT Info
255
257 {
258 // Adjust points on either side of Nyquist for odd NPTS.
259 w1(binParams.halfNPTS - 1) = Nyq - halfRes;
260 w1(binParams.halfNPTS) = Nyq + halfRes;
261 }
262 else
263 {
264 // Make sure we hit Nyquist exactly for even NPTS.
265 w1(binParams.halfNPTS - 1) = Nyq;
266 }
267
268 // Adjust the last point.
269 // Assuming NPTS is passed correctly and maps to w1.size() for the conversion
270 w1(w1.size() - 1) = dftParams.sampleRate - freqRes;
271
272 // Get the right grid based on range, centerdc, etc.
273 return finalGrid(w1, Nyq, false /*TODO: Check this*/, freqRange);
274 }
275
277 {
278 // You need to have already computed stft at this point!!
279 jassert(m_inputSize != 0);
280 int nx = m_inputSize + /*Careful this might cause problem!*/ dftParams.winSize;
281 size_t nCol = std::floor((nx - dftParams.nOverlap) / dftParams.hopSize);
282
283 // Determine the number of columns of the STFT output(i.e., the S output)
284 Eigen::ArrayXf colOffsets = Eigen::ArrayXf::LinSpaced(nCol, 0, (nCol - 1) * dftParams.hopSize);
285 Eigen::VectorXf columns = (colOffsets + (dftParams.winSize / 2.0)).transpose() / dftParams.sampleRate;
286 std::vector<float> columnsSTL(columns.data(), columns.data() + columns.size());
287
288 return columnsSTL;
289 }
290
292 {
293 // You need to have already computed stft at this point!!
294 jassert(m_inputSize != 0);
295 int nx = m_inputSize + /*Careful this might cause problem!*/ dftParams.winSize;
296 size_t nCol = std::floor((nx - dftParams.nOverlap) / dftParams.hopSize);
297
298 // Determine the number of columns of the STFT output(i.e., the S output)
299 Eigen::ArrayXf colOffsets = Eigen::ArrayXf::LinSpaced(nCol, 0, (nCol - 1) * dftParams.hopSize);
300 Eigen::VectorXf columns = (colOffsets + (dftParams.winSize / 2.0)).transpose() / dftParams.sampleRate;
301
302 return columns;
303 }
304
306 {
307 jassert(binParams.binSize != -1.0f);
308
309 return binParams.binSize;
310 }
311
313 {
314 binParams.isNPTSodd = false;
315 if (static_cast<int>(NPTS) % 2 != 0)
316 binParams.isNPTSodd = true;
317
319 binParams.halfNPTS = (NPTS + 1) / 2;
320 else
321 binParams.halfNPTS = NPTS / 2 + 1;
322
323 binParams.isHalfNPTSodd = false;
324 if (static_cast<int>(binParams.halfNPTS) % 2 != 0)
326
329 else
331 }
332
333 Eigen::VectorXf ShortTimeFourierTransform::finalGrid(const Eigen::VectorXf& w1, float Nyq, bool centerDC,
335 {
336 jassert(binParams.halfNPTS != -1 && binParams.quarterNPTS != -1); // You need to have calculated them by now!
337
338 Eigen::VectorXf w;
339 switch (freqRange)
340 {
342 w.resize(dftParams.fftSize);
343 if (centerDC)
344 {
346 Eigen::VectorXf temp(negEndPt * 2);
347 temp.head(negEndPt) = w1.segment(1, negEndPt).reverse();
348 temp.tail(negEndPt) = w1.head(negEndPt);
349 w = temp;
350 }
351 else
352 {
353 w = w1;
354 }
355 break;
356 }
358 w = w1.head(binParams.halfNPTS);
359 if (centerDC)
360 {
362 Eigen::VectorXf temp(negEndPt * 2);
363 temp.head(negEndPt) = w1.segment(1, negEndPt).reverse();
364 temp.tail(negEndPt) = w1.head(binParams.quarterNPTS);
365 w = temp;
366 if (dftParams.fftSize % 4 == 0)
367 {
368 w(w.size() - 1) = Nyq / 2;
369 }
370 }
371 break;
372 }
373 default:
374 throw std::runtime_error("Unsupported frequency range.");
375 }
376 return w;
377 }
378
379} // namespace krotos
struct krotos::ShortTimeFourierTransform::DFTParams dftParams
std::vector< float > istft(std::vector< std::vector< float > > stftMatrix)
Definition ShortTimeFourierTransform.cpp:134
void drawSpectrogram(juce::Image &image, const std::vector< std::vector< float > > &stftMatrix)
Definition ShortTimeFourierTransform.cpp:171
FrequencyRange
Definition ShortTimeFourierTransform.h:27
enum krotos::ShortTimeFourierTransform::fftMode m_mode
void NPTSinfo(float NPTS)
Definition ShortTimeFourierTransform.cpp:312
Eigen::VectorXf getBins(ShortTimeFourierTransform::FrequencyRange freqRange)
Calculates and returns the frequency bins for a given frequency range within the context of a short-t...
Definition ShortTimeFourierTransform.cpp:247
Eigen::MatrixXf processSignal(const Eigen::VectorXf inputSignal)
Definition ShortTimeFourierTransform.cpp:81
struct krotos::ShortTimeFourierTransform::BinParams binParams
fftMode
Definition ShortTimeFourierTransform.h:19
@ freqMagOnly
Definition ShortTimeFourierTransform.h:20
@ realOnly
Definition ShortTimeFourierTransform.h:21
std::vector< float > m_window
Definition ShortTimeFourierTransform.h:143
Eigen::VectorXf sigFrame
Definition ShortTimeFourierTransform.h:148
std::vector< float > getColumnsSTL()
Definition ShortTimeFourierTransform.cpp:276
Eigen::VectorXf finalGrid(const Eigen::VectorXf &w1, float Nyq, bool centerDC, ShortTimeFourierTransform::FrequencyRange freqRange)
Definition ShortTimeFourierTransform.cpp:333
const float m_scalingConstant
Definition ShortTimeFourierTransform.h:154
int m_numFrames
Definition ShortTimeFourierTransform.h:151
ShortTimeFourierTransform(int winSizeSamples, int fftSizeSamples, int hopSize, WindowType winMethod, fftMode mode, int sampleRate)
Definition ShortTimeFourierTransform.cpp:3
std::vector< std::vector< float > > stft(const AudioSampleBuffer &inputSignal)
Definition ShortTimeFourierTransform.cpp:26
juce::dsp::FFT m_fft
Definition ShortTimeFourierTransform.h:141
std::vector< float > getBinsSTL(ShortTimeFourierTransform::FrequencyRange freqRange)
Definition ShortTimeFourierTransform.cpp:215
float getFFTBinSize() const
Definition ShortTimeFourierTransform.cpp:305
std::vector< float > m_sigFrame
Definition ShortTimeFourierTransform.h:146
Eigen::VectorXf getColumns()
Computes and returns the time indices for the center of each STFT window.
Definition ShortTimeFourierTransform.cpp:291
int m_inputSize
Definition ShortTimeFourierTransform.h:156
std::vector< float > generateWindow(int sizeInSamples, WindowType windowType)
Definition WindowFunctions.cpp:12
Eigen::VectorXf generateWindowEigen(int sizeInSamples, WindowType windowType)
Definition WindowFunctions.cpp:49
Definition AirAbsorptionFilter.cpp:2
WindowType
Definition WindowFunctions.h:6
bool isNPTSodd
Definition ShortTimeFourierTransform.h:129
float binSize
Definition ShortTimeFourierTransform.h:133
int quarterNPTS
Definition ShortTimeFourierTransform.h:132
bool isHalfNPTSodd
Definition ShortTimeFourierTransform.h:131
int halfNPTS
Definition ShortTimeFourierTransform.h:130
int hopSize
Definition ShortTimeFourierTransform.h:38
int fftSize
Definition ShortTimeFourierTransform.h:37
int sampleRate
Definition ShortTimeFourierTransform.h:40
Eigen::VectorXf window
Definition ShortTimeFourierTransform.h:42
int nOverlap
Definition ShortTimeFourierTransform.h:39
int winSize
Definition ShortTimeFourierTransform.h:36