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

View File

@@ -9,7 +9,7 @@
#include <memory>
// forward declares
namespace { class AudioDeviceHelper; }
namespace { class AudioDevicePrivate; }
class QAudioInput;
class AudioDevice : public BaseDevice
@@ -22,24 +22,18 @@ public:
void start() 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; }
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; }
void setDevice(const QAudioDeviceInfo &device) { Q_ASSERT(!running()); m_device = device; }
private:
const std::unique_ptr<AudioDeviceHelper> m_helper;
std::unique_ptr<QAudioInput> m_input;
std::unique_ptr<AudioDevicePrivate> m_private;
int m_samplerate;
int m_framerate;
QAudioDeviceInfo m_device;
};

View File

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

View File

@@ -19,8 +19,8 @@ public:
int samplerate() const override { return m_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; }
int framerate() const { return m_framerate; }
void setFramerate(int framerate) { Q_ASSERT(!running()); m_framerate = framerate; }
protected:
void timerEvent(QTimerEvent *event) override;

View File

@@ -6,88 +6,164 @@
#include <QMessageBox>
#include <QStringBuilder>
#include <QRadioButton>
#include <QLabel>
#include <QDebug>
// local includes
#include "audiodevice.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) :
QMainWindow{parent},
m_ui{std::make_unique<Ui::MainWindow>()},
m_audioDevices{QAudioDeviceInfo::availableDevices(QAudio::AudioInput)},
m_input{std::make_unique<AudioDevice>()}
//m_input{std::make_unique<FakeDevice>()}
m_statusLabel{*new QLabel}
{
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)
{
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
if(name.contains("monitor"))
{
m_ui->comboBoxDevices->setCurrentIndex(m_ui->comboBoxDevices->count()-1);
}
action->setChecked(true);
}
for (const auto samplerate : { 44100, 48000, 96000, 192000 })
m_ui->comboBoxSamplerate->addItem(tr("%0").arg(samplerate), samplerate);
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 })
// setting up menu Samplerates
for (const auto samplerate : samplerates)
{
auto radioButton = new QRadioButton(QString::number(factor));
connect(radioButton, &QRadioButton::pressed, this, [factor,&widget=*m_ui->widget](){
widget.setFactor(factor);
});
m_ui->horizontalLayout->addWidget(radioButton);
auto action = m_ui->menu_Samplerate->addAction(tr("%0").arg(samplerate));
action->setCheckable(true);
m_samplerateGroup.addAction(action);
}
if (m_ui->comboBoxDevices->count())
toggle();
m_samplerateGroup.actions().first()->setChecked(true);
// setting up menu Refreshrates
for (const auto refreshrate : refreshrates)
{
auto action = m_ui->menu_Refreshrate->addAction(tr("%0FPS").arg(refreshrate));
action->setCheckable(true);
m_refreshrateGroup.addAction(action);
}
void MainWindow::toggle()
{
if (!m_ui->comboBoxDevices->count())
{
QMessageBox::warning(this, tr("Failed to start!"), tr("Failed to start!") % "\n\n" % tr("No audio devices available!"));
return;
const auto index = std::find(std::begin(refreshrates), std::end(refreshrates), m_ui->widget->fps());
if (index != std::end(refreshrates))
m_refreshrateGroup.actions().at(std::distance(std::begin(refreshrates), index))->setChecked(true);
}
if (m_input->running())
connect(&m_refreshrateGroup, &QActionGroup::triggered, this, &MainWindow::refreshRateChanged);
// setting up menu Zoom
for (const auto zoomlevel : zoomlevels)
{
m_input->stop();
m_ui->comboBoxDevices->setEnabled(true);
m_ui->comboBoxSamplerate->setEnabled(true);
m_ui->pushButtonToggle->setText("");
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
start();
}
void MainWindow::start()
{
m_input->setSamplerate(m_ui->comboBoxSamplerate->currentData().toInt());
m_input->setFramerate(60);
m_input = std::make_unique<AudioDevice>();
//m_input = std::make_unique<FakeDevice>();
{
auto *checked = m_samplerateGroup.checkedAction();
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()))
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_ui->comboBoxDevices->setEnabled(false);
m_ui->comboBoxSamplerate->setEnabled(false);
m_ui->pushButtonToggle->setText("▮▮");
setActionsEnabled(m_deviceGroup.actions(), false);
setActionsEnabled(m_samplerateGroup.actions(), false);
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;

View File

@@ -2,12 +2,14 @@
// Qt includes
#include <QMainWindow>
#include <QActionGroup>
// system includes
#include <memory>
// forward declares
class QAudioDeviceInfo;
class QLabel;
namespace Ui { class MainWindow; }
class BaseDevice;
@@ -20,12 +22,19 @@ public:
~MainWindow() override;
private slots:
void toggle();
void start();
void stop();
void refreshRateChanged();
void zoomChanged();
private:
const std::unique_ptr<Ui::MainWindow> m_ui;
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">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<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="OsciWidget" name="widget"/>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
@@ -81,19 +23,57 @@
<height>20</height>
</rect>
</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 class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
<action name="action_Quit">
<property name="text">
<string>&amp;Quit</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
</action>
<action name="actionStart">
<property name="text">
<string>Start</string>
</property>
</action>
<action name="actionStop">
<property name="text">
<string>Stop</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
@@ -104,5 +84,22 @@
</customwidget>
</customwidgets>
<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>

View File

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

View File

@@ -11,7 +11,7 @@ OsciWidget::OsciWidget(QWidget *parent) :
QOpenGLWidget{parent},
m_redrawTimerId(startTimer(1000/m_fps))
{
m_fpsTimer.start();
m_statsTimer.start();
}
void OsciWidget::setFps(int fps)
@@ -35,30 +35,35 @@ void OsciWidget::paintEvent(QPaintEvent *event)
QWidget::paintEvent(event);
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_callbacksCounter = 0;
m_fpsTimer.restart();
m_statsTimer.restart();
}
QPainter painter;
painter.begin(this);
if (m_pixmap.size() != size())
m_pixmap = QPixmap(size());
// draw background
painter.setBrush(Qt::black);
painter.drawRect(rect());
QPainter painter;
painter.begin(&m_pixmap);
// 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
QPen pen;
pen.setWidth(2);
pen.setColor(QColor(0, 255, 0));
painter.setPen(pen);
painter.translate(width()/2, height()/2);
painter.translate(m_pixmap.width()/2, m_pixmap.height()/2);
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{
int(point.x() * factor * width),
@@ -75,26 +80,23 @@ void OsciWidget::paintEvent(QPaintEvent *event)
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));
m_lastPoint = p;
}
painter.resetTransform();
painter.setOpacity(1);
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.resetTransform();
painter.end();
painter.begin(this);
painter.drawPixmap(0, 0, m_pixmap);
painter.end();
m_buffer.clear();
}
void OsciWidget::timerEvent(QTimerEvent *event)

View File

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