From 772376e4405455b88be87325ba86e0ad0033fb8c Mon Sep 17 00:00:00 2001 From: mrbesen Date: Mon, 13 Dec 2021 00:28:04 +0100 Subject: [PATCH] first working --- .gitignore | 73 +++++++++++++++ .gitmodules | 6 ++ QxtGlobalShortcut | 1 + include/mainwindow.h | 34 +++++++ include/sound.h | 36 ++++++++ include/sounddevice.h | 44 +++++++++ miniaudio | 1 + pulseaudiosetup.sh | 9 ++ src/main.cpp | 11 +++ src/mainwindow.cpp | 53 +++++++++++ src/sound.cpp | 115 +++++++++++++++++++++++ src/sounddevice.cpp | 206 ++++++++++++++++++++++++++++++++++++++++++ testqt.pro | 47 ++++++++++ testqt_de_DE.ts | 3 + ui/mainwindow.ui | 58 ++++++++++++ 15 files changed, 697 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 160000 QxtGlobalShortcut create mode 100644 include/mainwindow.h create mode 100644 include/sound.h create mode 100644 include/sounddevice.h create mode 160000 miniaudio create mode 100644 pulseaudiosetup.sh create mode 100644 src/main.cpp create mode 100644 src/mainwindow.cpp create mode 100644 src/sound.cpp create mode 100644 src/sounddevice.cpp create mode 100644 testqt.pro create mode 100644 testqt_de_DE.ts create mode 100644 ui/mainwindow.ui diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..03b4c9c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "QxtGlobalShortcut"] + path = QxtGlobalShortcut + url = https://github.com/MOZGIII/QxtGlobalShortcut.git +[submodule "miniaudio"] + path = miniaudio + url = https://github.com/mackron/miniaudio.git diff --git a/QxtGlobalShortcut b/QxtGlobalShortcut new file mode 160000 index 0000000..8613223 --- /dev/null +++ b/QxtGlobalShortcut @@ -0,0 +1 @@ +Subproject commit 861322355e29a22105ee0809dc3fb68940573335 diff --git a/include/mainwindow.h b/include/mainwindow.h new file mode 100644 index 0000000..734f9fe --- /dev/null +++ b/include/mainwindow.h @@ -0,0 +1,34 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include "qxtglobalshortcut.h" + +#include "sound.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +public slots: + void buttonPressed(); + void shortcut(); + void shortcut2(); + +private: + void playSound(); + + Ui::MainWindow *ui; + QShortcut* sc; + QxtGlobalShortcut* globalShortcut; +}; +#endif // MAINWINDOW_H diff --git a/include/sound.h b/include/sound.h new file mode 100644 index 0000000..128dbc3 --- /dev/null +++ b/include/sound.h @@ -0,0 +1,36 @@ +#pragma once + +#include "miniaudio.h" + +#include +#include +#include +#include + +#include "sounddevice.h" + +class Sound { +public: + static Sound& instance(); + static void deinit(); + + void addPlayback(const std::string& name, float volume = 1.f); + bool addDefaultDevice(); + bool addDeviceWithName(const std::string& name); + void stopAll(); + + const static std::string FOLDER; +private: + Sound(); + ~Sound(); + + void init(); + + ma_context context; + + std::vector devices; + + static Sound* inst; + + friend void sound_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); +}; diff --git a/include/sounddevice.h b/include/sounddevice.h new file mode 100644 index 0000000..0ad92c4 --- /dev/null +++ b/include/sounddevice.h @@ -0,0 +1,44 @@ + +#pragma once + +#include "miniaudio.h" + +#include +#include + +class SoundDevice { + SoundDevice(); + +public: + ~SoundDevice(); + static SoundDevice* createDevice(ma_context* ctx, const std::string& name); // get device with name + static SoundDevice* createDevice(ma_context* ctx, const ma_device_id* did = NULL); + + void stop(); + void addPlayback(const std::string& name, float volume = 1.f); + + void startDevice(); + void cleanupDecoders(); //fertige decoder löschen + + // callback + void sound_callback(void* outbuffer, ma_uint32 frameCount); + +private: + struct Playback { + ma_decoder decoder; + float volume; + bool isDone = false; + Playback() {} + }; + + struct SoundData { + std::list playbacks; //liste der decoder + unsigned int decoderCount = 0; + unsigned int musicdecoderCount = 0; + ma_mutex* mutex; + }; + + bool deviceRunning = false; + ma_device device; + SoundData data; +}; \ No newline at end of file diff --git a/miniaudio b/miniaudio new file mode 160000 index 0000000..4203697 --- /dev/null +++ b/miniaudio @@ -0,0 +1 @@ +Subproject commit 4203697b38b25e100bedfc73f3fa28cd8707705e diff --git a/pulseaudiosetup.sh b/pulseaudiosetup.sh new file mode 100644 index 0000000..5a9b626 --- /dev/null +++ b/pulseaudiosetup.sh @@ -0,0 +1,9 @@ +virtualDeviceName="VirtualMic" +realMic="alsa_input.pci-0000_24_00.0.analog-stereo.remapped" + +pacmd load-module module-null-sink "sink_name=${virtualDeviceName}" +pacmd update-sink-proplist "${virtualDeviceName}" "device.description=${virtualDeviceName}" +pacmd load-module module-virtual-source "source_name=${virtualDeviceName}-Source" "master=${virtualDeviceName}.monitor" +pacmd load-module module-loopback "sink=${virtualDeviceName}" "source=${realMic}" "latency_msec=1" + +# todo: set new sink as default \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1cd93da --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp new file mode 100644 index 0000000..7804089 --- /dev/null +++ b/src/mainwindow.cpp @@ -0,0 +1,53 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + QPushButton* btn = ui->pushButton; + QObject::connect(btn, SIGNAL( clicked() ), this, SLOT( buttonPressed() )); + + sc = new QShortcut(QKeySequence("Shift+1"), this, 0, 0, Qt::ApplicationShortcut); + QObject::connect(sc, SIGNAL( activated() ), this, SLOT( shortcut() )); + + globalShortcut = new QxtGlobalShortcut(QKeySequence("Shift+F1")); + QObject::connect(globalShortcut, SIGNAL(activated()), this, SLOT( shortcut2() )); + + Sound& sound = Sound::instance(); // init sound + sound.addDeviceWithName("VirtualMic"); + sound.addDefaultDevice(); +} + +MainWindow::~MainWindow() { + delete ui; + + Sound::deinit(); +} + +void MainWindow::buttonPressed() { + std::cout << "Button Pressed" << std::endl; + + playSound(); +} + +void MainWindow::shortcut() { + std::cout << "Shortcut Pressed" << std::endl; + + playSound(); +} + +void MainWindow::shortcut2() { + std::cout << "Shortcut2 Pressed!!" << std::endl; + + playSound(); +} + +void MainWindow::playSound() { + //Sound::instance().addPlayback("Uwu_voice-xjrU3N8M4eo-251.mp3"); + Sound::instance().addPlayback("bonk.wav"); +} diff --git a/src/sound.cpp b/src/sound.cpp new file mode 100644 index 0000000..21ed8ea --- /dev/null +++ b/src/sound.cpp @@ -0,0 +1,115 @@ +// Diese Datei ist stark an den examples von miniaudio orientiert (vor allem simple_mixing.c). Die Struktur ähnelt sich daher möglicherweise. + + +#define MA_DEBUG_OUTPUT +//#define MA_LOG_LEVEL MA_LOG_LEVEL_VERBOSE + +#ifdef WIN32 +#ifndef __wtypes_h__ +#include +#endif + +#ifndef __WINDEF_ +#include +#endif + +#ifndef _WINUSER_ +#include +#endif + +#ifndef __RPC_H__ +#include +#endif +#endif + +#include "sound.h" + +#define DR_FLAC_IMPLEMENTATION +#include "extras/dr_flac.h" //flac +#define DR_MP3_IMPLEMENTATION +#include "extras/dr_mp3.h" //mp3 +#define DR_WAV_IMPLEMENTATION +#include "extras/dr_wav.h" //wav + +#include "extras/stb_vorbis.c" //vorbis + +#define MINIAUDIO_IMPLEMENTATION +#include "miniaudio.h" + +#include //std::max +#include +#include +#include +#include + + +Sound* Sound::inst = nullptr; +const std::string Sound::FOLDER = "/home/yannis/prog/ts3/soundboardsounds/"; + +Sound& Sound::instance() { + if (!inst) { + inst = new Sound(); //TODO: needs delete + inst->init(); + } + return *inst; +} + +void Sound::deinit() { + if(inst) { + inst->stopAll(); + delete inst; + inst = nullptr; + } + + std::cout << "Sound deinited" << std::endl; +} + +void Sound::addPlayback(const std::string& name, float volume) { + for(SoundDevice* sd : devices) { + sd->addPlayback(name, volume); + } +} + +bool Sound::addDefaultDevice() { + SoundDevice* sd = SoundDevice::createDevice(&context); + if(sd) { + devices.push_back(sd); + } + return sd; +} + +bool Sound::addDeviceWithName(const std::string& name) { + SoundDevice* sd = SoundDevice::createDevice(&context, name); + if(sd) { + devices.push_back(sd); + } + return sd; +} + +void Sound::stopAll() { + for(SoundDevice* sd : devices) { + sd->stop(); + } +} + +Sound::Sound() { +} + +Sound::~Sound() { + stopAll(); + for(SoundDevice* sd : devices) { + delete sd; + } + devices.clear(); + ma_context_uninit(&context); +} + +void Sound::init() { + //dies wird getrennt vom ctor, da es beim ma_device_start sonst zu einer race condition kommen kann + + // enumerate devices + if (ma_context_init(NULL, 0, NULL, &context) != MA_SUCCESS) { + std::cout << "Failed to initialize Sound context." << std::endl; + throw new std::exception(); + } +} diff --git a/src/sounddevice.cpp b/src/sounddevice.cpp new file mode 100644 index 0000000..0c05e35 --- /dev/null +++ b/src/sounddevice.cpp @@ -0,0 +1,206 @@ +#include "sounddevice.h" + +#include +#include + +#include "sound.h" + +#define CHANNELCOUNT 2 +#define MINSTREAMCOUNT 5 + +static void global_sound_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) { + //es kann davon ausgegangen werden, das der buffer mit stille gefüllt ist + + //decoder + if (!pDevice->pUserData) return; + + ((SoundDevice*) pDevice->pUserData)->sound_callback(pOutput, frameCount); + + (void) pInput; //ignore input +} + +static ma_uint64 readDecoderandAdd(ma_decoder* decoder, float volume, unsigned int streamDivider, ma_uint32 frameCount, void* outputBuffer) { + assert(frameCount > 0); + + //read sound + int16_t* buffer = new int16_t[frameCount * CHANNELCOUNT]; + ma_uint64 frameCountRead = ma_decoder_read_pcm_frames(decoder, buffer, frameCount); + + if (frameCountRead > frameCount) frameCountRead = frameCount; //drop unwanted frames (sollte eigentlich nicht nötig sein) + + //zum mixen: samples durch anzahl decoder teilen (overflow verhindern), volume factor anwenden und auf outputbuffer addieren + int16_t* outbuf = (int16_t*) outputBuffer; + for (ma_uint64 i = 0; i < frameCountRead * CHANNELCOUNT; i++) { + outbuf[i] += (buffer[i] * volume) / streamDivider; //TODO: use sse / avx + } + + delete[] buffer; + + return frameCountRead; +} + + +void SoundDevice::sound_callback(void* outbuffer, ma_uint32 frameCount) { + ma_mutex_lock(data.mutex); + + unsigned int decoderCount = data.decoderCount; + + unsigned int streamDivider = std::max(decoderCount, MINSTREAMCOUNT); //nicht listplaybacks.size() verwenden, da manche playbacks möglicherweise noch fertig sind, aber noch nicht aus der list entfernt wurden. (wird von anderem thread gemacht) + unsigned int count = 0; //nummer des aktuellen decoders (nur für debugging) + + for (SoundDevice::Playback* pb : data.playbacks) { + if (pb->isDone) continue; //fertige encoder ignorieren + + // std::cout << "Play decoder " << (++count) << " of " << decoderCount << std::endl; //DEBUG + + //informationen beschaffen + ma_decoder* pDecoder = &(pb->decoder); + float volume = pb->volume; + + //decoder "reinaddieren" + ma_uint64 read = readDecoderandAdd(pDecoder, volume, streamDivider, frameCount, outbuffer); + + //decoder fertig -> nicht mehr verwenden + if (read < frameCount) { + pb->isDone = true; + --data.decoderCount; + } + } + + ma_mutex_unlock(data.mutex); +} + +SoundDevice::SoundDevice() { + data.mutex = new ma_mutex(); + ma_mutex_init(data.mutex); +} + +SoundDevice::~SoundDevice() { + ma_device_uninit(&device); + ma_mutex_uninit(data.mutex); + delete data.mutex; +} + +SoundDevice* SoundDevice::createDevice(ma_context* ctx, const std::string& name) { + ma_device_info* pPlaybackDeviceInfos; + ma_uint32 playbackDeviceCount; + ma_result result = ma_context_get_devices(ctx, &pPlaybackDeviceInfos, &playbackDeviceCount, NULL, 0); + int8_t choosenDevice = -1; + + std::cout << " " << playbackDeviceCount << " playback devices found:" << std::endl; + for(uint8_t i = 0; i < playbackDeviceCount; i++) { + std::cout << " " << (int) i << ": " << pPlaybackDeviceInfos[i].name << std::endl; + + if(pPlaybackDeviceInfos[i].name == name) { + choosenDevice = i; + } + } + + if(choosenDevice == -1) { + std::cout << "Device \"" << name << "\" not found!" << std::endl; + return nullptr; + } + + return createDevice(ctx, &pPlaybackDeviceInfos[choosenDevice].id); +} + +SoundDevice* SoundDevice::createDevice(ma_context* ctx, const ma_device_id* did) { + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.format = ma_format::ma_format_s16; + deviceConfig.playback.channels = CHANNELCOUNT; + if(did != NULL) + deviceConfig.playback.pDeviceID = did; + + deviceConfig.sampleRate = 44100; + deviceConfig.dataCallback = global_sound_callback; + deviceConfig.pulse.pStreamNamePlayback = "MySoundboard"; + + SoundDevice* sd = new SoundDevice(); + deviceConfig.pUserData = sd; + + if (ma_device_init(ctx, &deviceConfig, &sd->device) != MA_SUCCESS) { + std::cout << "Failed to open sound device." << std::endl; + + delete sd; + return nullptr; + } + + std::cout << "Sound playback device \"" << sd->device.playback.name << "\" initilized." << std::endl; + return sd; +} + +void SoundDevice::stop() { + if(deviceRunning) + ma_device_stop(&device); + + ma_mutex_lock(data.mutex); + for(Playback* pb : data.playbacks) { + ma_decoder_uninit(&pb->decoder); + delete pb; + } + data.playbacks.clear(); + ma_mutex_unlock(data.mutex); + + if(deviceRunning) + std::cout << "Sound playback device \"" << device.playback.name << "\" stopped." << std::endl; + + deviceRunning = false; +} + +void SoundDevice::addPlayback(const std::string& name, float volume) { + cleanupDecoders(); + + Playback* pb = new Playback(); + + if (ma_decoder_init_file((Sound::FOLDER + name).c_str(), NULL, &(pb->decoder)) != MA_SUCCESS) { + std::cout << "Sound datei: " << name << " konnte nicht geladen werden!" << std::endl; + throw std::exception(); + } + + pb->volume = volume; + pb->isDone = false; + + ma_mutex_lock(data.mutex); + + data.playbacks.push_back(pb); + ++data.decoderCount; + ma_mutex_unlock(data.mutex); + + startDevice(); +} + +void SoundDevice::startDevice() { + if (deviceRunning) return; + + if (ma_device_start(&device) != MA_SUCCESS) { + std::cout << "Failed to start sound device \"" << device.playback.name << "\"" << std::endl; + stop(); + ma_device_uninit(&device); + throw std::exception(); + } + + deviceRunning = true; + + std::cout << "Sound device \"" << device.playback.name << "\" started." << std::endl; +} + +void SoundDevice::cleanupDecoders() { + unsigned int count = 0; + ma_mutex_lock(data.mutex); + for (std::list::iterator it = data.playbacks.begin(); it != data.playbacks.end(); ) { + Playback* pb = *it; + if (pb->isDone) { + //uninit decoder + ma_decoder_uninit(&pb->decoder); + count++; + data.playbacks.erase(it++); + delete pb; + } + else + ++it; + } + ma_mutex_unlock(data.mutex); + + if (count) + std::cout << "removed " << count << " decoder" << std::endl; +} diff --git a/testqt.pro b/testqt.pro new file mode 100644 index 0000000..88a1a91 --- /dev/null +++ b/testqt.pro @@ -0,0 +1,47 @@ +QT += core gui multimedia + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++17 +CONFIG += debug_info +LIBS += -lX11 -ldl + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + src/main.cpp \ + src/mainwindow.cpp \ + src/sound.cpp \ + src/sounddevice.cpp + +HEADERS += \ + include/mainwindow.h \ + include/sound.h \ + include/sounddevice.h \ + miniaudio/miniaudio.h + +FORMS += \ + ui/mainwindow.ui + +TRANSLATIONS += \ + testqt_de_DE.ts + +INCLUDEPATH += $$PWD/include/ \ + $$PWD/miniaudio/ + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +# Adding QxtGlobalShortcut +include(QxtGlobalShortcut/QxtGlobalShortcut.pri) diff --git a/testqt_de_DE.ts b/testqt_de_DE.ts new file mode 100644 index 0000000..bf8dfe0 --- /dev/null +++ b/testqt_de_DE.ts @@ -0,0 +1,3 @@ + + + diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui new file mode 100644 index 0000000..d96df0e --- /dev/null +++ b/ui/mainwindow.ui @@ -0,0 +1,58 @@ + + + MainWindow + + + + 0 + 0 + 778 + 613 + + + + MainWindow + + + + + + 130 + 110 + 151 + 19 + + + + Hello, world! + + + + + + 120 + 150 + 100 + 27 + + + + Testbutton + + + + + + + 0 + 0 + 778 + 24 + + + + + + + +