refactorizada la GUI, ola y audioengine se ejecutan fuera de widgets.

Opción para ejecutar sin mostrar la GUI. Los controles de la interfaz no
son reactivos, no están conectados a las órdenes de audio.
cambio en la personalidad dmx para procesar los canales en mejor orden.
evita mandar dos veces los canales dobles por cada dmx frame, incluyendo file/folder.
This commit is contained in:
snt 2024-05-04 01:43:44 +02:00
parent 4ee82c5e5f
commit 1fccbf64fd
20 changed files with 416 additions and 323 deletions

View file

@ -3,6 +3,8 @@ TARGET = libremediaserver-audio
QT += webkitwidgets widgets QT += webkitwidgets widgets
HEADERS += src/libremediaserver-audio.h \ HEADERS += src/libremediaserver-audio.h \
src/dmxwidget.h \ src/dmxwidget.h \
src/libremediaserver-audio-gui.h \
src/main.h \
src/miniaudio.h \ src/miniaudio.h \
src/medialibrary.h \ src/medialibrary.h \
src/miniaudioengine.h \ src/miniaudioengine.h \
@ -15,6 +17,7 @@ HEADERS += src/libremediaserver-audio.h \
src/slidergroup.h src/slidergroup.h
SOURCES += src/main.cpp \ SOURCES += src/main.cpp \
src/dmxwidget.cpp \ src/dmxwidget.cpp \
src/libremediaserver-audio-gui.cpp \
src/miniaudio.c \ src/miniaudio.c \
src/libremediaserver-audio.cpp \ src/libremediaserver-audio.cpp \
src/medialibrary.cpp \ src/medialibrary.cpp \
@ -24,7 +27,7 @@ SOURCES += src/main.cpp \
src/audiowidget.cpp \ src/audiowidget.cpp \
src/settings.cpp \ src/settings.cpp \
src/slidergroup.cpp src/slidergroup.cpp
FORMS += src/libremediaserver-audio.ui FORMS += src/libremediaserver-audio-gui.ui
CCFLAG += -msse2 -mavx2 #-fsanitize=address -g -O0 CCFLAG += -msse2 -mavx2 #-fsanitize=address -g -O0
QMAKE_CXXFLAGS += $$(CXXFLAG) QMAKE_CXXFLAGS += $$(CXXFLAG)
#QMAKE_CXXFLAGS += -fsanitize=address -g -O0 #QMAKE_CXXFLAGS += -fsanitize=address -g -O0

View file

@ -1,64 +1,67 @@
#include "audiolayerwidget.h" #include "audiolayerwidget.h"
#include <QComboBox>
AudioLayerWidget::AudioLayerWidget(QWidget *parent, int layer):
AudioLayerWidget::AudioLayerWidget(QWidget *parent, QString name, int layer): QWidget(parent)
QGroupBox(parent)
, m_layer(layer) , m_layer(layer)
, m_suspendResumeButton(0) , m_suspendResumeButton(0)
{ {
this->setTitle(name);
QVBoxLayout *layout = new QVBoxLayout; QVBoxLayout *layout = new QVBoxLayout;
QHBoxLayout *progressTime = new QHBoxLayout; m_suspendResumeButton = new QPushButton(this);
m_suspendResumeButton->setText(StatusStr[Status::Iddle]);
m_suspendResumeButton->setMaximumWidth(200);
//connect(m_suspendResumeButton, SIGNAL(clicked()), SLOT(toggleSuspendResume()));
layout->addWidget(m_suspendResumeButton);
m_progress = new QProgressBar(this);
m_progress->setOrientation(Qt::Horizontal);
m_progress->setRange(0, 0);
m_progress->setValue(0);
m_progress->setFormat("%v / %m");
m_progress->setMaximumWidth(200);
layout->addWidget(m_progress);
m_progressTime = new QTimeEdit; m_progressTime = new QTimeEdit;
m_progressTime->text(); m_progressTime->setToolTip("Current Time");
m_progressTime->setObjectName("Current Time");
m_progressTime->setDisplayFormat("h:mm:ss:zzz"); m_progressTime->setDisplayFormat("h:mm:ss:zzz");
m_progressTime->setReadOnly(true); m_progressTime->setReadOnly(true);
m_progressTime->setButtonSymbols(QAbstractSpinBox::NoButtons); m_progressTime->setButtonSymbols(QAbstractSpinBox::NoButtons);
m_progressTime->setMaximumWidth(90); m_progressTime->setMaximumWidth(88);
m_progressTime->setFocusPolicy(Qt::NoFocus); m_progressTime->setFocusPolicy(Qt::NoFocus);
progressTime->addWidget(m_progressTime);
m_totalTimeValue = new QTimeEdit; m_totalTimeValue = new QTimeEdit;
m_totalTimeValue->setObjectName("Track Length");
m_totalTimeValue->setToolTip("Track Length");
m_totalTimeValue->setDisplayFormat("h:mm:ss:zzz"); m_totalTimeValue->setDisplayFormat("h:mm:ss:zzz");
m_totalTimeValue->setReadOnly(true); m_totalTimeValue->setReadOnly(true);
m_totalTimeValue->setButtonSymbols(QAbstractSpinBox::NoButtons); m_totalTimeValue->setButtonSymbols(QAbstractSpinBox::NoButtons);
m_totalTimeValue->setMaximumWidth(90); m_totalTimeValue->setMaximumWidth(88);
m_totalTimeValue->setFocusPolicy(Qt::NoFocus); m_totalTimeValue->setFocusPolicy(Qt::NoFocus);
progressTime->addWidget(m_totalTimeValue); QHBoxLayout *status = new QHBoxLayout;
layout->addLayout(progressTime); status->addWidget(m_progressTime);
status->addWidget(m_totalTimeValue);
m_progressSlider = new QSlider(Qt::Horizontal); layout->addLayout(status);
m_progressSlider->setFocusPolicy(Qt::NoFocus); QVBoxLayout *playback = new QVBoxLayout;
layout->addWidget(m_progressSlider);
QGridLayout *status = new QGridLayout;
m_statusValue = new QLabel;
status->addWidget(m_statusValue, 0, 0);
m_folderValue = new QLabel; m_folderValue = new QLabel;
m_folderValue->setMaximumWidth(200); m_folderValue->setMaximumWidth(200);
status->addWidget(m_folderValue, 1, 0); playback->addWidget(m_folderValue);
m_fileValue = new QLabel; m_fileValue = new QLabel;
m_fileValue->setMaximumWidth(200); m_fileValue->setMaximumWidth(200);
status->addWidget(m_fileValue, 2, 0); playback->addWidget(m_fileValue);
layout->addLayout(status); layout->addLayout(playback);
QHBoxLayout *volumeBox = new QHBoxLayout; QHBoxLayout *volumeBox = new QHBoxLayout;
m_volume = new SliderGroup("Vol", 0 , 100, 2, NULL); m_volume = new SliderGroup(0 , 100, 2, NULL);
volumeBox->addWidget(m_volume); volumeBox->addWidget(m_volume);
connect(m_volume, SIGNAL(valueChanged(float)), this, SLOT(volumeChanged(float))); connect(m_volume, SIGNAL(valueChanged(float)), this, SLOT(volumeChanged(float)));
m_pan = new SliderGroup("Pan", 0 , 255, 0, NULL); m_pan = new SliderGroup(0 , 255, 0, NULL);
volumeBox->addWidget(m_pan); volumeBox->addWidget(m_pan);
connect(m_pan, SIGNAL(valueChanged(float)), this, SLOT(panChanged(float))); connect(m_pan, SIGNAL(valueChanged(float)), this, SLOT(panChanged(float)));
m_pitch = new SliderGroup("Pitch", 0 , 255, 0, NULL); m_pitch = new SliderGroup(0 , 255, 0, NULL);
volumeBox->addWidget(m_pitch); volumeBox->addWidget(m_pitch);
connect(m_pitch, SIGNAL(valueChanged(float)), this, SLOT(pitchChanged(float))); connect(m_pitch, SIGNAL(valueChanged(float)), this, SLOT(pitchChanged(float)));
layout->addLayout(volumeBox); layout->addLayout(volumeBox);
m_suspendResumeButton = new QPushButton(this);
m_suspendResumeButton->setText(StatusStr[Status::Stopped]);
connect(m_suspendResumeButton, SIGNAL(clicked()), SLOT(toggleSuspendResume()));
layout->addWidget(m_suspendResumeButton);
this->setLayout(layout); this->setLayout(layout);
} }
@ -95,6 +98,7 @@ void AudioLayerWidget::toggleSuspendResume()
case Status::Stopped: case Status::Stopped:
this->setPlaybackStatus(Status::PlayingOnce); this->setPlaybackStatus(Status::PlayingOnce);
emit uiPlaybackChanged(m_layer, Status::PlayingOnce); emit uiPlaybackChanged(m_layer, Status::PlayingOnce);
case Status::Iddle:
break; break;
} }
} }
@ -129,43 +133,40 @@ void AudioLayerWidget::fileLoaded(QString file)
m_folderValue->setText(list.at(size - 2)); m_folderValue->setText(list.at(size - 2));
m_fileValue->setText(list.at(size - 1)); m_fileValue->setText(list.at(size - 1));
} }
this->setPlaybackStatus(Status::Stopped);
} }
void AudioLayerWidget::setPlaybackStatus(Status status) void AudioLayerWidget::setPlaybackStatus(Status status)
{ {
m_status = status; m_status = status;
if (status == Status::Stopped) if (status == Status::Stopped)
m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(0)); m_progress->setValue(0);
m_statusValue->blockSignals(true);
m_suspendResumeButton->blockSignals(true); m_suspendResumeButton->blockSignals(true);
m_statusValue->setText(StatusStr[status]);
m_suspendResumeButton->setText(StatusStr[status]); m_suspendResumeButton->setText(StatusStr[status]);
switch (m_status) {
case Status::Paused:
m_statusValue->setStyleSheet("QLabel { color : red; }");
break;
case Status::PlayingLoop:
case Status::PlayingOnce:
m_statusValue->setStyleSheet("QLabel { color : green; }");
break;
case Status::Stopped:
m_statusValue->setStyleSheet("QLabel { color : red; }");
break;
}
m_statusValue->blockSignals(false);
m_suspendResumeButton->blockSignals(false); m_suspendResumeButton->blockSignals(false);
} }
void AudioLayerWidget::durationChanged(float dur) void AudioLayerWidget::durationChanged(float dur)
{ {
dur *= 1000; m_progress->blockSignals(true);
m_progressSlider->setMaximum(dur); m_progressTime->blockSignals(true);
m_totalTimeValue->blockSignals(true);
m_progress->setRange(0, dur);
m_progress->setValue(0);
m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(0));
m_totalTimeValue->setTime(QTime::fromMSecsSinceStartOfDay(dur)); m_totalTimeValue->setTime(QTime::fromMSecsSinceStartOfDay(dur));
m_progress->blockSignals(false);
m_progressTime->blockSignals(false);
m_totalTimeValue->blockSignals(false);
} }
void AudioLayerWidget::refreshUi(float progress) void AudioLayerWidget::refreshUi(float progress)
{ {
progress *= 1000; progress *= 1000;
m_progressSlider->setValue(progress); m_progress->blockSignals(true);
m_progressTime->blockSignals(true);
m_progress->setValue(progress);
m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(progress)); m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(progress));
m_progress->blockSignals(false);
m_progressTime->blockSignals(false);
} }

View file

@ -4,16 +4,17 @@
#include <QPushButton> #include <QPushButton>
#include <QTimeEdit> #include <QTimeEdit>
#include <QLabel> #include <QLabel>
#include <QProgressBar>
#include "defines.h" #include "defines.h"
#include "slidergroup.h" #include "slidergroup.h"
class AudioLayerWidget : public QGroupBox class AudioLayerWidget : public QWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit AudioLayerWidget(QWidget *parent = 0, QString name = "Layer", int layer = 0); explicit AudioLayerWidget(QWidget *parent = 0, int layer = 0);
~AudioLayerWidget(); ~AudioLayerWidget();
void setVol(float vol); void setVol(float vol);
void resume(); void resume();
@ -27,15 +28,14 @@ private:
Status m_status; Status m_status;
int m_layer; int m_layer;
QPushButton *m_suspendResumeButton; QPushButton *m_suspendResumeButton;
QLabel * m_statusValue;
QLabel *m_fileValue; QLabel *m_fileValue;
QLabel * m_folderValue; QLabel * m_folderValue;
SliderGroup *m_volume; SliderGroup *m_volume;
SliderGroup *m_pan; SliderGroup *m_pan;
SliderGroup *m_pitch; SliderGroup *m_pitch;
QSlider *m_progressSlider;
QTimeEdit *m_progressTime; QTimeEdit *m_progressTime;
QTimeEdit *m_totalTimeValue; QTimeEdit *m_totalTimeValue;
QProgressBar *m_progress;
public slots: public slots:
void toggleSuspendResume(); void toggleSuspendResume();

View file

@ -3,119 +3,66 @@
AudioWidget::AudioWidget() : AudioWidget::AudioWidget() :
m_layout(new QHBoxLayout()) m_layout(new QHBoxLayout())
, m_refreshUi(new QTimer(this))
{ {
for (int i= 0; i < Settings::getInstance()->getLayersNumber(); i++ ) { for (int i= 0; i < Settings::getInstance()->getLayersNumber(); i++ ) {
AudioLayerWidget *alw = new AudioLayerWidget(this, tr("Layer %1").arg(i + 1), i); AudioLayerWidget *alw = new AudioLayerWidget(this, i);
m_layout->insertWidget(i, alw); m_layout->insertWidget(i, alw);
connect(alw, SIGNAL(uiSliderChanged(int, Slider, int)), this, SLOT(uiSliderAction(int, Slider, int))); connect(alw, SIGNAL(uiSliderChanged(int, Slider, int)), this, SLOT(uiSliderAction(int, Slider, int)));
connect(alw, SIGNAL(uiPlaybackChanged(int, Status)), this, SLOT(uiChangePlaybackStatus(int, Status))); connect(alw, SIGNAL(uiPlaybackChanged(int, Status)), this, SLOT(uiChangePlaybackStatus(int, Status)));
} }
setLayout(m_layout); setLayout(m_layout);
connect(m_refreshUi, SIGNAL(timeout()), this, SLOT(refreshUi()));
m_refreshUi->start(UI_REFRESH_TIME);
} }
bool AudioWidget::startEngine(int id) void AudioWidget::mediaLoaded(int layer, QString file, float duration)
{ {
return (m_mae.startEngine(id));
}
bool AudioWidget::startEngine()
{
return (m_mae.startEngine(Settings::getInstance()->getAudioDeviceId()));
}
void AudioWidget::stopEngine()
{
m_mae.stopEngine();
}
void AudioWidget::mediaLoaded(int layer, QString file)
{
ma_result result;
if (m_currentMedia[layer].compare(file) == 0 ) {
return;
}
if (!QFile::exists(file)) {
qWarning("Can not access to file %s", file.toLatin1().constData());
return;
}
result = m_mae.loadMedia(layer, file.toLatin1().data());
if (result != MA_SUCCESS) {
qWarning("can not open file %s", file.toLatin1().constData());
return;
}
m_currentMedia[layer] = file;
float pLength = m_mae.getDuration(layer);
qInfo("File loaded: %s - Duration: %f secs", file.toLatin1().constData(), pLength);
m_mae.printFormatInfo(layer);
QLayoutItem * const item = m_layout->itemAt(layer); QLayoutItem * const item = m_layout->itemAt(layer);
dynamic_cast<AudioLayerWidget *>(item->widget())->fileLoaded(file); dynamic_cast<AudioLayerWidget *>(item->widget())->fileLoaded(file);
dynamic_cast<AudioLayerWidget *>(item->widget())->durationChanged(pLength); dynamic_cast<AudioLayerWidget *>(item->widget())->durationChanged(duration);
} }
void AudioWidget::volChanged(int layer, float vol) { void AudioWidget::volChanged(int layer, float vol) {
m_mae.volChanged(layer, vol);
QLayoutItem * const item = m_layout->itemAt(layer); QLayoutItem * const item = m_layout->itemAt(layer);
dynamic_cast<AudioLayerWidget *>(item->widget())->setVol(vol); dynamic_cast<AudioLayerWidget *>(item->widget())->setVol(vol);
} }
void AudioWidget::panChanged(int layer, int pan) { void AudioWidget::panChanged(int layer, int pan) {
m_mae.panChanged(layer, pan);
QLayoutItem * const item = m_layout->itemAt(layer); QLayoutItem * const item = m_layout->itemAt(layer);
dynamic_cast<AudioLayerWidget *>(item->widget())->setPan(pan); dynamic_cast<AudioLayerWidget *>(item->widget())->setPan(pan);
} }
void AudioWidget::pitchChanged(int layer, int pitch) { void AudioWidget::pitchChanged(int layer, int pitch) {
m_mae.pitchChanged(layer, pitch);
QLayoutItem * const item = m_layout->itemAt(layer); QLayoutItem * const item = m_layout->itemAt(layer);
dynamic_cast<AudioLayerWidget *>(item->widget())->setPitch(pitch); dynamic_cast<AudioLayerWidget *>(item->widget())->setPitch(pitch);
} }
void AudioWidget::playbackChanged(int layer, Status status) void AudioWidget::playbackChanged(int layer, Status status)
{ {
m_mae.playbackChanged(layer, status);
QLayoutItem * const item = m_layout->itemAt(layer); QLayoutItem * const item = m_layout->itemAt(layer);
dynamic_cast<AudioLayerWidget *>(item->widget())->setPlaybackStatus(status); dynamic_cast<AudioLayerWidget *>(item->widget())->setPlaybackStatus(status);
} }
void AudioWidget::entryPointChanged(int layer, int cursor) void AudioWidget::cursorChanged(int layer, float cursor)
{ {
m_mae.setCursor(layer, cursor);
QLayoutItem * const item = m_layout->itemAt(layer); QLayoutItem * const item = m_layout->itemAt(layer);
AudioLayerWidget *aw = dynamic_cast<AudioLayerWidget *>(item->widget()); AudioLayerWidget *alw = dynamic_cast<AudioLayerWidget *>(item->widget());
aw->refreshUi(m_mae.getCursor(layer)); alw->refreshUi(cursor);
}
void AudioWidget::refreshUi() {
for (int i= 0; i < Settings::getInstance()->getLayersNumber(); i++ ) {
QLayoutItem * const item = m_layout->itemAt(i);
AudioLayerWidget *aw = dynamic_cast<AudioLayerWidget *>(item->widget());
Status s = aw->getPlaybackStatus();
if (s == Status::PlayingOnce || s == Status::PlayingLoop) {
aw->refreshUi(m_mae.getCursor(i));
}
}
} }
void AudioWidget::uiSliderAction(int layer, Slider s, int value) void AudioWidget::uiSliderAction(int layer, Slider s, int value)
{ {
switch (s){ switch (s){
case Slider::Volume: case Slider::Volume:
m_mae.volChanged(layer, value); emit uiVolChanged(layer, value);
break; break;
case Slider::Pan: case Slider::Pan:
m_mae.panChanged(layer, value); emit uiPanChanged(layer, value);
break; break;
case Slider::Pitch: case Slider::Pitch:
m_mae.pitchChanged(layer, value); emit uiPitchChanged(layer, value);
break; break;
} }
} }
void AudioWidget::uiChangePlaybackStatus(int layer, Status s) { void AudioWidget::uiChangePlaybackStatus(int layer, Status s) {
m_mae.playbackChanged(layer, s); emit uiPlaybackChanged(layer, s);
} }

View file

@ -14,28 +14,25 @@ class AudioWidget : public QWidget
public: public:
AudioWidget(); AudioWidget();
bool startEngine(); void mediaLoaded(int layer, QString media, float duration);
bool startEngine(int id);
void stopEngine();
void mediaLoaded(int layer, QString media );
void volChanged(int layer, float vol); void volChanged(int layer, float vol);
void panChanged(int layer, int pan); void panChanged(int layer, int pan);
void pitchChanged(int layer, int pitch); void pitchChanged(int layer, int pitch);
void playbackChanged(int layer, Status status); void playbackChanged(int layer, Status status);
void entryPointChanged(int layer, int cursor); void cursorChanged(int layer, float cursor);
private:
MiniAudioEngine m_mae;
QString m_currentMedia[MAX_LAYERS];
QHBoxLayout *m_layout; QHBoxLayout *m_layout;
QTimer *m_refreshUi;
public slots: public slots:
void uiSliderAction(int layer, Slider s, int value); void uiSliderAction(int layer, Slider s, int value);
void uiChangePlaybackStatus(int layer, Status s); void uiChangePlaybackStatus(int layer, Status s);
private slots: signals:
void refreshUi(); void uiMediaLoaded(int layer, QString media );
void uiVolChanged(int layer, float vol);
void uiPanChanged(int layer, int pan);
void uiPitchChanged(int layer, int pitch);
void uiPlaybackChanged(int layer, Status status);
void uiEntryPointChanged(int layer, int cursor);
}; };
#endif // AUDIOWIDGET_H #endif // AUDIOWIDGET_H

View file

@ -1,8 +1,7 @@
#ifndef DEFINES_H #ifndef DEFINES_H
#define DEFINES_H #define DEFINES_H
//#define VERSION "LibreMediaServerAudio 0.2.0 Antigona Release" #define VERSION "LibreMediaServerAudio 0.2.0 Antigona Release"
#define VERSION "Kike Substitutor - No AI required - v0.2.0"
#define COPYRIGHT "(C) 2014-2024 Santi Noreña <lms@criptomart.net>" #define COPYRIGHT "(C) 2014-2024 Santi Noreña <lms@criptomart.net>"
#define LICENSE "GPL 3 Licensed. See LICENSE.txt.\nSound guys are not allowed to use this software." #define LICENSE "GPL 3 Licensed. See LICENSE.txt.\nSound guys are not allowed to use this software."
#define DEFAULT_FILE "lms-audio.xlm" #define DEFAULT_FILE "lms-audio.xlm"
@ -22,6 +21,7 @@ enum Status
Paused, Paused,
PlayingOnce, PlayingOnce,
PlayingLoop, PlayingLoop,
Iddle
}; };
static const char* StatusStr[] = static const char* StatusStr[] =
@ -30,6 +30,7 @@ static const char* StatusStr[] =
"Pause", "Pause",
"Playing One", "Playing One",
"Playing Loop", "Playing Loop",
"Iddle",
0x0 0x0
}; };

View file

@ -1,39 +1,16 @@
#ifndef DMXPERSONALITY_H #ifndef DMXPERSONALITY_H
#define DMXPERSONALITY_H #define DMXPERSONALITY_H
/** Define the DMX personality to avoid dealing with #define VOLUME_COARSE 3
* numbers and change it easyly in case #define PAN 6
* #define DMX_FOLDER 0
1 - Volumen Coarse #define DMX_FILE 1
2 - Pan #define PLAYBACK 8
3 - Folder #define VOLUME_FINE 2
4 - File #define ENTRY_POINT_COARSE 5
5 - Playback #define ENTRY_POINT_FINE 4
0-24 : Play once. #define PITCH 7
25-49: Stop. Returns to start of file.
50-74: Pause. It keeps the time of reproductions.
75-99: Play loop.
6 - Control - Reservado, sin uso en este momento.
7 - Volume Fine
8 - Entry Point Coarse - Punto de entrada de reproducción.
9 - Entry Point Fine - El valor de estos dos canales en centésimas de segundo.
10 - Pan
11 - Pitch
*/
// ToDo: Mejor inicializacion, primero folder, file, después params, ultimo playback.7 #define LAYER_CHANNELS 9
// quitar CONTROL no usado
#define VOLUME_COARSE 0
#define PAN 1
#define DMX_FOLDER 2
#define DMX_FILE 3
#define PLAYBACK 4
#define CONTROL 5
#define VOLUME_FINE 6
#define ENTRY_POINT_COARSE 7
#define ENTRY_POINT_FINE 8
#define PITCH 9
#define LAYER_CHANNELS 10
#endif // DMXPERSONALITY_H #endif // DMXPERSONALITY_H

View file

@ -0,0 +1,48 @@
/*
Libre Media Server Audio - An Open source Media Server for arts and performing.
(c) Criptomart - Santiago Noreña 2012-2024 <lms@criptomart.net>
https://git.criptomart.net/libremediaserver
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "libremediaserver-audio-gui.h"
libreMediaServerAudioUi::libreMediaServerAudioUi(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
this->setWindowTitle(VERSION);
m_aw = new AudioWidget;
setCentralWidget(m_aw);
m_dmxWidget = new dmxWidget(this);
QDockWidget *topWidget = new QDockWidget(tr("Master"), this);
topWidget->setAllowedAreas(Qt::TopDockWidgetArea);
topWidget->setWidget(m_dmxWidget);
addDockWidget(Qt::TopDockWidgetArea, topWidget);
connect(ui.actionLaunch_OLA_Setup, SIGNAL(triggered()), this, SLOT(olasetup()));
}
libreMediaServerAudioUi::~libreMediaServerAudioUi()
{
}
void libreMediaServerAudioUi::olasetup()
{
QWebView *view = new QWebView();
view->load(QUrl("http://localhost:9090/ola.html"));
view->show();
}

View file

@ -0,0 +1,48 @@
/*
Libre Media Server Audio - An Open source Media Server for arts and performing.
(c) Criptomart - Santiago Noreña 2012-2024 <lms@criptomart.net>
https://git.criptomart.net/libremediaserver
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LIBREMEDIASERVERAUDIOUI_H
#define LIBREMEDIASERVERAUDIOUI_H
#include <QDockWidget>
#include <QWebView>
#include "audiowidget.h"
#include "dmxwidget.h"
#include "defines.h"
#include "ui_libremediaserver-audio-gui.h"
class libreMediaServerAudioUi : public QMainWindow
{
Q_OBJECT
public:
libreMediaServerAudioUi(QWidget *parent = 0);
virtual ~libreMediaServerAudioUi();
AudioWidget *m_aw;
dmxWidget *m_dmxWidget;
private:
Ui::LibreMediaServerAudio ui;
private slots:
void olasetup();
};
#endif // LIBREMEDIASERVERAUDIOUI_H

View file

@ -21,50 +21,35 @@
#include "libremediaserver-audio.h" #include "libremediaserver-audio.h"
libreMediaServerAudio::libreMediaServerAudio(QStringList args, QWidget *parent) libreMediaServerAudio::libreMediaServerAudio(bool gui)
: QMainWindow(parent)
{ {
Q_UNUSED(args); m_ui = gui;
qDebug() << VERSION;
qDebug() << COPYRIGHT;
qDebug() << LICENSE;
ui.setupUi(this);
this->setWindowTitle(VERSION);
Settings *set = Settings::getInstance(); Settings *set = Settings::getInstance();
set->readFile(); set->readFile();
m_mediaLibrary = new MediaLibrary; m_mediaLibrary = new MediaLibrary;
m_mediaLibrary->initMediaLibrary(); m_mediaLibrary->initMediaLibrary();
m_aw = new AudioWidget;
setCentralWidget(m_aw);
m_dmxWidget = new dmxWidget(this);
QDockWidget *topWidget = new QDockWidget(tr("Master"), this);
topWidget->setAllowedAreas(Qt::TopDockWidgetArea);
topWidget->setWidget(m_dmxWidget);
addDockWidget(Qt::TopDockWidgetArea, topWidget);
m_ola = new olaThread(this, set->getLayersNumber()); m_ola = new olaThread(this, set->getLayersNumber());
Q_CHECK_PTR(m_ola); Q_CHECK_PTR(m_ola);
m_ola->blockSignals(true); m_ola->blockSignals(true);
connect(m_ola, SIGNAL (universeReceived(int)), m_dmxWidget, SLOT(updateWatchDMX(int)));
connect(m_ola, SIGNAL(dmxOutput(int, int, int)), this, SLOT(dmxInput(int, int, int))); connect(m_ola, SIGNAL(dmxOutput(int, int, int)), this, SLOT(dmxInput(int, int, int)));
m_ola->registerUniverse(); m_ola->registerUniverse();
connect(ui.actionLaunch_OLA_Setup, SIGNAL(triggered()), this, SLOT(olasetup())); m_mae.startEngine(set->getAudioDeviceId());
m_aw->startEngine();
qDebug("Init Complete."); qDebug("Init Complete.");
m_ola->blockSignals(false); m_ola->blockSignals(false);
m_ola->start(QThread::TimeCriticalPriority ); m_ola->start(QThread::TimeCriticalPriority );
#ifndef NOGUI
if (m_ui) {
m_refreshUi = new QTimer(this);
connect(m_refreshUi, SIGNAL(timeout()), this, SLOT(refreshUi()));
m_refreshUi->start(UI_REFRESH_TIME);
}
#endif
} }
libreMediaServerAudio::~libreMediaServerAudio() libreMediaServerAudio::~libreMediaServerAudio()
{ {
m_ola->stop(); m_ola->stop();
m_aw->stopEngine(); m_mae.stopEngine();
}
void libreMediaServerAudio::olasetup()
{
QWebView *view = new QWebView();
view->load(QUrl("http://localhost:9090/ola.html"));
view->show();
} }
void libreMediaServerAudio::dmxInput(int layer, int channel, int value) void libreMediaServerAudio::dmxInput(int layer, int channel, int value)
@ -73,54 +58,82 @@ void libreMediaServerAudio::dmxInput(int layer, int channel, int value)
return; return;
QString mediaFile = NULL; QString mediaFile = NULL;
int aux; int aux;
switch(channel){ if (channel == DMX_FOLDER || channel == DMX_FILE){
case DMX_FOLDER: int folder = (value >> 8) & 0x000000FF;
aux = m_ola->getValue(layer, DMX_FILE); int file = value & 0x000000FF;
mediaFile = m_mediaLibrary->requestNewFile(value, aux); mediaFile = m_mediaLibrary->requestNewFile(folder, file);
if (QFile::exists(mediaFile)) if (strcmp(mediaFile.toLatin1().constData(), m_currentMedia[layer].toLatin1().constData()) == 0)
m_aw->mediaLoaded(layer, mediaFile); return;
break; if (QFile::exists(mediaFile)){
case DMX_FILE: m_mae.loadMedia(layer, mediaFile.toLatin1().data());
aux = m_ola->getValue(layer, DMX_FOLDER); #ifndef NOGUI
mediaFile = m_mediaLibrary->requestNewFile(aux, value); if (m_ui)
if (QFile::exists(mediaFile)) m_lmsUi->m_aw->mediaLoaded(layer, mediaFile, m_mae.getDuration(layer));
m_aw->mediaLoaded(layer, mediaFile); #endif
break; m_currentMedia[layer] = mediaFile;
case VOLUME_COARSE: }
case VOLUME_FINE: } else if (channel == VOLUME_COARSE || channel == VOLUME_FINE) {
m_aw->volChanged(layer, (value / 65025.0f)); m_mae.volChanged(layer, (value / 65025.0f));
break; #ifndef NOGUI
case PAN: if (m_ui)
m_aw->panChanged(layer, value); m_lmsUi->m_aw->volChanged(layer, (value / 650.25f));
break; #endif
case PITCH: } else if (channel == PAN) {
m_aw->pitchChanged(layer, value); m_mae.panChanged(layer, value);
break; #ifndef NOGUI
case ENTRY_POINT_COARSE: if (m_ui)
case ENTRY_POINT_FINE: m_lmsUi->m_aw->panChanged(layer, value);
m_aw->entryPointChanged(layer, value); #endif
break; } else if (channel == PITCH) {
case PLAYBACK: m_mae.pitchChanged(layer, value);
if (value == 0) #ifndef NOGUI
break; if (m_ui)
aux = value / 25; m_lmsUi->m_aw->pitchChanged(layer, value);
switch (aux) { #endif
case 0 : } else if (channel == ENTRY_POINT_COARSE || channel == ENTRY_POINT_FINE) {
m_aw->playbackChanged(layer, PlayingOnce); m_mae.setCursor(layer, value);
break; #ifndef NOGUI
case 1 : if (m_ui)
m_aw->playbackChanged(layer, Stopped); m_lmsUi->m_aw->cursorChanged(layer, m_mae.getCursor(layer));
break; #endif
case 2 :
m_aw->playbackChanged(layer, Paused); } else if (channel == PLAYBACK && value > 0) {
break; Status s = m_mae.getStatus(layer);
case 3 : aux = value / 25;
m_aw->playbackChanged(layer, PlayingLoop); if (s != aux) {
break; if (aux == 0)
default : s = Status::PlayingOnce;
break; else if (aux == 1)
s = Status::Stopped;
else if (aux == 2)
s = Status::Paused;
else if (aux == 3)
s = Status::PlayingLoop;
m_mae.playbackChanged(layer, s);
#ifndef NOGUI
if (m_ui)
m_lmsUi->m_aw->playbackChanged(layer, s);
#endif
} }
default:
break;
} }
} }
#ifndef NOGUI
void libreMediaServerAudio::refreshUi() {
if (!m_ui)
return;
for (int i= 0; i < Settings::getInstance()->getLayersNumber(); i++ ) {
Status s = m_mae.getStatus(i);
if (s == Status::PlayingOnce || s == Status::PlayingLoop) {
m_lmsUi->m_aw->cursorChanged(i, m_mae.getCursor(i));
}
}
}
void libreMediaServerAudio::setUi(libreMediaServerAudioUi *lmsUi)
{
m_lmsUi = lmsUi;
connect(m_ola, SIGNAL(universeReceived(int)), m_lmsUi->m_dmxWidget, SLOT(updateWatchDMX(int)));
};
#endif

View file

@ -20,35 +20,41 @@
#ifndef LIBREMEDIASERVERAUDIO_H #ifndef LIBREMEDIASERVERAUDIO_H
#define LIBREMEDIASERVERAUDIO_H #define LIBREMEDIASERVERAUDIO_H
#include <QDockWidget>
#include <QWebView>
#include "audiowidget.h"
#include "medialibrary.h" #include "medialibrary.h"
#include "olathread.h" #include "olathread.h"
#include "settings.h" #include "settings.h"
#include "dmxwidget.h"
#include "defines.h" #include "defines.h"
#include "ui_libremediaserver-audio.h" #ifndef NOGUI
#include "libremediaserver-audio-gui.h"
#endif
class libreMediaServerAudio : public QMainWindow class libreMediaServerAudio : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
libreMediaServerAudio (QStringList args, QWidget *parent = 0); libreMediaServerAudio(bool gui = false);
virtual ~libreMediaServerAudio(); virtual ~libreMediaServerAudio();
Ui::LibreMediaServerAudio ui; olaThread *m_ola;
#ifndef NOGUI
void setUi(libreMediaServerAudioUi *lmsUi);
#endif
private: private:
AudioWidget *m_aw;
dmxWidget *m_dmxWidget;
olaThread *m_ola;
MediaLibrary *m_mediaLibrary; MediaLibrary *m_mediaLibrary;
MiniAudioEngine m_mae;
QString m_currentMedia[MAX_LAYERS];
#ifndef NOGUI
bool m_ui;
QTimer *m_refreshUi;
libreMediaServerAudioUi *m_lmsUi;
#endif
private slots: private slots:
void olasetup();
void dmxInput(int layer, int channel, int value); void dmxInput(int layer, int channel, int value);
#ifndef NOGUI
void refreshUi();
#endif
}; };
#endif // LIBREMEDIASERVERAUDIO_H #endif // LIBREMEDIASERVERAUDIO_H

View file

@ -18,34 +18,29 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "libremediaserver-audio.h" #include "main.h"
bool hasUi(int &argc, char *argv[])
{
for (int i = 1; i < argc; ++i) {
if (!strcmp(argv[i], "--gui"))
return true;
}
return false;
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
QApplication app(argc, argv); QApplication app(argc, argv);
QStringList args = app.arguments();
if (args.size() > 1) libreMediaServerAudio lms(hasUi(argc, argv));
#ifndef NOGUI
if (hasUi(argc, argv))
{ {
if (args.contains("-v")) libreMediaServerAudioUi *lmsUi = new libreMediaServerAudioUi();
{ lms.setUi(lmsUi);
qDebug() << VERSION; lmsUi->show();
qDebug() << COPYRIGHT;
qDebug() << LICENSE;
return 0;
}
if (args.contains("-h"))
{
qDebug() << VERSION;
qDebug() << COPYRIGHT;
qDebug() << LICENSE;
qDebug() << "Help for command line options:";
qDebug() << "-v show the version and exits";
qDebug() << "-h this help";
return 0;
}
} }
libreMediaServerAudio libreMediaServerAudio(args); #endif
libreMediaServerAudio.show();
return app.exec(); return app.exec();
} }

20
src/main.h Normal file
View file

@ -0,0 +1,20 @@
#ifndef MAIN_H
#define MAIN_H
#include <QCoreApplication>
#include <QApplication>
#include "medialibrary.h"
#include "olathread.h"
#include "settings.h"
#include "libremediaserver-audio.h"
#include "libremediaserver-audio-gui.h"
#include "defines.h"
olaThread *m_ola;
MediaLibrary *m_mediaLibrary;
// slots
void dmxInput(int layer, int channel, int value);
#endif // MAIN_H

View file

@ -115,7 +115,6 @@ ma_result MiniAudioEngine::loadMedia(int layer, char *file)
if (m_mediaLoaded[layer] == true) if (m_mediaLoaded[layer] == true)
{ {
qInfo("removing sound %i", layer);
ma_sound_uninit(&m_currentSound[layer]); ma_sound_uninit(&m_currentSound[layer]);
m_mediaLoaded[layer] = false; m_mediaLoaded[layer] = false;
} }
@ -137,16 +136,21 @@ ma_result MiniAudioEngine::loadMedia(int layer, char *file)
float MiniAudioEngine::getDuration(int layer) float MiniAudioEngine::getDuration(int layer)
{ {
ma_result result; ma_result result;
float ret = 0; ma_uint64 lengthInPCMFrames;
ma_uint32 sampleRate;
float ret;
if (m_mediaLoaded[layer] == false) if (m_mediaLoaded[layer] == false)
return MA_DOES_NOT_EXIST; return MA_DOES_NOT_EXIST;
result = ma_sound_get_length_in_seconds(&m_currentSound[layer], &ret); result = ma_sound_get_length_in_pcm_frames(&m_currentSound[layer], &lengthInPCMFrames);
if (result != MA_SUCCESS) if (result != MA_SUCCESS) {
{ return result;
qWarning("Can not get duration %i", layer);
ret = 0;
} }
result = ma_sound_get_data_format(&m_currentSound[layer], NULL, NULL, &sampleRate, NULL, 0);
if (result != MA_SUCCESS) {
return result;
}
ret = 1000.0f * (lengthInPCMFrames / float(sampleRate));
return ret; return ret;
} }
@ -161,7 +165,7 @@ float MiniAudioEngine::getCursor(int layer)
if (result != MA_SUCCESS) if (result != MA_SUCCESS)
{ {
qWarning("Can not get cursor %i", layer); qWarning("Can not get cursor %i", layer);
ret = 0; ret = -1;
} }
return ret; return ret;
} }
@ -231,6 +235,8 @@ void MiniAudioEngine::playbackChanged(int layer, Status status)
ma_sound_set_looping(&m_currentSound[layer], false); ma_sound_set_looping(&m_currentSound[layer], false);
ma_sound_start(&m_currentSound[layer]); ma_sound_start(&m_currentSound[layer]);
break; break;
default:
break;
} }
} }
@ -244,3 +250,17 @@ void MiniAudioEngine::setCursor(int layer, int cursor)
f = (cursor * f) / 65025; f = (cursor * f) / 65025;
ma_sound_seek_to_pcm_frame(&m_currentSound[layer], f); ma_sound_seek_to_pcm_frame(&m_currentSound[layer], f);
} }
Status MiniAudioEngine::getStatus(int layer)
{
if (m_mediaLoaded[layer] == ma_bool8(false))
return Status::Iddle;
if (ma_sound_is_playing(&m_currentSound[layer])) {
if (ma_sound_is_looping(&m_currentSound[layer]))
return Status::PlayingLoop;
return Status::PlayingOnce;
}
if (this->getDuration(layer) > 0)
return Status::Paused;
return Status::Stopped;
}

View file

@ -8,7 +8,7 @@
class MiniAudioEngine class MiniAudioEngine
{ {
friend class AudioWidget; friend class libreMediaServerAudio;
public: public:
MiniAudioEngine(); MiniAudioEngine();
@ -26,6 +26,7 @@ protected:
float getCursor(int layer); float getCursor(int layer);
void setCursor(int layer, int cursor); void setCursor(int layer, int cursor);
ma_result printFormatInfo(int layer); ma_result printFormatInfo(int layer);
Status getStatus(int layer);
private: private:
ma_resource_manager_config resourceManagerConfig; ma_resource_manager_config resourceManagerConfig;

View file

@ -63,39 +63,58 @@ void olaThread::stop()
void olaThread::NewDmx(const ola::client::DMXMetadata &data, void olaThread::NewDmx(const ola::client::DMXMetadata &data,
const ola::DmxBuffer &buffer) const ola::DmxBuffer &buffer)
{ {
bool volSent = false;
bool entrySent = false;
foreach (const dmxSetting &i, m_dmxSettings) { foreach (const dmxSetting &i, m_dmxSettings) {
if(i.universe == data.universe && i.address > -1) { if(i.universe == data.universe && i.address > -1) {
bool volSent = false;
bool entrySent = false;
bool fileSent = false;
for (int j = 0; j < LAYER_CHANNELS; j++){ for (int j = 0; j < LAYER_CHANNELS; j++){
int value = buffer.Get((i.address) + j); int value = buffer.Get((i.address) + j);
if (m_dmx[i.layer][j] != value) { if (m_dmx[i.layer][j] != value) {
m_dmx[i.layer][j] = value; m_dmx[i.layer][j] = value;
switch (j) { switch (j) {
case VOLUME_COARSE: case DMX_FOLDER:
value = (value * 0x100) + buffer.Get(i.address + VOLUME_FINE); value *= 0x100;
value += buffer.Get(i.address + DMX_FILE);
emit dmxOutput(i.layer,j,value); emit dmxOutput(i.layer,j,value);
volSent = true; m_dmx[i.layer][DMX_FILE] = buffer.Get(i.address + DMX_FILE);
fileSent = true;
break; break;
case ENTRY_POINT_COARSE: case DMX_FILE:
value = (value * 0x100) + buffer.Get(i.address + ENTRY_POINT_FINE); if (fileSent)
break;
value += buffer.Get(i.address + DMX_FOLDER) * 0x100;
emit dmxOutput(i.layer,j,value); emit dmxOutput(i.layer,j,value);
entrySent = true; m_dmx[i.layer][DMX_FOLDER] = buffer.Get(i.address + DMX_FOLDER);
fileSent = true;
break; break;
case VOLUME_FINE: case VOLUME_FINE:
if (volSent == false) value = (buffer.Get(i.address + VOLUME_COARSE) * 0x100) + value;
{ emit dmxOutput(i.layer,j,value);
value = (buffer.Get(i.address + VOLUME_COARSE) * 0x100) + value; m_dmx[i.layer][VOLUME_COARSE] = buffer.Get(i.address + VOLUME_COARSE);
emit dmxOutput(i.layer,j,value); volSent = true;
} break;
case VOLUME_COARSE:
if (volSent)
break;
value = (value * 0x100) + buffer.Get(i.address + VOLUME_FINE);
emit dmxOutput(i.layer,j,value);
m_dmx[i.layer][VOLUME_FINE] = buffer.Get(i.address + VOLUME_FINE);
volSent = true;
break; break;
case ENTRY_POINT_FINE: case ENTRY_POINT_FINE:
if (entrySent == false) value = (buffer.Get(i.address + ENTRY_POINT_COARSE) * 0x100) + value;
{ emit dmxOutput(i.layer,j,value);
value = (buffer.Get(i.address + ENTRY_POINT_COARSE) * 0x100) + value; m_dmx[i.layer][ENTRY_POINT_COARSE] = buffer.Get(i.address + ENTRY_POINT_COARSE);
emit dmxOutput(i.layer,j,value); entrySent = true;
} break;
case ENTRY_POINT_COARSE:
if (entrySent)
break;
value = (value * 0x100) + buffer.Get(i.address + ENTRY_POINT_FINE);
emit dmxOutput(i.layer,j,value);
m_dmx[i.layer][ENTRY_POINT_FINE] = buffer.Get(i.address + ENTRY_POINT_FINE);
entrySent = true;
break; break;
default: default:
emit dmxOutput(i.layer,j,value); emit dmxOutput(i.layer,j,value);
@ -127,7 +146,7 @@ bool olaThread::CheckDataLoss() {
void olaThread::socketClosed() void olaThread::socketClosed()
{ {
qWarning("ola closed connection. Try reopening it... "); qWarning("ola daemon closed connection, reopening it... ");
m_clientWrapper->GetSelectServer()->Terminate(); m_clientWrapper->GetSelectServer()->Terminate();
m_client = NULL; m_client = NULL;
m_clientWrapper = NULL; m_clientWrapper = NULL;

View file

@ -53,7 +53,7 @@ private:
if (error.Success()) { if (error.Success()) {
qDebug("Register Universe success"); qDebug("Register Universe success");
} else { } else {
qWarning("Register command failed: %s", error.Error().c_str()); qCritical("Register command failed: %s", error.Error().c_str());
} }
} }
/** /**

View file

@ -1,45 +1,43 @@
#include "slidergroup.h" #include "slidergroup.h"
SliderGroup::SliderGroup(const QString &title, \ SliderGroup::SliderGroup(int min,
int min,
int max, int max,
int decimals, int decimals,
QWidget *parent) QWidget *parent)
: QGroupBox(title, parent) : QWidget(parent)
{ {
this->setFlat(true); QVBoxLayout *layout = new QVBoxLayout;
this->setTitle(title); layout->setAlignment(Qt::AlignHCenter);
this->setMaximumWidth(65);
slider = new QSlider(Qt::Orientation::Vertical); slider = new QSlider(Qt::Orientation::Vertical);
slider->setFocusPolicy(Qt::StrongFocus); slider->setFocusPolicy(Qt::StrongFocus);
slider->setTickPosition(QSlider::TicksBothSides); slider->setTickPosition(QSlider::TicksBothSides);
slider->setTickInterval((max - min) / 11); slider->setTickInterval((max - min) / 11);
slider->setSingleStep(1); slider->setSingleStep(1);
slider->setRange(min, max); slider->setRange(min, max);
slider->setMaximumWidth(65);
valueBox = new QDoubleSpinBox(); valueBox = new QDoubleSpinBox();
valueBox->setFocusPolicy(Qt::NoFocus); valueBox->setFocusPolicy(Qt::NoFocus);
valueBox->setButtonSymbols(QAbstractSpinBox::NoButtons); valueBox->setButtonSymbols(QAbstractSpinBox::NoButtons);
valueBox->setMaximumWidth(45); valueBox->setMaximumWidth(65);
valueBox->setRange(min, max); valueBox->setRange(min, max);
valueBox->setDecimals(decimals); valueBox->setDecimals(decimals);
connect(slider, SIGNAL(valueChanged(int)), this, SLOT(sliderValueChanged(int))); connect(slider, SIGNAL(valueChanged(int)), this, SLOT(sliderValueChanged(int)));
QVBoxLayout *slidersLayout = new QVBoxLayout(); layout->addWidget(slider);
slidersLayout->addWidget(valueBox); layout->addWidget(valueBox);
slidersLayout->addWidget(slider); this->setLayout(layout);
setLayout(slidersLayout);
} }
void SliderGroup::sliderValueChanged(int value) void SliderGroup::sliderValueChanged(int value)
{ {
valueBox->setValue(value);
if (valueBox->decimals() > 1) if (valueBox->decimals() > 1)
value /= 100.0f; value /= 100.0f;
emit valueChanged(value); emit valueChanged(value);
valueBox->setValue(value);
}; };
void SliderGroup::setValue(float value) void SliderGroup::setValue(float value)
{ {
if (valueBox->decimals() > 1)
value *= 100.0f;
slider->setValue(value); slider->setValue(value);
valueBox->setValue(value); valueBox->setValue(value);
} }

View file

@ -6,16 +6,15 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QSlider> #include <QSlider>
class SliderGroup : public QGroupBox class SliderGroup : public QWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
SliderGroup(const QString &title, SliderGroup(int min,
int min, int max,
int max, int decimals,
int decimals, QWidget *parent = nullptr);
QWidget *parent = nullptr);
signals: signals:
void valueChanged(float value); void valueChanged(float value);