]> git.sesse.net Git - kdenlive/blob - src/audioscopes/ffttools.cpp
Audio Spectrum: Moved FFT calculation to FFTTools for re-use
[kdenlive] / src / audioscopes / ffttools.cpp
1 /***************************************************************************
2  *   Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com)      *
3  *   This file is part of kdenlive. See www.kdenlive.org.                  *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  ***************************************************************************/
10
11 #include <math.h>
12 #include <iostream>
13
14 #include <QString>
15
16 #include "ffttools.h"
17
18 #define DEBUG_FFTTOOLS
19 #ifdef DEBUG_FFTTOOLS
20 #include <QDebug>
21 #include <QTime>
22 #endif
23
24 FFTTools::FFTTools() :
25         m_fftCfgs(),
26         m_windowFunctions()
27 {
28 }
29 FFTTools::~FFTTools()
30 {
31     QHash<QString, kiss_fftr_cfg>::iterator i;
32     for (i = m_fftCfgs.begin(); i != m_fftCfgs.end(); i++) {
33         free(*i);
34     }
35 }
36
37 const QString FFTTools::windowSignature(const WindowType windowType, const int size, const float param)
38 {
39     return QString("s%1_t%2_p%3").arg(size).arg(windowType).arg(param, 0, 'f', 3);
40 }
41 const QString FFTTools::cfgSignature(const int size)
42 {
43     return QString("s%1").arg(size);
44 }
45
46 // http://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
47 const QVector<float> FFTTools::window(const WindowType windowType, const int size, const float param)
48 {
49     // Deliberately avoid converting size to a float
50     // to keep mid an integer.
51     float mid = (size-1)/2;
52     float max = size-1;
53     QVector<float> window;
54
55     switch (windowType) {
56     case Window_Rect:
57         return QVector<float>(size+1, 1);
58         break;
59     case Window_Triangle:
60         window = QVector<float>(size+1);
61
62         for (int x = 0; x < mid; x++) {
63             window[x] = x/mid + (mid-x)/mid*param;
64         }
65         for (int x = mid; x < size; x++) {
66             window[x] = (x-mid)/(max-mid) * param + (max-x)/(max-mid);
67         }
68         window[size] = .5 + param/2;
69
70 #ifdef DEBUG_FFTTOOLS
71         qDebug() << "Triangle window (factor " << window[size] << "):";
72         for (int i = 0; i < size; i++) {
73             qDebug() << window[i];
74         }
75         qDebug() << "Triangle window end.";
76 #endif
77
78         return window;
79         break;
80     case Window_Hamming:
81         // Use a quick version of the Hamming window here: Instead of
82         // interpolating values between (-max/2) and (max/2)
83         // we use integer values instead, ranging from -mid to (max-mid).
84         window = QVector<float>(size+1);
85
86         for (int x = 0; x < size; x++) {
87             window[x] = .54 + .46 * cos( 2*M_PI*(x-mid) / size );
88         }
89
90         // Integrating the cosine over the window function results in
91         // an area of 0; So only the constant factor 0.54 counts.
92         window[size] = .54;
93
94 #ifdef DEBUG_FFTTOOLS
95         qDebug() << "Hanning window (factor " << window[size] << "):";
96         for (int i = 0; i < size; i++) {
97             qDebug() << window[i];
98         }
99         qDebug() << "Hanning window end.";
100 #endif
101
102         return window;
103         break;
104     }
105     Q_ASSERT(false);
106     return QVector<float>();
107 }
108
109 void FFTTools::fftNormalized(const QVector<int16_t> audioFrame, const uint channel, const uint numChannels, float *freqSpectrum,
110                              const WindowType windowType, const uint windowSize, const float param)
111 {
112 #ifdef DEBUG_FFTTOOLS
113     QTime start = QTime::currentTime();
114 #endif
115
116     const uint numSamples = audioFrame.size()/numChannels;
117
118     Q_ASSERT((windowSize & 1) == 0);
119
120     const QString cfgSig = cfgSignature(windowSize);
121     const QString winSig = windowSignature(windowType, windowSize, param);
122
123
124     // Get the kiss_fft configuration from the config cache
125     // or build a new configuration if the requested one is not available.
126     kiss_fftr_cfg myCfg;
127     if (m_fftCfgs.contains(cfgSig)) {
128 #ifdef DEBUG_FFTTOOLS
129         qDebug() << "Re-using FFT configuration with size " << windowSize;
130 #endif
131         myCfg = m_fftCfgs.value(cfgSig);
132     } else {
133 #ifdef DEBUG_FFTTOOLS
134         qDebug() << "Creating FFT configuration with size " << windowSize;
135 #endif
136         myCfg = kiss_fftr_alloc(windowSize, 0,0,0);
137         m_fftCfgs.insert(cfgSig, myCfg);
138     }
139
140     // Get the window function from the cache
141     // (except for a rectangular window; nothing to to there.
142     QVector<float> window;
143     float windowScaleFactor = 1;
144     if (windowType != FFTTools::Window_Rect) {
145
146         if (m_windowFunctions.contains(winSig)) {
147 #ifdef DEBUG_FFTTOOLS
148             qDebug() << "Re-using window function with signature " << winSig;
149 #endif
150             window = m_windowFunctions.value(winSig);
151         } else {
152 #ifdef DEBUG_FFTTOOLS
153             qDebug() << "Building new window function with signature " << winSig;
154 #endif
155             window = FFTTools::window(windowType, windowSize, 0);
156             m_windowFunctions.insert(winSig, window);
157         }
158         windowScaleFactor = 1.0/window[windowSize];
159     }
160
161
162     // Prepare frequency space vector. The resulting FFT vector is only half as long.
163     kiss_fft_cpx freqData[windowSize/2];
164     float data[windowSize];
165
166     // Copy the first channel's audio into a vector for the FFT display;
167     // Fill the data vector indices that cannot be covered with sample data with 0
168     if (numSamples < windowSize) {
169         std::fill(&data[numSamples], &data[windowSize-1], 0);
170     }
171     // Normalize signals to [0,1] to get correct dB values later on
172     for (int i = 0; i < numSamples && i < windowSize; i++) {
173         // Performance note: Benchmarking has shown that using the if/else inside the loop
174         // does not do noticeable worse than keeping it outside (perhaps the branch predictor
175         // is good enough), so it remains in there for better readability.
176         if (windowType != FFTTools::Window_Rect) {
177             data[i] = (float) audioFrame.data()[i*numChannels] / 32767.0f * window[i];
178         } else {
179             data[i] = (float) audioFrame.data()[i*numChannels] / 32767.0f;
180         }
181     }
182
183     // Calculate the Fast Fourier Transform for the input data
184     kiss_fftr(myCfg, data, freqData);
185
186
187     // Logarithmic scale: 20 * log ( 2 * magnitude / N ) with magnitude = sqrt(r² + i²)
188     // with N = FFT size (after FFT, 1/2 window size)
189     for (int i = 0; i < windowSize/2; i++) {
190         // Logarithmic scale: 20 * log ( 2 * magnitude / N ) with magnitude = sqrt(r² + i²)
191         // with N = FFT size (after FFT, 1/2 window size)
192         freqSpectrum[i] = 20*log(pow(pow(fabs(freqData[i].r * windowScaleFactor),2) + pow(fabs(freqData[i].i * windowScaleFactor),2), .5)/((float)windowSize/2.0f))/log(10);;
193     }
194
195 #ifdef DEBUG_FFTTOOLS
196     qDebug() << "Calculated FFT in " << start.elapsed() << " ms.";
197 #endif
198 }
199
200 #ifdef DEBUG_FFTTOOLS
201 #undef DEBUG_FFTTOOLS
202 #endif