diff --git a/audiodevice.cpp b/audiodevice.cpp index a2f4d4f..7926988 100644 --- a/audiodevice.cpp +++ b/audiodevice.cpp @@ -3,6 +3,7 @@ // Qt includes #include #include +#include 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(*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(m_device, format); - m_input->start(m_helper.get()); - m_input->setBufferSize(m_samplerate/m_framerate*sizeof(qint16)*2); + m_private = std::make_unique(*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) diff --git a/audiodevice.h b/audiodevice.h index 0c559dc..bcd319b 100644 --- a/audiodevice.h +++ b/audiodevice.h @@ -9,7 +9,7 @@ #include // 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 m_helper; - - std::unique_ptr m_input; + std::unique_ptr m_private; int m_samplerate; - int m_framerate; QAudioDeviceInfo m_device; }; diff --git a/basedevice.h b/basedevice.h index d14db8e..cbc45b8 100644 --- a/basedevice.h +++ b/basedevice.h @@ -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); }; diff --git a/fakedevice.h b/fakedevice.h index f120096..b8df4f8 100644 --- a/fakedevice.h +++ b/fakedevice.h @@ -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; diff --git a/mainwindow.cpp b/mainwindow.cpp index fe8fa54..e6d0d3c 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -6,88 +6,164 @@ #include #include #include +#include +#include // 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 +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()}, m_audioDevices{QAudioDeviceInfo::availableDevices(QAudio::AudioInput)}, - m_input{std::make_unique()} - //m_input{std::make_unique()} + 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(&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); -void MainWindow::toggle() -{ - if (!m_ui->comboBoxDevices->count()) + // setting up menu Refreshrates + for (const auto refreshrate : refreshrates) { - QMessageBox::warning(this, tr("Failed to start!"), tr("Failed to start!") % "\n\n" % tr("No audio devices available!")); - return; + auto action = m_ui->menu_Refreshrate->addAction(tr("%0FPS").arg(refreshrate)); + action->setCheckable(true); + m_refreshrateGroup.addAction(action); } - if (m_input->running()) { - m_input->stop(); - m_ui->comboBoxDevices->setEnabled(true); - m_ui->comboBoxSamplerate->setEnabled(true); - m_ui->pushButtonToggle->setText("▶"); + 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); + } + + 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 - { - m_input->setSamplerate(m_ui->comboBoxSamplerate->currentData().toInt()); - m_input->setFramerate(60); - if (auto audioDevice = dynamic_cast(m_input.get())) - audioDevice->setDevice(m_audioDevices.at(m_ui->comboBoxDevices->currentIndex())); - m_input->start(); + start(); +} - m_ui->comboBoxDevices->setEnabled(false); - m_ui->comboBoxSamplerate->setEnabled(false); - m_ui->pushButtonToggle->setText("▮▮"); +void MainWindow::start() +{ + m_input = std::make_unique(); + //m_input = std::make_unique(); + + { + 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(m_input.get())) + { + const auto device = m_audioDevices.at(m_deviceGroup.actions().indexOf(m_deviceGroup.checkedAction())); + qDebug() << "setDevice" << device.deviceName(); + audioDevice->setDevice(device); + } + m_input->start(); + + 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; diff --git a/mainwindow.h b/mainwindow.h index 838b856..9a290f2 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -2,12 +2,14 @@ // Qt includes #include +#include // system includes #include // 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 m_ui; const QList m_audioDevices; - const std::unique_ptr m_input; + std::unique_ptr m_input; + + QActionGroup m_deviceGroup{this}, m_samplerateGroup{this}, m_refreshrateGroup{this}, m_zoomlevelsGroup{this}; + + QLabel &m_statusLabel; }; diff --git a/mainwindow.ui b/mainwindow.ui index 73b5d8a..f083690 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -13,65 +13,7 @@ MainWindow - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - Device: - - - - - - - - - - - - - - - - PushButton - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - + @@ -81,19 +23,57 @@ 20 + + + &File + + + + + + + + + &Device + + + + + &Samplerate + + + + + &Refreshrate + + + + + &Zoom + + + + + + + - - - toolBar + + + &Quit - - TopToolBarArea - - - false - - + + + + Start + + + + + Stop + + @@ -104,5 +84,22 @@ - + + + action_Quit + triggered() + MainWindow + close() + + + -1 + -1 + + + 399 + 429 + + + + diff --git a/oscicommon.h b/oscicommon.h index a02c40d..39e7d19 100644 --- a/oscicommon.h +++ b/oscicommon.h @@ -5,6 +5,7 @@ template struct SamplePairT { + typedef T Type; T first, second; }; diff --git a/osciwidget.cpp b/osciwidget.cpp index f53965d..8d18efc 100644 --- a/osciwidget.cpp +++ b/osciwidget.cpp @@ -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) diff --git a/osciwidget.h b/osciwidget.h index ae2cc1c..26bd932 100644 --- a/osciwidget.h +++ b/osciwidget.h @@ -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 m_buffer; + + QPixmap m_pixmap; };