Improved controls with menus

This commit is contained in:
2019-09-07 19:19:24 +02:00
parent 8076271edc
commit b99a695159
10 changed files with 271 additions and 165 deletions

View File

@@ -3,6 +3,7 @@
// Qt includes // Qt includes
#include <QIODevice> #include <QIODevice>
#include <QAudioInput> #include <QAudioInput>
#include <QDebug>
namespace { namespace {
//! private helper to allow QAudioInput to write to a io device //! private helper to allow QAudioInput to write to a io device
@@ -10,6 +11,7 @@ class AudioDeviceHelper : public QIODevice
{ {
public: public:
explicit AudioDeviceHelper(AudioDevice &audioDevice); explicit AudioDeviceHelper(AudioDevice &audioDevice);
~AudioDeviceHelper();
qint64 readData(char *data, qint64 maxlen) override; qint64 readData(char *data, qint64 maxlen) override;
qint64 writeData(const char *data, qint64 len) override; qint64 writeData(const char *data, qint64 len) override;
@@ -17,11 +19,26 @@ public:
private: private:
AudioDevice &m_audioDevice; AudioDevice &m_audioDevice;
}; };
struct AudioDevicePrivate {
AudioDevicePrivate(AudioDevice &audioDevice, const QAudioDeviceInfo &audioDeviceInfo, const QAudioFormat &format) :
helper(audioDevice), input(audioDeviceInfo, format)
{
qDebug() << audioDeviceInfo.deviceName();
}
~AudioDevicePrivate()
{
qDebug() << "called";
}
AudioDeviceHelper helper;
QAudioInput input;
};
} }
AudioDevice::AudioDevice(QObject *parent) : AudioDevice::AudioDevice(QObject *parent) :
BaseDevice{parent}, BaseDevice{parent}
m_helper(std::make_unique<AudioDeviceHelper>(*this))
{ {
} }
@@ -29,6 +46,8 @@ AudioDevice::~AudioDevice() = default;
void AudioDevice::start() void AudioDevice::start()
{ {
qDebug() << m_device.deviceName();
QAudioFormat format; QAudioFormat format;
format.setSampleRate(m_samplerate); format.setSampleRate(m_samplerate);
format.setChannelCount(2); format.setChannelCount(2);
@@ -37,14 +56,15 @@ void AudioDevice::start()
format.setCodec("audio/pcm"); format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian); format.setByteOrder(QAudioFormat::LittleEndian);
m_input = std::make_unique<QAudioInput>(m_device, format); m_private = std::make_unique<AudioDevicePrivate>(*this, m_device, format);
m_input->start(m_helper.get()); m_private->input.start(&m_private->helper);
m_input->setBufferSize(m_samplerate/m_framerate*sizeof(qint16)*2); //m_private->input.setBufferSize(m_samplerate/m_framerate*sizeof(qint16)*2);
} }
void AudioDevice::stop() void AudioDevice::stop()
{ {
m_input = nullptr; qDebug() << "called";
m_private = nullptr;
} }
@@ -54,9 +74,15 @@ AudioDeviceHelper::AudioDeviceHelper(AudioDevice &audioDevice) :
QIODevice(&audioDevice), QIODevice(&audioDevice),
m_audioDevice(audioDevice) m_audioDevice(audioDevice)
{ {
qDebug() << "called";
setOpenMode(QIODevice::WriteOnly); setOpenMode(QIODevice::WriteOnly);
} }
AudioDeviceHelper::~AudioDeviceHelper()
{
qDebug() << "called";
}
qint64 AudioDeviceHelper::readData(char *data, qint64 maxlen) qint64 AudioDeviceHelper::readData(char *data, qint64 maxlen)
{ {
Q_UNUSED(data) Q_UNUSED(data)

View File

@@ -9,7 +9,7 @@
#include <memory> #include <memory>
// forward declares // forward declares
namespace { class AudioDeviceHelper; } namespace { class AudioDevicePrivate; }
class QAudioInput; class QAudioInput;
class AudioDevice : public BaseDevice class AudioDevice : public BaseDevice
@@ -22,24 +22,18 @@ public:
void start() override; void start() override;
void stop() override; void stop() override;
bool running() const override { return m_input != nullptr; } bool running() const override { return m_private != nullptr; }
int samplerate() const override { return m_samplerate; } int samplerate() const override { return m_samplerate; }
void setSamplerate(int samplerate) override { Q_ASSERT(!running()); m_samplerate = samplerate; } void setSamplerate(int samplerate) override { Q_ASSERT(!running()); m_samplerate = samplerate; }
int framerate() const override { return m_framerate; }
void setFramerate(int framerate) override { Q_ASSERT(!running()); m_framerate = framerate; }
const auto &device() const { return m_device; } const auto &device() const { return m_device; }
void setDevice(const QAudioDeviceInfo &device) { Q_ASSERT(!running()); m_device = device; } void setDevice(const QAudioDeviceInfo &device) { Q_ASSERT(!running()); m_device = device; }
private: private:
const std::unique_ptr<AudioDeviceHelper> m_helper; std::unique_ptr<AudioDevicePrivate> m_private;
std::unique_ptr<QAudioInput> m_input;
int m_samplerate; int m_samplerate;
int m_framerate;
QAudioDeviceInfo m_device; QAudioDeviceInfo m_device;
}; };

View File

@@ -18,9 +18,6 @@ public:
virtual int samplerate() const = 0; virtual int samplerate() const = 0;
virtual void setSamplerate(int samplerate) = 0; virtual void setSamplerate(int samplerate) = 0;
virtual int framerate() const = 0;
virtual void setFramerate(int framerate) = 0;
signals: signals:
void samplesReceived(const SamplePair *begin, const SamplePair *end); void samplesReceived(const SamplePair *begin, const SamplePair *end);
}; };

View File

@@ -19,8 +19,8 @@ public:
int samplerate() const override { return m_samplerate; } int samplerate() const override { return m_samplerate; }
void setSamplerate(int samplerate) override { Q_ASSERT(!running()); m_samplerate = samplerate; } void setSamplerate(int samplerate) override { Q_ASSERT(!running()); m_samplerate = samplerate; }
int framerate() const override { return m_framerate; } int framerate() const { return m_framerate; }
void setFramerate(int framerate) override { Q_ASSERT(!running()); m_framerate = framerate; } void setFramerate(int framerate) { Q_ASSERT(!running()); m_framerate = framerate; }
protected: protected:
void timerEvent(QTimerEvent *event) override; void timerEvent(QTimerEvent *event) override;

View File

@@ -6,88 +6,164 @@
#include <QMessageBox> #include <QMessageBox>
#include <QStringBuilder> #include <QStringBuilder>
#include <QRadioButton> #include <QRadioButton>
#include <QLabel>
#include <QDebug>
// local includes // local includes
#include "audiodevice.h" #include "audiodevice.h"
#include "fakedevice.h" #include "fakedevice.h"
namespace {
constexpr int samplerates[] = { 44100, 48000, 96000, 192000 };
constexpr int refreshrates[] = { 15, 30, 50, 60 };
constexpr int zoomlevels[] = { 50, 75, 100, 200, 400, 800 };
template<typename T>
void setActionsEnabled(const T &actions, bool enabled)
{
for(auto action : actions)
action->setEnabled(enabled);
}
}
MainWindow::MainWindow(QWidget *parent) : MainWindow::MainWindow(QWidget *parent) :
QMainWindow{parent}, QMainWindow{parent},
m_ui{std::make_unique<Ui::MainWindow>()}, m_ui{std::make_unique<Ui::MainWindow>()},
m_audioDevices{QAudioDeviceInfo::availableDevices(QAudio::AudioInput)}, m_audioDevices{QAudioDeviceInfo::availableDevices(QAudio::AudioInput)},
m_input{std::make_unique<AudioDevice>()} m_statusLabel{*new QLabel}
//m_input{std::make_unique<FakeDevice>()}
{ {
m_ui->setupUi(this); m_ui->setupUi(this);
connect(m_input.get(), &BaseDevice::samplesReceived, m_ui->widget, &OsciWidget::renderSamples); m_ui->statusbar->addWidget(&m_statusLabel);
connect(m_ui->widget, &OsciWidget::statusUpdate, &m_statusLabel, &QLabel::setText);
// setting up menu File
connect(m_ui->actionStart, &QAction::triggered, this, &MainWindow::start);
connect(m_ui->actionStop, &QAction::triggered, this, &MainWindow::stop);
m_ui->action_Quit->setShortcut(QKeySequence::Quit);
// setting up menu Devices
for (const auto &device : m_audioDevices) for (const auto &device : m_audioDevices)
{ {
auto name = device.deviceName(); auto name = device.deviceName();
m_ui->comboBoxDevices->addItem(name); const auto action = m_ui->menu_Device->addAction(name);
action->setCheckable(true);
m_deviceGroup.addAction(action);
// Select last element containing monitor if available // Select last element containing monitor if available
if(name.contains("monitor")) if(name.contains("monitor"))
{ action->setChecked(true);
m_ui->comboBoxDevices->setCurrentIndex(m_ui->comboBoxDevices->count()-1);
}
} }
for (const auto samplerate : { 44100, 48000, 96000, 192000 }) // setting up menu Samplerates
m_ui->comboBoxSamplerate->addItem(tr("%0").arg(samplerate), samplerate); for (const auto samplerate : samplerates)
connect(m_ui->pushButtonToggle, &QAbstractButton::pressed, this, &MainWindow::toggle);
for (const auto framerate : {15, 30, 50, 60})
m_ui->comboBoxFps->addItem(tr("%0 FPS").arg(framerate), framerate);
connect(m_ui->comboBoxFps, qOverload<int>(&QComboBox::currentIndexChanged), m_ui->widget, [this](){
m_ui->widget->setFps(m_ui->comboBoxFps->currentData().toInt());
});
auto buttonGroup = new QButtonGroup;
buttonGroup->setExclusive(true);
for (auto factor : { .5f, 1.f, 2.f, 4.f, 8.f })
{ {
auto radioButton = new QRadioButton(QString::number(factor)); auto action = m_ui->menu_Samplerate->addAction(tr("%0").arg(samplerate));
connect(radioButton, &QRadioButton::pressed, this, [factor,&widget=*m_ui->widget](){ action->setCheckable(true);
widget.setFactor(factor); m_samplerateGroup.addAction(action);
});
m_ui->horizontalLayout->addWidget(radioButton);
} }
if (m_ui->comboBoxDevices->count()) m_samplerateGroup.actions().first()->setChecked(true);
toggle();
}
void MainWindow::toggle() // setting up menu Refreshrates
{ for (const auto refreshrate : refreshrates)
if (!m_ui->comboBoxDevices->count())
{ {
QMessageBox::warning(this, tr("Failed to start!"), tr("Failed to start!") % "\n\n" % tr("No audio devices available!")); auto action = m_ui->menu_Refreshrate->addAction(tr("%0FPS").arg(refreshrate));
return; action->setCheckable(true);
m_refreshrateGroup.addAction(action);
} }
if (m_input->running())
{ {
m_input->stop(); const auto index = std::find(std::begin(refreshrates), std::end(refreshrates), m_ui->widget->fps());
m_ui->comboBoxDevices->setEnabled(true); if (index != std::end(refreshrates))
m_ui->comboBoxSamplerate->setEnabled(true); m_refreshrateGroup.actions().at(std::distance(std::begin(refreshrates), index))->setChecked(true);
m_ui->pushButtonToggle->setText(""); }
connect(&m_refreshrateGroup, &QActionGroup::triggered, this, &MainWindow::refreshRateChanged);
// setting up menu Zoom
for (const auto zoomlevel : zoomlevels)
{
auto action = m_ui->menu_Zoom->addAction(tr("%0%").arg(zoomlevel));
action->setCheckable(true);
m_zoomlevelsGroup.addAction(action);
}
{
const auto index = std::find(std::begin(zoomlevels), std::end(zoomlevels), m_ui->widget->factor()*100);
if (index != std::end(zoomlevels))
m_zoomlevelsGroup.actions().at(std::distance(std::begin(zoomlevels), index))->setChecked(true);
}
connect(&m_zoomlevelsGroup, &QActionGroup::triggered, this, &MainWindow::zoomChanged);
// autostart
if (m_audioDevices.isEmpty())
{
m_ui->actionStart->setEnabled(false);
m_ui->actionStop->setEnabled(false);
} }
else else
start();
}
void MainWindow::start()
{
m_input = std::make_unique<AudioDevice>();
//m_input = std::make_unique<FakeDevice>();
{ {
m_input->setSamplerate(m_ui->comboBoxSamplerate->currentData().toInt()); auto *checked = m_samplerateGroup.checkedAction();
m_input->setFramerate(60); const auto index = m_samplerateGroup.actions().indexOf(checked);
const auto samplerate = samplerates[index];
qDebug() << "samplerate: checked =" << checked << "index =" << index << "value =" << samplerate;
m_input->setSamplerate(samplerate);
}
connect(m_input.get(), &BaseDevice::samplesReceived, m_ui->widget, &OsciWidget::renderSamples);
if (auto audioDevice = dynamic_cast<AudioDevice*>(m_input.get())) if (auto audioDevice = dynamic_cast<AudioDevice*>(m_input.get()))
audioDevice->setDevice(m_audioDevices.at(m_ui->comboBoxDevices->currentIndex())); {
const auto device = m_audioDevices.at(m_deviceGroup.actions().indexOf(m_deviceGroup.checkedAction()));
qDebug() << "setDevice" << device.deviceName();
audioDevice->setDevice(device);
}
m_input->start(); m_input->start();
m_ui->comboBoxDevices->setEnabled(false); setActionsEnabled(m_deviceGroup.actions(), false);
m_ui->comboBoxSamplerate->setEnabled(false); setActionsEnabled(m_samplerateGroup.actions(), false);
m_ui->pushButtonToggle->setText("▮▮"); m_ui->actionStart->setEnabled(false);
} m_ui->actionStop->setEnabled(true);
}
void MainWindow::stop()
{
m_input = nullptr;
setActionsEnabled(m_deviceGroup.actions(), true);
setActionsEnabled(m_samplerateGroup.actions(), true);
m_ui->actionStart->setEnabled(true);
m_ui->actionStop->setEnabled(false);
}
void MainWindow::refreshRateChanged()
{
auto *checked = m_refreshrateGroup.checkedAction();
const auto index = m_refreshrateGroup.actions().indexOf(checked);
const auto refreshrate = refreshrates[index];
qDebug() << "refreshrate: checked =" << checked << "index =" << index << "value =" << refreshrate;
m_ui->widget->setFps(refreshrate);
}
void MainWindow::zoomChanged()
{
auto *checked = m_zoomlevelsGroup.checkedAction();
const auto index = m_zoomlevelsGroup.actions().indexOf(checked);
const auto zoomlevel = zoomlevels[index];
qDebug() << "zoomlevel: checked =" << checked << "index =" << index << "value =" << zoomlevel;
m_ui->widget->setFactor(zoomlevel/100.f);
} }
MainWindow::~MainWindow() = default; MainWindow::~MainWindow() = default;

View File

@@ -2,12 +2,14 @@
// Qt includes // Qt includes
#include <QMainWindow> #include <QMainWindow>
#include <QActionGroup>
// system includes // system includes
#include <memory> #include <memory>
// forward declares // forward declares
class QAudioDeviceInfo; class QAudioDeviceInfo;
class QLabel;
namespace Ui { class MainWindow; } namespace Ui { class MainWindow; }
class BaseDevice; class BaseDevice;
@@ -20,12 +22,19 @@ public:
~MainWindow() override; ~MainWindow() override;
private slots: private slots:
void toggle(); void start();
void stop();
void refreshRateChanged();
void zoomChanged();
private: private:
const std::unique_ptr<Ui::MainWindow> m_ui; const std::unique_ptr<Ui::MainWindow> m_ui;
const QList<QAudioDeviceInfo> m_audioDevices; const QList<QAudioDeviceInfo> m_audioDevices;
const std::unique_ptr<BaseDevice> m_input; std::unique_ptr<BaseDevice> m_input;
QActionGroup m_deviceGroup{this}, m_samplerateGroup{this}, m_refreshrateGroup{this}, m_zoomlevelsGroup{this};
QLabel &m_statusLabel;
}; };

View File

@@ -13,65 +13,7 @@
<property name="windowTitle"> <property name="windowTitle">
<string>MainWindow</string> <string>MainWindow</string>
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="OsciWidget" name="widget"/>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="labelDevice">
<property name="text">
<string>Device:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBoxDevices"/>
</item>
<item>
<widget class="QComboBox" name="comboBoxSamplerate"/>
</item>
<item>
<widget class="QComboBox" name="comboBoxFps"/>
</item>
<item>
<widget class="QPushButton" name="pushButtonToggle">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="OsciWidget" name="widget" native="true"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar"> <widget class="QMenuBar" name="menubar">
<property name="geometry"> <property name="geometry">
<rect> <rect>
@@ -81,19 +23,57 @@
<height>20</height> <height>20</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menu_File">
<property name="title">
<string>&amp;File</string>
</property>
<addaction name="actionStart"/>
<addaction name="actionStop"/>
<addaction name="separator"/>
<addaction name="action_Quit"/>
</widget>
<widget class="QMenu" name="menu_Device">
<property name="title">
<string>&amp;Device</string>
</property>
</widget>
<widget class="QMenu" name="menu_Samplerate">
<property name="title">
<string>&amp;Samplerate</string>
</property>
</widget>
<widget class="QMenu" name="menu_Refreshrate">
<property name="title">
<string>&amp;Refreshrate</string>
</property>
</widget>
<widget class="QMenu" name="menu_Zoom">
<property name="title">
<string>&amp;Zoom</string>
</property>
</widget>
<addaction name="menu_File"/>
<addaction name="menu_Device"/>
<addaction name="menu_Samplerate"/>
<addaction name="menu_Refreshrate"/>
<addaction name="menu_Zoom"/>
</widget> </widget>
<widget class="QStatusBar" name="statusbar"/> <widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar"> <action name="action_Quit">
<property name="windowTitle"> <property name="text">
<string>toolBar</string> <string>&amp;Quit</string>
</property> </property>
<attribute name="toolBarArea"> </action>
<enum>TopToolBarArea</enum> <action name="actionStart">
</attribute> <property name="text">
<attribute name="toolBarBreak"> <string>Start</string>
<bool>false</bool> </property>
</attribute> </action>
</widget> <action name="actionStop">
<property name="text">
<string>Stop</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
@@ -104,5 +84,22 @@
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections/> <connections>
<connection>
<sender>action_Quit</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
<y>429</y>
</hint>
</hints>
</connection>
</connections>
</ui> </ui>

View File

@@ -5,6 +5,7 @@
template<typename T> template<typename T>
struct SamplePairT { struct SamplePairT {
typedef T Type;
T first, second; T first, second;
}; };

View File

@@ -11,7 +11,7 @@ OsciWidget::OsciWidget(QWidget *parent) :
QOpenGLWidget{parent}, QOpenGLWidget{parent},
m_redrawTimerId(startTimer(1000/m_fps)) m_redrawTimerId(startTimer(1000/m_fps))
{ {
m_fpsTimer.start(); m_statsTimer.start();
} }
void OsciWidget::setFps(int fps) void OsciWidget::setFps(int fps)
@@ -35,30 +35,35 @@ void OsciWidget::paintEvent(QPaintEvent *event)
QWidget::paintEvent(event); QWidget::paintEvent(event);
m_frameCounter++; m_frameCounter++;
if (m_fpsTimer.hasExpired(1000)) if (m_statsTimer.hasExpired(1000))
{ {
m_statsDisplay = QString("%0FPS (%1 callbacks)").arg(m_frameCounter).arg(m_callbacksCounter); emit statusUpdate(QString("%0FPS (%1 audio callbacks)").arg(m_frameCounter).arg(m_callbacksCounter));
m_frameCounter = 0; m_frameCounter = 0;
m_callbacksCounter = 0; m_callbacksCounter = 0;
m_fpsTimer.restart(); m_statsTimer.restart();
} }
QPainter painter; if (m_pixmap.size() != size())
painter.begin(this); m_pixmap = QPixmap(size());
// draw background QPainter painter;
painter.setBrush(Qt::black); painter.begin(&m_pixmap);
painter.drawRect(rect());
// darkening last frame
painter.setCompositionMode(QPainter::CompositionMode_Multiply);
painter.setPen({});
painter.setBrush(QColor(150,150,150 ));
painter.drawRect(m_pixmap.rect());
// drawing new lines ontop // drawing new lines ontop
QPen pen; QPen pen;
pen.setWidth(2); pen.setWidth(2);
pen.setColor(QColor(0, 255, 0)); pen.setColor(QColor(0, 255, 0));
painter.setPen(pen); painter.setPen(pen);
painter.translate(width()/2, height()/2); painter.translate(m_pixmap.width()/2, m_pixmap.height()/2);
painter.setCompositionMode(QPainter::CompositionMode_Plus); painter.setCompositionMode(QPainter::CompositionMode_Plus);
const auto pointToCoordinates = [width=width()/2,height=height()/2,factor=m_factor](const QPointF &point) const auto pointToCoordinates = [width=m_pixmap.width()/2,height=m_pixmap.height()/2,factor=m_factor](const QPointF &point)
{ {
return QPoint{ return QPoint{
int(point.x() * factor * width), int(point.x() * factor * width),
@@ -75,26 +80,23 @@ void OsciWidget::paintEvent(QPaintEvent *event)
const QLineF line(m_lastPoint, p); const QLineF line(m_lastPoint, p);
painter.setOpacity(std::min(1.0, 1. / ((line.length() * 100) + 1))); painter.setOpacity(std::min(1.0, 1. / ((line.length() * 75) + 1)));
painter.drawLine(pointToCoordinates(m_lastPoint), pointToCoordinates(p)); painter.drawLine(pointToCoordinates(m_lastPoint), pointToCoordinates(p));
m_lastPoint = p; m_lastPoint = p;
} }
painter.resetTransform();
painter.setOpacity(1); painter.setOpacity(1);
painter.resetTransform();
m_buffer.clear();
// draw stats
painter.setPen(Qt::white);
painter.setBrush(Qt::white);
QFont font;
font.setPixelSize(24);
painter.drawText(20, 20, m_statsDisplay);
painter.end(); painter.end();
painter.begin(this);
painter.drawPixmap(0, 0, m_pixmap);
painter.end();
m_buffer.clear();
} }
void OsciWidget::timerEvent(QTimerEvent *event) void OsciWidget::timerEvent(QTimerEvent *event)

View File

@@ -22,6 +22,9 @@ public:
float factor() const { return m_factor; } float factor() const { return m_factor; }
int fps() const { return m_fps; } int fps() const { return m_fps; }
signals:
void statusUpdate(const QString &status);
public slots: public slots:
void setFactor(float factor) { m_factor = factor; } void setFactor(float factor) { m_factor = factor; }
void setFps(int fps); void setFps(int fps);
@@ -38,11 +41,12 @@ private:
QPointF m_lastPoint; QPointF m_lastPoint;
int m_frameCounter{0}, m_callbacksCounter{0}; int m_frameCounter{0}, m_callbacksCounter{0};
QString m_statsDisplay; QElapsedTimer m_statsTimer;
QElapsedTimer m_fpsTimer;
int m_fps{15}; int m_fps{15};
int m_redrawTimerId; int m_redrawTimerId;
std::vector<SamplePair> m_buffer; std::vector<SamplePair> m_buffer;
QPixmap m_pixmap;
}; };