diff --git a/audiodevice.cpp b/audiodevice.cpp index 7926988..88443b2 100644 --- a/audiodevice.cpp +++ b/audiodevice.cpp @@ -3,15 +3,13 @@ // Qt includes #include #include -#include namespace { //! private helper to allow QAudioInput to write to a io device class AudioDeviceHelper : public QIODevice { public: - explicit AudioDeviceHelper(AudioDevice &audioDevice); - ~AudioDeviceHelper(); + explicit AudioDeviceHelper(AudioDevice &audioDevice, QObject *parent = nullptr); qint64 readData(char *data, qint64 maxlen) override; qint64 writeData(const char *data, qint64 len) override; @@ -20,17 +18,9 @@ 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"; - } +class AudioDevicePrivate { +public: + AudioDevicePrivate(AudioDevice &audioDevice, const QAudioDeviceInfo &audioDeviceInfo, const QAudioFormat &format); AudioDeviceHelper helper; QAudioInput input; @@ -46,12 +36,12 @@ AudioDevice::~AudioDevice() = default; void AudioDevice::start() { - qDebug() << m_device.deviceName(); + Q_ASSERT(!running()); QAudioFormat format; format.setSampleRate(m_samplerate); format.setChannelCount(2); - format.setSampleSize(16); + format.setSampleSize(sizeof(SamplePair::Type) * 8); format.setSampleType(QAudioFormat::SignedInt); format.setCodec("audio/pcm"); format.setByteOrder(QAudioFormat::LittleEndian); @@ -63,26 +53,20 @@ void AudioDevice::start() void AudioDevice::stop() { - qDebug() << "called"; + Q_ASSERT(running()); + m_private = nullptr; } namespace { -AudioDeviceHelper::AudioDeviceHelper(AudioDevice &audioDevice) : - QIODevice(&audioDevice), - m_audioDevice(audioDevice) +AudioDeviceHelper::AudioDeviceHelper(AudioDevice &audioDevice, QObject *parent) : + QIODevice{parent}, m_audioDevice(audioDevice) { - qDebug() << "called"; setOpenMode(QIODevice::WriteOnly); } -AudioDeviceHelper::~AudioDeviceHelper() -{ - qDebug() << "called"; -} - qint64 AudioDeviceHelper::readData(char *data, qint64 maxlen) { Q_UNUSED(data) @@ -93,8 +77,15 @@ qint64 AudioDeviceHelper::readData(char *data, qint64 maxlen) qint64 AudioDeviceHelper::writeData(const char *data, qint64 len) { Q_ASSERT(len % sizeof(SamplePair) == 0); - emit m_audioDevice.samplesReceived(reinterpret_cast(data), - reinterpret_cast(data + (len/sizeof(SamplePair)))); + + m_audioDevice.emitSamples(reinterpret_cast(data), + reinterpret_cast(data) + (len/sizeof(SamplePair))); + return len; } + +AudioDevicePrivate::AudioDevicePrivate(AudioDevice &audioDevice, const QAudioDeviceInfo &audioDeviceInfo, const QAudioFormat &format) : + helper{audioDevice}, input{audioDeviceInfo, format} +{ +} } diff --git a/basedevice.h b/basedevice.h index cbc45b8..8613e6f 100644 --- a/basedevice.h +++ b/basedevice.h @@ -1,7 +1,9 @@ #pragma once +// Qt includes #include +// local includes #include "oscicommon.h" class BaseDevice : public QObject @@ -18,6 +20,8 @@ public: virtual int samplerate() const = 0; virtual void setSamplerate(int samplerate) = 0; + void emitSamples(const SamplePair *begin, const SamplePair *end) { emit samplesReceived(begin, end); } + signals: void samplesReceived(const SamplePair *begin, const SamplePair *end); }; diff --git a/basetonegenerator.cpp b/basetonegenerator.cpp new file mode 100644 index 0000000..94e65be --- /dev/null +++ b/basetonegenerator.cpp @@ -0,0 +1,87 @@ +#include "basetonegenerator.h" + +// Qt includes +#include + +namespace +{ +//! private helper to allow QAudioOutput to read from a io device +class BaseToneGeneratorHelper : public QIODevice +{ +public: + BaseToneGeneratorHelper(BaseToneGenerator &generator, QObject *parent = nullptr); + + qint64 readData(char *data, qint64 maxlen) override; + qint64 writeData(const char *data, qint64 len) override; + +private: + BaseToneGenerator &m_generator; +}; + +class BaseToneGeneratorPrivate +{ +public: + BaseToneGeneratorPrivate(BaseToneGenerator &generator, const QAudioDeviceInfo &audioDeviceInfo, const QAudioFormat &format); + ~BaseToneGeneratorPrivate() { output.stop(); } + + BaseToneGeneratorHelper helper; + QAudioOutput output; +}; + +} + +BaseToneGenerator::BaseToneGenerator() = default; + +BaseToneGenerator::~BaseToneGenerator() = default; + +void BaseToneGenerator::start() +{ + Q_ASSERT(!running()); + + QAudioFormat format; + format.setSampleRate(m_samplerate); + format.setChannelCount(2); + format.setSampleSize(sizeof(SamplePair::Type) * 8); + format.setSampleType(QAudioFormat::SignedInt); + format.setCodec("audio/pcm"); + format.setByteOrder(QAudioFormat::LittleEndian); + + m_private = std::make_unique(*this, m_device, format); + m_private->output.start(&m_private->helper); + +} + +void BaseToneGenerator::stop() +{ + Q_ASSERT(running()); + + m_private = nullptr; +} + +namespace { +BaseToneGeneratorHelper::BaseToneGeneratorHelper(BaseToneGenerator &generator, QObject *parent) : + QIODevice{parent}, m_generator{generator} +{ + setOpenMode(QIODevice::ReadOnly); +} + +qint64 BaseToneGeneratorHelper::readData(char *data, qint64 maxlen) +{ + Q_ASSERT(maxlen % sizeof(SamplePair) == 0); + + return m_generator.fill(reinterpret_cast(data), + reinterpret_cast(data) + (maxlen/sizeof(SamplePair))) * sizeof(SamplePair); +} + +qint64 BaseToneGeneratorHelper::writeData(const char *data, qint64 len) +{ + Q_UNUSED(data) + Q_UNUSED(len) + qFatal("writing is not allowed!"); +} + +BaseToneGeneratorPrivate::BaseToneGeneratorPrivate(BaseToneGenerator &generator, const QAudioDeviceInfo &audioDeviceInfo, const QAudioFormat &format) : + helper{generator}, output{audioDeviceInfo, format} +{ +} +} diff --git a/basetonegenerator.h b/basetonegenerator.h new file mode 100644 index 0000000..1c8b25f --- /dev/null +++ b/basetonegenerator.h @@ -0,0 +1,39 @@ +#pragma once + +// system includes +#include + +// Qt includes +#include + +// local includes +#include "oscicommon.h" + +// forward declares +namespace { class BaseToneGeneratorPrivate; } + +class BaseToneGenerator +{ +public: + BaseToneGenerator(); + virtual ~BaseToneGenerator(); + + void start(); + void stop(); + bool running() const { return static_cast(m_private); } + + int samplerate() const { return m_samplerate; } + void setSamplerate(int samplerate) { Q_ASSERT(!running()); m_samplerate = samplerate; } + + const auto &device() const { return m_device; } + void setDevice(const QAudioDeviceInfo &device) { Q_ASSERT(!running()); m_device = device; } + + virtual std::size_t fill(SamplePair *begin, SamplePair *end) = 0; + +private: + std::unique_ptr m_private; + + int m_samplerate; + + QAudioDeviceInfo m_device; +}; diff --git a/debugtonegenerator.cpp b/debugtonegenerator.cpp new file mode 100644 index 0000000..96324f8 --- /dev/null +++ b/debugtonegenerator.cpp @@ -0,0 +1,24 @@ +#include "debugtonegenerator.h" + +// system includes +#include +#include + +std::size_t DebugToneGenerator::fill(SamplePair *begin, SamplePair *end) +{ + for (auto iter = begin; iter != end; iter++) + { + Q_ASSERT(iter <= end); + iter->first = std::sin(m_counter) * std::numeric_limits::max() / 2; + iter->second = std::sin((m_counter*8.f) + std::sin(m_offset)*10.) *(0.5 * (.7f + (std::sin(m_counter - M_PI/2) * .3f))) * std::numeric_limits::max() / 2; + + m_counter+= 1. / samplerate() * (1000. + (std::sin(m_freq) * 500.)); + m_offset+=0.00001f; + m_freq+=1. / samplerate() * 4; + } + + while (m_counter >= M_PI * 2) + m_counter -= M_PI * 2; + + return std::distance(begin, end); +} diff --git a/debugtonegenerator.h b/debugtonegenerator.h new file mode 100644 index 0000000..c97109b --- /dev/null +++ b/debugtonegenerator.h @@ -0,0 +1,14 @@ +#include "basetonegenerator.h" + +class DebugToneGenerator : public BaseToneGenerator +{ +public: + using BaseToneGenerator::BaseToneGenerator; + + std::size_t fill(SamplePair *begin, SamplePair *end) override; + +private: + float m_counter{0.f}; + float m_offset{0.}; + float m_freq{0.}; +}; diff --git a/fakedevice.cpp b/fakedevice.cpp deleted file mode 100644 index b354cdf..0000000 --- a/fakedevice.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "fakedevice.h" - -// Qt includes -#include - -// system includes -#include - -void FakeDevice::start() -{ - Q_ASSERT(!running()); - m_bufferSize = m_samplerate/m_framerate; - m_buffer = std::make_unique(m_bufferSize); - m_timerId = startTimer(1000/m_framerate); -} - -void FakeDevice::stop() -{ - Q_ASSERT(running()); - killTimer(m_timerId); - m_buffer = nullptr; -} - -bool FakeDevice::running() const -{ - return m_timerId != -1; -} - -void FakeDevice::timerEvent(QTimerEvent *event) -{ - if (event->timerId() == m_timerId) - { - for (SamplePair *pair = m_buffer.get(); - pair != m_buffer.get() + m_bufferSize; - pair++) - { - pair->first = std::sin(m_dingsDesHaltHochZaehlt) * std::numeric_limits::max(); - pair->second = std::cos(m_dingsDesHaltHochZaehlt) * std::numeric_limits::max(); - m_dingsDesHaltHochZaehlt += 0.05; - } - - emit samplesReceived(m_buffer.get(), m_buffer.get() + m_bufferSize); - } - else - QObject::timerEvent(event); -} diff --git a/fakedevice.h b/fakedevice.h deleted file mode 100644 index b8df4f8..0000000 --- a/fakedevice.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "basedevice.h" - -// system includes -#include - -class FakeDevice : public BaseDevice -{ - Q_OBJECT - -public: - using BaseDevice::BaseDevice; - - void start() override; - void stop() override; - bool running() const override; - - int samplerate() const override { return m_samplerate; } - void setSamplerate(int samplerate) override { Q_ASSERT(!running()); m_samplerate = samplerate; } - - int framerate() const { return m_framerate; } - void setFramerate(int framerate) { Q_ASSERT(!running()); m_framerate = framerate; } - -protected: - void timerEvent(QTimerEvent *event) override; - -private: - double m_dingsDesHaltHochZaehlt{0.}; - int m_timerId{-1}; - - std::unique_ptr m_buffer; - std::size_t m_bufferSize; - - int m_samplerate{44100}; - int m_framerate{15}; -}; diff --git a/main.cpp b/main.cpp index 06bcad2..2be6c2d 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,8 @@ +// Qt includes #include #include +// local includes #include "mainwindow.h" int main(int argc, char *argv[]) diff --git a/mainwindow.cpp b/mainwindow.cpp index d595c9f..680b789 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,6 +1,9 @@ #include "mainwindow.h" #include "ui_mainwindow.h" +// system includes +#include + // Qt includes #include #include @@ -10,7 +13,7 @@ // local includes #include "audiodevice.h" -#include "fakedevice.h" +#include "debugtonegenerator.h" namespace { constexpr int samplerates[] = { 44100, 48000, 96000, 192000 }; @@ -30,7 +33,8 @@ void setActionsEnabled(const T &actions, bool enabled) MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent}, m_ui{std::make_unique()}, - m_audioDevices{QAudioDeviceInfo::availableDevices(QAudio::AudioInput)}, + m_inputDevices{QAudioDeviceInfo::availableDevices(QAudio::AudioInput)}, + m_outputDevices{QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)}, m_statusLabel{*new QLabel} { m_ui->setupUi(this); @@ -44,19 +48,32 @@ MainWindow::MainWindow(QWidget *parent) : connect(m_ui->actionStop, &QAction::triggered, this, &MainWindow::stop); m_ui->actionQuit->setShortcut(QKeySequence::Quit); - // setting up menu Devices - for (const auto &device : m_audioDevices) + // setting up menu InputDevices + for (const auto &device : m_inputDevices) { auto name = device.deviceName(); - const auto action = m_ui->menuDevice->addAction(name); + const auto action = m_ui->menuInputDevice->addAction(name); action->setCheckable(true); - m_deviceGroup.addAction(action); + m_inputDeviceGroup.addAction(action); // Select last element containing monitor if available if(name.contains("monitor")) action->setChecked(true); } + // setting up menu OutputDevices + for (const auto &device : m_outputDevices) + { + auto name = device.deviceName(); + const auto action = m_ui->menuOutputDevice->addAction(name); + action->setCheckable(true); + m_outputDeviceGroup.addAction(action); + + // Select last element containing analog-stereo if available + if(name.contains("analog-stereo")) + action->setChecked(true); + } + // setting up menu Samplerates for (const auto samplerate : samplerates) { @@ -100,6 +117,8 @@ MainWindow::MainWindow(QWidget *parent) : connect(&m_zoomlevelsGroup, &QActionGroup::triggered, this, &MainWindow::zoomChanged); //setting up menu Debug + connect(m_ui->actionToneGenerator, &QAction::triggered, this, &MainWindow::startGenerator); + { auto widgetAction = new QWidgetAction(this); auto widget = new QWidget; @@ -123,7 +142,7 @@ MainWindow::MainWindow(QWidget *parent) : } // autostart - if (m_audioDevices.isEmpty()) + if (m_inputDevices.isEmpty()) { m_ui->actionStart->setEnabled(false); m_ui->actionStop->setEnabled(false); @@ -134,28 +153,20 @@ MainWindow::MainWindow(QWidget *parent) : 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); + auto input = std::make_unique(); + // setDevice is AudioDevice specific API + input->setDevice(m_inputDevices.at(m_inputDeviceGroup.actions().indexOf(m_inputDeviceGroup.checkedAction()))); + m_input = std::move(input); } + 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_inputDeviceGroup.actions(), false); setActionsEnabled(m_samplerateGroup.actions(), false); m_ui->actionStart->setEnabled(false); m_ui->actionStop->setEnabled(true); @@ -164,7 +175,7 @@ void MainWindow::start() void MainWindow::stop() { m_input = nullptr; - setActionsEnabled(m_deviceGroup.actions(), true); + setActionsEnabled(m_inputDeviceGroup.actions(), true); setActionsEnabled(m_samplerateGroup.actions(), true); m_ui->actionStart->setEnabled(true); m_ui->actionStop->setEnabled(false); @@ -188,4 +199,29 @@ void MainWindow::zoomChanged() m_ui->widget->setFactor(zoomlevel/100.f); } +void MainWindow::startGenerator() +{ + m_generator = nullptr; + m_generator = std::make_unique(); + m_generator->setDevice(m_outputDevices.at(m_outputDeviceGroup.actions().indexOf(m_outputDeviceGroup.checkedAction()))); + m_generator->setSamplerate(samplerate()); + m_generator->start(); +} + +int MainWindow::samplerate() const +{ + auto *checked = m_samplerateGroup.checkedAction(); + if (!checked) + throw std::runtime_error(tr("No samplerate selected!").toStdString()); + + const auto index = m_samplerateGroup.actions().indexOf(checked); + if (index < 0) + throw std::runtime_error(tr("Unknown samplerate selected!").toStdString()); + + if (index >= std::distance(std::begin(samplerates), std::end(samplerates))) + throw std::runtime_error(tr("Index out of range!").toStdString()); + + return samplerates[index]; +} + MainWindow::~MainWindow() = default; diff --git a/mainwindow.h b/mainwindow.h index 9a290f2..1ba9e91 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -13,6 +13,8 @@ class QLabel; namespace Ui { class MainWindow; } class BaseDevice; +class BaseToneGenerator; + class MainWindow : public QMainWindow { Q_OBJECT @@ -26,15 +28,20 @@ private slots: void stop(); void refreshRateChanged(); void zoomChanged(); + void startGenerator(); private: + int samplerate() const; + const std::unique_ptr m_ui; - const QList m_audioDevices; + const QList m_inputDevices, m_outputDevices; std::unique_ptr m_input; - QActionGroup m_deviceGroup{this}, m_samplerateGroup{this}, m_refreshrateGroup{this}, m_zoomlevelsGroup{this}; + QActionGroup m_inputDeviceGroup{this}, m_outputDeviceGroup{this}, m_samplerateGroup{this}, m_refreshrateGroup{this}, m_zoomlevelsGroup{this}; QLabel &m_statusLabel; + + std::unique_ptr m_generator; }; diff --git a/mainwindow.ui b/mainwindow.ui index b6343a5..6836f20 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -32,9 +32,9 @@ - + - &Device + &Input device @@ -56,9 +56,16 @@ Debug + + + + + &Output device + - + + @@ -80,6 +87,11 @@ Stop + + + &Tone generator + + diff --git a/oscilloscope.pro b/oscilloscope.pro index b8f279d..6f4708f 100644 --- a/oscilloscope.pro +++ b/oscilloscope.pro @@ -6,7 +6,8 @@ DEFINES += QT_DEPRECATED_WARNINGS QT_DISABLE_DEPRECATED_BEFORE=0x060000 SOURCES += \ audiodevice.cpp \ basedevice.cpp \ - fakedevice.cpp \ + basetonegenerator.cpp \ + debugtonegenerator.cpp \ main.cpp \ mainwindow.cpp \ osciwidget.cpp @@ -14,7 +15,8 @@ SOURCES += \ HEADERS += \ audiodevice.h \ basedevice.h \ - fakedevice.h \ + basetonegenerator.h \ + debugtonegenerator.h \ mainwindow.h \ oscicommon.h \ osciwidget.h diff --git a/osciwidget.cpp b/osciwidget.cpp index 4a7f1ce..b55ece1 100644 --- a/osciwidget.cpp +++ b/osciwidget.cpp @@ -1,7 +1,9 @@ #include "osciwidget.h" +// system includes #include +// Qt includes #include #include #include diff --git a/osciwidget.h b/osciwidget.h index f757d4a..6a596cb 100644 --- a/osciwidget.h +++ b/osciwidget.h @@ -41,7 +41,7 @@ protected: private: float m_factor{2.f}; - int m_fps{15}, m_afterglow{175}; + int m_fps{30}, m_afterglow{175}; float m_lightspeed{35.f}; std::vector m_buffer;