├── QSpectrogram.pro ├── README.md ├── fftcuda.cu ├── fftcuda.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── pulsethread.cpp ├── pulsethread.h ├── qspectrogram.cpp ├── qspectrogram.h ├── spectrogram.cpp └── spectrogram.h /QSpectrogram.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2016-12-31T02:15:11 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | LIBS += -lpulse -lpulse-simple -O3 9 | 10 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 11 | 12 | TARGET = QSpectrogram 13 | TEMPLATE = app 14 | QMAKE_CXXFLAGS += -O3 15 | 16 | #CUDA_SOURCES += fftcuda.cu 17 | #LIBS += -lcudart -lcuda -lcufft 18 | 19 | #cuda.input = CUDA_SOURCES 20 | #cuda.output = fftcuda.o 21 | #cuda.commands = nvcc -arch=sm_20 -c ../QSpectogram/fftcuda.cu -o fftcuda.o -lcufft 22 | QMAKE_EXTRA_COMPILERS += cuda 23 | 24 | SOURCES += main.cpp\ 25 | mainwindow.cpp \ 26 | spectrogram.cpp \ 27 | qspectrogram.cpp \ 28 | pulsethread.cpp 29 | 30 | HEADERS += mainwindow.h \ 31 | spectrogram.h \ 32 | qspectrogram.h \ 33 | pulsethread.h \ 34 | fftcuda.h 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QSpectrogram 2 | 3 | Quick implementation of a Qt Widget for real-time drawing of Spectrograms. 4 | 5 | [![Spectrogram of NanOrgan synthesis of Chaconne in F minor by Johann Pachelbel.](https://img.youtube.com/vi/k01uoFl3krw/0.jpg)] 6 | (https://www.youtube.com/watch?v=k01uoFl3krw) 7 | 8 | -------------------------------------------------------------------------------- /fftcuda.cu: -------------------------------------------------------------------------------- 1 | #include "cufft.h" 2 | #include "cuda_runtime_api.h" 3 | 4 | typedef float2 Complex; 5 | 6 | void 7 | PerformCUDAFFT(float *inputData, float *outputData, unsigned int numSamples) { 8 | cufftHandle plan; 9 | cufftComplex *inputDataG, *outputDataG; 10 | int i; 11 | 12 | float *inputDataC, *outputDataC; 13 | outputDataC = (float*) malloc(sizeof(float) * numSamples * 2); 14 | inputDataC = (float*) malloc(sizeof(float) * numSamples * 2); 15 | 16 | for (i = 0; i < numSamples; i++) { 17 | inputDataC[i*2] = inputData[i]; 18 | inputDataC[i*2 + 1] = 0.0f; 19 | } 20 | 21 | cudaMalloc((void**)&inputDataG, sizeof(cufftComplex)*numSamples); 22 | cudaMalloc((void**)&outputDataG, sizeof(cufftComplex)*numSamples); 23 | 24 | cudaMemcpy(inputDataG, inputDataC, sizeof(cufftComplex)*numSamples, cudaMemcpyHostToDevice); 25 | cufftPlan1d(&plan, numSamples, CUFFT_C2C, 1); 26 | cufftExecC2C(plan, inputDataG, outputDataG, CUFFT_FORWARD); 27 | cufftDestroy(plan); 28 | cudaMemcpy(outputDataC, outputDataG, sizeof(cufftComplex)*numSamples, cudaMemcpyDeviceToHost); 29 | cudaFree(inputDataG); 30 | cudaFree(outputDataG); 31 | 32 | for (i = 0; i < numSamples; i++) { 33 | outputData[i] = outputDataC[i * 2]; 34 | } 35 | free(outputDataC); 36 | free(inputDataC); 37 | } 38 | -------------------------------------------------------------------------------- /fftcuda.h: -------------------------------------------------------------------------------- 1 | #ifndef FFTCUDA_H 2 | #define FFTCUDA_H 3 | 4 | void PerformCUDAFFT(float *inputData, float *outputData, unsigned int numSamples); 5 | 6 | #endif // FFTCUDA_H 7 | 8 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | MainWindow w; 8 | w.show(); 9 | 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "spectrogram.h" 3 | #include "qspectrogram.h" 4 | #include "pulsethread.h" 5 | #include 6 | #include 7 | 8 | MainWindow::MainWindow(QWidget *parent) 9 | : QMainWindow(parent) { 10 | spectrogram = new Spectrogram(44100, 44100 * 60, 256, 8192); 11 | 12 | spectrogramWidget = new QSpectrogram(spectrogram, this); 13 | setCentralWidget(spectrogramWidget); 14 | 15 | resize(1024, 600); 16 | QString device("alsa_output.pci-0000_00_1f.3.analog-stereo.monitor"); 17 | //device = "alsa_input.pci-0000_00_1f.3.analog-stereo"; 18 | 19 | pulseThread = new PulseThread(device, 44100, 1024); 20 | pulseThread->start(); 21 | 22 | connect(pulseThread, SIGNAL(bufferFilled(float*,uint)), 23 | spectrogramWidget, SLOT(processData(float*,uint))); 24 | } 25 | 26 | void 27 | MainWindow::keyPressEvent(QKeyEvent *event) { 28 | qDebug("keyPress %d", event->key()); 29 | 30 | switch (event->key()) { 31 | case Qt::Key_F1: 32 | spectrogramWidget->setMinAmpl(0.0001); 33 | break; 34 | case Qt::Key_F2: 35 | spectrogramWidget->setMinAmpl(0.001); 36 | break; 37 | case Qt::Key_F3: 38 | spectrogramWidget->setMinAmpl(0.01); 39 | break; 40 | case Qt::Key_F4: 41 | spectrogramWidget->setMaxAmpl(0.1); 42 | break; 43 | case Qt::Key_F5: 44 | spectrogramWidget->setMaxAmpl(1.0); 45 | break; 46 | case Qt::Key_F6: 47 | spectrogramWidget->setMaxAmpl(10.0); 48 | break; 49 | } 50 | } 51 | 52 | MainWindow::~MainWindow() { 53 | delete spectrogram; 54 | spectrogram = 0; 55 | } 56 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include "spectrogram.h" 6 | #include "qspectrogram.h" 7 | #include "pulsethread.h" 8 | 9 | class MainWindow : public QMainWindow 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | MainWindow(QWidget *parent = 0); 15 | ~MainWindow(); 16 | 17 | PulseThread *pulseThread; 18 | protected: 19 | void keyPressEvent(QKeyEvent *event); 20 | private: 21 | Spectrogram *spectrogram; 22 | QSpectrogram *spectrogramWidget; 23 | }; 24 | 25 | #endif // MAINWINDOW_H 26 | -------------------------------------------------------------------------------- /pulsethread.cpp: -------------------------------------------------------------------------------- 1 | #include "pulsethread.h" 2 | #include 3 | #include 4 | 5 | #define RAW_BUFFERSIZE 128 6 | 7 | PulseThread::PulseThread(const QString &_pulseDevice, 8 | unsigned int _sampleRate, 9 | unsigned int _bufferSize) { 10 | pulseDevice = _pulseDevice; 11 | sampleRate = _sampleRate; 12 | bufferSize = _bufferSize; 13 | 14 | bufferLeft = new float[bufferSize]; 15 | bufferRight = new float[bufferSize]; 16 | copyBufferLeft = new float[bufferSize]; 17 | copyBufferRight = new float[bufferSize]; 18 | 19 | stopped = true; 20 | paSampleSpec.format = PA_SAMPLE_FLOAT32; 21 | paSampleSpec.rate = sampleRate; 22 | paSampleSpec.channels = 2; 23 | 24 | bufferIndex = 0; 25 | } 26 | 27 | PulseThread::~PulseThread() { 28 | delete [] bufferLeft; 29 | delete [] bufferRight; 30 | delete [] copyBufferLeft; 31 | delete [] copyBufferRight; 32 | bufferLeft = 0; 33 | bufferRight = 0; 34 | copyBufferLeft = 0; 35 | copyBufferRight = 0; 36 | } 37 | 38 | void 39 | PulseThread::run() { 40 | stopped = false; 41 | int error; 42 | float rawBuffer[RAW_BUFFERSIZE]; 43 | 44 | paSimple = pa_simple_new(NULL, 45 | "alsaspecview", 46 | PA_STREAM_RECORD, 47 | qPrintable(pulseDevice), 48 | "record", 49 | &paSampleSpec, 50 | NULL, 51 | NULL, 52 | &error); 53 | if (!paSimple) { 54 | qErrnoWarning(error, pa_strerror(error)); 55 | QCoreApplication::quit(); 56 | } 57 | 58 | for (;;) { 59 | int numRead, error; 60 | 61 | numRead = pa_simple_read(paSimple, rawBuffer, RAW_BUFFERSIZE, &error); 62 | 63 | if (numRead < 0) { 64 | qErrnoWarning(error, pa_strerror(error)); 65 | QCoreApplication::quit(); 66 | } 67 | if (numRead % 2 != 0) { 68 | qWarning("Non-even number of samples read!"); 69 | } else { 70 | for (int indRead = 0; indRead < RAW_BUFFERSIZE / 8; indRead++) { 71 | bufferLeft[bufferIndex] = rawBuffer[indRead*2]; 72 | bufferRight[bufferIndex] = rawBuffer[indRead*2 + 1]; 73 | 74 | bufferIndex++; 75 | 76 | if (bufferIndex == bufferSize) { 77 | 78 | for (int unsigned ind = 0; ind < bufferSize; ind++) { 79 | copyBufferLeft[ind] = bufferLeft[ind]; 80 | copyBufferRight[ind] = bufferRight[ind]; 81 | } 82 | 83 | emit bufferFilled(copyBufferLeft, bufferSize); 84 | bufferIndex = 0; 85 | } 86 | } 87 | } 88 | 89 | } 90 | } 91 | 92 | void 93 | PulseThread::stop() { 94 | if (paSimple) { 95 | pa_simple_free(paSimple); 96 | } 97 | stopped = true; 98 | } 99 | -------------------------------------------------------------------------------- /pulsethread.h: -------------------------------------------------------------------------------- 1 | #ifndef PULSETHREAD_H 2 | #define PULSETHREAD_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | class PulseThread : public QThread { 14 | Q_OBJECT 15 | public: 16 | PulseThread(const QString &_pulseDevice, 17 | unsigned int _sampleRate, 18 | unsigned int _bufferSize); 19 | ~PulseThread(); 20 | void stop(); 21 | 22 | QString pulseDevice; 23 | 24 | unsigned int sampleRate; 25 | unsigned int bufferSize; 26 | signals: 27 | void bufferFilled(float *outputBufferLeft, 28 | //float *outputBufferRight, 29 | unsigned int bufferLength); 30 | protected: 31 | void run(); 32 | private: 33 | volatile bool stopped; 34 | float *bufferLeft, *bufferRight; 35 | float *copyBufferLeft, *copyBufferRight; 36 | 37 | unsigned int bufferIndex; 38 | 39 | pa_sample_spec paSampleSpec; 40 | pa_simple *paSimple; 41 | }; 42 | 43 | #endif // PULSETHREAD_H 44 | -------------------------------------------------------------------------------- /qspectrogram.cpp: -------------------------------------------------------------------------------- 1 | #include "qspectrogram.h" 2 | 3 | #include 4 | 5 | QSpectrogram::QSpectrogram(Spectrogram *_spectrogram, 6 | QWidget *parent, 7 | double _minFreq, 8 | double _maxFreq, 9 | double _minAmpl, 10 | double _maxAmpl) : QWidget(parent) { 11 | spectrogram = _spectrogram; 12 | minFreq = _minFreq; 13 | maxFreq = _maxFreq; 14 | minAmpl = _minAmpl; 15 | maxAmpl = _maxAmpl; 16 | 17 | paddingX = 20; 18 | paddingY = 20; 19 | xlabelSpacing = 40; 20 | ylabelSpacing = 80; 21 | 22 | freqTickBig = 10000.0; 23 | freqTickSmall = 1000.0; 24 | 25 | drawMode = DRAWMODE_SCROLL; 26 | layoutMode = LAYOUT_HORIZONTAL; 27 | drawTimeGrid = true; 28 | drawFreqGrid = true; 29 | drawColorbar = true; 30 | drawSpectrum = true; 31 | drawWaveform = true; 32 | 33 | waveformWidth = 100; 34 | spectrumWidth = 100; 35 | colorBarWidth = 50; 36 | 37 | backgroundColor = QColor(0, 0, 0); 38 | gridColor = QColor(128, 128, 128); 39 | 40 | timeScroll = 0.0; 41 | image = 0; 42 | 43 | logScaleFreq = true; 44 | logScaleAmpl = true; 45 | 46 | QVector color0, color1, color2, color3, color4; 47 | color0.push_back(0.0); color0.push_back(0.0); color0.push_back(32.0);color0.push_back(0.0); 48 | color1.push_back(0.0);color1.push_back(0.0); color1.push_back(255.0);color1.push_back(0.25); 49 | color2.push_back(0.0);color2.push_back(255.0);color2.push_back(255.0);color2.push_back(0.5); 50 | color3.push_back(255.0);color3.push_back(255.0);color3.push_back(0.0);color3.push_back(0.75); 51 | color4.push_back(255.0);color4.push_back(0.0);color4.push_back(0.0);color4.push_back(1.0000); 52 | colormap.push_back(color0); 53 | colormap.push_back(color1); 54 | colormap.push_back(color2); 55 | colormap.push_back(color3); 56 | colormap.push_back(color4); 57 | } 58 | 59 | void 60 | QSpectrogram::evalColormap(float value, int &r, int &g, int &b) { 61 | int nRGB = colormap.size(); 62 | 63 | QVector RGB; 64 | QVector RGBnext; 65 | 66 | for (int indRGB = 0; indRGB < nRGB-1; indRGB++) { 67 | RGB = colormap[indRGB]; 68 | RGBnext = colormap[indRGB+1]; 69 | 70 | if (value < RGB[3]) { 71 | r = (int)RGB[0]; 72 | g = (int)RGB[1]; 73 | b = (int)RGB[2]; 74 | return; 75 | } else if (value <= RGBnext[3]) { 76 | float valcoeff = (value - RGB[3]) / (RGBnext[3] - RGB[3]); 77 | 78 | r = (int)(RGB[0] * (1.0 - valcoeff) + valcoeff * RGBnext[0]); 79 | g = (int)(RGB[1] * (1.0 - valcoeff) + valcoeff * RGBnext[1]); 80 | b = (int)(RGB[2] * (1.0 - valcoeff) + valcoeff * RGBnext[2]); 81 | return; 82 | } 83 | } 84 | r = (int)RGBnext[0]; 85 | g = (int)RGBnext[1]; 86 | b = (int)RGBnext[2]; 87 | } 88 | 89 | void 90 | QSpectrogram::paintEvent(QPaintEvent *event) { 91 | QStylePainter painter(this); 92 | painter.drawPixmap(0, 0, pixmap); 93 | Q_UNUSED(event); 94 | } 95 | 96 | void 97 | QSpectrogram::resizeEvent(QResizeEvent *event) { 98 | plotwidth = width() - paddingX * 2 - ylabelSpacing; 99 | plotheight = height() - paddingY * 2 - xlabelSpacing; 100 | plotx = paddingX + ylabelSpacing; 101 | ploty = paddingY; 102 | 103 | if (layoutMode == LAYOUT_HORIZONTAL) { 104 | if (drawSpectrum) { 105 | plotwidth -= spectrumWidth; 106 | plotwidth -= colorBarWidth; 107 | } 108 | if (drawWaveform) { 109 | plotheight -= waveformWidth; 110 | ploty += waveformWidth; 111 | } 112 | } 113 | if (layoutMode == LAYOUT_VERTICAL) { 114 | if (drawSpectrum) { 115 | ploty += spectrumWidth; 116 | plotheight -= spectrumWidth; 117 | plotheight -= colorBarWidth; 118 | } 119 | if (drawWaveform) { 120 | plotwidth -= waveformWidth; 121 | } 122 | } 123 | 124 | renderImage(0, true); 125 | refreshPixmap(); 126 | Q_UNUSED(event); 127 | } 128 | 129 | int 130 | QSpectrogram::freqToPixel(double freq) { 131 | double minCoord; 132 | double imageSize; 133 | 134 | if (layoutMode == LAYOUT_HORIZONTAL) { 135 | minCoord = (double)ploty; 136 | imageSize = (double)plotheight; 137 | } else if (layoutMode == LAYOUT_VERTICAL) { 138 | minCoord = (double)plotx; 139 | imageSize = (double)plotwidth; 140 | } else { 141 | qWarning("freqToPixel : Non-supported Layout Mode %d!", layoutMode); 142 | return 0; 143 | } 144 | 145 | if (logScaleFreq) { 146 | // a + b * log10(minFreq) = maxCoord 147 | // a + b * log10(maxFreq) = minCoord 148 | // => b * (log10(minFreq) - log10(maxFreq) = maxCoord - minCoord 149 | // => b = (maxCoord - minCoord) / (log10(minFreq) - log10(maxFreq) 150 | // => a = maxCoord - b * log10(minFreq) 151 | 152 | double b = imageSize / (log10(minFreq) - log10(maxFreq)); 153 | double a = minCoord - b * log10(maxFreq); 154 | 155 | //c + d * (plotx + plotwidth) = plotx 156 | //c + d * (plotx) = plotx + plotwidth 157 | // => d * plotwidth = -plotwidth 158 | // => d = -1; 159 | // => c - plotx = plotx + plotwidth 160 | // c = 2 * plotx + plotwidth; 161 | // 2 * plotx + plotwidth - x 162 | 163 | if (layoutMode == LAYOUT_HORIZONTAL) { 164 | return (int)(a + b * log10(freq)); 165 | } else if (layoutMode == LAYOUT_VERTICAL) { 166 | return (int)(2 * minCoord + imageSize - a - b * log10(freq)); 167 | } 168 | } else { 169 | // a + b * minFreq = maxCoord 170 | // a + b * maxFreq = minCoord 171 | // => b * (minFreq - maxFreq) = plotsize 172 | // => b = plotsize / (minFreq - maxFreq) 173 | 174 | double b = imageSize / (minFreq - maxFreq); 175 | double a = minCoord - maxFreq * b; 176 | if (layoutMode == LAYOUT_HORIZONTAL) { 177 | return (int)(a + b * freq); 178 | } else if (layoutMode == LAYOUT_VERTICAL) { 179 | return (int)(2 * minCoord + imageSize - a - b * freq); 180 | } 181 | } 182 | return 0; // dummy 183 | } 184 | 185 | int 186 | QSpectrogram::timeToPixel(double time) { 187 | double deltaTime = spectrogram->getDeltaTime(); 188 | double headTime = spectrogram->getHeadTime(); 189 | double timeWidth = ((double)plotwidth) * deltaTime; 190 | 191 | // a + b * headTime = plotx + plotwidth 192 | // a + b * (headTime - timeWidth ) = plotx 193 | // => plotwidth = b * timeWidth 194 | // => b = plotWidth / timeWidth 195 | // => a = plotx + plotwidth - b * headTime 196 | 197 | if (layoutMode == LAYOUT_HORIZONTAL) { 198 | double b = plotwidth / timeWidth; 199 | double a = plotx + plotwidth - b * headTime; 200 | 201 | double x_double = a + b * (time - timeScroll); 202 | return (int) x_double; 203 | } else if (layoutMode == LAYOUT_VERTICAL) { 204 | double b = plotheight / timeWidth; 205 | double a = ploty + plotheight - b * headTime; 206 | 207 | double y_double = a + b * (time - timeScroll); 208 | return (int) y_double; 209 | } 210 | return 0; // dummy 211 | } 212 | 213 | void 214 | QSpectrogram::drawGrid(QPainter &painter) { 215 | double deltaTime = spectrogram->getDeltaTime(); 216 | double headTime = spectrogram->getHeadTime(); 217 | double timeWidth = (double)(plotwidth) * deltaTime; 218 | 219 | //qDebug("deltaTime %f, headTime %f, timeWidth %f", deltaTime, headTime, timeWidth); 220 | 221 | QPen thickPen, thinPen, dashPen; 222 | thinPen.setStyle(Qt::SolidLine); 223 | thinPen.setWidth(1); 224 | thinPen.setColor(gridColor); 225 | dashPen.setStyle(Qt::DotLine); 226 | dashPen.setWidth(1); 227 | dashPen.setColor(gridColor); 228 | 229 | painter.setPen(dashPen); 230 | painter.drawRect(plotx, ploty, plotwidth, plotheight); 231 | for (unsigned int indSec = 1; indSec <= (unsigned int)(10.0*timeWidth); indSec++) { 232 | 233 | if (layoutMode == LAYOUT_HORIZONTAL) { 234 | int x = timeToPixel(headTime - 0.1 * (double)indSec); 235 | if (x > (int)plotx && x < (int)(plotwidth + plotx)) { 236 | painter.drawLine(x, ploty, x, ploty + plotheight); 237 | } 238 | } else if (layoutMode == LAYOUT_VERTICAL) { 239 | int y = timeToPixel(headTime - 0.1 * (double)indSec); 240 | if (y > (int)ploty && y < (int)(plotheight + ploty)) { 241 | painter.drawLine(plotx, y, plotx + plotwidth, y); 242 | } 243 | } 244 | } 245 | painter.setPen(thinPen); 246 | for (unsigned int indSec = 0; indSec <= (unsigned int)timeWidth; indSec++) { 247 | if (layoutMode == LAYOUT_HORIZONTAL) { 248 | int x = timeToPixel(headTime - (double)indSec); 249 | if (x >= (int)plotx && x <= (int)(plotwidth + plotx)) { 250 | painter.drawLine(x, ploty, x, ploty + plotheight + 10); 251 | } 252 | } else if (layoutMode == LAYOUT_VERTICAL) { 253 | int y = timeToPixel(headTime - (double)indSec); 254 | if (y >= (int)ploty && y <= (int)(plotheight + ploty)) { 255 | painter.drawLine(plotx-10, y, plotx + plotwidth, y); 256 | } 257 | } 258 | } 259 | 260 | if (logScaleFreq) { 261 | for (int lfreq = (int) log10(minFreq); lfreq <= (int)log10(maxFreq); lfreq++) { 262 | double freq = pow(10.0, lfreq); 263 | 264 | painter.setPen(dashPen); 265 | for (int fmult = 1; fmult < 10; fmult++) { 266 | if (layoutMode == LAYOUT_HORIZONTAL) { 267 | int y = freqToPixel(freq*(double)fmult); 268 | if (y >= (int)ploty && y <= (int)(ploty + plotheight)) { 269 | painter.drawLine(plotx, y, plotx + plotwidth, y); 270 | } 271 | } else if (layoutMode == LAYOUT_VERTICAL) { 272 | int x = freqToPixel(freq*(double)fmult); 273 | if (x >= (int)plotx && x <= (int)(plotx + plotwidth)) { 274 | painter.drawLine(x, ploty, x, ploty + plotheight); 275 | } 276 | } 277 | } 278 | 279 | painter.setPen(thinPen); 280 | if (layoutMode == LAYOUT_HORIZONTAL) { 281 | int y = freqToPixel(freq); 282 | painter.drawText(QRect(paddingX-15 , y-10, ylabelSpacing, 20), Qt::AlignRight | Qt::AlignVCenter, 283 | QString::number(freq) + QString(" Hz")); 284 | if (y >= (int)ploty && y <= (int)(ploty + plotheight)) { 285 | painter.drawLine(plotx-10, y, plotx + plotwidth, y); 286 | } 287 | } else if (layoutMode == LAYOUT_VERTICAL) { 288 | int x = freqToPixel(freq); 289 | painter.drawText(QRect(x-30, ploty + plotheight + 15, 60, 20), Qt::AlignHCenter, 290 | QString::number(freq) + QString(" Hz")); 291 | if (x >= (int)plotx && x <= (int)(plotx + plotwidth)) { 292 | painter.drawLine(x, ploty, x, ploty + plotheight + 10); 293 | } 294 | } 295 | } 296 | } else { 297 | for (double freq = floor(minFreq/freqTickSmall); freq <= floor(maxFreq/freqTickSmall); freq += 1) { 298 | painter.setPen(dashPen); 299 | if (layoutMode == LAYOUT_HORIZONTAL) { 300 | int y = freqToPixel(freq * freqTickSmall); 301 | if (y >= (int)ploty && y <= (int)(ploty + plotheight)) { 302 | painter.drawLine(plotx-10, y, plotx + plotwidth, y); 303 | painter.drawText(QRect(paddingX-15 , y-10, ylabelSpacing, 20), Qt::AlignRight | Qt::AlignVCenter, 304 | QString::number(freq * freqTickSmall) + QString(" Hz")); 305 | } 306 | } else if (layoutMode == LAYOUT_VERTICAL) { 307 | int x = freqToPixel(freq * freqTickSmall); 308 | if (x >= (int)plotx && x <= (int)(plotx + plotwidth)) { 309 | painter.drawLine(x, ploty, x, ploty + plotheight); 310 | } 311 | 312 | } 313 | } 314 | for (double freq = floor(minFreq/freqTickBig); freq <= floor(maxFreq/freqTickBig); freq += 1) { 315 | painter.setPen(thinPen); 316 | if (layoutMode == LAYOUT_HORIZONTAL) { 317 | int y = freqToPixel(freq * freqTickBig); 318 | if (y >= (int)ploty && y <= (int)(ploty + plotheight)) { 319 | painter.drawLine(plotx-10, y, plotx + plotwidth, y); 320 | } 321 | } else if (layoutMode == LAYOUT_VERTICAL) { 322 | int x = freqToPixel(freq * freqTickBig); 323 | if (x >= (int)plotx && x <= (int)(plotx + plotwidth)) { 324 | painter.drawLine(x, ploty, x, ploty + plotheight + 10); 325 | painter.drawText(QRect(x, ploty + plotheight + 10, 80, 20), Qt::AlignHCenter, 326 | QString::number(freq * freqTickBig) + QString(" Hz")); 327 | } 328 | } 329 | } 330 | } 331 | } 332 | 333 | void 334 | QSpectrogram::drawWaveformPlot(QPainter &painter) { 335 | std::list::iterator itMin = spectrogram->waveEnvelopeMin.end(); 336 | std::list::iterator itMax = spectrogram->waveEnvelopeMax.end(); 337 | 338 | unsigned int pixel = 0; 339 | 340 | if (layoutMode == LAYOUT_HORIZONTAL) { 341 | pixel= plotx + plotwidth; 342 | } 343 | if (layoutMode == LAYOUT_VERTICAL) { 344 | pixel= ploty + plotheight; 345 | } 346 | for (;;) { 347 | if (itMin == spectrogram->waveEnvelopeMin.begin()) break; 348 | if (itMax == spectrogram->waveEnvelopeMax.begin()) break; 349 | if (pixel < plotx) break; 350 | 351 | int minValue = (int)(0.5 * ((float)waveformWidth)*(*itMin)); 352 | int maxValue = (int)(0.5 * ((float)waveformWidth)*(*itMax)); 353 | 354 | if (layoutMode == LAYOUT_HORIZONTAL) { 355 | painter.drawLine(pixel, ploty - waveformWidth / 2 + minValue, 356 | pixel, ploty - waveformWidth / 2 + maxValue); 357 | } 358 | if (layoutMode == LAYOUT_VERTICAL) { 359 | painter.drawLine(plotx + plotwidth + waveformWidth / 2 + minValue, pixel, 360 | plotx + plotwidth + waveformWidth / 2 + maxValue, pixel); 361 | } 362 | 363 | pixel--; 364 | itMin--; 365 | itMax--; 366 | } 367 | } 368 | 369 | void 370 | QSpectrogram::drawSpectrumPlot(QPainter &painter) { 371 | if (spectrogram->spectrogramData.empty()) return; 372 | 373 | std::vector lineData = spectrogram->spectrogramData.back(); 374 | 375 | Q_ASSERT(!spectrogram->spectrogramData.empty()); 376 | Q_ASSERT(spectrogram->frequencyList.size() >= lineData.size()); 377 | Q_ASSERT(lineData.size() > 0); 378 | 379 | QPen thinPen; 380 | thinPen.setStyle(Qt::SolidLine); 381 | thinPen.setWidth(1); 382 | thinPen.setColor(gridColor); 383 | painter.setPen(thinPen); 384 | 385 | float minAmplS = 100.0 * minAmpl; 386 | 387 | unsigned int prevpixel = 0, prevvalue = 0; 388 | for (unsigned int ind_freq = 0; ind_freq < lineData.size(); ind_freq++) { 389 | unsigned int pixel = freqToPixel(spectrogram->frequencyList[ind_freq]); 390 | 391 | float value = lineData[ind_freq]; 392 | float lvalue = (((float)spectrumWidth) / (log10(maxAmpl) - log10(minAmplS))) * ( log10(value) - log10(minAmplS) ); 393 | unsigned int ivalue = std::max((int) lvalue, 0); 394 | 395 | if (layoutMode == LAYOUT_HORIZONTAL) { 396 | if (ind_freq > 0 && pixel >= ploty && prevpixel >= ploty && pixel <= ploty + plotheight && prevpixel <= ploty + plotheight) { 397 | painter.drawLine(plotx + plotwidth + 5, pixel, 398 | plotx + plotwidth + ivalue + 5, pixel); 399 | } 400 | } else if (layoutMode == LAYOUT_VERTICAL) { 401 | if (ind_freq > 0 && pixel >= plotx && prevpixel >= plotx && pixel <= plotx + plotwidth && prevpixel <= plotx + plotwidth) { 402 | painter.drawLine(pixel, ploty - ivalue, prevpixel, ploty - prevvalue); 403 | } 404 | 405 | } 406 | 407 | prevvalue = ivalue; 408 | prevpixel = pixel; 409 | } 410 | } 411 | 412 | void 413 | QSpectrogram::renderImage(unsigned int newLines, bool redraw) { 414 | 415 | if (image) { 416 | QImage *oldimage = image; 417 | image = new QImage(plotwidth, plotheight, QImage::Format_RGB32); 418 | image->fill(backgroundColor); 419 | 420 | if (layoutMode == LAYOUT_HORIZONTAL) { 421 | QPainter painter(image); 422 | //QImage tmpImage = oldimage->copy(newLines-1, 0, plotwidth-newLines+1, plotheight); 423 | QImage tmpImage = oldimage->copy(newLines, 0, plotwidth-newLines+1, plotheight); 424 | painter.drawImage(0, 0, tmpImage); 425 | } else if (layoutMode == LAYOUT_VERTICAL) { 426 | QPainter painter(image); 427 | //QImage tmpImage = oldimage->copy(0, newLines-1, plotwidth, plotheight-newLines+1); 428 | QImage tmpImage = oldimage->copy(0, newLines, plotwidth, plotheight-newLines+1); 429 | painter.drawImage(0, 0, tmpImage); 430 | } 431 | delete oldimage; 432 | } else { 433 | image = new QImage(plotwidth, plotheight, QImage::Format_RGB32); 434 | image->fill(backgroundColor); 435 | } 436 | 437 | unsigned int ind_line = 0; 438 | 439 | if (redraw) { 440 | newLines = spectrogram->spectrogramData.size(); 441 | } 442 | 443 | std::vector pixelList; 444 | for (unsigned int ind_freq = 0; ind_freq < spectrogram->frequencyList.size(); ind_freq++) 445 | pixelList.push_back(freqToPixel(spectrogram->frequencyList[ind_freq])); 446 | 447 | for (std::list >::iterator it = spectrogram->spectrogramData.begin(); 448 | it != spectrogram->spectrogramData.end(); it++) { 449 | 450 | if (ind_line > spectrogram->spectrogramData.size() - newLines - 1) { 451 | std::vector lineData = *it; 452 | if (layoutMode == LAYOUT_HORIZONTAL) { 453 | int prevy = plotheight; 454 | float prevvalue = 0.0; 455 | 456 | for (unsigned int ind_freq = 0; ind_freq < lineData.size()/2; ind_freq++) { 457 | int y = pixelList[ind_freq]-ploty; 458 | int x = plotwidth - spectrogram->spectrogramData.size() + ind_line; 459 | 460 | if (y < prevy && y > 0 && x >= 0 && x < (int)plotwidth && prevy < (int)plotheight) { 461 | float value = lineData[ind_freq]; 462 | int r, g, b; 463 | 464 | //value = (log10(value) + 3.0) / 5.0; 465 | 466 | if (logScaleAmpl) { 467 | value = (1.0 / (log10(maxAmpl) - log10(minAmpl))) * ( log10(value) - log10(minAmpl) ); 468 | } else { 469 | value = (value - minAmpl)/(maxAmpl - minAmpl); 470 | } 471 | if (value < 0) { 472 | value = 0.0; 473 | } 474 | if (value > 0.01) { 475 | 476 | for (int yi = y; yi < prevy; yi++) { 477 | float interp_value = prevvalue + (value - prevvalue) * (float)(yi - prevy)/((float)(y - prevy)); 478 | evalColormap(interp_value, r, g, b); 479 | int ivalue = qRgb(r, g, b); 480 | 481 | if (yi >= 0 && yi < (int)plotheight) 482 | image->setPixel(x, yi, ivalue); 483 | } 484 | } 485 | prevvalue = value; 486 | } 487 | prevy = y; 488 | } 489 | } else if (layoutMode == LAYOUT_VERTICAL) { 490 | int prevx = 0; 491 | 492 | for (unsigned int ind_freq = 0; ind_freq < lineData.size()/2; ind_freq++) { 493 | int x = pixelList[ind_freq]-plotx; 494 | int y = plotheight - spectrogram->spectrogramData.size() + ind_line; 495 | 496 | if (x > prevx && x < (int)plotwidth && y >= 0 && y < (int)plotheight && prevx < (int)plotwidth) { 497 | float value = lineData[ind_freq]; 498 | int r, g, b; 499 | 500 | // a + b * log10(maxAmpl) = 1.0 501 | // a + b * log10(minAmpl) = 0 502 | // b * (log10(maxAmpl) - log10(minAmpl) = 1.0 503 | // => b = 1.0 / (log10(maxAmpl) - log10(minAmpl)) 504 | // a = - b * log10(minAmpl) 505 | //value = (log10(value) + 3.0) / 5.0; 506 | 507 | if (logScaleAmpl) { 508 | value = (1.0 / (log10(maxAmpl) - log10(minAmpl))) * ( log10(value) - log10(minAmpl) ); 509 | } else { 510 | value = (value - minAmpl)/(maxAmpl - minAmpl); 511 | } 512 | 513 | if (value < 0) { 514 | value = 0.0; 515 | } 516 | if (value > 0.01) { 517 | 518 | evalColormap(value, r, g, b); 519 | int ivalue = qRgb(r, g, b); 520 | 521 | for (int xi = prevx; xi < x; xi++) 522 | if (xi >= 0 && xi < (int)plotwidth) 523 | image->setPixel(xi, y, ivalue); 524 | } 525 | } 526 | prevx = x; 527 | } 528 | 529 | } 530 | } 531 | ind_line++; 532 | } 533 | } 534 | 535 | void 536 | QSpectrogram::drawColorbarPlot(QPainter &painter) { 537 | int r, g, b; 538 | QPen colorPen; 539 | 540 | int startX = plotx + plotwidth + spectrumWidth; 541 | for (unsigned int y = ploty; y < ploty + plotheight; y++) { 542 | float value = 1.0 - ((float)(y-ploty)) / ((float)plotheight); 543 | 544 | evalColormap(value, r, g, b); 545 | colorPen.setStyle(Qt::SolidLine); 546 | colorPen.setWidth(1); 547 | colorPen.setColor(QColor(r, g, b)); 548 | painter.setPen(colorPen); 549 | 550 | if (drawSpectrum) { 551 | painter.drawLine(startX + colorBarWidth/2, y, startX + colorBarWidth - 5, y); 552 | } 553 | } 554 | colorPen.setColor(gridColor); 555 | painter.setPen(colorPen); 556 | painter.drawRect(startX + colorBarWidth/2, ploty, colorBarWidth/2 - 5 , plotheight); 557 | 558 | float logMin = log10(minAmpl); 559 | float logMax = log10(maxAmpl); 560 | 561 | int logMinInt = (int)floor(logMin); 562 | int logMaxInt = (int)floor(logMax); 563 | 564 | if (logMinInt > logMaxInt) exit(0); 565 | 566 | for (int logIt = logMinInt; logIt <= logMaxInt; logIt++) { 567 | double value = pow(10.0, (double)logIt); 568 | 569 | double dBvalue = 20.0 * logIt; 570 | if (value >= minAmpl && value <= maxAmpl) { 571 | int y = ploty + plotheight - (int)((double)plotheight) * ((double)logIt - logMin)/(logMax - logMin); 572 | painter.drawLine(startX + colorBarWidth/2-5, y, startX + colorBarWidth - 5, y); 573 | painter.drawText(QRect(startX-35, y-6, colorBarWidth, 20), Qt::AlignRight, 574 | QString::number((int)dBvalue) + QString(" dB")); 575 | } 576 | } 577 | } 578 | 579 | void 580 | QSpectrogram::refreshPixmap() { 581 | pixmap = QPixmap(size()); 582 | pixmap.fill(backgroundColor); 583 | QPainter painter(&pixmap); 584 | 585 | if (image) { 586 | painter.drawImage(plotx, ploty, *image); 587 | } 588 | if (drawSpectrum) { 589 | drawSpectrumPlot(painter); 590 | } 591 | if (drawWaveform) { 592 | drawWaveformPlot(painter); 593 | } 594 | if (drawColorbar) { 595 | drawColorbarPlot(painter); 596 | } 597 | drawGrid(painter); 598 | update(); 599 | } 600 | 601 | void 602 | QSpectrogram::toggleColorbar(bool visible) { 603 | drawColorbar = visible; 604 | resizeEvent(0); 605 | } 606 | 607 | void 608 | QSpectrogram::toggleWaveform(bool visible) { 609 | drawWaveform = visible; 610 | resizeEvent(0); 611 | } 612 | 613 | void 614 | QSpectrogram::toggleSpectrum(bool visible) { 615 | drawSpectrum = visible; 616 | resizeEvent(0); 617 | } 618 | 619 | void 620 | QSpectrogram::toggleTimeGrid(bool visible) { 621 | drawTimeGrid = visible; 622 | resizeEvent(0); 623 | } 624 | 625 | void 626 | QSpectrogram::toggleFreqGrid(bool visible) { 627 | drawFreqGrid = visible; 628 | resizeEvent(0); 629 | } 630 | 631 | void 632 | QSpectrogram::toggleLogScaleFreq(bool logscale) { 633 | logScaleFreq = logscale; 634 | resizeEvent(0); 635 | } 636 | 637 | void 638 | QSpectrogram::toggleLogScaleAmpl(bool logscale) { 639 | logScaleAmpl = logscale; 640 | resizeEvent(0); 641 | } 642 | 643 | void 644 | QSpectrogram::setMaxFreq(double _maxFreq) { 645 | maxFreq = _maxFreq; 646 | refreshPixmap(); 647 | } 648 | 649 | void 650 | QSpectrogram::setMinFreq(double _minFreq) { 651 | minFreq = _minFreq; 652 | refreshPixmap(); 653 | } 654 | 655 | void 656 | QSpectrogram::setMaxAmpl(double _maxAmpl) { 657 | maxAmpl = _maxAmpl; 658 | refreshPixmap(); 659 | } 660 | 661 | void 662 | QSpectrogram::setMinAmpl(double _minAmpl) { 663 | minAmpl = _minAmpl; 664 | refreshPixmap(); 665 | } 666 | 667 | void 668 | QSpectrogram::setLayoutMode(unsigned int layoutMode) { 669 | Q_UNUSED(layoutMode); 670 | } 671 | 672 | void 673 | QSpectrogram::processData(float *buffer, unsigned int bufferLength) { 674 | unsigned int newLines = spectrogram->processData(buffer, bufferLength); 675 | double minValue = 0.0; 676 | for (unsigned int indBuffer = 0; indBuffer < bufferLength; indBuffer++) { 677 | if (fabs(buffer[indBuffer]) > minValue) 678 | minValue = fabs(buffer[indBuffer]); 679 | } 680 | 681 | renderImage(newLines, false); 682 | refreshPixmap(); 683 | } 684 | 685 | -------------------------------------------------------------------------------- /qspectrogram.h: -------------------------------------------------------------------------------- 1 | #ifndef QSPECTROGRAM_H 2 | #define QSPECTROGRAM_H 3 | 4 | #include 5 | #include "spectrogram.h" 6 | 7 | class QSpectrogram : public QWidget 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit QSpectrogram(Spectrogram *_spectrogram, 12 | QWidget *parent = 0, 13 | double _minFreq = 10.0f, 14 | double _maxFreq = 21000.0f, 15 | double _minAmpl = 1e-5, 16 | double _maxAmpl = 1.0f); 17 | protected: 18 | void paintEvent(QPaintEvent *event); 19 | void resizeEvent(QResizeEvent *event); 20 | signals: 21 | public slots: 22 | void processData(float *buffer, 23 | unsigned int bufferLength); 24 | void toggleColorbar(bool visible); 25 | void toggleWaveform(bool visible); 26 | void toggleSpectrum(bool visible); 27 | void toggleTimeGrid(bool visible); 28 | void toggleFreqGrid(bool visible); 29 | void toggleLogScaleFreq(bool logscale); 30 | void toggleLogScaleAmpl(bool logscale); 31 | 32 | void setMaxFreq(double maxFreq); 33 | void setMinFreq(double minFreq); 34 | void setMaxAmpl(double maxAmpl); 35 | void setMinAmpl(double minAmpl); 36 | 37 | void setLayoutMode(unsigned int layoutMode); 38 | private: 39 | void drawGrid(QPainter &painter); 40 | void drawSpectrumPlot(QPainter &painter); 41 | void drawWaveformPlot(QPainter &painter); 42 | void drawColorbarPlot(QPainter &painter); 43 | void refreshPixmap(); 44 | void renderImage(unsigned int newLines, 45 | bool redraw); 46 | 47 | int freqToPixel(double freq); 48 | int timeToPixel(double time); 49 | 50 | // Limits of the plot; 51 | double minFreq; 52 | double maxFreq; 53 | double minAmpl; 54 | double maxAmpl; 55 | 56 | double freqTickBig; 57 | double freqTickSmall; 58 | 59 | unsigned int paddingX; 60 | unsigned int paddingY; 61 | unsigned int ylabelSpacing; 62 | unsigned int xlabelSpacing; 63 | 64 | unsigned int colorBarWidth; 65 | unsigned int spectrumWidth; 66 | unsigned int waveformWidth; 67 | 68 | unsigned int plotx, ploty, plotwidth, plotheight; 69 | 70 | double timeScroll; 71 | 72 | enum { 73 | DRAWMODE_OFF, 74 | DRAWMODE_SCROLL, 75 | DRAWMODE_SWEEP_SINGLE, 76 | DRAWMODE_SWEEP_REPEAT 77 | }; 78 | 79 | enum { 80 | LAYOUT_HORIZONTAL, 81 | LAYOUT_VERTICAL 82 | }; 83 | 84 | void evalColormap(float value, int &r, int &g, int &b); 85 | 86 | unsigned int drawMode; 87 | unsigned int layoutMode; 88 | bool logScaleFreq; 89 | bool logScaleAmpl; 90 | bool drawTimeGrid; 91 | bool drawFreqGrid; 92 | bool drawColorbar; 93 | bool drawSpectrum; 94 | bool drawWaveform; 95 | 96 | QImage *image; 97 | QPixmap pixmap; 98 | QColor backgroundColor; 99 | QColor gridColor; 100 | 101 | QVector > colormap; 102 | 103 | Spectrogram *spectrogram; 104 | }; 105 | 106 | #endif // QSPECTROGRAM_H 107 | -------------------------------------------------------------------------------- /spectrogram.cpp: -------------------------------------------------------------------------------- 1 | #include "spectrogram.h" 2 | #include 3 | #include 4 | #include "fftcuda.h" 5 | 6 | Spectrogram::Spectrogram(unsigned int _sampleRate, 7 | unsigned int _sampleLength, 8 | unsigned int _samplesPerLine, 9 | unsigned int _numLines) { 10 | 11 | sampleRate = _sampleRate; 12 | sampleLength = _sampleLength; 13 | samplesPerLine = _samplesPerLine; 14 | numLines = _numLines; 15 | ringBufferSize = (numLines - 1) * samplesPerLine + sampleLength; 16 | sampleCounter = 0; 17 | 18 | waveRingBuffer = new float[ringBufferSize]; 19 | std::fill_n(waveRingBuffer, ringBufferSize, 0.0f); 20 | ringBufferInd = 0; 21 | 22 | fftSize = 4096; 23 | 24 | headTime = 0.0f; 25 | deltaTime = 0.0f; 26 | deltaTime = ((double)samplesPerLine)/((double)sampleRate); 27 | frequencyList.clear(); 28 | for (unsigned int indFreq = 0; indFreq < fftSize; indFreq++) { 29 | float freq = ((float)(indFreq)) * ((float)sampleRate) /((float)fftSize); 30 | frequencyList.push_back(freq); 31 | } 32 | } 33 | 34 | Spectrogram::~Spectrogram() { 35 | delete [] waveRingBuffer; 36 | waveRingBuffer = 0; 37 | } 38 | 39 | unsigned int 40 | Spectrogram::processData(float *buffer, 41 | unsigned int bufferLength) { 42 | unsigned int newLines = 0; 43 | 44 | float waveEnvMin = 0, waveEnvMax = 0; 45 | 46 | for (unsigned int bufferInd = 0; bufferInd < bufferLength; bufferInd++) { 47 | float value = buffer[bufferInd]; 48 | 49 | if (value > waveEnvMax) { 50 | waveEnvMax = value; 51 | } 52 | if (value < waveEnvMin) { 53 | waveEnvMin = value; 54 | } 55 | 56 | waveRingBuffer[ringBufferInd] = value; 57 | ringBufferInd = (ringBufferInd + 1) % ringBufferSize; 58 | sampleCounter++; 59 | 60 | if (sampleCounter == fftSize) { 61 | sampleCounter -= samplesPerLine; 62 | 63 | newLines++; 64 | 65 | // Fill the fftData array with most recent sample data from the ring buffer: 66 | float *fftAbs = new float[fftSize]; 67 | #ifdef CUDA_FFT 68 | float *fftData = new float[fftSize]; 69 | #else 70 | std::complex *fftData = new std::complex[fftSize]; 71 | #endif 72 | unsigned int startIndex = (ringBufferInd - fftSize + ringBufferSize) % ringBufferSize; 73 | 74 | for (unsigned int indBuffer = 0; indBuffer < fftSize; indBuffer++) { 75 | unsigned int sampleIndex = (startIndex + indBuffer) % ringBufferSize; 76 | fftData[indBuffer] = waveRingBuffer[sampleIndex]; 77 | //std::cout << sampleIndex << " " << indBuffer << " " << fftData[indBuffer] << std::endl; 78 | } 79 | #ifdef CUDA_FFT 80 | PerformCUDAFFT(fftData, fftData, fftSize); 81 | #else 82 | FFTCompute(fftData, fftSize); 83 | #endif 84 | // Compute the absolute value of each complex Fouerier coefficient and assemble 85 | // them into a array: 86 | for (unsigned int indBuffer = 0; indBuffer < fftSize; indBuffer++) { 87 | fftAbs[indBuffer] = std::abs(fftData[indBuffer]) / ((float)fftSize); 88 | } 89 | // Store the new line in the spectrogram: 90 | addLine(fftAbs, fftSize, waveEnvMin, waveEnvMax); 91 | 92 | delete [] fftData; 93 | delete [] fftAbs; 94 | } 95 | } 96 | 97 | return newLines; 98 | } 99 | 100 | void 101 | Spectrogram::removeFoot(unsigned int numLines) { 102 | for (unsigned int indLine = 0; indLine < numLines; indLine++) { 103 | assert(!spectrogramData.empty()); 104 | assert(!timeList.empty()); 105 | spectrogramData.pop_front(); 106 | timeList.pop_front(); 107 | 108 | waveEnvelopeMin.pop_front();; 109 | waveEnvelopeMax.pop_front();; 110 | 111 | footTime += deltaTime; 112 | } 113 | } 114 | 115 | void 116 | Spectrogram::addLine(float *fourierData, 117 | unsigned int dataLength, 118 | float envMin, 119 | float envMax) { 120 | std::vector fourierDataVec; 121 | 122 | if (spectrogramData.size() >= numLines) { 123 | removeFoot(spectrogramData.size() - numLines + 1); 124 | } 125 | fourierDataVec.assign(fourierData, fourierData + dataLength); 126 | spectrogramData.push_back(fourierDataVec); 127 | waveEnvelopeMax.push_back(envMax); 128 | waveEnvelopeMin.push_back(envMin); 129 | 130 | headTime += deltaTime; 131 | timeList.push_back(headTime); 132 | } 133 | 134 | void 135 | Spectrogram::FFTCompute(std::complex *data, 136 | unsigned int dataLength) { 137 | for (unsigned int pos= 0; pos < dataLength; pos++) { 138 | unsigned int mask = dataLength; 139 | unsigned int mirrormask = 1; 140 | unsigned int target = 0; 141 | 142 | while (mask != 1) { 143 | mask >>= 1; 144 | if (pos & mirrormask) 145 | target |= mask; 146 | mirrormask <<= 1; 147 | } 148 | if (target > pos) { 149 | std::complex tmp = data[pos]; 150 | data[pos] = data[target]; 151 | data[target] = tmp; 152 | } 153 | } 154 | 155 | for (unsigned int step = 1; step < dataLength; step <<= 1) { 156 | const unsigned int jump = step << 1; 157 | const float delta = M_PI / float(step); 158 | const float sine = sin(delta * 0.5); 159 | const std::complex mult (-2.*sine*sine, sin(delta)); 160 | std::complex factor(1.0, 0.0); 161 | 162 | for (unsigned int group = 0; group < step; ++group) { 163 | for (unsigned int pair = group; pair < dataLength; pair += jump) { 164 | const unsigned int match = pair + step; 165 | const std::complex prod(factor * data[match]); 166 | data[match] = data[pair] - prod; 167 | data[pair] += prod; 168 | } 169 | factor = mult * factor + factor; 170 | } 171 | } 172 | } 173 | 174 | double 175 | Spectrogram::getDeltaTime() { 176 | return deltaTime; 177 | } 178 | 179 | double 180 | Spectrogram::getHeadTime() { 181 | return headTime; 182 | } 183 | 184 | double 185 | Spectrogram::getFootTime() { 186 | return footTime; 187 | } 188 | -------------------------------------------------------------------------------- /spectrogram.h: -------------------------------------------------------------------------------- 1 | #ifndef SPECTROGRAM_H 2 | #define SPECTROGRAM_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class Spectrogram { 9 | public: 10 | Spectrogram(unsigned int _sampleRate, 11 | unsigned int _sampleLength, 12 | unsigned int _samplesPerLine, 13 | unsigned int _numLines); 14 | ~Spectrogram(); 15 | 16 | unsigned int processData(float *buffer, 17 | unsigned int bufferLength); 18 | 19 | double getDeltaTime(); 20 | double getFootTime(); 21 | double getHeadTime(); 22 | 23 | std::list > spectrogramData; 24 | std::list waveEnvelopeMin; 25 | std::list waveEnvelopeMax; 26 | std::list timeList; 27 | std::vector frequencyList; 28 | private: 29 | void removeFoot(unsigned int numLines); 30 | void addLine(float *fourierData, 31 | unsigned int dataLength, 32 | float envmin, 33 | float envmax); 34 | void FFTCompute(std::complex *data, 35 | unsigned int dataLength); 36 | 37 | unsigned int sampleRate; 38 | unsigned int sampleLength; 39 | unsigned int samplesPerLine; 40 | unsigned int fftSize; 41 | unsigned int numLines; 42 | 43 | float *waveRingBuffer; 44 | unsigned int ringBufferSize; 45 | unsigned int ringBufferInd; 46 | unsigned int sampleCounter; 47 | 48 | double headTime; 49 | double footTime; 50 | double deltaTime; 51 | 52 | }; 53 | 54 | #endif // SPECTROGRAM_H 55 | --------------------------------------------------------------------------------