Krotos Modules 3
Loading...
Searching...
No Matches
HarmonicRepresentation.cpp
Go to the documentation of this file.
1//=====================================================================================
7//=====================================================================================
8
10
11//=====================================================================================
12namespace krotos
13{
14 HarmonicRepresentation::HarmonicRepresentation(float sampleRate) : m_sampleRate(sampleRate)
15 {
16 float innerExpression = std::round(std::log2(8.0f * sampleRate / 50.0f)) - 1.0f;
17 // calculate the maximum of hopSize and innerExpression divided by hopSize
18 m_maxSwipepWinSize = config.hopSize * std::ceil(std::pow(2.0f, innerExpression) / config.hopSize);
19 }
20
21 Eigen::MatrixXf HarmonicRepresentation::processSignal(std::vector<float>& inputSignal)
22 {
23 // TODO:: There is a copy here, need to go one level above and pass VectorXf when called!!!!
24 Eigen::VectorXf signal = Eigen::VectorXf::Map(&inputSignal[0], inputSignal.size());
25
26 // -- Compute spectrum -- TODO::Debug and rewrite STFT as part of
27 // https://krotosltd.atlassian.net/jira/software/c/projects/RD/boards/43?selectedIssue=RD-271 Init stft
28 auto stftObj = std::make_unique<ShortTimeFourierTransform>(
31
32 // If the window is centred at t, this is the starting index at which to
33 // look up the sound.value which you want to multiply by the window.It is a
34 // negative number because(almost) half of the window will be before time t
35 // and half after.In fact, if the length of the window N is an even number,
36 // it is set up so this number equals(N / 2 - 1).If the length of the window
37 // is odd, this number equals(N - 1) / 2.
38 int winFirstRelIdx = std::floor((config.winSize - 1) / 2.0f);
39 // This is the last index at which to look up signal values and is equal to
40 // (N - 1) / 2 if the length N of the window is odd and N / 2 if the length of the
41 // window is even.This means that in the even case, the window has an
42 // unequal number of past and future values, i.e., time t is not the centre
43 // of the window, but slightly to the left of the centre of the window
44 // (before it).
45 int winLastRelIdx = std::ceil((config.winSize - 1) / 2.0f);
46
47 // Perform stft
48 auto distr = stftObj->processSignal(signal);
49 // Only return frequencies below Nyquist rate
50 distr = distr.block(0, 0, config.fftSize / 2, distr.cols()).eval();
51 // Get the power distribution
52 distr = distr.array().square();
53 // Remove window energy
54 float windowEnergy = stftObj->dftParams.window.sum();
55 windowEnergy = windowEnergy * windowEnergy;
56 distr = distr / windowEnergy;
57
58 // Access stft column vector
59 auto tSupport = stftObj->getColumns();
60 tSupport = tSupport.array() - (static_cast<float>(winFirstRelIdx) / m_sampleRate);
61
62 // Access stft frequency vector
63 auto fSupport = stftObj->getBins(ShortTimeFourierTransform::FrequencyRange::Half);
64 // Only return frequencies below Nyquist
65 fSupport = fSupport.head(config.fftSize / 2).eval();
66
67 constexpr int nFreqCorrs = static_cast<int>(10.0f / 0.1f) + 1;
68 Eigen::VectorXf freqCorrs = Eigen::VectorXf::LinSpaced(nFreqCorrs, -5.0f, 5.0f);
69 int nInharmCoeffs = static_cast<int>((0.001f - 0.0f) / 0.00005f) + 1;
70 Eigen::VectorXf inharmCoeffs = Eigen::VectorXf::LinSpaced(nInharmCoeffs, 0.0f, 0.001f);
71
72 // Adjusting time support vector
73 int startIdx = 0;
74 int endIdx = tSupport.size() - 1;
75 tSupport.segment(startIdx, endIdx - startIdx + 1).array() -= tSupport(startIdx);
76 int tSize = endIdx - startIdx + 1;
77 Eigen::MatrixXf distrExtr = distr.middleCols(startIdx, endIdx - startIdx + 1); // Not the same size
78
79 // Init Swipe -> TODO:: Make it a member var to avoid reinstantiation of vectors
80 auto swipeObj = std::make_unique<SWIPE_PitchEstimation>(m_sampleRate, config.hopSize / m_sampleRate,
83
84 auto pitchStrengthPairs = swipeObj->calculatePitchStrengthPairs(inputSignal); // 1. estPitches, 2. estStrengths
85 Eigen::VectorXf estTimes = swipeObj->getTimes();
86
87 // replace NaNs in pitches with median
88 replaceNanWithMedian(pitchStrengthPairs.first);
89 // replace NaN values with -Inf using array operations in pitch strengths
90 pitchStrengthPairs.second = pitchStrengthPairs.second.unaryExpr(
91 [](float x) { return std::isnan(x) ? -std::numeric_limits<float>::infinity() : x; });
92
93 // This matrix is the return value of the function and contains the harmonic information of the signal
94 Eigen::MatrixXf value;
95 Eigen::VectorXf fundamentalFreqs;
96
97 // determine if sample is harmonic or inharmonic with respect to threshold
98 if (pitchStrengthPairs.second.maxCoeff() > harmRep.threshold)
99 {
100 Eigen::MatrixXf estTimePitchPairs(estTimes.size(), 2);
101
102 jassert(m_maxSwipepWinSize != 0.0f && m_sampleRate != 0.0f); // Something went wrong
103 estTimePitchPairs.col(0) = estTimes.array();
104 estTimePitchPairs.col(1) = pitchStrengthPairs.first;
105
106 // Estimate f0 at times at which spectrogram was evaluated by interpolating
107 // between times when f0 was evaluated
108 fundamentalFreqs = fevalbp(estTimePitchPairs, tSupport);
109 fundamentalFreqs.transpose();
110 }
111 else
112 {
113 // If sound not harmonic, we just fill in with 0s as fundamental estimate,
114 // otherwise this will cause there to be misalignment if the sound
115 // analysed is a chunk of a whole sound.
116 value.resize(2 * harmRep.nHarms, tSize);
117 // Fill harmRep.value with zeros
118 value.setZero();
119
120 return value;
121 }
122
123 // Corrected Frequencies indexed by Time and Frequency(nb_frame, nb_FrqCorrs)
124 // This is a range of frequencies around f0 the harmonics of which are
125 // searched for spectral peaks.
126 Eigen::MatrixXf corrFreqsTF =
127 (fundamentalFreqs.replicate(1, nFreqCorrs).array() + freqCorrs.transpose().replicate(tSize, 1).array())
128 .matrix();
129
130 Eigen::MatrixXf inharmFactorsHI(harmRep.nHarms, nInharmCoeffs);
131 Eigen::VectorXf columnVectorHarms = Eigen::VectorXf::LinSpaced(harmRep.nHarms, 1, harmRep.nHarms);
132 Eigen::MatrixXf repeatedColumnVectorHarms = columnVectorHarms.replicate(1, nInharmCoeffs);
133 Eigen::VectorXf squares = columnVectorHarms.array().square();
134 Eigen::MatrixXf secondTerm = ((squares * inharmCoeffs.transpose()).array() + 1).array().sqrt();
135 inharmFactorsHI = repeatedColumnVectorHarms.array() * secondTerm.array();
136
137 // Reshape corrFreqsTF and inharmFactorsHI
138 Eigen::MatrixXf reshapedCorrFreqsTF = corrFreqsTF.reshaped(tSize * nFreqCorrs, 1);
139 Eigen::MatrixXf reshapedInharmFactorsHI = inharmFactorsHI.reshaped(1, harmRep.nHarms * nInharmCoeffs);
140 Eigen::MatrixXf multiplied = reshapedCorrFreqsTF * reshapedInharmFactorsHI;
141
142 // Reshape multiplied into multiple little 2-D matrices (4-D MATLAB simulation)
143 std::vector<Eigen::MatrixXf> reshapedMultiplied =
144 calculateReshaped2D(multiplied, tSize, nFreqCorrs, nInharmCoeffs);
145 // Calculate fSupIdcsTFHI by applying transformation and clipping on submatrices of reshapedMultiplied
146 std::vector<Eigen::MatrixXi> fSupIdcsTFHI =
147 applyTransformation(reshapedMultiplied, stftObj->getFFTBinSize(), fSupport.size());
148
149 // Calculate distrIdcsTFHI by repeating the tSize vector, multiplying by fftSize and adding to fSupIdcsTFHI
150 std::vector<Eigen::MatrixXi> distrIdcsTFHI =
151 calculateDistrIdcs(tSize, nFreqCorrs, nInharmCoeffs, fSupport.size(), fSupIdcsTFHI);
152
153 // Extract subMatrices from distr based on indices
154 std::vector<Eigen::MatrixXf> submatrices_cell =
155 extractSubmatrices(/*TODO::Make this back to dist*/ distr, distrIdcsTFHI);
156 // Compute totalErgTFI by summing matrices across the 3rd dimension
157 std::vector<Eigen::MatrixXf> totalErgTFI =
158 computeTotalErgTFI(submatrices_cell, tSize, nFreqCorrs, nInharmCoeffs);
159
160 // Calculte scoreTI matrix
161 Eigen::MatrixXf scoreTI(tSize, nInharmCoeffs);
162 // Iterate over each 2D matrix in the vector
163 for (int i = 0; i < totalErgTFI.size(); ++i)
164 {
165 scoreTI.col(i) = totalErgTFI[i].rowwise().maxCoeff();
166 }
167
168 // If the maximum score is within 1 % of the first score, just choose the first
169 // score.That is, just choose the inharmonicity coefficient at index 1.
170 auto [maxScoreTI, inharmCoeffIdcsT] = maxRowValues(scoreTI);
171 // threshold it
172 float threshold = 0.01f;
173 for (int i = 0; i < maxScoreTI.size(); ++i)
174 {
175 if ((maxScoreTI(i) - scoreTI(i, 0)) / scoreTI(i, 0) <= threshold)
176 {
177 inharmCoeffIdcsT(i) = 0;
178 }
179 }
180 // Basic time and freq vectors
181 Eigen::VectorXi basicTimeIndices = Eigen::VectorXi::LinSpaced(tSize, 1, tSize);
182 Eigen::VectorXi basicFreqCorrIndices = Eigen::VectorXi::LinSpaced(nFreqCorrs, 1, nFreqCorrs);
183
184 Eigen::MatrixXi adjustedTimeIndices =
185 (basicTimeIndices.array() + tSize * nFreqCorrs * (inharmCoeffIdcsT.array())).replicate(1, nFreqCorrs);
186 Eigen::MatrixXi freqCorrRepeatIndices =
187 ((basicFreqCorrIndices.array() - 1) * tSize).replicate(1, tSize).transpose();
188 Eigen::MatrixXi combinedIndices = adjustedTimeIndices.array() + freqCorrRepeatIndices.array();
189 Eigen::MatrixXi colIdcs =
190 Eigen::Map<Eigen::MatrixXi>(combinedIndices.data(), combinedIndices.size(), 1).array() - 1;
191
192 // Processing to populate total energy matrix using the flattened indices
193 Eigen::MatrixXf totalErgTF(tSize, nFreqCorrs);
194 for (int i = 0; i < colIdcs.size(); i++)
195 {
196 // Extract correct subMatrix
197 int counter = std::floor(colIdcs(i) / (tSize * nFreqCorrs));
198 auto& subMatrix = totalErgTFI[counter];
199 // Extract element
200 int linearIndex = colIdcs(i) % (tSize * nFreqCorrs);
201 // Calculate row and column indices from linear index
202 int row = linearIndex % subMatrix.rows();
203 int col = linearIndex / subMatrix.rows();
204
205 // Store the fetched value into fSupIdcsHTF
206 totalErgTF(i) = subMatrix(row, col);
207 }
208
209 // Adjusted indices calculation for harmonic and frequency correction.
210 Eigen::VectorXi harmonicAdjustedIndices =
211 (tSize * nFreqCorrs * harmRep.nHarms * (inharmCoeffIdcsT.array())).array() + basicTimeIndices.array();
212 std::vector<Eigen::MatrixXi> freqCorrectionMatrices(harmRep.nHarms,
213 harmonicAdjustedIndices.replicate(1, nFreqCorrs));
214
215 std::vector<Eigen::MatrixXi> replicated_freq_indices(harmRep.nHarms);
216 Eigen::MatrixXi freqCorrectionRepeatedIndices = basicFreqCorrIndices.transpose().replicate(tSize, 1);
217
218 // Replicate freq_indices row-wise tSize times for each submatrix
219 for (int i = 0; i < harmRep.nHarms; i++)
220 {
221 Eigen::MatrixXi submatrix = Eigen::MatrixXi::Zero(tSize, nFreqCorrs);
222 for (int j = 0; j < tSize; j++)
223 {
224 submatrix.row(j) = basicFreqCorrIndices;
225 }
226 replicated_freq_indices[i] = submatrix;
227 }
228 // Generate indeces for harmonic repetitions
229 Eigen::VectorXi harm_indices = Eigen::VectorXi::LinSpaced(harmRep.nHarms, 1, harmRep.nHarms);
230 std::vector<Eigen::MatrixXi> replicated_harm_indices(harmRep.nHarms);
231 // Replicate each element of harm_indices accordingly
232 for (int i = 0; i < harmRep.nHarms; i++)
233 {
234 Eigen::MatrixXi submatrix = Eigen::MatrixXi::Zero(tSize, nFreqCorrs);
235 for (int j = 0; j < tSize; j++)
236 {
237 submatrix.row(j).fill((harm_indices(i) - 1) *
238 nFreqCorrs); // Fill each row with the current element of harm_indices
239 }
240 replicated_harm_indices[i] = submatrix;
241 }
242
243 // Combine frequency and harmonic indeces
244 std::vector<Eigen::MatrixXi> combinedHarmonicFreqIndices(harmRep.nHarms);
245 for (size_t i = 0; i < combinedHarmonicFreqIndices.size(); ++i)
246 {
247 // Add corresponding matrices element-wise
248 Eigen::MatrixXi sum =
249 (replicated_harm_indices[i].array() + replicated_freq_indices[i].array() - 1) * tSize +
250 freqCorrectionMatrices[i].array();
251 combinedHarmonicFreqIndices[i] = sum.array() - 1 /*for c++ indexing!*/;
252 }
253
254 // Reshape the matrices into a single column vector
255 Eigen::VectorXi colIdcsNew(harmRep.nHarms * tSize * nFreqCorrs);
256 int index = 0;
257 for (size_t i = 0; i < replicated_harm_indices.size(); ++i)
258 {
259 auto subMatrix = combinedHarmonicFreqIndices[i];
260 for (int i = 0; i < subMatrix.cols(); i++)
261 {
262 // Copy the current column into the vector
263 colIdcsNew.segment(index, subMatrix.rows()) = subMatrix.col(i);
264 index += subMatrix.rows(); // Increment index
265 }
266 }
267
268 // Calculate fSupIdcsHTF
269 std::vector<Eigen::MatrixXi> fSupIdcsHTF(harmRep.nHarms, Eigen::MatrixXi::Zero(tSize, nFreqCorrs));
270 int setCounter = 0;
271 for (int i = 0; i < colIdcsNew.size(); i++)
272 {
273 // Extract correct subMatrix
274 int counter = std::floor(colIdcsNew(i) / (tSize * nFreqCorrs));
275 auto& subMatrix = fSupIdcsTFHI[counter];
276 // Extract element
277 int linearIndex = colIdcsNew(i) % (tSize * nFreqCorrs);
278 // Calculate row and column indices from linear index
279 int row = linearIndex % subMatrix.rows();
280 int col = linearIndex / subMatrix.rows();
281
282 // Calculate correct subMatrix of fSupIdcsHTF to store in
283 if (i % (tSize * nFreqCorrs) == 0 && i > 0)
284 ++setCounter;
285 // Store the fetched value into fSupIdcsHTF
286 fSupIdcsHTF[setCounter](row, col) = subMatrix(row, col);
287 }
288 // Permute fSupIdcsHTF
289 std::vector<Eigen::MatrixXi> permfSupIdcsHTF(nFreqCorrs, Eigen::MatrixXi::Zero(harmRep.nHarms, tSize));
290 for (int c = 0; c < harmRep.nHarms; c++) // for each submatrix
291 {
292 for (int i = 0; i < tSize; i++) // for each row
293 {
294 for (int j = 0; j < nFreqCorrs; j++) // for each column
295 {
296 // Retrieve element of original
297 auto elem = fSupIdcsHTF[c](i, j);
298 // Permute it
299 permfSupIdcsHTF[j](c, i) = elem;
300 }
301 }
302 }
303
304 // Find the indices of the maximum element in each column
305 Eigen::VectorXi freqCorrIdcsT(totalErgTF.rows());
306 for (int i = 0; i < totalErgTF.rows(); ++i)
307 {
308 float maxVal = totalErgTF.row(i).maxCoeff(); // Find the maximum value in the current row
309 for (int j = 0; j < totalErgTF.cols(); ++j)
310 {
311 if (totalErgTF(i, j) == maxVal)
312 {
313 freqCorrIdcsT(i) = j; // Store the index of the maximum element in maxIndices
314 break; // Exit the loop once the maximum is found
315 }
316 }
317 }
318
319 // Calculate colIdcs
320 Eigen::MatrixXi harmonicIndicesRepeated = harm_indices.replicate(1, tSize);
321 Eigen::VectorXi freqCorrectionAdjustment = tSize * (freqCorrIdcsT.transpose().array()) - 1;
322 Eigen::VectorXi combinedIndexCalculation = (basicTimeIndices + freqCorrectionAdjustment) * harmRep.nHarms;
323 Eigen::MatrixXi combinedIndicesReplicated = combinedIndexCalculation.transpose().replicate(harmRep.nHarms, 1);
324 Eigen::MatrixXi finalIndicesMatrix = harmonicIndicesRepeated + combinedIndicesReplicated;
325 // Reshape the elements into a column vector
326 Eigen::VectorXi columnVector =
327 Eigen::Map<Eigen::VectorXi>(finalIndicesMatrix.data(), finalIndicesMatrix.size());
328 Eigen::VectorXi colIdcsNewNew = columnVector.array() - 1; // for C++
329
330 // Calculate partialFreqs
331 Eigen::MatrixXf partialFreqs(harmRep.nHarms, tSize);
332 partialFreqs.setZero();
333 Eigen::VectorXi idxVec(colIdcsNewNew.size());
334 for (int i = 0; i < colIdcsNewNew.size(); i++)
335 {
336 // Extract correct subMatrix
337 int counter = std::floor(colIdcsNewNew(i) / (tSize * harmRep.nHarms));
338 auto& subMatrix = permfSupIdcsHTF[counter];
339 // Extract element
340 int linearIndex = colIdcsNewNew(i) % (tSize * harmRep.nHarms);
341 // Calculate row and column indices from linear index
342 int row = linearIndex % subMatrix.rows();
343 int col = linearIndex / subMatrix.rows();
344
345 // Store the fetched value into fSupIdcsHTF
346 idxVec(i) = subMatrix(row, col);
347 }
348
349 // Ensure the indices are within the valid range for fSupport
350 jassert(idxVec.maxCoeff() < fSupport.size() && idxVec.minCoeff() >= 0);
351
352 // Assign elements from fSupport to partialFreqs
353 Eigen::VectorXf fSupportElems = idxVec.unaryExpr([&](int idx) { return fSupport(idx); });
354 partialFreqs = Eigen::Map<Eigen::MatrixXf>(fSupportElems.data(), harmRep.nHarms, tSize);
355
356 // Calculate partialAmps
357 Eigen::VectorXi scaled_sequence = Eigen::VectorXi::LinSpaced(tSize, 0, tSize - 1)
358 .transpose()
359 .replicate(harmRep.nHarms, 1)
360 .reshaped(harmRep.nHarms * tSize, 1)
361 .array() *
362 fSupport.size();
363 Eigen::VectorXi summed_values = idxVec + scaled_sequence;
364
365 // Access elements of distr using summed_values and store in partialAmps
366 Eigen::MatrixXf partialAmps =
367 summed_values.unaryExpr([&](int idx) { return distr(idx); }).reshaped(harmRep.nHarms, tSize);
368
369 value.conservativeResize(partialFreqs.rows() + partialAmps.rows(), partialFreqs.cols());
370 value << partialFreqs, partialAmps;
371
372 return value;
373 }
374
375 void HarmonicRepresentation::replaceNanWithMedian(Eigen::VectorXf& estPitches)
376 {
377 // Create a vector to store non-NaN values
378 std::vector<float> nonNanValues;
379
380 // Find non-NaN values using Eigen's array operations
381 nonNanValues.reserve(estPitches.size());
382 for (int i = 0; i < estPitches.size(); ++i)
383 {
384 if (!std::isnan(estPitches(i)))
385 {
386 nonNanValues.push_back(estPitches(i));
387 }
388 }
389
390 // Calculate median of non-NaN values
391 float medianValue = 0.0f;
392 if (!nonNanValues.empty())
393 {
394 std::sort(nonNanValues.begin(), nonNanValues.end());
395 size_t middle = nonNanValues.size() / 2;
396 if (nonNanValues.size() % 2 == 0)
397 {
398 medianValue = (nonNanValues[middle - 1] + nonNanValues[middle]) / 2.0f;
399 }
400 else
401 {
402 medianValue = nonNanValues[middle];
403 }
404 }
405
406 // Replace NaN values in estPitches with median
407 estPitches = estPitches.unaryExpr([medianValue](float v) { return std::isnan(v) ? medianValue : v; });
408 }
409
410 Eigen::VectorXf HarmonicRepresentation::fevalbp(const Eigen::MatrixXf& bp, const Eigen::VectorXf& x_v)
411 {
412 // Check for NaN values in bp and x_v
413 jassert(!bp.array().isNaN().any());
414 jassert(!x_v.array().isNaN().any());
415
416 Eigen::VectorXf y_v(x_v.size());
417
418 // Find indices where x_v is less than the first time in bp
419 Eigen::VectorXi pos1 = findIndices(x_v, -std::numeric_limits<float>::infinity(), bp(0, 0));
420 // Check if pos1 is not empty
421 if (pos1.size() > 0)
422 {
423 // Set the values of y_v at indices in pos1 to the corresponding value from bp
424 for (int i = 0; i < pos1.size(); ++i)
425 {
426 y_v(pos1(i)) = bp(0, 1);
427 }
428 }
429
430 // Find indices where x_v is greater than the last time in bp
431 Eigen::VectorXi pos2 = findIndices(x_v, bp(bp.rows() - 1, 0), std::numeric_limits<float>::infinity());
432 // Check if pos1 is not empty
433 if (pos2.size() > 0)
434 {
435 // Set the values of y_v at indices in pos1 to the corresponding value from bp
436 for (int i = 0; i < pos2.size(); ++i)
437 {
438 y_v(pos2(i)) = bp(bp.rows() - 1, 1);
439 }
440 }
441
442 // Find indices where x_v is within the range of times in bp
443 Eigen::ArrayXi pos = findIndices(x_v, bp(0, 0), bp(bp.rows() - 1, 0));
444
445 if (pos.size() > 1)
446 {
447 // If there are more than one index with the above property(which is probably
448 // almost always the case, no ? ) estimate the pitch at the times in x_v with the
449 // above property by interpolating on the(time, pitch) pairs in bp
450 y_v(pos, 0) = LinearInterpolator::interpolate2DEigen(bp.col(0), bp.col(1), x_v(pos));
451 }
452 else
453 {
454 // Vector of length equal to the number of times at which we would like to have values
455 y_v.setZero();
456
457 for (int n = 0; n < x_v.size(); n++)
458 {
459 float x = x_v(n);
460
461 // Calculate the differences between x and the elements of the first column of bp
462 Eigen::VectorXf diff = bp.col(0).array() - x;
463
464 // Find the index of the minimum absolute difference
465 int minimum_pos;
466 float minimum_value = diff.array().abs().minCoeff(&minimum_pos);
467
468 // calculate the number of rows in bp
469 // (this shouldn't need to be calculated every time)
470 int L = bp.rows();
471
472 // (Shouldn't L be only of 1 dimension?)
473 if ((bp(minimum_pos, 0) == x) || (L == 1) || ((bp(minimum_pos, 0) < x) && (minimum_pos == L)) ||
474 ((bp(minimum_pos, 0) > x) && (minimum_pos == 0)))
475 {
476 // If the closest time in bp is exactly equal to x OR
477 // if the number of rows in bp is equal to 1 OR
478 // if the closest time is less than x and its index is the last one
479 // in bp OR
480 // if the closest time is greater than x and its index is the first one in bp
481 // SET y_v AT THIS INDEX n TO THE PITCH AT THE TIME CLOSEST TO x IN bp
482 y_v(n) = bp(minimum_pos, 1);
483 }
484 else if (bp(minimum_pos, 0) < x)
485 {
486 // Otherwise, if the time closest to x is less than x(but is not the last
487 // row - value in bp) linearly interpolate between the frequency at the index
488 // closest to x and the one afterward to obtain the frequency to put in y_v(n)
489 y_v(n) = (bp(minimum_pos + 1, 1) - bp(minimum_pos, 1)) /
490 (bp(minimum_pos + 1, 0) - bp(minimum_pos, 0)) * (x - bp(minimum_pos, 0)) +
491 bp(minimum_pos, 1);
492 }
493 else if (bp(minimum_pos, 0) > x)
494 {
495 // If the time closest to x is greater than x (but is not the first row-value in bp)
496 // linearly interpolate between the index one before the index closest to x and the one
497 // closest to x to obtain the frequency to put in y_v(n)
498 y_v(n) = (bp(minimum_pos, 1) - bp(minimum_pos - 1, 1)) /
499 (bp(minimum_pos, 0) - bp(minimum_pos - 1, 0)) * (x - bp(minimum_pos - 1, 0)) +
500 bp(minimum_pos - 1, 1);
501 }
502 else
503 {
504 // If none of the above cases match, throw an error
505 throw std::runtime_error("Not a valid case");
506 }
507 }
508 }
509
510 return y_v;
511 }
512
513 Eigen::VectorXi HarmonicRepresentation::findIndices(const Eigen::VectorXf& x_v, float threshMin, float threshMax)
514 {
515 // Adjust threshMin and threshMax for 'less than' and 'greater than' cases
516 // If only one threshold is provided, the function works for 'less than' or 'greater than' depending on the
517 // threshold
518 Eigen::Array<bool, Eigen::Dynamic, 1> mask = (x_v.array() >= threshMin) && (x_v.array() <= threshMax);
519
520 // Count true elements in the mask to allocate space for indices
521 Eigen::VectorXi indices;
522 Eigen::Index numIndices = mask.cast<int>().sum();
523 indices.resize(numIndices);
524
525 int index = 0;
526 for (int i = 0; i < mask.size(); ++i)
527 {
528 if (mask(i))
529 {
530 indices(index++) = i;
531 }
532 }
533
534 return indices;
535 }
536
537 std::vector<Eigen::MatrixXf> HarmonicRepresentation::calculateReshaped2D(const Eigen::MatrixXf& multiplied,
538 int tSize, int nFreqCorrs,
539 int nInharmCoeffs)
540 {
541 std::vector<Eigen::MatrixXf> reshapedMultiplied;
542 reshapedMultiplied.reserve(harmRep.nHarms * nInharmCoeffs);
543
544 int counter = 0;
545 int expectedElements = tSize * nFreqCorrs * harmRep.nHarms * nInharmCoeffs;
546
547 jassert(multiplied.size() == expectedElements); // Validity check for sufficient data in 'multiplied'
548
549 for (int i = 0; i < harmRep.nHarms; ++i)
550 {
551 for (int j = 0; j < nInharmCoeffs; ++j)
552 {
553 // Extract elements from multiplied
554 Eigen::MatrixXf c_mat =
555 Eigen::Map<const Eigen::MatrixXf>(multiplied.data() + counter, tSize, nFreqCorrs);
556 // Store matrix in the vector
557 reshapedMultiplied.push_back(c_mat);
558 // Update counter
559 counter += tSize * nFreqCorrs;
560 }
561 }
562
563 return reshapedMultiplied;
564 }
565
567 const std::vector<Eigen::MatrixXf>& reshapedMultiplied2_d, float binSize, int fSize)
568 {
569 std::vector<Eigen::MatrixXi> fSupIdcsTFHI;
570
571 for (const auto& mat : reshapedMultiplied2_d)
572 {
573 // Apply the transformation to the current matrix
574 Eigen::MatrixXf scaledArray = (1.0f / binSize) * mat.array();
575 // Round each element of the scaled array
576 Eigen::MatrixXf roundedArray = scaledArray.array().round();
577 // Add 1 to each element of the rounded array
578 Eigen::MatrixXi transformedMat = (roundedArray.array() /* + 1*/).cast<int>();
579 // Clip values exceeding fSize
580 transformedMat = transformedMat.array().min(fSize);
581 // Store the transformed matrix
582 fSupIdcsTFHI.push_back(transformedMat);
583 }
584
585 return fSupIdcsTFHI;
586 }
587
588 std::vector<Eigen::MatrixXi> HarmonicRepresentation::calculateDistrIdcs(
589 int tSize, int nFreqCorrs, int nInharmCoeffs, int fSize, std::vector<Eigen::MatrixXi>& fSupIdcsTFHI2_d)
590 {
591 std::vector<Eigen::MatrixXi> s_cell;
592 s_cell.reserve(harmRep.nHarms * nInharmCoeffs); // Pre-allocate memory
593
594 // Part a) Distribute elements from (0:tSize-1) into smaller matrices
595 for (int i = 0; i < harmRep.nHarms; ++i)
596 {
597 for (int j = 0; j < nInharmCoeffs; ++j)
598 {
599 // Create a matrix with dimensions (tSize, nFreqCorrs)
600 Eigen::MatrixXi s_mat = Eigen::MatrixXi::Zero(tSize, nFreqCorrs);
601 // Fill it with elements from (0:tSize-1)
602 for (int k = 0; k < tSize; ++k)
603 {
604 for (int l = 0; l < nFreqCorrs; ++l)
605 {
606 s_mat(k, l) = k * fSize;
607 }
608 }
609 // Store the matrix in the cell array
610 s_cell.push_back(s_mat);
611 }
612 }
613
614 // Part b) Add all matrices
615 std::vector<Eigen::MatrixXi> distrIdcsTFHI;
616 distrIdcsTFHI.reserve(fSupIdcsTFHI2_d.size()); // Pre-allocate memory
617
618 for (size_t i = 0; i < fSupIdcsTFHI2_d.size(); ++i)
619 {
620 // Add matrices from cells element-wise
621 Eigen::MatrixXi combinedMatrix = fSupIdcsTFHI2_d[i] + s_cell[i];
622 distrIdcsTFHI.push_back(combinedMatrix);
623 }
624
625 return distrIdcsTFHI;
626 }
627
628 std::vector<Eigen::MatrixXf> HarmonicRepresentation::extractSubmatrices(
629 const Eigen::MatrixXf& distr, const std::vector<Eigen::MatrixXi>& distrIdcsTFHI2_d)
630 {
631 std::vector<Eigen::MatrixXf> submatrices_cell;
632
633 for (const auto& indices : distrIdcsTFHI2_d)
634 {
635 // Initialize a submatrix with the same dimensions as indices
636 Eigen::MatrixXf submatrix(indices.rows(), indices.cols());
637
638 // Iterate over each element in indices and access the corresponding element in distr
639 for (int i = 0; i < indices.rows(); ++i)
640 {
641 for (int j = 0; j < indices.cols(); ++j)
642 {
643 // Calculate the linear index from the row and column indices of indices
644 int linearIndex = indices(i, j);
645
646 // Access the corresponding element in distr and assign it to the submatrix
647 submatrix(i, j) = distr(linearIndex);
648 }
649 }
650
651 // Store the submatrix in the result vector
652 submatrices_cell.push_back(submatrix);
653 }
654
655 return submatrices_cell;
656 }
657
658 std::vector<Eigen::MatrixXf> HarmonicRepresentation::computeTotalErgTFI(
659 const std::vector<Eigen::MatrixXf>& submatrices_cell, int tSize, int nFreqCorrs, int nInharmCoeffs)
660 {
661 std::vector<Eigen::MatrixXf> totalErgTFI(nInharmCoeffs);
662
663 // Iterate over each pair of matrices in the cell
664 for (int i = 0; i < nInharmCoeffs; ++i)
665 {
666 // Initialize a matrix to store the sum of the current pair
667 Eigen::MatrixXf currentSum = Eigen::MatrixXf::Zero(tSize, nFreqCorrs);
668 // Iterate over each matrix in the current pair
669 for (int j = 0; j < harmRep.nHarms; ++j)
670 {
671 // Add the current matrix to the currentSum
672 currentSum += submatrices_cell[i * harmRep.nHarms + j];
673 }
674 // Assign the currentSum matrix to the corresponding index in totalErgTFI2_d
675 totalErgTFI[i] = currentSum;
676 }
677
678 return totalErgTFI;
679 }
680
681 std::pair<Eigen::VectorXf, Eigen::VectorXi> HarmonicRepresentation::maxRowValues(const Eigen::MatrixXf& matrix)
682 {
683 // Vector to store max values
684 Eigen::VectorXf maxValues(matrix.rows());
685 // Vector to store corresponding column indices
686 Eigen::VectorXi maxIndices(matrix.rows());
687
688 for (int row = 0; row < matrix.rows(); ++row)
689 {
690 float maxVal;
691 int maxIdx;
692 maxVal = matrix.row(row).maxCoeff(&maxIdx); // Get max value and index in current row
693 maxValues(row) = maxVal;
694 maxIndices(row) = maxIdx;
695 }
696
697 return {maxValues, maxIndices};
698 }
699
700} // namespace krotos
Eigen::MatrixXf processSignal(std::vector< float > &inputSignal)
Processes the input signal and returns the matrix of partial frequencies and partial amplitudes.
Definition HarmonicRepresentation.cpp:21
std::pair< Eigen::VectorXf, Eigen::VectorXi > maxRowValues(const Eigen::MatrixXf &matrix)
Definition HarmonicRepresentation.cpp:681
struct krotos::HarmonicRepresentation::HarmRepParams harmRep
HarmonicRepresentation(float sampleRate)
Definition HarmonicRepresentation.cpp:14
std::vector< Eigen::MatrixXi > calculateDistrIdcs(int tSize, int nFreqCorrs, int nInharmCoeffs, int fSize, std::vector< Eigen::MatrixXi > &fSupIdcsTFHI2_d)
Definition HarmonicRepresentation.cpp:588
Eigen::VectorXf fevalbp(const Eigen::MatrixXf &bp, const Eigen::VectorXf &x_v)
Estimates pitches by linear interpolation.
Definition HarmonicRepresentation.cpp:410
const float m_sampleRate
Definition HarmonicRepresentation.h:131
std::vector< Eigen::MatrixXi > applyTransformation(const std::vector< Eigen::MatrixXf > &reshapedMultiplied2_d, float binSize, int fSize)
Definition HarmonicRepresentation.cpp:566
struct krotos::HarmonicRepresentation::SwipeParams swipe
std::vector< Eigen::MatrixXf > computeTotalErgTFI(const std::vector< Eigen::MatrixXf > &submatrices_cell, int tSize, int nFreqCorrs, int nInharmCoeffs)
Definition HarmonicRepresentation.cpp:658
std::vector< Eigen::MatrixXf > extractSubmatrices(const Eigen::MatrixXf &distr, const std::vector< Eigen::MatrixXi > &distrIdcsTFHI2_d)
Definition HarmonicRepresentation.cpp:628
struct krotos::HarmonicRepresentation::ConfigParams config
float m_maxSwipepWinSize
Definition HarmonicRepresentation.h:132
std::vector< Eigen::MatrixXf > calculateReshaped2D(const Eigen::MatrixXf &multiplied, int tSize, int nFreqCorrs, int nInharmCoeffs)
Reshapes a flat Eigen matrix into a vector of 2D matrices, intended for handling multidimensional mat...
Definition HarmonicRepresentation.cpp:537
Eigen::VectorXi findIndices(const Eigen::VectorXf &x_v, float threshMin=-std::numeric_limits< float >::infinity(), float threshMax=std::numeric_limits< float >::infinity())
Definition HarmonicRepresentation.cpp:513
void replaceNanWithMedian(Eigen::VectorXf &inputVector)
Replaces NaN values in the input vector with the median value.
Definition HarmonicRepresentation.cpp:375
static Eigen::MatrixXf interpolate2DEigen(const Eigen::VectorXf &xValues, const Eigen::MatrixXf &yValues, const Eigen::VectorXf &vals)
Definition KrotosIntrepolators.cpp:413
@ freqMagOnly
Definition ShortTimeFourierTransform.h:20
Definition AirAbsorptionFilter.cpp:2
const int fftSize
Definition HarmonicRepresentation.h:56
const int winSize
Definition HarmonicRepresentation.h:55
const int hopSize
Definition HarmonicRepresentation.h:54
const int nHarms
Definition HarmonicRepresentation.h:45
const float threshold
Definition HarmonicRepresentation.h:44
const float ERBStep
Definition HarmonicRepresentation.h:64
const float normOverlap
Definition HarmonicRepresentation.h:65
const float strenThresh
Definition HarmonicRepresentation.h:66
const float log2PitchStep
Definition HarmonicRepresentation.h:67