File indexing completed on 2024-04-28 04:51:59

0001 /*
0002     SPDX-FileCopyrightText: 2010 Simon Andreas Eugster <simon.eu@gmail.com>
0003     This file is part of kdenlive. See www.kdenlive.org.
0004 
0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "fftTools.h"
0009 
0010 #include <cmath>
0011 #include <iostream>
0012 
0013 #include <QString>
0014 
0015 // Uncomment for debugging, like writing a GNU Octave .m file to /tmp
0016 //#define DEBUG_FFTTOOLS
0017 
0018 #ifdef DEBUG_FFTTOOLS
0019 #include "kdenlive_debug.h"
0020 #include <QTime>
0021 #include <fstream>
0022 #endif
0023 
0024 FFTTools::FFTTools()
0025     : m_fftCfgs()
0026     , m_windowFunctions()
0027 {
0028 }
0029 FFTTools::~FFTTools()
0030 {
0031     QHash<QString, kiss_fftr_cfg>::iterator i;
0032     for (i = m_fftCfgs.begin(); i != m_fftCfgs.end(); ++i) {
0033         free(*i);
0034     }
0035 }
0036 
0037 const QString FFTTools::windowSignature(const WindowType windowType, const int size, const float param)
0038 {
0039     return QStringLiteral("s%1_t%2_p%3").arg(size).arg(windowType).arg(param, 0, 'f', 3);
0040 }
0041 const QString FFTTools::cfgSignature(const int size)
0042 {
0043     return QStringLiteral("s%1").arg(size);
0044 }
0045 
0046 // https://cplusplus.syntaxerrors.info/index.php?title=Cannot_declare_member_function_%E2%80%98static_int_Foo::bar%28%29%E2%80%99_to_have_static_linkage
0047 const QVector<float> FFTTools::window(const WindowType windowType, const int size, const float param)
0048 {
0049     Q_ASSERT(size > 0);
0050 
0051     // Deliberately avoid converting size to a float
0052     // to keep mid an integer.
0053     int mid = (int(size - 1) / 2);
0054     int max = (size - 1);
0055     QVector<float> window;
0056 
0057     switch (windowType) {
0058     case Window_Rect:
0059         return QVector<float>(size + 1, 1);
0060     case Window_Triangle:
0061         window = QVector<float>(size + 1);
0062 
0063         for (int x = 0; x < mid; ++x) {
0064             window[x] = float(x) / mid + float(mid - x) / mid * param;
0065         }
0066         for (int x = mid; x < size; ++x) {
0067             window[x] = float(x - mid) / (max - mid) * param + float(max - x) / (max - mid);
0068         }
0069         window[size] = .5f + param / 2;
0070 
0071 #ifdef DEBUG_FFTTOOLS
0072         qCDebug(KDENLIVE_LOG) << "Triangle window (factor " << window[size] << "):";
0073         for (int i = 0; i < size; ++i) {
0074             qCDebug(KDENLIVE_LOG) << window[i];
0075         }
0076         qCDebug(KDENLIVE_LOG) << "Triangle window end.";
0077 #endif
0078 
0079         return window;
0080     case Window_Hamming:
0081         // Use a quick version of the Hamming window here: Instead of
0082         // interpolating values between (-max/2) and (max/2)
0083         // we use integer values instead, ranging from -mid to (max-mid).
0084         window = QVector<float>(size + 1);
0085 
0086         for (int x = 0; x < size; ++x) {
0087             window[x] = .54f + .46f * cosf(2.f * float(M_PI) * float(x - mid) / size);
0088         }
0089 
0090         // Integrating the cosine over the window function results in
0091         // an area of 0; So only the constant factor 0.54 counts.
0092         window[size] = .54f;
0093 
0094 #ifdef DEBUG_FFTTOOLS
0095         qCDebug(KDENLIVE_LOG) << "Hanning window (factor " << window[size] << "):";
0096         for (int i = 0; i < size; ++i) {
0097             qCDebug(KDENLIVE_LOG) << window[i];
0098         }
0099         qCDebug(KDENLIVE_LOG) << "Hanning window end.";
0100 #endif
0101 
0102         return window;
0103     }
0104     Q_ASSERT(false);
0105     return QVector<float>();
0106 }
0107 
0108 void FFTTools::fftNormalized(const audioShortVector &audioFrame, const uint channel, const uint numChannels, float *freqSpectrum, const WindowType windowType,
0109                              const uint windowSize, const float param)
0110 {
0111 #ifdef DEBUG_FFTTOOLS
0112     QTime start = QTime::currentTime();
0113 #endif
0114 
0115     const uint numSamples = uint(audioFrame.size()) / numChannels;
0116 
0117     if (((windowSize & 1) != 0u) || windowSize < 2) {
0118         return;
0119     }
0120 
0121     const QString cfgSig = cfgSignature(int(windowSize));
0122     const QString winSig = windowSignature(windowType, int(windowSize), param);
0123 
0124     // Get the kiss_fft configuration from the config cache
0125     // or build a new configuration if the requested one is not available.
0126     kiss_fftr_cfg myCfg;
0127     if (m_fftCfgs.contains(cfgSig)) {
0128 #ifdef DEBUG_FFTTOOLS
0129         qCDebug(KDENLIVE_LOG) << "Re-using FFT configuration with size " << windowSize;
0130 #endif
0131         myCfg = m_fftCfgs.value(cfgSig);
0132     } else {
0133 #ifdef DEBUG_FFTTOOLS
0134         qCDebug(KDENLIVE_LOG) << "Creating FFT configuration with size " << windowSize;
0135 #endif
0136         myCfg = kiss_fftr_alloc(int(windowSize), 0, nullptr, nullptr);
0137         m_fftCfgs.insert(cfgSig, myCfg);
0138     }
0139 
0140     // Get the window function from the cache
0141     // (except for a rectangular window; nothing to do there).
0142     QVector<float> window;
0143     float windowScaleFactor = 1;
0144     if (windowType != FFTTools::Window_Rect) {
0145 
0146         if (m_windowFunctions.contains(winSig)) {
0147 #ifdef DEBUG_FFTTOOLS
0148             qCDebug(KDENLIVE_LOG) << "Re-using window function with signature " << winSig;
0149 #endif
0150             window = m_windowFunctions.value(winSig);
0151         } else {
0152 #ifdef DEBUG_FFTTOOLS
0153             qCDebug(KDENLIVE_LOG) << "Building new window function with signature " << winSig;
0154 #endif
0155             window = FFTTools::window(windowType, int(windowSize), 0);
0156             m_windowFunctions.insert(winSig, window);
0157         }
0158         windowScaleFactor = 1.0f / window[int(windowSize)];
0159     }
0160 
0161     // Prepare frequency space vector. The resulting FFT vector is only half as long.
0162     auto *freqData = new kiss_fft_cpx[size_t(windowSize) / 2];
0163     auto *data = new float[size_t(windowSize)];
0164 
0165     // Copy the first channel's audio into a vector for the FFT display;
0166     // Fill the data vector indices that cannot be covered with sample data with 0
0167     if (numSamples < windowSize) {
0168         std::fill(&data[numSamples], &data[windowSize - 1], 0);
0169     }
0170     // Normalize signals to [0,1] to get correct dB values later on
0171     for (uint i = 0; i < numSamples && i < windowSize; ++i) {
0172         // Performance note: Benchmarking has shown that using the if/else inside the loop
0173         // does not do noticeable worse than keeping it outside (perhaps the branch predictor
0174         // is good enough), so it remains in there for better readability.
0175         if (windowType != FFTTools::Window_Rect) {
0176             data[i] = float(audioFrame.data()[i * numChannels + channel]) / 32767.0f * window[int(i)];
0177         } else {
0178             data[i] = float(audioFrame.data()[i * numChannels + channel]) / 32767.0f;
0179         }
0180     }
0181 
0182     // Calculate the Fast Fourier Transform for the input data
0183     kiss_fftr(myCfg, data, freqData);
0184 
0185     // Logarithmic scale: 20 * log ( 2 * magnitude / N ) with magnitude = sqrt(r² + i²)
0186     // with N = FFT size (after FFT, 1/2 window size)
0187     for (uint i = 0; i < windowSize / 2; ++i) {
0188         // Logarithmic scale: 20 * log ( 2 * magnitude / N ) with magnitude = sqrt(r² + i²)
0189         // with N = FFT size (after FFT, 1/2 window size)
0190         freqSpectrum[i] =
0191             20 *
0192             logf(powf(powf(fabs(freqData[i].r * windowScaleFactor), 2) + powf(fabs(freqData[i].i * windowScaleFactor), 2), .5) / (float(windowSize) / 2.0f)) /
0193             logf(10);
0194         ;
0195     }
0196 
0197 #ifdef DEBUG_FFTTOOLS
0198     std::ofstream mFile;
0199     mFile.open("/tmp/freq.m");
0200     if (!mFile) {
0201         qCDebug(KDENLIVE_LOG) << "Opening file failed.";
0202     } else {
0203         mFile << "val = [ ";
0204 
0205         for (int sample = 0; sample < 256; ++sample) {
0206             mFile << data[sample] << ' ';
0207         }
0208         mFile << " ];\n";
0209 
0210         mFile << "freq = [ ";
0211         for (int sample = 0; sample < 256; ++sample) {
0212             mFile << freqData[sample].r << '+' << freqData[sample].i << "*i ";
0213         }
0214         mFile << " ];\n";
0215 
0216         mFile.close();
0217         qCDebug(KDENLIVE_LOG) << "File written.";
0218     }
0219 #endif
0220 
0221 #ifdef DEBUG_FFTTOOLS
0222     qCDebug(KDENLIVE_LOG) << "Calculated FFT in " << start.elapsed() << " ms.";
0223 #endif
0224     delete[] freqData;
0225     delete[] data;
0226 }
0227 
0228 const QVector<float> FFTTools::interpolatePeakPreserving(const QVector<float> &in, const uint targetSize, uint left, uint right, float fill)
0229 {
0230 #ifdef DEBUG_FFTTOOLS
0231     QTime start = QTime::currentTime();
0232 #endif
0233 
0234     if (right == 0) {
0235         Q_ASSERT(in.size() > 0);
0236         right = uint(in.size()) - 1;
0237     }
0238     Q_ASSERT(targetSize > 0);
0239     Q_ASSERT(left < right);
0240 
0241     QVector<float> out(static_cast<int>(targetSize));
0242 
0243     float x;
0244     int xi;
0245     int i;
0246     if ((float(right - left)) / targetSize < 2.f) {
0247         float x_prev = 0;
0248         for (i = 0; i < int(targetSize); ++i) {
0249 
0250             // i:  Target index
0251             // x:  Interpolated source index (float!)
0252             // xi: floor(x)
0253 
0254             // Transform [0,targetSize-1] to [left,right]
0255             x = float(i) / (targetSize - 1) * (right - left) + left;
0256             xi = int(floor(x));
0257 
0258             if (x > float(in.size() - 1)) {
0259                 // This may happen if right > in.size()-1; Fill the rest of the vector
0260                 // with the default value now.
0261                 break;
0262             }
0263 
0264             // Use linear interpolation in order to get smoother display
0265             if (xi == 0 || xi == in.size() - 1) {
0266                 // ... except if we are at the left or right border of the input signal.
0267                 // Special case here since we consider previous and future values as well for
0268                 // the actual interpolation (not possible here).
0269                 out[i] = in[xi];
0270             } else {
0271                 if (in[xi] > in[xi + 1] && x_prev < float(xi)) {
0272                     // This is a hack to preserve peaks.
0273                     // Consider f = {0, 100, 0}
0274                     //          x = {0.5,  1.5}
0275                     // Then x is 50 both times, and the 100 peak is lost.
0276                     // Get it back here for the first x after the peak (which is at xi).
0277                     // (x is the first after the peak if the previous x was smaller than floor(x).)
0278                     out[i] = in[xi];
0279                 } else {
0280                     out[i] = (xi + 1.f - x) * in[xi] + (x - xi) * in[xi + 1];
0281                 }
0282             }
0283             x_prev = x;
0284         }
0285     } else {
0286         // If there are more than 2 samples per pixel in average, then use the maximum of them
0287         // since by only looking at the left sample we might miss some maxima.
0288         int src = int(left);
0289         for (i = 0; i < int(targetSize); ++i) {
0290 
0291             // x:  right bound
0292             // xi: floor(x)
0293             x = float(i + 1) / (targetSize - 1) * (right - left) + left;
0294             xi = int(floor(x));
0295             out[i] = fill;
0296 
0297             for (; src < xi && src < in.size(); ++src) {
0298                 if (out[i] < in[src]) {
0299                     out[i] = in[src];
0300                 }
0301             }
0302 
0303             //             xi_prev = xi;
0304         }
0305     }
0306     // Fill the rest of the vector if the right border exceeds the input vector.
0307     for (; i < int(targetSize); ++i) {
0308         out[i] = fill;
0309     }
0310 
0311 #ifdef DEBUG_FFTTOOLS
0312     qCDebug(KDENLIVE_LOG) << "Interpolated " << targetSize << " nodes from " << in.size() << " input points in " << start.elapsed() << " ms";
0313 #endif
0314 
0315     return out;
0316 }
0317 
0318 #ifdef DEBUG_FFTTOOLS
0319 #undef DEBUG_FFTTOOLS
0320 #endif