#pragma once
#include <QWidget>
#include <QHBoxLayout>
#include <QLabel>
#include <QPixmap>
#include <QPainter>
#include <QTimer>
#include <fftw3.h>
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
class SpectrumWidget : public QOpenGLWidget, protected QOpenGLFunctions {
Q_OBJECT
public:
explicit SpectrumWidget(QWidget* parent = nullptr) :
QOpenGLWidget(parent),
m_data(1000, 0),
m_spectrumData(m_data.size()),
m_timer(new QTimer(this)),
m_xRot(-90.0f),
m_yRot(0.0f),
m_zRot(0.0f)
{
}
~SpectrumWidget() override {
delete m_timer;
fftw_destroy_plan(m_plan);
}
void start() {
m_timer->start(50);
}
void stop() {
m_timer->stop();
}
protected:
void initializeGL() override {
initializeOpenGLFunctions();
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_SMOOTH);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepth(1.0f);
}
void resizeGL(int w, int h) override {
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0f, static_cast<GLfloat>(w)/static_cast<GLfloat>(h), 0.1f, 100.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void paintGL() override {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(0.0f, 0.0f, -10.0f);
glRotatef(m_xRot, 1.0f, 0.0f, 0.0f);
glRotatef(m_yRot, 0.0f, 1.0f, 0.0f);
glRotatef(m_zRot, 0.0f, 0.0f, 1.0f);
// 绘制频谱
glBegin(GL_QUADS);
for (int i = 0; i < m_spectrumData.size() / 2; ++i) {
GLfloat x1 = static_cast<GLfloat>(i * 2);
GLfloat y1 = static_cast<GLfloat>(m_spectrumData[i]);
GLfloat x2 = static_cast<GLfloat>(i * 2 + 2);
GLfloat y2 = static_cast<GLfloat>(m_spectrumData[i + 1]);
glColor3f(y1 / 200.0f, 0.0f, y2 / 200.0f);
glVertex3f(x1, y1, 0.0f);
glVertex3f(x2, y2, 0.0f);
glVertex3f(x2, 0.0f, 0.0f);
glVertex3f(x1, 0.0f, 0.0f);
}
glEnd();
}
private:
void onTimeout() {
// 更新数据
for (int i = 1; i < m_data.size(); ++i) {
m_data[i - 1] = m_data[i];
}
m_data[m_data.size() - 1] = rand() % 200;
// 计算频谱
fftw_execute(m_plan);
// 更新图像
m_spectrumData = m_spectrumData.mid(1);
m_spectrumData.append(m_spectrumData.last());
for (int i = 0; i < m_spectrumData.size(); ++i) {
m_spectrumData[i] = static_cast<int>(m_spectrumData[i] * 0.9 + m_data[i] * 0.1);
}
update();
}
void mousePressEvent(QMouseEvent* event) override {
m_lastPos = event->pos();
}
void mouseMoveEvent(QMouseEvent* event) override {
int dx = event->x() - m_lastPos.x();
int dy = event->y() - m_lastPos.y();
if (event->buttons() & Qt::LeftButton) {
m_xRot += dy;
m_yRot += dx;
} else if (event->buttons() & Qt::RightButton) {
m_xRot += dy;
m_zRot += dx;
}
m_lastPos = event->pos();
update();
}
private:
QVector<double> m_data;
QVector<double> m_spectrumData;
QTimer* m_timer;
fftw_plan m_plan;
int m_xRot, m_yRot, m_zRot;
QPoint m_lastPos;
};
该Demo继承自QOpenGLWidget,实现了一个用于显示3D频谱图的控件。在该控件中,使用QVector来保存频谱图中每一点的数据以及用于计算频谱的原始数据。使用QOpenGLWidget来绘制频谱图,并通过鼠标事件来控制频谱的旋转。
在绘制频谱时,我们使用OpenGL的立方体来表达每个频谱数据点。为了使图像更加美观,我们通过颜色来代表高度。代码中,我们通过调用QOpenGLFunctions类中的函数来初始化OpenGL环境,并实现了三个OpenGL事件函数,分别是initializeGL、resizeGL和paintGL。在paintGL函数中,对于每一个频率,我们以其对应的高度作为颜色的绿色通道值,然后绘制该立方体。
需要注意的是,为了使频谱更易于观察,代码还实现了鼠标拖动事件处理函数,通过鼠标拖动来控制频谱的角度,从而使频谱的变化更加立体丰富。