r3 light gui

This commit is contained in:
2021-04-29 19:26:48 +02:00
parent 3dc8321055
commit 84535ecca7
8 changed files with 948 additions and 4 deletions

26
audioformat.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <QAudioFormat>
namespace {
constexpr int frameRate = 44100;
constexpr int channelCount = 2;
constexpr int sampleSize = 32;
constexpr auto codec = "audio/pcm";
constexpr QAudioFormat::Endian byteOrder = QAudioFormat::LittleEndian;
constexpr QAudioFormat::SampleType sampleType = QAudioFormat::Float;
using sample_t = float;
using frame_t = std::array<sample_t, channelCount>;
auto makeFormat()
{
QAudioFormat format;
format.setSampleRate(frameRate);
format.setChannelCount(channelCount);
format.setSampleSize(sampleSize);
format.setCodec(codec);
format.setByteOrder(byteOrder);
format.setSampleType(sampleType);
return format;
}
}

32
bpmdetector.cpp Normal file
View File

@ -0,0 +1,32 @@
#include "bpmdetector.h"
// system includes
#include <algorithm>
// Qt includes
#include <QDebug>
BpmDetector::BpmDetector(QObject *parent) :
QIODevice{parent}
{
setOpenMode(QIODevice::WriteOnly);
}
qint64 BpmDetector::readData(char *data, qint64 maxlen)
{
qCritical("read not supported");
return -1;
}
qint64 BpmDetector::writeData(const char *data, qint64 len)
{
QVector<frame_t> frames{int(len / sizeof(frame_t))};
frames.resize(len / sizeof(frame_t));
const auto begin = reinterpret_cast<const frame_t *>(data);
std::copy(begin, begin + frames.size(), std::begin(frames));
emit receivedFrames(frames);
return len;
}

22
bpmdetector.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
// Qt includes
#include <QIODevice>
#include <QVector>
// local includes
#include "audioformat.h"
class BpmDetector : public QIODevice
{
Q_OBJECT
public:
explicit BpmDetector(QObject *parent = nullptr);
qint64 readData(char *data, qint64 maxlen) override;
qint64 writeData(const char *data, qint64 len) override;
signals:
void receivedFrames(const QVector<frame_t> &frames);
};

View File

@ -1,14 +1,22 @@
#include <QCoreApplication>
#include <QApplication>
#include <QDebug>
#include <QTimer>
#include <QJsonObject>
#include <QJsonArray>
#include "r3client.h"
#include "mainwindow.h"
#include <memory>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QApplication a(argc, argv);
MainWindow mainWindow;
mainWindow.show();
return a.exec();
const auto arguments = [&](){
auto arguments = a.arguments();

272
mainwindow.cpp Normal file
View File

@ -0,0 +1,272 @@
#include "mainwindow.h"
// system includes
#include <algorithm>
// Qt includes
#include <QMetaEnum>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
#include <QAudioInput>
#include <QJsonDocument>
namespace {
template<typename QEnum>
QString enumToString(const QEnum value)
{
return QMetaEnum::fromType<QEnum>().valueToKey(value);
}
}
MainWindow::MainWindow(QWidget *parent) :
QMainWindow{parent}
{
m_ui.setupUi(this);
connect(m_ui.connectButton, &QAbstractButton::pressed, this, &MainWindow::connectPressed);
connect(m_ui.disconnectButton, &QAbstractButton::pressed, this, &MainWindow::disconnectPressed);
connect(m_ui.lamp1OnButton, &QAbstractButton::pressed, this, [&](){ lightCmd("basiclight1", "on"); });
connect(m_ui.lamp1OffButton, &QAbstractButton::pressed, this, [&](){ lightCmd("basiclight1", "off"); });
connect(m_ui.lamp2OnButton, &QAbstractButton::pressed, this, [&](){ lightCmd("basiclight2", "on"); });
connect(m_ui.lamp2OffButton, &QAbstractButton::pressed, this, [&](){ lightCmd("basiclight2", "off"); });
connect(m_ui.lamp3OnButton, &QAbstractButton::pressed, this, [&](){ lightCmd("basiclight3", "on"); });
connect(m_ui.lamp3OffButton, &QAbstractButton::pressed, this, [&](){ lightCmd("basiclight3", "off"); });
connect(m_ui.lamp4OnButton, &QAbstractButton::pressed, this, [&](){ lightCmd("basiclight4", "on"); });
connect(m_ui.lamp4OffButton, &QAbstractButton::pressed, this, [&](){ lightCmd("basiclight4", "off"); });
connect(m_ui.lamp5OnButton, &QAbstractButton::pressed, this, [&](){ lightCmd("basiclight5", "on"); });
connect(m_ui.lamp5OffButton, &QAbstractButton::pressed, this, [&](){ lightCmd("basiclight5", "off"); });
connect(m_ui.lamp6OnButton, &QAbstractButton::pressed, this, [&](){ lightCmd("basiclight6", "on"); });
connect(m_ui.lamp6OffButton, &QAbstractButton::pressed, this, [&](){ lightCmd("basiclight6", "off"); });
connect(m_ui.toggleAllButton, &QAbstractButton::pressed, this, &MainWindow::toggleAllPressed);
connect(m_ui.discoLeftButton, &QAbstractButton::pressed, this, &MainWindow::discoLeftPressed);
connect(m_ui.discoRightButton, &QAbstractButton::pressed, this, &MainWindow::discoRightPressed);
connect(m_ui.bpmButton, &QAbstractButton::pressed, this, &MainWindow::bpmPressed);
connect(m_ui.openAudioDeviceButton, &QAbstractButton::pressed, this, &MainWindow::openAudioDevicePressed);
connect(m_ui.closeAudioDeviceButton, &QAbstractButton::pressed, this, &MainWindow::closeAudioDevicePressed);
connect(&m_client, &R3Client::connected, this, &MainWindow::connected);
connect(&m_client, &R3Client::disconnected, this, &MainWindow::disconnected);
connect(&m_client, &R3Client::error, this, &MainWindow::error);
connect(&m_client, &R3Client::statusReceived, this, &MainWindow::statusReceived);
updateAudioDevices();
m_timer.setTimerType(Qt::PreciseTimer);
connect(&m_timer, &QTimer::timeout, this, &MainWindow::bpmTimeout);
connect(m_ui.spinBox, &QSpinBox::valueChanged, &m_timer, qOverload<int>(&QTimer::setInterval));
connect(&m_detector, &BpmDetector::receivedFrames, this, &MainWindow::receivedFrames);
connect(&m_timer2, &QTimer::timeout, this, [&](){
const sample_t min = -m_min * 100;
m_ui.verticalSlider->setValue(min);
m_min = 0;
const sample_t max = m_max * 100;
m_ui.verticalSlider_2->setValue(max);
m_max = 0;
const sample_t avg = m_distanceSum / m_count * 100.;
m_ui.verticalSlider_3->setValue(avg);
m_distanceSum = 0.;
m_count = 0;
const auto selectedWert = [&](){
if (m_ui.radioButton->isChecked())
return min;
else if (m_ui.radioButton_2->isChecked())
return max;
else if (m_ui.radioButton_3->isChecked())
return avg;
}();
if (selectedWert > m_ui.verticalSlider_4->value())
switch (m_ui.comboBox->currentIndex())
{
case 0: discoLeftPressed(); break;
case 1: discoRightPressed(); break;
case 2: toggleAllPressed(); break;
}
});
m_timer2.start(1000/20);
}
MainWindow::~MainWindow() = default;
void MainWindow::connectPressed()
{
log("Connecting...");
m_client.open();
}
void MainWindow::disconnectPressed()
{
log("Disconnecting...");
m_client.close();
}
void MainWindow::connected()
{
log("Connected successfully!");
}
void MainWindow::disconnected()
{
log("Disconnected!");
}
void MainWindow::error(QAbstractSocket::SocketError error)
{
log(QString{"Error: %0"}.arg(enumToString(error)));
}
void MainWindow::statusReceived(const QString &ctx, const QJsonValue &jsonValue)
{
qDebug() << jsonValue;
log(QString{"ctx=%0"}.arg(ctx));
}
void MainWindow::toggleAllPressed()
{
lightCmd("basiclight1", m_toggleState ? "on" : "off");
lightCmd("basiclight2", m_toggleState ? "on" : "off");
lightCmd("basiclight3", m_toggleState ? "on" : "off");
lightCmd("basiclight4", m_toggleState ? "on" : "off");
lightCmd("basiclight5", m_toggleState ? "on" : "off");
lightCmd("basiclight6", m_toggleState ? "on" : "off");
m_toggleState = !m_toggleState;
}
void MainWindow::discoLeftPressed()
{
if (m_discoState == 0)
m_discoState = 5;
else
m_discoState--;
updateDisco();
}
void MainWindow::discoRightPressed()
{
if (m_discoState == 5)
m_discoState = 0;
else
m_discoState++;
updateDisco();
}
void MainWindow::bpmPressed()
{
discoRightPressed();
if (m_bpmTap)
{
m_bpmTap->count++;
const auto elapsed = m_bpmTap->begin.msecsTo(QDateTime::currentDateTime());
m_bpmTap->msPerBeat = elapsed / m_bpmTap->count;
m_bpmTap->bpm = 60000 / m_bpmTap->msPerBeat;
m_ui.bpmButton->setText(QString{"%0 %1 BPM"}.arg(m_bpmTap->count).arg(m_bpmTap->bpm));
m_ui.spinBox->setValue(m_bpmTap->msPerBeat);
m_timer.start(m_bpmTap->msPerBeat);
m_bpmTap->flag = false;
}
else
{
m_timer.start(2000);
m_bpmTap = BpmTap{};
}
}
void MainWindow::openAudioDevicePressed()
{
const auto index = m_ui.audioDeviceSelection->currentIndex();
if (index < 0 || index >= m_devices.count())
return;
m_input = std::make_unique<QAudioInput>(m_devices.at(index), makeFormat());
m_input->start(&m_detector);
}
void MainWindow::closeAudioDevicePressed()
{
m_input = nullptr;
}
void MainWindow::bpmTimeout()
{
if (m_bpmTap)
{
if (!m_bpmTap->flag)
{
m_bpmTap->flag = true;
return;
}
if (m_bpmTap->count)
{
qDebug() << "starting normal";
discoRightPressed();
}
else
{
m_ui.bpmButton->setText(tr("BPM tap"));
qDebug() << "aborted";
m_timer.stop();
}
m_bpmTap = std::nullopt;
}
else
{
qDebug() << "disco timer";
discoRightPressed();
}
}
void MainWindow::receivedFrames(const QVector<frame_t> &frames)
{
for (const auto &frame : frames)
{
if (frame[0] < m_min)
m_min = frame[0];
if (frame[0] > m_max)
m_max = frame[0];
m_distanceSum += std::abs(frame[0]);
m_count++;
}
}
void MainWindow::log(const QString &msg)
{
m_ui.logView->appendPlainText(QString{"%0 %1"}.arg(QDateTime::currentDateTime().toString(), msg));
}
void MainWindow::lightCmd(const QString &light, const QString &status)
{
m_client.sendMQTT("action/GoLightCtrl/" + light, QJsonObject{ {"Action", status }});
}
void MainWindow::updateDisco()
{
lightCmd("basiclight1", m_discoState == 0 ? "on" : "off");
lightCmd("basiclight2", m_discoState == 1 ? "on" : "off");
lightCmd("basiclight3", m_discoState == 2 ? "on" : "off");
lightCmd("basiclight4", m_discoState == 3 ? "on" : "off");
lightCmd("basiclight5", m_discoState == 4 ? "on" : "off");
lightCmd("basiclight6", m_discoState == 5 ? "on" : "off");
}
void MainWindow::updateAudioDevices()
{
m_ui.audioDeviceSelection->clear();
m_devices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
for (const auto &x : m_devices)
m_ui.audioDeviceSelection->addItem(x.deviceName());
}

81
mainwindow.h Normal file
View File

@ -0,0 +1,81 @@
#pragma once
// system includes
#include <memory>
#include <optional>
// Qt includes
#include <QMainWindow>
#include <QAbstractSocket>
#include <QDateTime>
#include <QTimer>
#include <QList>
#include <QAudioDeviceInfo>
// local includes
#include "ui_mainwindow.h"
#include "r3client.h"
#include "bpmdetector.h"
#include "audioformat.h"
class QAudioInput;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void connectPressed();
void disconnectPressed();
void connected();
void disconnected();
void error(QAbstractSocket::SocketError error);
void statusReceived(const QString &ctx, const QJsonValue &jsonValue);
void toggleAllPressed();
void discoLeftPressed();
void discoRightPressed();
void bpmPressed();
void openAudioDevicePressed();
void closeAudioDevicePressed();
void bpmTimeout();
void receivedFrames(const QVector<frame_t> &frames);
private:
void log(const QString &msg);
void lightCmd(const QString &light, const QString &status);
void updateDisco();
void updateAudioDevices();
private:
Ui::MainWindow m_ui;
R3Client m_client;
bool m_toggleState{};
uint8_t m_discoState{};
struct BpmTap { QDateTime begin{QDateTime::currentDateTime()}; int count{}; int msPerBeat, bpm; bool flag{}; };
std::optional<BpmTap> m_bpmTap;
QTimer m_timer;
QList<QAudioDeviceInfo> m_devices;
std::unique_ptr<QAudioInput> m_input;
BpmDetector m_detector;
sample_t m_min{}, m_max{};
double m_distanceSum{};
std::size_t m_count{};
QTimer m_timer2;
};

495
mainwindow.ui Normal file
View File

@ -0,0 +1,495 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QPushButton" name="connectButton">
<property name="geometry">
<rect>
<x>70</x>
<y>30</y>
<width>80</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Connect</string>
</property>
</widget>
<widget class="QPushButton" name="disconnectButton">
<property name="geometry">
<rect>
<x>160</x>
<y>30</y>
<width>80</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Disconnect</string>
</property>
</widget>
<widget class="QPlainTextEdit" name="logView">
<property name="geometry">
<rect>
<x>20</x>
<y>280</y>
<width>591</width>
<height>231</height>
</rect>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
<widget class="QWidget" name="gridLayoutWidget">
<property name="geometry">
<rect>
<x>120</x>
<y>60</y>
<width>481</width>
<height>201</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QLabel" name="lamp4Label">
<property name="text">
<string>Lamp 4</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lamp4OnButton">
<property name="text">
<string>On</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lamp4OffButton">
<property name="text">
<string>Off</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="lamp1Label">
<property name="text">
<string>Lamp 1</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lamp1OnButton">
<property name="text">
<string>On</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lamp1OffButton">
<property name="text">
<string>Off</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="2">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="lamp2Label">
<property name="text">
<string>Lamp 2</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lamp2OnButton">
<property name="text">
<string>On</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lamp2OffButton">
<property name="text">
<string>Off</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="2">
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="QLabel" name="lamp5Label">
<property name="text">
<string>Lamp 5</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lamp5OnButton">
<property name="text">
<string>On</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lamp5OffButton">
<property name="text">
<string>Off</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="4">
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QLabel" name="lamp3Label">
<property name="text">
<string>Lamp 3</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lamp3OnButton">
<property name="text">
<string>On</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lamp3OffButton">
<property name="text">
<string>Off</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="4">
<layout class="QVBoxLayout" name="verticalLayout_10">
<item>
<widget class="QLabel" name="lamp6Label">
<property name="text">
<string>Lamp 6</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lamp6OnButton">
<property name="text">
<string>On</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lamp6OffButton">
<property name="text">
<string>Off</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="3">
<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>
<item row="1" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QPushButton" name="discoLeftButton">
<property name="geometry">
<rect>
<x>620</x>
<y>130</y>
<width>80</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Disco L</string>
</property>
</widget>
<widget class="QPushButton" name="toggleAllButton">
<property name="geometry">
<rect>
<x>660</x>
<y>100</y>
<width>80</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Toggle all</string>
</property>
</widget>
<widget class="QPushButton" name="discoRightButton">
<property name="geometry">
<rect>
<x>710</x>
<y>130</y>
<width>80</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Disco R</string>
</property>
</widget>
<widget class="QPushButton" name="bpmButton">
<property name="geometry">
<rect>
<x>630</x>
<y>190</y>
<width>151</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>BPM tap</string>
</property>
</widget>
<widget class="QSpinBox" name="spinBox">
<property name="geometry">
<rect>
<x>660</x>
<y>220</y>
<width>61</width>
<height>24</height>
</rect>
</property>
<property name="maximum">
<number>10000</number>
</property>
</widget>
<widget class="QComboBox" name="audioDeviceSelection">
<property name="geometry">
<rect>
<x>630</x>
<y>300</y>
<width>131</width>
<height>23</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="openAudioDeviceButton">
<property name="geometry">
<rect>
<x>620</x>
<y>340</y>
<width>80</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Open</string>
</property>
</widget>
<widget class="QPushButton" name="closeAudioDeviceButton">
<property name="geometry">
<rect>
<x>710</x>
<y>340</y>
<width>80</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Close</string>
</property>
</widget>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>620</x>
<y>370</y>
<width>170</width>
<height>151</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="radioButton">
<property name="text">
<string/>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="verticalSlider">
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="radioButton_2">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="verticalSlider_2">
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QRadioButton" name="radioButton_3">
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="verticalSlider_3">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>0</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QSlider" name="verticalSlider_4">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QComboBox" name="comboBox">
<property name="geometry">
<rect>
<x>620</x>
<y>520</y>
<width>79</width>
<height>23</height>
</rect>
</property>
<item>
<property name="text">
<string>Disco L</string>
</property>
</item>
<item>
<property name="text">
<string>Disco R</string>
</property>
</item>
<item>
<property name="text">
<string>Toggle All</string>
</property>
</item>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>20</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,12 +1,20 @@
QT = core network websockets
QT = core gui widgets network websockets multimedia
CONFIG += c++14
CONFIG += c++latest
DEFINES += QT_DEPRECATED_WARNINGS QT_DISABLE_DEPRECATED_BEFORE=0x060000
SOURCES += \
bpmdetector.cpp \
main.cpp \
mainwindow.cpp \
r3client.cpp
HEADERS += \
audioformat.h \
bpmdetector.h \
mainwindow.h \
r3client.h
FORMS += \
mainwindow.ui