Skip to content

Commit 9494c57

Browse files
committedJan 30, 2017
Initial commit.
0 parents  commit 9494c57

11 files changed

+1002
-0
lines changed
 

‎QSpectrogram.pro

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
SOURCES += main.cpp\
17+
mainwindow.cpp \
18+
spectrogram.cpp \
19+
qspectrogram.cpp \
20+
pulsethread.cpp
21+
22+
HEADERS += mainwindow.h \
23+
spectrogram.h \
24+
qspectrogram.h \
25+
pulsethread.h

‎README.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
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+

‎main.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include "mainwindow.h"
2+
#include <QApplication>
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+
}

‎mainwindow.cpp

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include "mainwindow.h"
2+
#include "spectrogram.h"
3+
#include "qspectrogram.h"
4+
#include "pulsethread.h"
5+
#include <QDebug>
6+
7+
MainWindow::MainWindow(QWidget *parent)
8+
: QMainWindow(parent) {
9+
spectrogram = new Spectrogram(44100, 44100 * 60, 256, 1024);
10+
11+
spectrogramWidget = new QSpectrogram(spectrogram, this);
12+
setCentralWidget(spectrogramWidget);
13+
14+
resize(1024, 600);
15+
QString device("alsa_output.pci-0000_00_1f.3.analog-stereo.monitor");
16+
//device = "alsa_input.pci-0000_00_1f.3.analog-stereo";
17+
18+
pulseThread = new PulseThread(device, 44100, 1024);
19+
pulseThread->start();
20+
21+
connect(pulseThread, SIGNAL(bufferFilled(float*,uint)),
22+
spectrogramWidget, SLOT(processData(float*,uint)));
23+
}
24+
25+
MainWindow::~MainWindow() {
26+
delete spectrogram;
27+
spectrogram = 0;
28+
}

‎mainwindow.h

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#ifndef MAINWINDOW_H
2+
#define MAINWINDOW_H
3+
4+
#include <QMainWindow>
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+
private:
19+
Spectrogram *spectrogram;
20+
QSpectrogram *spectrogramWidget;
21+
};
22+
23+
#endif // MAINWINDOW_H

‎pulsethread.cpp

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#include "pulsethread.h"
2+
#include <QCoreApplication>
3+
#include <QDebug>
4+
5+
#define RAW_BUFFERSIZE 8192
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+
if (numRead < 0) {
63+
qErrnoWarning(error, pa_strerror(error));
64+
QCoreApplication::quit();
65+
}
66+
if (numRead % 2 != 0) {
67+
qWarning("Non-even number of samples read!");
68+
} else {
69+
for (int indRead = 0; indRead < RAW_BUFFERSIZE / 8; indRead++) {
70+
bufferLeft[bufferIndex] = rawBuffer[indRead*2];
71+
bufferRight[bufferIndex] = rawBuffer[indRead*2 + 1];
72+
73+
bufferIndex++;
74+
75+
if (bufferIndex == bufferSize) {
76+
//qDebug() << "read";
77+
78+
for (int 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+
for (int ind = bufferSize-513; ind >= 0; ind--) {
86+
bufferLeft[ind] = copyBufferLeft[ind + 512];
87+
bufferRight[ind] = copyBufferRight[ind + 512];
88+
}
89+
bufferIndex = bufferSize - 512;*/
90+
}
91+
}
92+
}
93+
94+
}
95+
}
96+
97+
void
98+
PulseThread::stop() {
99+
if (paSimple) {
100+
pa_simple_free(paSimple);
101+
}
102+
stopped = true;
103+
}

‎pulsethread.h

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#ifndef PULSETHREAD_H
2+
#define PULSETHREAD_H
3+
4+
#include <QThread>
5+
6+
#include <stdio.h>
7+
#include <unistd.h>
8+
#include <string.h>
9+
#include <errno.h>
10+
#include <pulse/simple.h>
11+
#include <pulse/error.h>
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

‎qspectrogram.cpp

+454
Large diffs are not rendered by default.

‎qspectrogram.h

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#ifndef QSPECTROGRAM_H
2+
#define QSPECTROGRAM_H
3+
4+
#include <QWidget>
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+
private:
25+
void drawGrid(QPainter &painter);
26+
void refreshPixmap();
27+
void renderImage(unsigned int newLines,
28+
bool redraw);
29+
30+
int freqToPixel(double freq);
31+
int timeToPixel(double time);
32+
33+
// Limits of the plot;
34+
double minFreq;
35+
double maxFreq;
36+
double minAmpl;
37+
double maxAmpl;
38+
39+
double freqTickBig;
40+
double freqTickSmall;
41+
42+
unsigned int paddingX;
43+
unsigned int paddingY;
44+
unsigned int ylabelSpacing;
45+
unsigned int xlabelSpacing;
46+
47+
unsigned int plotx, ploty, plotwidth, plotheight;
48+
49+
double timeScroll;
50+
51+
enum {
52+
DRAWMODE_OFF,
53+
DRAWMODE_SCROLL,
54+
DRAWMODE_SWEEP_SINGLE,
55+
DRAWMODE_SWEEP_REPEAT
56+
};
57+
58+
enum {
59+
LAYOUT_HORIZONTAL,
60+
LAYOUT_VERTICAL
61+
};
62+
63+
void evalColormap(float value, int &r, int &g, int &b);
64+
65+
unsigned int drawMode;
66+
unsigned int layoutMode;
67+
bool logScaleFreq;
68+
bool logScaleAmpl;
69+
bool drawTimeGrid;
70+
bool drawFreqGrid;
71+
bool drawColorbar;
72+
73+
QImage *image;
74+
QPixmap pixmap;
75+
QColor backgroundColor;
76+
QColor gridColor;
77+
78+
QVector<QVector<float> > colormap;
79+
80+
Spectrogram *spectrogram;
81+
};
82+
83+
#endif // QSPECTROGRAM_H

‎spectrogram.cpp

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#include "spectrogram.h"
2+
#include <assert.h>
3+
#include <iostream>
4+
5+
Spectrogram::Spectrogram(unsigned int _sampleRate,
6+
unsigned int _sampleLength,
7+
unsigned int _samplesPerLine,
8+
unsigned int _numLines) {
9+
sampleRate = _sampleRate;
10+
sampleLength = _sampleLength;
11+
samplesPerLine = _samplesPerLine;
12+
numLines = _numLines;
13+
ringBufferSize = (numLines - 1) * samplesPerLine + sampleLength;
14+
sampleCounter = 0;
15+
16+
waveRingBuffer = new float[ringBufferSize];
17+
std::fill_n(waveRingBuffer, ringBufferSize, 0.0f);
18+
19+
headTime = 0.0f;
20+
deltaTime = 0.0f;
21+
deltaTime = ((double)samplesPerLine)/((double)sampleRate);
22+
fftSize = 4096;
23+
24+
for (unsigned int indFreq = 0; indFreq < fftSize; indFreq++) {
25+
float freq = ((float)(indFreq)) * ((float)sampleRate) /((float)fftSize);
26+
std::cout << freq << std::endl;
27+
frequencyList.push_back(freq);
28+
}
29+
}
30+
31+
Spectrogram::~Spectrogram() {
32+
delete [] waveRingBuffer;
33+
waveRingBuffer = 0;
34+
}
35+
36+
void
37+
Spectrogram::setSampleParameters(unsigned int _sampleRate,
38+
unsigned int _sampleLength,
39+
unsigned int _samplesPerLine,
40+
bool recompute) {
41+
42+
}
43+
44+
unsigned int
45+
Spectrogram::processData(float *buffer,
46+
unsigned int bufferLength) {
47+
unsigned int newLines = 0;
48+
49+
for (unsigned int bufferInd = 0; bufferInd < bufferLength; bufferInd++) {
50+
waveRingBuffer[ringBufferInd] = buffer[bufferInd];
51+
ringBufferInd = (ringBufferInd + 1) % ringBufferSize;
52+
sampleCounter++;
53+
54+
//std::cout << bufferInd << std::endl;
55+
56+
if (sampleCounter == fftSize) {
57+
sampleCounter -= samplesPerLine;
58+
59+
newLines++;
60+
61+
//std::cout << "FFT" << std::endl;
62+
63+
// Fill the fftData array with most recent sample data from the ring buffer:
64+
std::complex<float> *fftData = new std::complex<float>[fftSize];
65+
float *fftAbs = new float[fftSize];
66+
unsigned int startIndex = (ringBufferInd - fftSize + ringBufferSize) % ringBufferSize;
67+
68+
for (unsigned int indBuffer = 0; indBuffer < fftSize; indBuffer++) {
69+
unsigned int sampleIndex = (startIndex + indBuffer) % ringBufferSize;
70+
fftData[indBuffer] = waveRingBuffer[sampleIndex];
71+
//std::cout << sampleIndex << " " << indBuffer << " " << fftData[indBuffer] << std::endl;
72+
}
73+
74+
FFTCompute(fftData, fftSize);
75+
76+
// Compute the absolute value of each complex Fouerier coefficient and assemble
77+
// them into a array:
78+
for (unsigned int indBuffer = 0; indBuffer < fftSize; indBuffer++) {
79+
fftAbs[indBuffer] = std::abs(fftData[indBuffer]) / ((float)fftSize);
80+
}
81+
// Store the new line in the spectrogram:
82+
addLine(fftAbs, fftSize);
83+
84+
delete [] fftData;
85+
delete [] fftAbs;
86+
}
87+
}
88+
return newLines;
89+
}
90+
91+
void
92+
Spectrogram::removeFoot(unsigned int numLines) {
93+
for (unsigned int indLine = 0; indLine < numLines; indLine++) {
94+
assert(!spectrogramData.empty());
95+
assert(!timeList.empty());
96+
spectrogramData.pop_front();
97+
timeList.pop_front();
98+
99+
footTime += deltaTime;
100+
}
101+
}
102+
103+
void
104+
Spectrogram::addLine(float *fourierData,
105+
unsigned int dataLength) {
106+
std::vector<float> fourierDataVec;
107+
108+
if (spectrogramData.size() >= numLines) {
109+
removeFoot(spectrogramData.size() - numLines + 1);
110+
}
111+
fourierDataVec.assign(fourierData, fourierData + dataLength);
112+
spectrogramData.push_back(fourierDataVec);
113+
114+
headTime += deltaTime;
115+
timeList.push_back(headTime);
116+
}
117+
118+
void
119+
Spectrogram::FFTCompute(std::complex<float> *data,
120+
unsigned int dataLength) {
121+
for (unsigned int pos= 0; pos < dataLength; pos++) {
122+
unsigned int mask = dataLength;
123+
unsigned int mirrormask = 1;
124+
unsigned int target = 0;
125+
126+
while (mask != 1) {
127+
mask >>= 1;
128+
if (pos & mirrormask)
129+
target |= mask;
130+
mirrormask <<= 1;
131+
}
132+
if (target > pos) {
133+
std::complex<float> tmp = data[pos];
134+
data[pos] = data[target];
135+
data[target] = tmp;
136+
}
137+
}
138+
139+
for (unsigned int step = 1; step < dataLength; step <<= 1) {
140+
const unsigned int jump = step << 1;
141+
const float delta = M_PI / float(step);
142+
const float sine = sin(delta * 0.5);
143+
const std::complex<float> mult (-2.*sine*sine, sin(delta));
144+
std::complex<float> factor(1.0, 0.0);
145+
146+
for (unsigned int group = 0; group < step; ++group) {
147+
for (unsigned int pair = group; pair < dataLength; pair += jump) {
148+
const unsigned int match = pair + step;
149+
const std::complex<float> prod(factor * data[match]);
150+
data[match] = data[pair] - prod;
151+
data[pair] += prod;
152+
}
153+
factor = mult * factor + factor;
154+
}
155+
}
156+
}
157+
158+
double
159+
Spectrogram::getDeltaTime() {
160+
return deltaTime;
161+
}
162+
163+
double
164+
Spectrogram::getHeadTime() {
165+
return headTime;
166+
}
167+
168+
double
169+
Spectrogram::getFootTime() {
170+
return footTime;
171+
}

‎spectrogram.h

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#ifndef SPECTROGRAM_H
2+
#define SPECTROGRAM_H
3+
4+
#include <list>
5+
#include <vector>
6+
#include <complex>
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+
void setSampleParameters(unsigned int _sampleRate,
17+
unsigned int _sampleLength,
18+
unsigned int _samplesPerLine,
19+
bool recompute = true);
20+
unsigned int processData(float *buffer,
21+
unsigned int bufferLength);
22+
23+
double getDeltaTime();
24+
double getFootTime();
25+
double getHeadTime();
26+
27+
std::list<std::vector<float> > spectrogramData;
28+
std::list<float> timeList;
29+
std::vector<float> frequencyList;
30+
private:
31+
void removeFoot(unsigned int numLines);
32+
void addLine(float *fourierData,
33+
unsigned int dataLength);
34+
void FFTCompute(std::complex<float> *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

0 commit comments

Comments
 (0)
Please sign in to comment.