first working

This commit is contained in:
mrbesen 2021-12-13 00:28:04 +01:00
commit 772376e440
Signed by: MrBesen
GPG Key ID: 596B2350DCD67504
15 changed files with 697 additions and 0 deletions

73
.gitignore vendored Normal file
View File

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

6
.gitmodules vendored Normal file
View File

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

1
QxtGlobalShortcut Submodule

@ -0,0 +1 @@
Subproject commit 861322355e29a22105ee0809dc3fb68940573335

34
include/mainwindow.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QShortcut>
#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

36
include/sound.h Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include "miniaudio.h"
#include <string>
#include <list>
#include <mutex>
#include <vector>
#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<SoundDevice*> devices;
static Sound* inst;
friend void sound_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount);
};

44
include/sounddevice.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include "miniaudio.h"
#include <list>
#include <string>
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<Playback*> playbacks; //liste der decoder
unsigned int decoderCount = 0;
unsigned int musicdecoderCount = 0;
ma_mutex* mutex;
};
bool deviceRunning = false;
ma_device device;
SoundData data;
};

1
miniaudio Submodule

@ -0,0 +1 @@
Subproject commit 4203697b38b25e100bedfc73f3fa28cd8707705e

9
pulseaudiosetup.sh Normal file
View File

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

11
src/main.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

53
src/mainwindow.cpp Normal file
View File

@ -0,0 +1,53 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <iostream>
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");
}

115
src/sound.cpp Normal file
View File

@ -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 <wtypes.h>
#endif
#ifndef __WINDEF_
#include <windef.h>
#endif
#ifndef _WINUSER_
#include <winuser.h>
#endif
#ifndef __RPC_H__
#include <rpc.h>
#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 <algorithm> //std::max
#include <cstring>
#include <cassert>
#include <cmath>
#include <iostream>
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();
}
}

206
src/sounddevice.cpp Normal file
View File

@ -0,0 +1,206 @@
#include "sounddevice.h"
#include <cassert>
#include <iostream>
#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<unsigned int>(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<Playback*>::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;
}

47
testqt.pro Normal file
View File

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

3
testqt_de_DE.ts Normal file
View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="testqt_de_DE"></TS>

58
ui/mainwindow.ui Normal file
View File

@ -0,0 +1,58 @@
<?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>778</width>
<height>613</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>130</x>
<y>110</y>
<width>151</width>
<height>19</height>
</rect>
</property>
<property name="text">
<string>Hello, world!</string>
</property>
</widget>
<widget class="QPushButton" name="pushButton">
<property name="geometry">
<rect>
<x>120</x>
<y>150</y>
<width>100</width>
<height>27</height>
</rect>
</property>
<property name="text">
<string>Testbutton</string>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>778</width>
<height>24</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>