Antigona Release #1
20 changed files with 271 additions and 160 deletions
|
@ -27,6 +27,8 @@ v 0.2.0 Antígona (24/04/2024)
|
|||
+ Compilation without GUI (-DNOGUI).
|
||||
+ New Status "Iddle" in playbacks if is not loaded.
|
||||
+ New DMX personality version, better sort for audio needs (first load media, set vol, pan, etc, last playback order);
|
||||
+ Refresh layer values when it loads a new sound file.
|
||||
+ No QtSignals for sending data, better performance about 20% in my machine. Now, libremediaserver only updates values in AudioWidget, ui refresh is doing with a timer in audiowidget, so there is not problems between graphical and ola thread (the callback).
|
||||
|
||||
v 0.1.3 Leúcade (19/04/2024)
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<dmxSettings fileVersion="1" layersNumber="4" path="/home/snt/Documentos/lab/lms/media/sound">
|
||||
<audioDevice id="3" />
|
||||
<layer0 dmx="1" universe="1" />
|
||||
<layer1 dmx="17" universe="1" />
|
||||
<layer2 dmx="33" universe="1" />
|
||||
<layer3 dmx="49" universe="1" />
|
||||
</dmxSettings>
|
||||
<lmsAudio ui="1" layersNumber="4" path="../media/sound" >
|
||||
<audioDevice devicesNumber="2" id0="3" id1="4" />
|
||||
<layer id="0" dmx="1" universe="1" />
|
||||
<layer id="1" dmx="17" universe="1" />
|
||||
<layer id="2" dmx="33" universe="1" />
|
||||
<layer id="3" dmx="49" universe="1" />
|
||||
</lmsAudio>
|
||||
|
|
|
@ -45,9 +45,13 @@ v 0.2.1
|
|||
- Ui/Ux: Dar la opción clickeando en el widget de tiempo de poner una cuenta atrás en vez de hacia delante.
|
||||
- Logs, verbosity, timestamp.
|
||||
- New play mode without pitch control, it saves resources. MA_SOUND_FLAG_NO_PITCH
|
||||
- Vumeter or indicator about audio output in layer and master.
|
||||
- Vumeter or indicator about audio output in layer and master, add to sliderGroup.
|
||||
- QSlider can not accept floats and it can no manage high frequency updates.
|
||||
- SettingsDialog.
|
||||
- Load/save conf file.
|
||||
- ¿Exit Point? is it needed?
|
||||
- Hardening: check return errors, catch execptions, i'm too happy....
|
||||
- Hardening: check return errors, try/catch exceptions, i'm too happy....
|
||||
- Tests: errors on wrong conf file.
|
||||
- BUGFIX: there are some small clicks when changing volume and play/stop/pause. vol 24 bits? microfades in engine? setVol 0 before changing playback state?
|
||||
- refactorize dmxInput: loadMedia, changePlayBack,
|
||||
- BUGFIX: in nogui mode we need more info print at terminal (load media, change playback status).
|
||||
|
|
|
@ -52,6 +52,7 @@ AudioLayerWidget::AudioLayerWidget(QWidget *parent, int layer):
|
|||
m_progressTime->setReadOnly(true);
|
||||
m_progressTime->setButtonSymbols(QAbstractSpinBox::NoButtons);
|
||||
m_progressTime->setMinimumWidth(80);
|
||||
m_progressTime->setMaximumWidth(80);
|
||||
m_progressTime->setFocusPolicy(Qt::NoFocus);
|
||||
m_progressTime->setAlignment(Qt::AlignHCenter);
|
||||
m_progressTime->setContentsMargins(0,0,0,0);
|
||||
|
@ -138,7 +139,7 @@ void AudioLayerWidget::openMediaDialog()
|
|||
QStringList fileNames;
|
||||
fileNames = dialog.selectedFiles();
|
||||
emit uiLoadMedia(m_layer, fileNames.at(0));
|
||||
this->fileLoaded(fileNames.at(0));
|
||||
this->setMediaFile(fileNames.at(0));
|
||||
}
|
||||
|
||||
// from DMX signals
|
||||
|
@ -163,7 +164,7 @@ void AudioLayerWidget::setPitch(int pitch)
|
|||
m_pitch->blockSignals(false);
|
||||
}
|
||||
|
||||
void AudioLayerWidget::fileLoaded(QString file)
|
||||
void AudioLayerWidget::setMediaFile(QString file)
|
||||
{
|
||||
QStringList list = file.split("/");
|
||||
int size = list.size();
|
||||
|
@ -174,19 +175,18 @@ void AudioLayerWidget::fileLoaded(QString file)
|
|||
this->setPlaybackStatus(Status::Stopped);
|
||||
}
|
||||
|
||||
void AudioLayerWidget::setPlaybackStatus(Status status)
|
||||
void AudioLayerWidget::setPlaybackStatus(Status s)
|
||||
{
|
||||
if (!strcmp(StatusStr[status], m_suspendResumeButton->text().toLatin1().constData()))
|
||||
return;
|
||||
Status status = static_cast<Status>(s);
|
||||
m_suspendResumeButton->blockSignals(true);
|
||||
m_status = status;
|
||||
if (status == Status::Stopped)
|
||||
refreshCurrentTime(0);
|
||||
//if (status == Status::Stopped)
|
||||
// refreshCurrentTime(0);
|
||||
m_suspendResumeButton->setText(StatusStr[status]);
|
||||
m_suspendResumeButton->blockSignals(false);
|
||||
}
|
||||
|
||||
void AudioLayerWidget::durationChanged(float dur)
|
||||
void AudioLayerWidget::setDuration(float dur)
|
||||
{
|
||||
m_progress->blockSignals(true);
|
||||
m_progressTime->blockSignals(true);
|
||||
|
@ -200,7 +200,7 @@ void AudioLayerWidget::durationChanged(float dur)
|
|||
m_totalTimeValue->blockSignals(false);
|
||||
}
|
||||
|
||||
void AudioLayerWidget::refreshCurrentTime(float progress)
|
||||
void AudioLayerWidget::setCurrentTime(float progress)
|
||||
{
|
||||
progress *= 1000;
|
||||
m_progress->blockSignals(true);
|
||||
|
|
|
@ -18,13 +18,6 @@ class AudioLayerWidget : public QWidget
|
|||
public:
|
||||
explicit AudioLayerWidget(QWidget *parent = 0, int layer = 0);
|
||||
~AudioLayerWidget();
|
||||
void setVol(float vol);
|
||||
void resume();
|
||||
void setPan(int pan);
|
||||
void setPitch(int pitch);
|
||||
void setLoop(bool on);
|
||||
void setPlaybackStatus(Status status);
|
||||
inline Status getPlaybackStatus() { return m_status; }
|
||||
|
||||
private:
|
||||
Status m_status;
|
||||
|
@ -39,17 +32,23 @@ private:
|
|||
QTimeEdit *m_totalTimeValue;
|
||||
QProgressBar *m_progress;
|
||||
|
||||
// From DMX
|
||||
public slots:
|
||||
void setMediaFile(QString file);
|
||||
void setDuration(float dur);
|
||||
void setCurrentTime(float progress);
|
||||
void setPlaybackStatus(Status status);
|
||||
void setVol(float vol);
|
||||
void setPan(int pan);
|
||||
void setPitch(int pitch);
|
||||
|
||||
// From Ui
|
||||
private slots:
|
||||
void openMediaDialog();
|
||||
void toggleSuspendResume();
|
||||
void volumeChanged(int vol);
|
||||
void panChanged(int pan);
|
||||
void pitchChanged(int pitch);
|
||||
void fileLoaded(QString file);
|
||||
void durationChanged(float dur);
|
||||
void refreshCurrentTime(float progress);
|
||||
|
||||
private slots:
|
||||
void openMediaDialog();
|
||||
|
||||
signals:
|
||||
void uiPlaybackChanged(int layer, Status s);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include "audiowidget.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
AudioWidget::AudioWidget(QWidget *parent) :
|
||||
QWidget(parent)
|
||||
|
@ -15,39 +15,79 @@ AudioWidget::AudioWidget(QWidget *parent) :
|
|||
m_layout->setSpacing(0);
|
||||
m_layout->setContentsMargins(1, 1, 1, 1);
|
||||
setLayout(m_layout);
|
||||
m_refreshUi = new QTimer(this);
|
||||
connect(m_refreshUi, SIGNAL(timeout()), this, SLOT(refreshUi()));
|
||||
m_refreshUi->start(UI_REFRESH_TIME * 2);
|
||||
}
|
||||
|
||||
void AudioWidget::mediaLoaded(int layer, QString file, float duration)
|
||||
{
|
||||
QLayoutItem * const item = m_layout->itemAt(layer);
|
||||
dynamic_cast<AudioLayerWidget *>(item->widget())->fileLoaded(file);
|
||||
dynamic_cast<AudioLayerWidget *>(item->widget())->durationChanged(duration);
|
||||
m_layerUpdate[layer].media = file;
|
||||
m_layerUpdate[layer].duration = duration;
|
||||
m_layerUpdate[layer].updated = true;
|
||||
}
|
||||
|
||||
void AudioWidget::volChanged(int layer, float vol) {
|
||||
QLayoutItem * const item = m_layout->itemAt(layer);
|
||||
dynamic_cast<AudioLayerWidget *>(item->widget())->setVol(vol);
|
||||
m_layerUpdate[layer].vol = vol;
|
||||
m_layerUpdate[layer].updated = true;
|
||||
}
|
||||
|
||||
void AudioWidget::panChanged(int layer, int pan) {
|
||||
QLayoutItem * const item = m_layout->itemAt(layer);
|
||||
dynamic_cast<AudioLayerWidget *>(item->widget())->setPan(pan);
|
||||
m_layerUpdate[layer].pan = pan;
|
||||
m_layerUpdate[layer].updated = true;
|
||||
}
|
||||
|
||||
void AudioWidget::pitchChanged(int layer, int pitch) {
|
||||
QLayoutItem * const item = m_layout->itemAt(layer);
|
||||
dynamic_cast<AudioLayerWidget *>(item->widget())->setPitch(pitch);
|
||||
|
||||
m_layerUpdate[layer].pitch = pitch;
|
||||
m_layerUpdate[layer].updated = true;
|
||||
}
|
||||
|
||||
void AudioWidget::playbackChanged(int layer, Status status)
|
||||
{
|
||||
QLayoutItem * const item = m_layout->itemAt(layer);
|
||||
dynamic_cast<AudioLayerWidget *>(item->widget())->setPlaybackStatus(status);
|
||||
m_layerUpdate[layer].status = status;
|
||||
m_layerUpdate[layer].updated = true;
|
||||
}
|
||||
|
||||
void AudioWidget::cursorChanged(int layer, float cursor)
|
||||
{
|
||||
QLayoutItem * const item = m_layout->itemAt(layer);
|
||||
AudioLayerWidget *alw = dynamic_cast<AudioLayerWidget *>(item->widget());
|
||||
alw->refreshCurrentTime(cursor);
|
||||
m_layerUpdate[layer].cursor = floor(cursor * 1000) / 1000;
|
||||
m_layerUpdate[layer].updated = true;
|
||||
}
|
||||
|
||||
void AudioWidget::refreshUi()
|
||||
{
|
||||
for (int i = 0; i < MAX_LAYERS; i++)
|
||||
{
|
||||
if (m_layerUpdate[i].updated) {
|
||||
QLayoutItem * const item = m_layout->itemAt(i);
|
||||
AudioLayerWidget *alw = dynamic_cast<AudioLayerWidget *>(item->widget());
|
||||
if (m_layerUpdate[i].vol > -1) {
|
||||
alw->setVol(m_layerUpdate[i].vol);
|
||||
m_layerUpdate[i].vol = -1;
|
||||
}
|
||||
if (m_layerUpdate[i].cursor > -1) {
|
||||
alw->setCurrentTime(m_layerUpdate[i].cursor);
|
||||
m_layerUpdate[i].cursor = -1;
|
||||
}
|
||||
if (m_layerUpdate[i].pan > -1) {
|
||||
alw->setPan(m_layerUpdate[i].pan);
|
||||
m_layerUpdate[i].pan = -1;
|
||||
}
|
||||
if (m_layerUpdate[i].pitch > -1) {
|
||||
alw->setPitch(m_layerUpdate[i].pitch);
|
||||
m_layerUpdate[i].pitch = -1;
|
||||
}
|
||||
if (m_layerUpdate[i].status != Status::Iddle) {
|
||||
alw->setPlaybackStatus(m_layerUpdate[i].status);
|
||||
m_layerUpdate[i].status = Status::Iddle;
|
||||
}
|
||||
if (m_layerUpdate[i].duration > -1) {
|
||||
alw->setMediaFile(m_layerUpdate[i].media);
|
||||
alw->setDuration(m_layerUpdate[i].duration);
|
||||
m_layerUpdate[i].duration = -1;
|
||||
}
|
||||
m_layerUpdate[i].updated = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
#include "audiolayerwidget.h"
|
||||
#include "settings.h"
|
||||
#include "miniaudioengine.h"
|
||||
#include "defines.h" // MAX_LAYERS
|
||||
|
||||
class AudioWidget : public QWidget
|
||||
|
@ -14,14 +13,22 @@ class AudioWidget : public QWidget
|
|||
|
||||
public:
|
||||
AudioWidget(QWidget *parent = nullptr);
|
||||
void mediaLoaded(int layer, QString media, float duration);
|
||||
|
||||
private:
|
||||
QHBoxLayout *m_layout;
|
||||
layerData m_layerUpdate[MAX_LAYERS];
|
||||
QTimer *m_refreshUi;
|
||||
|
||||
public slots:
|
||||
void volChanged(int layer, float vol);
|
||||
void panChanged(int layer, int pan);
|
||||
void pitchChanged(int layer, int pitch);
|
||||
void playbackChanged(int layer, Status status);
|
||||
void cursorChanged(int layer, float cursor);
|
||||
QHBoxLayout *m_layout;
|
||||
void mediaLoaded(int layer, QString media, float duration);
|
||||
void playbackChanged(int layer, Status status);
|
||||
|
||||
private slots:
|
||||
void refreshUi();
|
||||
signals:
|
||||
void uiPlaybackChanged(int layer, Status s);
|
||||
void uiSliderChanged(int layer, Slider s, int vol);
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
#define LICENSE "GPL 3 Licensed. See LICENSE.txt."
|
||||
#define DEFAULT_FILE "lms-audio.xlm"
|
||||
#define MAX_LAYERS 4
|
||||
#define UI_REFRESH_TIME 93
|
||||
#define MAX_AUDIODEVICES 8
|
||||
#define UI_REFRESH_TIME 77
|
||||
#define FADE_TIME 25 // DMX Frame time, 40 fps, avoid clicks
|
||||
|
||||
// struct where save the DMX settings for each layer
|
||||
struct dmxSetting {
|
||||
int address;
|
||||
unsigned int universe;
|
||||
|
@ -41,5 +42,15 @@ enum Slider
|
|||
Pitch,
|
||||
};
|
||||
|
||||
#include <QString>
|
||||
struct layerData {
|
||||
QString media;
|
||||
Status status;
|
||||
bool updated;
|
||||
float vol;
|
||||
float cursor;
|
||||
int pan;
|
||||
int pitch;
|
||||
float duration;
|
||||
};
|
||||
#endif // DEFINES_H
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#define ENTRY_POINT_COARSE 5
|
||||
#define ENTRY_POINT_FINE 4
|
||||
#define PITCH 7
|
||||
|
||||
#define LAYER_CHANNELS 9
|
||||
|
||||
#endif // DMXPERSONALITY_H
|
||||
|
|
|
@ -38,7 +38,7 @@ libreMediaServerAudioUi::libreMediaServerAudioUi(QWidget *parent)
|
|||
this->setContentsMargins(5, 5, 5, 5);
|
||||
this->setStyleSheet(
|
||||
"color: white;"
|
||||
"background-color: gray;"
|
||||
"background-color: #4f4048;"
|
||||
"selection-color: blue;"
|
||||
"selection-background-color: green"
|
||||
);
|
||||
|
|
|
@ -21,11 +21,11 @@
|
|||
#include "libremediaserver-audio.h"
|
||||
|
||||
|
||||
libreMediaServerAudio::libreMediaServerAudio(bool gui)
|
||||
libreMediaServerAudio::libreMediaServerAudio()
|
||||
{
|
||||
m_ui = gui;
|
||||
Settings *set = Settings::getInstance();
|
||||
set->readFile();
|
||||
m_settings = Settings::getInstance();
|
||||
m_settings->readFile();
|
||||
m_ui = m_settings->getShowUi();
|
||||
m_mediaLibrary = new MediaLibrary;
|
||||
m_mediaLibrary->initMediaLibrary();
|
||||
for (int i = 0; i < MAX_LAYERS; i++) {
|
||||
|
@ -38,12 +38,11 @@ libreMediaServerAudio::libreMediaServerAudio(bool gui)
|
|||
m_updateUi[i][3] = -1;
|
||||
#endif
|
||||
}
|
||||
m_ola = new olaThread(this, set->getLayersNumber());
|
||||
m_ola = new olaThread(this, m_settings->getLayersNumber());
|
||||
Q_CHECK_PTR(m_ola);
|
||||
m_ola->blockSignals(true);
|
||||
connect(m_ola, SIGNAL(dmxOutput(int, int, int)), this, SLOT(dmxInput(int, int, int)));
|
||||
m_ola->registerUniverse();
|
||||
m_mae.startEngine(set->getAudioDeviceId());
|
||||
m_mae.startEngine(m_settings->getAudioDeviceId());
|
||||
qDebug("Core init Complete. Start reading DMX.");
|
||||
m_ola->blockSignals(false);
|
||||
#ifdef NOGUI
|
||||
|
@ -57,27 +56,29 @@ libreMediaServerAudio::~libreMediaServerAudio()
|
|||
m_mae.stopEngine();
|
||||
}
|
||||
|
||||
void libreMediaServerAudio::loadMedia(int layer, int folder, int file)
|
||||
{
|
||||
QString mediaFile = m_mediaLibrary->requestNewFile(folder, file);
|
||||
if (strcmp(mediaFile.toLatin1().constData(), m_currentMedia[layer].toLatin1().constData()) == 0)
|
||||
return;
|
||||
if (QFile::exists(mediaFile)){
|
||||
m_mae.loadMedia(layer, mediaFile.toLatin1().data());
|
||||
m_currentMedia[layer] = mediaFile;
|
||||
#ifndef NOGUI
|
||||
if (m_ui)
|
||||
m_lmsUi->m_aw->mediaLoaded(layer, mediaFile, m_mae.getDuration(layer));
|
||||
#endif
|
||||
m_mae.printFormatInfo(layer);
|
||||
}
|
||||
}
|
||||
|
||||
void libreMediaServerAudio::dmxInput(int layer, int channel, int value)
|
||||
{
|
||||
if (layer >= MAX_LAYERS || channel >= LAYER_CHANNELS)
|
||||
return;
|
||||
QString mediaFile = NULL;
|
||||
int aux;
|
||||
if (channel == DMX_FOLDER || channel == DMX_FILE){
|
||||
int folder = (value >> 8) & 0x000000FF;
|
||||
int file = value & 0x000000FF;
|
||||
mediaFile = m_mediaLibrary->requestNewFile(folder, file);
|
||||
if (strcmp(mediaFile.toLatin1().constData(), m_currentMedia[layer].toLatin1().constData()) == 0)
|
||||
return;
|
||||
if (QFile::exists(mediaFile)){
|
||||
m_mae.loadMedia(layer, mediaFile.toLatin1().data());
|
||||
m_currentMedia[layer] = mediaFile;
|
||||
#ifndef NOGUI
|
||||
if (m_ui)
|
||||
m_lmsUi->m_aw->mediaLoaded(layer, mediaFile, m_mae.getDuration(layer));
|
||||
#endif
|
||||
}
|
||||
} else if (channel == VOLUME_COARSE || channel == VOLUME_FINE) {
|
||||
if (channel == VOLUME_COARSE || channel == VOLUME_FINE) {
|
||||
float tmp = value / 65025.0f;
|
||||
m_mae.volChanged(layer, tmp);
|
||||
m_updateUi[layer][0] = tmp * 100.0f;
|
||||
|
@ -104,15 +105,17 @@ void libreMediaServerAudio::dmxInput(int layer, int channel, int value)
|
|||
m_mae.playbackChanged(layer, s);
|
||||
m_currentStatus[layer] = s;
|
||||
#ifndef NOGUI
|
||||
if (m_ui) m_lmsUi->m_aw->playbackChanged(layer, s);
|
||||
if (m_ui) {
|
||||
m_lmsUi->m_aw->playbackChanged(layer, s);
|
||||
m_lmsUi->m_aw->cursorChanged(layer, m_mae.getCursor(layer));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NOGUI
|
||||
void libreMediaServerAudio::refreshUi() {
|
||||
if (!m_ui) return;
|
||||
for (int i= 0; i < Settings::getInstance()->getLayersNumber(); i++ ) {
|
||||
for (int i= 0; i < m_settings->getLayersNumber(); i++ ) {
|
||||
if (m_updateUi[i][0] >= 0) {
|
||||
m_lmsUi->m_aw->volChanged(i, m_updateUi[i][0]);
|
||||
m_updateUi[i][0] = -1;
|
||||
|
@ -125,7 +128,9 @@ void libreMediaServerAudio::refreshUi() {
|
|||
m_lmsUi->m_aw->pitchChanged(i, m_updateUi[i][2]);
|
||||
m_updateUi[i][2] = -1;
|
||||
}
|
||||
if (m_updateUi[i][3] >= 0) {
|
||||
if (m_updateUi[i][3] >= 0 \
|
||||
|| m_currentStatus[i] == Status::PlayingOnce\
|
||||
|| m_currentStatus[i] == Status::PlayingLoop) {
|
||||
m_lmsUi->m_aw->cursorChanged(i, m_mae.getCursor(i));
|
||||
m_updateUi[i][3] = -1;
|
||||
}
|
||||
|
@ -135,6 +140,7 @@ void libreMediaServerAudio::refreshUi() {
|
|||
void libreMediaServerAudio::setUi(libreMediaServerAudioUi *lmsUi)
|
||||
{
|
||||
m_lmsUi = lmsUi;
|
||||
m_ui = true;
|
||||
connect(m_ola, SIGNAL(universeReceived(int)), m_lmsUi->m_dmxWidget, SLOT(updateWatchDMX(int)));
|
||||
connect(m_lmsUi->m_aw, SIGNAL(uiSliderChanged(int, Slider, int)), this, SLOT(uiSliderChanged(int, Slider, int)));
|
||||
connect(m_lmsUi->m_aw, SIGNAL(uiPlaybackChanged(int, Status)), this, SLOT(uiPlaybackChanged(int, Status)));
|
||||
|
@ -145,6 +151,7 @@ void libreMediaServerAudio::setUi(libreMediaServerAudioUi *lmsUi)
|
|||
m_ola->start(QThread::TimeCriticalPriority );
|
||||
};
|
||||
|
||||
// From Ui widgets
|
||||
void libreMediaServerAudio::uiSliderChanged(int layer, Slider s, int value)
|
||||
{
|
||||
switch (s){
|
||||
|
@ -166,8 +173,12 @@ void libreMediaServerAudio::uiPlaybackChanged(int layer, Status s)
|
|||
m_currentStatus[layer] = s;
|
||||
}
|
||||
|
||||
void libreMediaServerAudio::uiLoadMedia(int layer, QString s)
|
||||
void libreMediaServerAudio::uiLoadMedia(int layer, QString mediaFile)
|
||||
{
|
||||
m_mae.loadMedia(layer, s.toLatin1().data());
|
||||
if (strcmp(mediaFile.toLatin1().constData(), m_currentMedia[layer].toLatin1().constData()) == 0)
|
||||
return;
|
||||
m_mae.loadMedia(layer, mediaFile.toLatin1().data());
|
||||
m_currentMedia[layer] = mediaFile;
|
||||
m_lmsUi->m_aw->mediaLoaded(layer, mediaFile, m_mae.getDuration(layer));
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#define LIBREMEDIASERVERAUDIO_H
|
||||
|
||||
#include "medialibrary.h"
|
||||
#include "miniaudioengine.h"
|
||||
#include "olathread.h"
|
||||
#include "settings.h"
|
||||
#include "defines.h"
|
||||
|
@ -33,33 +34,35 @@ class libreMediaServerAudio : public QObject
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
libreMediaServerAudio(bool gui = false);
|
||||
libreMediaServerAudio();
|
||||
virtual ~libreMediaServerAudio();
|
||||
void dmxInput(int layer, int channel, int value);
|
||||
void loadMedia(int layer, int folder, int file);
|
||||
#ifndef NOGUI
|
||||
void setUi(libreMediaServerAudioUi *lmsUi);
|
||||
bool inline getShowUi() { return m_settings->getShowUi(); }
|
||||
#endif
|
||||
//static void NewDmx(const ola::client::DMXMetadata &data, const ola::DmxBuffer &buffer);
|
||||
|
||||
private:
|
||||
olaThread *m_ola;
|
||||
MediaLibrary *m_mediaLibrary;
|
||||
MiniAudioEngine m_mae;
|
||||
Settings *m_settings;
|
||||
QString m_currentMedia[MAX_LAYERS];
|
||||
Status m_currentStatus[MAX_LAYERS];
|
||||
static QList<dmxSetting> m_dmxSettings;
|
||||
#ifndef NOGUI
|
||||
QList<dmxSetting> m_dmxSettings;
|
||||
bool m_ui;
|
||||
#ifndef NOGUI
|
||||
QTimer *m_refreshUi;
|
||||
libreMediaServerAudioUi *m_lmsUi;
|
||||
float m_updateUi[MAX_LAYERS][4];
|
||||
#endif
|
||||
|
||||
private slots:
|
||||
#ifndef NOGUI
|
||||
void refreshUi();
|
||||
void uiSliderChanged(int layer, Slider s, int value);
|
||||
void uiPlaybackChanged(int layer, Status s);
|
||||
void uiLoadMedia(int layer, QString s);
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -32,9 +32,9 @@ bool hasUi(int &argc, char *argv[])
|
|||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
libreMediaServerAudio lms(hasUi(argc, argv));
|
||||
libreMediaServerAudio lms;
|
||||
#ifndef NOGUI
|
||||
if (hasUi(argc, argv))
|
||||
if (hasUi(argc, argv) || lms.getShowUi())
|
||||
{
|
||||
libreMediaServerAudioUi *lmsUi = new libreMediaServerAudioUi();
|
||||
lms.setUi(lmsUi);
|
||||
|
|
|
@ -4,6 +4,11 @@ MiniAudioEngine::MiniAudioEngine()
|
|||
{
|
||||
for (int i =0; i < MAX_LAYERS; i++) {
|
||||
m_mediaLoaded[i] = false;
|
||||
m_currentLayerValues[i].status = Status::Iddle;
|
||||
m_currentLayerValues[i].pan = 128;
|
||||
m_currentLayerValues[i].pitch = 128;
|
||||
m_currentLayerValues[i].vol = 0;
|
||||
m_currentLayerValues[i].cursor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,8 +84,8 @@ ma_result MiniAudioEngine::startContext()
|
|||
resourceManagerConfig = ma_resource_manager_config_init();
|
||||
resourceManagerConfig.decodedFormat = ma_format_f32; /* ma_format_f32 should almost always be used as that's what the engine (and most everything else) uses for mixing. */
|
||||
resourceManagerConfig.decodedChannels = 0;
|
||||
resourceManagerConfig.decodedSampleRate = 0;
|
||||
resourceManagerConfig.jobThreadCount = 0;
|
||||
resourceManagerConfig.decodedSampleRate = ma_standard_sample_rate_48000;
|
||||
resourceManagerConfig.jobThreadCount = 1;
|
||||
result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager);
|
||||
if (result != MA_SUCCESS) {
|
||||
qCritical("Failed to initialize audio resource manager.");
|
||||
|
@ -114,11 +119,9 @@ ma_result MiniAudioEngine::getAllAudioDevices()
|
|||
ma_result MiniAudioEngine::loadMedia(int layer, char *file)
|
||||
{
|
||||
ma_result result;
|
||||
float vol = -1;
|
||||
|
||||
if (m_mediaLoaded[layer] == true)
|
||||
{
|
||||
vol = ma_sound_get_volume(&m_currentSound[layer]);
|
||||
ma_sound_uninit(&m_currentSound[layer]);
|
||||
m_mediaLoaded[layer] = false;
|
||||
}
|
||||
|
@ -133,10 +136,8 @@ ma_result MiniAudioEngine::loadMedia(int layer, char *file)
|
|||
qWarning("Failed to load file %s", file);
|
||||
else {
|
||||
m_mediaLoaded[layer] = true;
|
||||
if (vol == -1)
|
||||
this->volChanged(layer, 0);
|
||||
else
|
||||
this->volChanged(layer, vol);
|
||||
this->refreshValues(layer);
|
||||
m_currentLayerValues[layer].media = file;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -191,7 +192,7 @@ ma_result MiniAudioEngine::printFormatInfo(int layer)
|
|||
qWarning("Failed to get data format %i\n", layer);
|
||||
return MA_INVALID_DATA;
|
||||
}
|
||||
qInfo("samples/sec: %u format: %u channels: %u", sampleRate, format, channels);
|
||||
qInfo() << "name:" << m_currentLayerValues[layer].media << "samples/sec:" << sampleRate << "format:" << format << "channels:" << channels;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -200,9 +201,10 @@ void MiniAudioEngine::volChanged(int layer, float vol)
|
|||
{
|
||||
if (m_mediaLoaded[layer] == false)
|
||||
return;
|
||||
if (vol > 1)
|
||||
vol = 1;
|
||||
ma_sound_group_set_volume(&m_currentSound[layer], vol);
|
||||
if (vol >= 1)
|
||||
vol = 0.99f;
|
||||
ma_sound_group_set_fade_in_milliseconds(&m_currentSound[layer], -1, vol, FADE_TIME);
|
||||
m_currentLayerValues[layer].vol = vol;
|
||||
}
|
||||
|
||||
void MiniAudioEngine::panChanged(int layer, float value)
|
||||
|
@ -213,6 +215,7 @@ void MiniAudioEngine::panChanged(int layer, float value)
|
|||
return;
|
||||
result = (value / 128.0) - 1.0;
|
||||
ma_sound_group_set_pan(&m_currentSound[layer], result);
|
||||
m_currentLayerValues[layer].pan = value;
|
||||
}
|
||||
|
||||
void MiniAudioEngine::pitchChanged(int layer, float value)
|
||||
|
@ -223,6 +226,7 @@ void MiniAudioEngine::pitchChanged(int layer, float value)
|
|||
return;
|
||||
result = value / 128.0;
|
||||
ma_sound_group_set_pitch(&m_currentSound[layer], result);
|
||||
m_currentLayerValues[layer].pitch = value;
|
||||
}
|
||||
|
||||
void MiniAudioEngine::playbackChanged(int layer, Status status)
|
||||
|
@ -235,7 +239,7 @@ void MiniAudioEngine::playbackChanged(int layer, Status status)
|
|||
break;
|
||||
case Status::Stopped:
|
||||
ma_sound_stop(&m_currentSound[layer]);
|
||||
ma_sound_seek_to_pcm_frame(&m_currentSound[layer], 0);
|
||||
this->setCursor(layer, m_currentLayerValues[layer].cursor);
|
||||
break;
|
||||
case Status::PlayingLoop:
|
||||
ma_sound_set_looping(&m_currentSound[layer], true);
|
||||
|
@ -248,30 +252,35 @@ void MiniAudioEngine::playbackChanged(int layer, Status status)
|
|||
default:
|
||||
break;
|
||||
}
|
||||
m_currentLayerValues[layer].status = status;
|
||||
}
|
||||
|
||||
void MiniAudioEngine::setCursor(int layer, int cursor)
|
||||
{
|
||||
ma_uint64 f;
|
||||
ma_uint64 end;
|
||||
|
||||
m_currentLayerValues[layer].cursor = cursor;
|
||||
if (m_mediaLoaded[layer] == false)
|
||||
return;
|
||||
ma_sound_get_length_in_pcm_frames(&m_currentSound[layer], &f);
|
||||
f = (cursor * f) / 65025;
|
||||
ma_sound_seek_to_pcm_frame(&m_currentSound[layer], f);
|
||||
// ToDo: change the loop entry point too
|
||||
ma_sound_get_length_in_pcm_frames(&m_currentSound[layer], &end);
|
||||
ma_uint64 start = (cursor * end) / 65025;
|
||||
ma_sound_seek_to_pcm_frame(&m_currentSound[layer], start);
|
||||
ma_result result = ma_data_source_set_loop_point_in_pcm_frames(&m_currentSound[layer], start, end);
|
||||
if (result != MA_SUCCESS) {
|
||||
return; // Failed to set the loop point.
|
||||
}
|
||||
}
|
||||
|
||||
Status MiniAudioEngine::getStatus(int layer)
|
||||
{
|
||||
if (m_mediaLoaded[layer] == 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;
|
||||
return m_currentLayerValues[layer].status;
|
||||
}
|
||||
|
||||
void MiniAudioEngine::refreshValues(int layer)
|
||||
{
|
||||
this->panChanged(layer, m_currentLayerValues[layer].pan);
|
||||
this->volChanged(layer, m_currentLayerValues[layer].vol);
|
||||
this->pitchChanged(layer, m_currentLayerValues[layer].pitch);
|
||||
this->playbackChanged(layer, m_currentLayerValues[layer].status);
|
||||
this->setCursor(layer, m_currentLayerValues[layer].cursor);
|
||||
}
|
||||
|
|
|
@ -40,10 +40,12 @@ private:
|
|||
ma_context context;
|
||||
ma_sound m_currentSound[MAX_LAYERS];
|
||||
ma_bool8 m_mediaLoaded[MAX_LAYERS];
|
||||
layerData m_currentLayerValues[MAX_LAYERS];
|
||||
|
||||
ma_result getAllAudioDevices();
|
||||
ma_result startDevice(int id);
|
||||
ma_result startContext();
|
||||
void refreshValues(int layer);
|
||||
};
|
||||
|
||||
#endif // MINIAUDIOENGINE_H
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "libremediaserver-audio.h"
|
||||
//#include "olathread.h"
|
||||
|
||||
olaThread::olaThread(QObject *parent, int layers) :
|
||||
m_counter(0)
|
||||
|
@ -36,7 +35,6 @@ void olaThread::init()
|
|||
}
|
||||
m_client = m_clientWrapper->GetClient();
|
||||
m_client->SetDMXCallback(ola::NewCallback(this, &olaThread::NewDmx));
|
||||
//m_client->SetDMXCallback(ola::NewCallback((libreMediaServerAudio *)this->parent(), libreMediaServerAudio::NewDmx));
|
||||
m_clientWrapper->GetSelectServer()->RegisterRepeatingTimeout(4000, ola::NewCallback(this, &olaThread::CheckDataLoss));
|
||||
m_client->SetCloseHandler(ola::NewSingleCallback(this, &olaThread::socketClosed));
|
||||
m_dmxSettings = Settings::getInstance()->getDmxSettings();
|
||||
|
@ -69,30 +67,31 @@ void olaThread::NewDmx(const ola::client::DMXMetadata &data,
|
|||
bool volSent = false;
|
||||
bool entrySent = false;
|
||||
bool fileSent = false;
|
||||
int aux;
|
||||
for (int j = 0; j < LAYER_CHANNELS; j++){
|
||||
int value = buffer.Get((i.address) + j);
|
||||
if (m_dmx[i.layer][j] != value) {
|
||||
m_dmx[i.layer][j] = value;
|
||||
switch (j) {
|
||||
case DMX_FOLDER:
|
||||
value *= 0x100;
|
||||
value += buffer.Get(i.address + DMX_FILE);
|
||||
qobject_cast<libreMediaServerAudio *>(parent())->dmxInput(i.layer, j, value);
|
||||
m_dmx[i.layer][DMX_FILE] = buffer.Get(i.address + DMX_FILE);
|
||||
aux = buffer.Get(i.address + DMX_FILE);
|
||||
qobject_cast<libreMediaServerAudio *>(parent())->loadMedia(i.layer, value, aux);
|
||||
m_dmx[i.layer][DMX_FILE] = aux;
|
||||
fileSent = true;
|
||||
break;
|
||||
case DMX_FILE:
|
||||
if (fileSent)
|
||||
break;
|
||||
value += buffer.Get(i.address + DMX_FOLDER) * 0x100;
|
||||
qobject_cast<libreMediaServerAudio *>(parent())->dmxInput(i.layer, j, value);
|
||||
m_dmx[i.layer][DMX_FOLDER] = buffer.Get(i.address + DMX_FOLDER);
|
||||
aux = buffer.Get(i.address + DMX_FOLDER);
|
||||
qobject_cast<libreMediaServerAudio *>(parent())->loadMedia(i.layer, aux, value);
|
||||
m_dmx[i.layer][DMX_FOLDER] = aux;
|
||||
fileSent = true;
|
||||
break;
|
||||
case VOLUME_FINE:
|
||||
value = (buffer.Get(i.address + VOLUME_COARSE) * 0x100) + value;
|
||||
aux = buffer.Get(i.address + VOLUME_COARSE);
|
||||
value += (aux * 0x100);
|
||||
qobject_cast<libreMediaServerAudio *>(parent())->dmxInput(i.layer, j, value);
|
||||
m_dmx[i.layer][VOLUME_COARSE] = buffer.Get(i.address + VOLUME_COARSE);
|
||||
m_dmx[i.layer][VOLUME_COARSE] = aux;
|
||||
volSent = true;
|
||||
break;
|
||||
case VOLUME_COARSE:
|
||||
|
@ -103,6 +102,9 @@ void olaThread::NewDmx(const ola::client::DMXMetadata &data,
|
|||
m_dmx[i.layer][VOLUME_FINE] = buffer.Get(i.address + VOLUME_FINE);
|
||||
volSent = true;
|
||||
break;
|
||||
case PLAYBACK:
|
||||
qobject_cast<libreMediaServerAudio *>(parent())->dmxInput(i.layer, j, value);
|
||||
break;
|
||||
case ENTRY_POINT_FINE:
|
||||
value = (buffer.Get(i.address + ENTRY_POINT_COARSE) * 0x100) + value;
|
||||
qobject_cast<libreMediaServerAudio *>(parent())->dmxInput(i.layer, j, value);
|
||||
|
|
|
@ -15,6 +15,7 @@ Settings::Settings(QObject *parent) :
|
|||
QObject(parent)
|
||||
{
|
||||
m_layersNumber = 0;
|
||||
m_ui = false;
|
||||
}
|
||||
|
||||
// Read the dmx settings for dmx.xml At the moment we need:
|
||||
|
@ -23,6 +24,7 @@ Settings::Settings(QObject *parent) :
|
|||
// - The first DMX channel of each source/layer
|
||||
// - The universe to bind in OLA
|
||||
// - Audio device id
|
||||
// - Show the Ui or not
|
||||
void Settings::readFromFile(QString file) {
|
||||
QFile* xmlFile = new QFile(file);
|
||||
if (!xmlFile->open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
|
@ -34,53 +36,56 @@ void Settings::readFromFile(QString file) {
|
|||
exit(1);
|
||||
}
|
||||
QXmlStreamReader* xmlReader = new QXmlStreamReader(xmlFile);
|
||||
int counter = 0;
|
||||
//Parse the XML until we reach end of it
|
||||
while(!xmlReader->atEnd() && !xmlReader->hasError() && counter < MAX_LAYERS) {
|
||||
// Read next element
|
||||
while(!xmlReader->atEnd() && !xmlReader->hasError()) {
|
||||
QXmlStreamReader::TokenType token = xmlReader->readNext();
|
||||
//If token is just StartDocument - go to next
|
||||
if(token == QXmlStreamReader::StartDocument) {
|
||||
continue;
|
||||
}
|
||||
//If token is StartElement - read it
|
||||
if(token == QXmlStreamReader::StartElement) {
|
||||
if(xmlReader->name() == "dmxSettings") {
|
||||
int version = xmlReader->attributes().value("fileVersion").toLocal8Bit().toInt();
|
||||
if(version == 1) {
|
||||
m_layersNumber = xmlReader->attributes().value("layersNumber").toLocal8Bit().toInt();
|
||||
m_pathmedia = xmlReader->attributes().value("path").toLocal8Bit();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(xmlReader->name() == "audioDevice") {
|
||||
m_audioDeviceId = xmlReader->attributes().value("id").toLocal8Bit().toInt();
|
||||
if(xmlReader->name() == "lmsAudio") {
|
||||
m_ui = xmlReader->attributes().value("ui").toLocal8Bit().toInt();
|
||||
m_layersNumber = xmlReader->attributes().value("layersNumber").toLocal8Bit().toInt();
|
||||
m_pathmedia = xmlReader->attributes().value("path").toLocal8Bit();
|
||||
continue;
|
||||
}
|
||||
QString add = "layer";
|
||||
add.append(QString("%1").arg(counter));
|
||||
if((xmlReader->name() == add)) {
|
||||
if(xmlReader->name() == "audioDevice") {
|
||||
m_audioDeviceQty = xmlReader->attributes().value("devicesNumber").toLocal8Bit().toInt();
|
||||
for (uint i = 0; i < m_audioDeviceQty; i++)
|
||||
{
|
||||
m_audioDeviceId[i] = xmlReader->attributes().value(QString("id%1").arg(i)).toLocal8Bit().toInt();
|
||||
}
|
||||
|
||||
}
|
||||
if(xmlReader->name() == "layer") {
|
||||
dmxSetting temp;
|
||||
temp.address = xmlReader->attributes().value("dmx").toLocal8Bit().toInt() - 1;
|
||||
temp.universe = xmlReader->attributes().value("universe").toLocal8Bit().toInt();
|
||||
temp.layer = counter;
|
||||
temp.layer = xmlReader->attributes().value("id").toLocal8Bit().toInt();
|
||||
m_settings.append(temp);
|
||||
if (!m_universe.contains(temp.universe)) {
|
||||
m_universe.insert(temp.universe);
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(xmlReader->hasError()) {
|
||||
QMessageBox::critical(NULL,"File xml Parse Error ", xmlReader->errorString(), QMessageBox::Ok);
|
||||
qWarning("File xml Parse Error %s", xmlReader->errorString().toLatin1().constData());
|
||||
return;
|
||||
// ToDo: manage this, open a dialog to load a new file.
|
||||
}
|
||||
xmlReader->clear();
|
||||
xmlFile->close();
|
||||
delete xmlReader;
|
||||
delete xmlFile;
|
||||
this->printSettings();
|
||||
}
|
||||
|
||||
void Settings::printSettings() {
|
||||
qInfo() << "Settings read;\nShow Ui:" << m_ui << "Layers:" << m_layersNumber << "Path:" << m_pathmedia <<"Audio Device qty:" << m_audioDeviceQty;
|
||||
for (uint i = 0; i < m_audioDeviceQty; i++)
|
||||
qInfo() << "Audio device id:" << m_audioDeviceId[i];
|
||||
for (int i = 0; i < m_layersNumber; i++)
|
||||
qInfo() << "Layer:" << m_settings[i].layer << "Address:" << m_settings[i].address << "Universe:" << m_settings[i].universe;
|
||||
}
|
||||
|
||||
void Settings::readFile() {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
#include <QSet>
|
||||
#include <QDebug>
|
||||
|
||||
#include "medialibrary.h"
|
||||
#include "audiowidget.h"
|
||||
|
@ -15,25 +16,27 @@ class Settings : public QObject
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Settings(QObject *parent = 0);
|
||||
static Settings *getInstance();
|
||||
inline QSet<int> getUniverses() { return m_universe; }
|
||||
inline QString getPathMedia() { return m_pathmedia; }
|
||||
inline QList<dmxSetting> getDmxSettings() { return m_settings; }
|
||||
inline int getLayersNumber() { return m_layersNumber; }
|
||||
inline int getAudioDeviceId() { return m_audioDeviceId[0]; }
|
||||
inline bool getShowUi() { return m_ui; }
|
||||
void readFile();
|
||||
inline int getAudioDeviceId() { return m_audioDeviceId; }
|
||||
void readFromFile(QString file);
|
||||
void printSettings();
|
||||
|
||||
private:
|
||||
static Settings *_instance;
|
||||
QList<dmxSetting> m_settings;
|
||||
QString m_pathmedia;
|
||||
uint m_audioDeviceId;
|
||||
uint m_audioDeviceId[MAX_AUDIODEVICES];
|
||||
uint m_audioDeviceQty;
|
||||
QSet<int> m_universe;
|
||||
int m_layersNumber;
|
||||
|
||||
explicit Settings(QObject *parent = 0);
|
||||
void readFromFile(QString file);
|
||||
|
||||
bool m_ui;
|
||||
};
|
||||
|
||||
#endif // SETTINGS_H
|
||||
|
|
|
@ -18,10 +18,11 @@ SliderGroup::SliderGroup(QString name,
|
|||
slider->setMinimumHeight(0);
|
||||
slider->setSingleStep(1);
|
||||
slider->setRange(min, max);
|
||||
slider->setValue(0);
|
||||
slider->setMinimumWidth(50);
|
||||
slider->setToolTip(name);
|
||||
slider->setStyleSheet("QSlider {"
|
||||
"border: 2px solid #685060;"
|
||||
"border: 1px solid #5a4855;"
|
||||
"margin: 0px;"
|
||||
"height: 200px;"
|
||||
"width: 50px;}"
|
||||
|
@ -32,17 +33,20 @@ SliderGroup::SliderGroup(QString name,
|
|||
valueBox->setButtonSymbols(QAbstractSpinBox::NoButtons);
|
||||
valueBox->setMinimumWidth(50);
|
||||
valueBox->setRange(min, max);
|
||||
valueBox->setValue(0);
|
||||
valueBox->setDecimals(decimals);
|
||||
valueBox->setObjectName(name);
|
||||
valueBox->setToolTip(name);
|
||||
valueBox->setAlignment(Qt::AlignHCenter);
|
||||
valueBox->setContentsMargins(0, 0, 0, 0);
|
||||
connect(slider, SIGNAL(valueChanged(int)), this, SLOT(sliderValueChanged(int)));
|
||||
//connect(slider, SIGNAL(mousePressEvent(QMouseEvent)), this, SLOT(mousePressEvent(QMouseEvent *)));
|
||||
layout->addWidget(slider);
|
||||
layout->addWidget(valueBox);
|
||||
this->setStyleSheet("border: 2px solid #685060;"
|
||||
this->setStyleSheet("border: 1px solid #5a4855;"
|
||||
"width: 50px;"
|
||||
"margin: 0px;"
|
||||
"background-color: #383034;"
|
||||
);
|
||||
layout->setSpacing(0);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
@ -67,3 +71,11 @@ void SliderGroup::setValue(float value)
|
|||
slider->blockSignals(false);
|
||||
valueBox->blockSignals(false);
|
||||
}
|
||||
|
||||
void SliderGroup::mousePressEvent(QMouseEvent* event) {
|
||||
Q_UNUSED(event);
|
||||
if (slider->isEnabled())
|
||||
slider->setDisabled(true);
|
||||
else
|
||||
slider->setDisabled(false);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ public slots:
|
|||
private:
|
||||
QSlider *slider;
|
||||
QDoubleSpinBox *valueBox;
|
||||
|
||||
void mousePressEvent(QMouseEvent* event);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Reference in a new issue