Implemented very basic tone generator

This commit is contained in:
2019-09-07 23:00:37 +02:00
parent f612e4e14f
commit 4c95889a91
15 changed files with 279 additions and 142 deletions

View File

@@ -3,15 +3,13 @@
// Qt includes
#include <QIODevice>
#include <QAudioInput>
#include <QDebug>
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<const SamplePair*>(data),
reinterpret_cast<const SamplePair*>(data + (len/sizeof(SamplePair))));
m_audioDevice.emitSamples(reinterpret_cast<const SamplePair*>(data),
reinterpret_cast<const SamplePair*>(data) + (len/sizeof(SamplePair)));
return len;
}
AudioDevicePrivate::AudioDevicePrivate(AudioDevice &audioDevice, const QAudioDeviceInfo &audioDeviceInfo, const QAudioFormat &format) :
helper{audioDevice}, input{audioDeviceInfo, format}
{
}
}

View File

@@ -1,7 +1,9 @@
#pragma once
// Qt includes
#include <QObject>
// 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);
};

87
basetonegenerator.cpp Normal file
View File

@@ -0,0 +1,87 @@
#include "basetonegenerator.h"
// Qt includes
#include <QAudioOutput>
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<BaseToneGeneratorPrivate>(*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<SamplePair*>(data),
reinterpret_cast<SamplePair*>(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}
{
}
}

39
basetonegenerator.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
// system includes
#include <memory>
// Qt includes
#include <QAudioDeviceInfo>
// 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<bool>(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<BaseToneGeneratorPrivate> m_private;
int m_samplerate;
QAudioDeviceInfo m_device;
};

24
debugtonegenerator.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include "debugtonegenerator.h"
// system includes
#include <cmath>
#include <limits>
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<SamplePair::Type>::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<SamplePair::Type>::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);
}

14
debugtonegenerator.h Normal file
View File

@@ -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.};
};

View File

@@ -1,46 +0,0 @@
#include "fakedevice.h"
// Qt includes
#include <QTimerEvent>
// system includes
#include <cmath>
void FakeDevice::start()
{
Q_ASSERT(!running());
m_bufferSize = m_samplerate/m_framerate;
m_buffer = std::make_unique<SamplePair[]>(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<qint16>::max();
pair->second = std::cos(m_dingsDesHaltHochZaehlt) * std::numeric_limits<qint16>::max();
m_dingsDesHaltHochZaehlt += 0.05;
}
emit samplesReceived(m_buffer.get(), m_buffer.get() + m_bufferSize);
}
else
QObject::timerEvent(event);
}

View File

@@ -1,37 +0,0 @@
#pragma once
#include "basedevice.h"
// system includes
#include <memory>
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<SamplePair[]> m_buffer;
std::size_t m_bufferSize;
int m_samplerate{44100};
int m_framerate{15};
};

View File

@@ -1,6 +1,8 @@
// Qt includes
#include <QApplication>
#include <QtGlobal>
// local includes
#include "mainwindow.h"
int main(int argc, char *argv[])

View File

@@ -1,6 +1,9 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
// system includes
#include <stdexcept>
// Qt includes
#include <QLabel>
#include <QWidgetAction>
@@ -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<Ui::MainWindow>()},
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<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);
auto input = std::make_unique<AudioDevice>();
// 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<AudioDevice*>(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<DebugToneGenerator>();
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;

View File

@@ -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<Ui::MainWindow> m_ui;
const QList<QAudioDeviceInfo> m_audioDevices;
const QList<QAudioDeviceInfo> m_inputDevices, m_outputDevices;
std::unique_ptr<BaseDevice> 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<BaseToneGenerator> m_generator;
};

View File

@@ -32,9 +32,9 @@
<addaction name="separator"/>
<addaction name="actionQuit"/>
</widget>
<widget class="QMenu" name="menuDevice">
<widget class="QMenu" name="menuInputDevice">
<property name="title">
<string>&amp;Device</string>
<string>&amp;Input device</string>
</property>
</widget>
<widget class="QMenu" name="menuSamplerate">
@@ -56,9 +56,16 @@
<property name="title">
<string>Debug</string>
</property>
<addaction name="actionToneGenerator"/>
</widget>
<widget class="QMenu" name="menuOutputDevice">
<property name="title">
<string>&amp;Output device</string>
</property>
</widget>
<addaction name="menuFile"/>
<addaction name="menuDevice"/>
<addaction name="menuInputDevice"/>
<addaction name="menuOutputDevice"/>
<addaction name="menuSamplerate"/>
<addaction name="menuRefreshrate"/>
<addaction name="menuZoom"/>
@@ -80,6 +87,11 @@
<string>Stop</string>
</property>
</action>
<action name="actionToneGenerator">
<property name="text">
<string>&amp;Tone generator</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@@ -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

View File

@@ -1,7 +1,9 @@
#include "osciwidget.h"
// system includes
#include <cmath>
// Qt includes
#include <QLineF>
#include <QDebug>
#include <QPainter>

View File

@@ -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<SamplePair> m_buffer;