diff --git a/docs/LibreMediaServer_Audio.hed b/docs/LibreMediaServer_Audio.hed index 5da3a28..fda3e2d 100644 --- a/docs/LibreMediaServer_Audio.hed +++ b/docs/LibreMediaServer_Audio.hed @@ -1,76 +1,79 @@ ް׆ˌĠ򝤫鿰맫͓Ԕ ҊÅЁ䍡 ӽ -䉭ėĈ -ϛ -Ӧ -ڑ -ė - -ݍ -Ȫ -웣傪Ⓨ֚ -ކՂ -߸“ -Ŷ -ѝ -ωƀ - - - - -ŕ -ʷí -̙ -򴹤 -ԥѼ܅ -𗹣َ -̚ -Რ - -Ņ -⽬ -̫ -喤쏎ʁǏ - -߬ػс -썎ޛ - -쏎 - - - -Ճ - -尨 -ގ - -ލ -ƕ - -ŗ - - - - - -ˤ - - - - - -쏐 - - - -쏎 -󩼿 - - - -ÛƘ - - - - +Ꭸʆ +ɑ +ɬ +򉇁 +Ԑɉ + +ٿդϨ҃ +񀠧ڔ +ڑŌ +򻴧 + + + + + + + + +쏎ބ +힇ݳ + +Ԓlj + +Ŷ§ +ÐŽÊ +𡭟Ē + +܊ +񢰽 +зм +񓎍 +ҭ +ܻۏ +ڑן +ޏ + +Ƴ + + + +Ǖ + +𓎍Ԏ + + +Ξ +ֆ + +ց + +ᱯ +݈ + +؈ + +馹 +۴ +𓎍 + + + + + +𓎑 + + + +Ɨ + + + +Ӌ +쏎 + + + diff --git a/docs/changelog.txt b/docs/changelog.txt index 48b0ddf..5c7e0b6 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -4,39 +4,45 @@ Libre Media Server Audio - An Open source Media Server for arts and performing. https://git.criptomart.net/libremediaserver ******************************************************************************* -Lbre Media Server ChangeLog +Libre Media Server ChangeLog -v 0.2.0 Antigona (24/04/2024) -+ change engine to miniaudio because is imposible pan in SFML and it has not access to low API and audio processing. -+ Refactor all audio methods to MiniAudioEngine. +v 0.2.0 Antígona (26/05/2024) ++ Change audio engine to miniaudio because is imposible pan in SFML and it has not access to low API and audio processing. ++ Refactor all audio methods to MiniAudioEngine class. + Select sound device output. -+ pan. -+ Show faders values. -+ play offset. ++ Pan. ++ Show faders values. New SliderGroup class. ++ Entry Point 16 bits. + Refactor AudioMasterWidget to AudioDMXReceptionWidget. -+ mp3, flac, wav (mp3 has given some errors seeking cursor...). -+ settings dialog not working, only read the conf file at startup. -+ variable number of layers. -+ olathread, send double channels only once for each dmx frame buffer. ++ Read mp3, flac, wav (mp3 has given some errors seeking cursor...). ++ Removed settings dialog, only read xml conf file at startup. ++ Real dynamic variable number of layers based on conf file setting. ++ OlaThread send double channels (volume, entry point, load media) only once for each dmx frame buffer. ++ Terminal mode without graphical interface. All audio methods has been refactorized out of QWidget world. ++ 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). ++ Load media files from ui clicking in the media labels. -v 0.1.3 Unreleased (19/04/2024) +v 0.1.3 Leúcade (19/04/2024) + Ubuntu 22.04 jammy. -+ Use SFML as audio engine. + Qt 5.15.3. -+ pitch. -+ loop. ++ Pitch. ++ Loop. v 0.1.2 Mayordomo (12/08/2015) -- GUI config -- Several bugs tested in real world -- variable layers -- SFML as audio engine +- GUI config. +- Several bugs tested in real world. +- Variable layers. +- SFML as audio engine. v 0.1.1 Pascual (24/09/2014) -+ First Version: 4 layers playing .ogg -+ Needs Open Lighting Arquitecture => 0.9.0 -+ Pure Data as audio engine ++ First Version: 4 layers playing .ogg. ++ Needs Open Lighting Arquitecture => 0.9.0. ++ Pure Data as audio engine. diff --git a/docs/lms-audio.xlm b/docs/lms-audio.xlm index cc498ca..4274267 100644 --- a/docs/lms-audio.xlm +++ b/docs/lms-audio.xlm @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + diff --git a/docs/roadmap.txt b/docs/roadmap.txt index 8ff57d7..0771257 100644 --- a/docs/roadmap.txt +++ b/docs/roadmap.txt @@ -5,7 +5,6 @@ https://git.criptomart.net/libremediaserver ******************************************************************************* Libre Media Server Roadmap -(en continuo crecimiento...) v 0.2.x - skin, UI/UX @@ -20,8 +19,8 @@ v 0.2.2 + hay que empaquetar OLA, incluirlo en el binario, o implementar sACN y linkarlo estáticamente. + https://github.com/ETCLabs/sACN - Qt6. -- audio processing (eq, rev, compresor, ...) por master y capa. -- CIPT/MSex, send icons play-pause-stop. +- Audio processing (eq, rev, compresor, ...) by master and layer. +- CIPT/MSex, send icons play/pause/stop. - Rasp build. - Octopus Sound Card support (6 outputs - 8 inputs). @@ -41,15 +40,15 @@ v 0.2.1 - audio device linked, outputs will be redirected there. - dmx address + universe settings. - Rose noise and sine generator in menu to test system. -- Keyboards strokes, load media files from ui. -- Dar la opción clickeando en el widget de tiempo de poner una cuenta atrás en vez de hacia delante. +- Ui/Ux; Keyboards strokes. +- 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. -- Bufgix: depurar errores cuando no carga la librería de medias, cambia el númmero de capas, cambia el universo, etc. -- New control mode without pitch control, it saves resources. MA_SOUND_FLAG_NO_PITCH -- Vumeter or indicator about audio output in layer and master. +- New play mode without pitch control, it saves resources. MA_SOUND_FLAG_NO_PITCH - SettingsDialog. - Load/save conf file. -- ¿stop offset? is it needed? -- decouple MiniAudioEngine from AudioWidget, starts whith no gui or with audio in a dedicated thread. -- New Status "Iddle" in playbacks if is not loaded. -- check return errors, we are too happy.... +- ¿Exit Point? is it needed? +- Hardening: check return errors, try/catch exceptions, i'm too happy.... +- Tests: errors on wrong conf file. + +v0.2.0: +- Vumeter or indicator about audio output in layer and master, add to sliderGroup. diff --git a/libremediaserver-audio.pro b/libremediaserver-audio.pro index a33e231..41d6ecd 100644 --- a/libremediaserver-audio.pro +++ b/libremediaserver-audio.pro @@ -2,7 +2,10 @@ TEMPLATE = app TARGET = libremediaserver-audio QT += webkitwidgets widgets HEADERS += src/libremediaserver-audio.h \ + src/clickablelabel.h \ src/dmxwidget.h \ + src/libremediaserver-audio-gui.h \ + src/main.h \ src/miniaudio.h \ src/medialibrary.h \ src/miniaudioengine.h \ @@ -14,7 +17,9 @@ HEADERS += src/libremediaserver-audio.h \ src/settings.h \ src/slidergroup.h SOURCES += src/main.cpp \ + src/clickablelabel.cpp \ src/dmxwidget.cpp \ + src/libremediaserver-audio-gui.cpp \ src/miniaudio.c \ src/libremediaserver-audio.cpp \ src/medialibrary.cpp \ @@ -24,10 +29,10 @@ SOURCES += src/main.cpp \ src/audiowidget.cpp \ src/settings.cpp \ src/slidergroup.cpp -FORMS += src/libremediaserver-audio.ui -CCFLAG += -msse2 -mavx2 #-fsanitize=address -g -O0 +FORMS += src/libremediaserver-audio-gui.ui +CCFLAG += -msse2 -mavx2 #-fsanitize=address -g3 -O0 QMAKE_CXXFLAGS += $$(CXXFLAG) -#QMAKE_CXXFLAGS += -fsanitize=address -g -O0 +#QMAKE_CXXFLAGS += -fsanitize=address -g3 -O0 QMAKE_CFLAGS += $$(CCFLAG) QMAKE_LFLAGS += $$(LDFLAG) LIBS += -lola -lolacommon -ldl -lpthread -lm diff --git a/src/audiolayerwidget.cpp b/src/audiolayerwidget.cpp index 99e1996..e8d22bd 100644 --- a/src/audiolayerwidget.cpp +++ b/src/audiolayerwidget.cpp @@ -1,64 +1,89 @@ #include "audiolayerwidget.h" +#include - -AudioLayerWidget::AudioLayerWidget(QWidget *parent, QString name, int layer): - QGroupBox(parent) +AudioLayerWidget::AudioLayerWidget(QWidget *parent, int layer): + QWidget(parent) , m_layer(layer) , m_suspendResumeButton(0) { - this->setTitle(name); QVBoxLayout *layout = new QVBoxLayout; - QHBoxLayout *progressTime = new QHBoxLayout; - m_progressTime = new QTimeEdit; - m_progressTime->text(); - m_progressTime->setDisplayFormat("h:mm:ss:zzz"); - m_progressTime->setReadOnly(true); - m_progressTime->setButtonSymbols(QAbstractSpinBox::NoButtons); - m_progressTime->setMaximumWidth(90); - m_progressTime->setFocusPolicy(Qt::NoFocus); - progressTime->addWidget(m_progressTime); - m_totalTimeValue = new QTimeEdit; - m_totalTimeValue->setDisplayFormat("h:mm:ss:zzz"); - m_totalTimeValue->setReadOnly(true); - m_totalTimeValue->setButtonSymbols(QAbstractSpinBox::NoButtons); - m_totalTimeValue->setMaximumWidth(90); - m_totalTimeValue->setFocusPolicy(Qt::NoFocus); - progressTime->addWidget(m_totalTimeValue); - layout->addLayout(progressTime); - - m_progressSlider = new QSlider(Qt::Horizontal); - m_progressSlider->setFocusPolicy(Qt::NoFocus); - layout->addWidget(m_progressSlider); - - QGridLayout *status = new QGridLayout; - m_statusValue = new QLabel; - status->addWidget(m_statusValue, 0, 0); - m_folderValue = new QLabel; - m_folderValue->setMaximumWidth(200); - status->addWidget(m_folderValue, 1, 0); - m_fileValue = new QLabel; - m_fileValue->setMaximumWidth(200); - status->addWidget(m_fileValue, 2, 0); - layout->addLayout(status); - - QHBoxLayout *volumeBox = new QHBoxLayout; - m_volume = new SliderGroup("Vol", 0 , 100, 2, NULL); - volumeBox->addWidget(m_volume); - connect(m_volume, SIGNAL(valueChanged(float)), this, SLOT(volumeChanged(float))); - m_pan = new SliderGroup("Pan", 0 , 255, 0, NULL); - volumeBox->addWidget(m_pan); - connect(m_pan, SIGNAL(valueChanged(float)), this, SLOT(panChanged(float))); - m_pitch = new SliderGroup("Pitch", 0 , 255, 0, NULL); - volumeBox->addWidget(m_pitch); - connect(m_pitch, SIGNAL(valueChanged(float)), this, SLOT(pitchChanged(float))); - layout->addLayout(volumeBox); + QVBoxLayout *playback = new QVBoxLayout; + m_folderValue = new ClickableLabel; + m_folderValue->setMaximumWidth(160); + m_folderValue->setAlignment(Qt::AlignLeft); + m_folderValue->setStyleSheet( + "color: white;" + "background-color: black;" + ); + playback->addWidget(m_folderValue); + m_fileValue = new ClickableLabel; + connect(m_fileValue, SIGNAL(clicked()), this, SLOT(openMediaDialog())); + connect(m_folderValue, SIGNAL(clicked()), this, SLOT(openMediaDialog())); + m_fileValue->setMaximumWidth(160); + m_fileValue->setAlignment(Qt::AlignLeft); + m_fileValue->setStyleSheet( + "color: white;" + "background-color: black;" + ); + playback->addWidget(m_fileValue); + playback->setSpacing(0); + playback->setContentsMargins(0, 0, 0, 0); + layout->addLayout(playback); m_suspendResumeButton = new QPushButton(this); - m_suspendResumeButton->setText(StatusStr[Status::Stopped]); + m_suspendResumeButton->setText(StatusStr[Status::Iddle]); 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"); + layout->addWidget(m_progress); + + m_progressTime = new QTimeEdit; + m_progressTime->setToolTip("Current Time"); + m_progressTime->setObjectName("Current Time"); + m_progressTime->setDisplayFormat("mm:ss:zz"); + 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); + m_totalTimeValue = new QTimeEdit; + m_totalTimeValue->setObjectName("Track Length"); + m_totalTimeValue->setToolTip("Track Length"); + m_totalTimeValue->setDisplayFormat("mm:ss:zzz"); + m_totalTimeValue->setReadOnly(true); + m_totalTimeValue->setButtonSymbols(QAbstractSpinBox::NoButtons); + m_totalTimeValue->setMinimumWidth(80); + m_totalTimeValue->setFocusPolicy(Qt::NoFocus); + m_totalTimeValue->setAlignment(Qt::AlignHCenter); + m_totalTimeValue->setContentsMargins(0,0,0,0); + QHBoxLayout *status = new QHBoxLayout; + status->addWidget(m_progressTime); + status->addWidget(m_totalTimeValue); + layout->addLayout(status); + QHBoxLayout *volumeBox = new QHBoxLayout; + m_volume = new SliderGroup("Vol", 0 , 100, 2, NULL); + volumeBox->addWidget(m_volume); + connect(m_volume, SIGNAL(valueChanged(int)), this, SLOT(volumeChanged(int))); + m_pan = new SliderGroup("Pan", 0 , 255, 0, NULL); + volumeBox->addWidget(m_pan); + connect(m_pan, SIGNAL(valueChanged(int)), this, SLOT(panChanged(int))); + m_pitch = new SliderGroup("Pitch", 0 , 255, 0, NULL); + volumeBox->addWidget(m_pitch); + volumeBox->setSpacing(0); + volumeBox->setContentsMargins(0, 0, 0, 0); + connect(m_pitch, SIGNAL(valueChanged(int)), this, SLOT(pitchChanged(int))); + layout->addLayout(volumeBox); + layout->setAlignment(Qt::AlignHCenter); + layout->setSpacing(0); + layout->setContentsMargins(2, 2, 2, 2); this->setLayout(layout); } @@ -68,17 +93,17 @@ AudioLayerWidget::~AudioLayerWidget() } // From UI. -void AudioLayerWidget::volumeChanged(float value) +void AudioLayerWidget::volumeChanged(int value) { emit(uiSliderChanged(m_layer, Slider::Volume, value)); } -void AudioLayerWidget::panChanged(float value) +void AudioLayerWidget::panChanged(int value) { emit(uiSliderChanged(m_layer, Slider::Pan, value)); } -void AudioLayerWidget::pitchChanged(float value) +void AudioLayerWidget::pitchChanged(int value) { emit(uiSliderChanged(m_layer, Slider::Pitch, value)); } @@ -93,12 +118,28 @@ void AudioLayerWidget::toggleSuspendResume() break; case Status::Paused: case Status::Stopped: - this->setPlaybackStatus(Status::PlayingOnce); - emit uiPlaybackChanged(m_layer, Status::PlayingOnce); + this->setPlaybackStatus(Status::PlayingLoop); + emit uiPlaybackChanged(m_layer, Status::PlayingLoop); + case Status::Iddle: break; } } +void AudioLayerWidget::openMediaDialog() +{ + QFileDialog dialog(this); + dialog.setFileMode(QFileDialog::ExistingFile); + dialog.setNameFilter(tr("Sound Tracks (*.mp3 *.mp3 *.flac *.wav")); + dialog.setViewMode(QFileDialog::Detail); + dialog.setDirectory(Settings::getInstance()->getPathMedia()); + if (!dialog.exec()) + return; + QStringList fileNames; + fileNames = dialog.selectedFiles(); + emit uiLoadMedia(m_layer, fileNames.at(0)); + this->setMediaFile(fileNames.at(0)); +} + // from DMX signals void AudioLayerWidget::setVol(float vol) { @@ -121,7 +162,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(); @@ -131,41 +172,36 @@ void AudioLayerWidget::fileLoaded(QString file) } } -void AudioLayerWidget::setPlaybackStatus(Status status) +void AudioLayerWidget::setPlaybackStatus(Status s) { - m_status = status; - if (status == Status::Stopped) - m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(0)); - m_statusValue->blockSignals(true); + Status status = static_cast(s); m_suspendResumeButton->blockSignals(true); - m_statusValue->setText(StatusStr[status]); + m_status = 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); } -void AudioLayerWidget::durationChanged(float dur) +void AudioLayerWidget::setDuration(float dur) { - dur *= 1000; - m_progressSlider->setMaximum(dur); + m_progress->blockSignals(true); + 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_progress->blockSignals(false); + m_progressTime->blockSignals(false); + m_totalTimeValue->blockSignals(false); } -void AudioLayerWidget::refreshUi(float progress) +void AudioLayerWidget::setCurrentTime(float progress) { 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_progress->blockSignals(false); + m_progressTime->blockSignals(false); } diff --git a/src/audiolayerwidget.h b/src/audiolayerwidget.h index 53401fb..9380b81 100644 --- a/src/audiolayerwidget.h +++ b/src/audiolayerwidget.h @@ -3,53 +3,57 @@ #include #include -#include +#include +#include #include "defines.h" #include "slidergroup.h" +#include "clickablelabel.h" +#include "settings.h" -class AudioLayerWidget : public QGroupBox +class AudioLayerWidget : public QWidget { Q_OBJECT public: - explicit AudioLayerWidget(QWidget *parent = 0, QString name = "Layer", int layer = 0); + 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; int m_layer; QPushButton *m_suspendResumeButton; - QLabel * m_statusValue; - QLabel *m_fileValue; - QLabel * m_folderValue; + ClickableLabel *m_fileValue; + ClickableLabel * m_folderValue; SliderGroup *m_volume; SliderGroup *m_pan; SliderGroup *m_pitch; - QSlider *m_progressSlider; QTimeEdit *m_progressTime; 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(float vol); - void panChanged(float pan); - void pitchChanged(float pitch); - void fileLoaded(QString file); - void durationChanged(float dur); - void refreshUi(float progress); + void volumeChanged(int vol); + void panChanged(int pan); + void pitchChanged(int pitch); signals: void uiPlaybackChanged(int layer, Status s); void uiSliderChanged(int layer, Slider s, int value); - + void uiLoadMedia(int layer, QString s); }; #endif // AUDIOLAYERWIDGET_H diff --git a/src/audiowidget.cpp b/src/audiowidget.cpp index 4b35a5c..8bd077c 100644 --- a/src/audiowidget.cpp +++ b/src/audiowidget.cpp @@ -1,121 +1,100 @@ #include "audiowidget.h" +#include - -AudioWidget::AudioWidget() : - m_layout(new QHBoxLayout()) - , m_refreshUi(new QTimer(this)) +AudioWidget::AudioWidget(QWidget *parent) : + QWidget(parent) + , m_layout(new QHBoxLayout()) { 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); - 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(uiSliderChanged(int, Slider, int)), this, SIGNAL(uiSliderChanged(int, Slider, int))); + connect(alw, SIGNAL(uiPlaybackChanged(int, Status)), this, SIGNAL(uiPlaybackChanged(int, Status))); + connect(alw, SIGNAL(uiLoadMedia(int, QString)), this, SIGNAL(uiLoadMedia(int, QString))); + m_layerUpdate[i].status = Status::Iddle; + m_layerUpdate[i].duration = 0; + m_layerUpdate[i].media = ""; + m_layerUpdate[i].vol = 0; + m_layerUpdate[i].pan = 128; + m_layerUpdate[i].pitch = 128; + m_layerUpdate[i].cursor = 0; } + 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); + m_refreshUi->start(UI_REFRESH_TIME * 1.5); } -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); - dynamic_cast(item->widget())->fileLoaded(file); - dynamic_cast(item->widget())->durationChanged(pLength); + m_layerUpdate[layer].media = file; + m_layerUpdate[layer].duration = duration; + m_layerUpdate[layer].updated = true; } void AudioWidget::volChanged(int layer, float vol) { - m_mae.volChanged(layer, vol); - QLayoutItem * const item = m_layout->itemAt(layer); - dynamic_cast(item->widget())->setVol(vol); + m_layerUpdate[layer].vol = vol; + m_layerUpdate[layer].updated = true; } void AudioWidget::panChanged(int layer, int pan) { - m_mae.panChanged(layer, pan); - QLayoutItem * const item = m_layout->itemAt(layer); - dynamic_cast(item->widget())->setPan(pan); + m_layerUpdate[layer].pan = pan; + m_layerUpdate[layer].updated = true; } void AudioWidget::pitchChanged(int layer, int pitch) { - m_mae.pitchChanged(layer, pitch); - QLayoutItem * const item = m_layout->itemAt(layer); - dynamic_cast(item->widget())->setPitch(pitch); + + m_layerUpdate[layer].pitch = pitch; + m_layerUpdate[layer].updated = true; } void AudioWidget::playbackChanged(int layer, Status status) { - m_mae.playbackChanged(layer, status); - QLayoutItem * const item = m_layout->itemAt(layer); - dynamic_cast(item->widget())->setPlaybackStatus(status); + m_layerUpdate[layer].status = status; + m_layerUpdate[layer].updated = true; } -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); - AudioLayerWidget *aw = dynamic_cast(item->widget()); - aw->refreshUi(m_mae.getCursor(layer)); + m_layerUpdate[layer].cursor = cursor; + m_layerUpdate[layer].updated = true; } -void AudioWidget::refreshUi() { - for (int i= 0; i < Settings::getInstance()->getLayersNumber(); i++ ) { - QLayoutItem * const item = m_layout->itemAt(i); - AudioLayerWidget *aw = dynamic_cast(item->widget()); - Status s = aw->getPlaybackStatus(); - if (s == Status::PlayingOnce || s == Status::PlayingLoop) { - aw->refreshUi(m_mae.getCursor(i)); +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(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; } } } - -void AudioWidget::uiSliderAction(int layer, Slider s, int value) -{ - switch (s){ - case Slider::Volume: - m_mae.volChanged(layer, value); - break; - case Slider::Pan: - m_mae.panChanged(layer, value); - break; - case Slider::Pitch: - m_mae.pitchChanged(layer, value); - break; - } -} - -void AudioWidget::uiChangePlaybackStatus(int layer, Status s) { - m_mae.playbackChanged(layer, s); -} - diff --git a/src/audiowidget.h b/src/audiowidget.h index 254e59e..dd9b1f1 100644 --- a/src/audiowidget.h +++ b/src/audiowidget.h @@ -5,7 +5,6 @@ #include "audiolayerwidget.h" #include "settings.h" -#include "miniaudioengine.h" #include "defines.h" // MAX_LAYERS class AudioWidget : public QWidget @@ -13,29 +12,27 @@ class AudioWidget : public QWidget Q_OBJECT public: - AudioWidget(); - bool startEngine(); - bool startEngine(int id); - void stopEngine(); - void mediaLoaded(int layer, QString media ); - 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 entryPointChanged(int layer, int cursor); + AudioWidget(QWidget *parent = nullptr); private: - MiniAudioEngine m_mae; - QString m_currentMedia[MAX_LAYERS]; QHBoxLayout *m_layout; + layerData m_layerUpdate[MAX_LAYERS]; QTimer *m_refreshUi; public slots: - void uiSliderAction(int layer, Slider s, int value); - void uiChangePlaybackStatus(int layer, Status s); + void volChanged(int layer, float vol); + void panChanged(int layer, int pan); + void pitchChanged(int layer, int pitch); + void cursorChanged(int layer, float cursor); + 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); + void uiLoadMedia(int layer, QString s); }; #endif // AUDIOWIDGET_H diff --git a/src/clickablelabel.cpp b/src/clickablelabel.cpp new file mode 100644 index 0000000..623493d --- /dev/null +++ b/src/clickablelabel.cpp @@ -0,0 +1,14 @@ +#include "clickablelabel.h" + +ClickableLabel::ClickableLabel(QWidget *parent, Qt::WindowFlags f) + : QLabel{parent} +{ + Q_UNUSED(f); +} + +ClickableLabel::~ClickableLabel() {} + +void ClickableLabel::mousePressEvent(QMouseEvent* event) { + Q_UNUSED(event); + emit clicked(); +} diff --git a/src/clickablelabel.h b/src/clickablelabel.h new file mode 100644 index 0000000..e75aae7 --- /dev/null +++ b/src/clickablelabel.h @@ -0,0 +1,22 @@ +#ifndef CLICKABLELABEL_H +#define CLICKABLELABEL_H + +#include +#include +#include + +class ClickableLabel : public QLabel +{ + Q_OBJECT +public: + explicit ClickableLabel(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()); + ~ClickableLabel(); + +signals: + void clicked(); + +protected: + void mousePressEvent(QMouseEvent* event); +}; + +#endif // CLICKABLELABEL_H diff --git a/src/defines.h b/src/defines.h index 68675c2..ff299c1 100644 --- a/src/defines.h +++ b/src/defines.h @@ -1,15 +1,15 @@ #ifndef DEFINES_H #define DEFINES_H -//#define VERSION "LibreMediaServerAudio 0.2.0 Antigona Release" -#define VERSION "Kike Substitutor - No AI required - v0.2.0" +#define VERSION "LibreMediaServerAudio 0.2.0 Antigona Release" #define COPYRIGHT "(C) 2014-2024 Santi Noreña " -#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." #define DEFAULT_FILE "lms-audio.xlm" -#define MAX_LAYERS 16 -#define UI_REFRESH_TIME 200 +#define MAX_LAYERS 4 +#define MAX_AUDIODEVICES 8 +#define UI_REFRESH_TIME 66 +#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; @@ -22,6 +22,10 @@ enum Status Paused, PlayingOnce, PlayingLoop, + Iddle, + PlayingFolder, + PlayingFolderLoop, + PlayingFolderRandom }; static const char* StatusStr[] = @@ -29,7 +33,11 @@ static const char* StatusStr[] = "Stop", "Pause", "Playing One", - "Playing Loop", + "Playing One Loop", + "Iddle", + "Playing Folder", + "Playing Folder Loop", + "Playing Folder Random", 0x0 }; @@ -40,5 +48,18 @@ enum Slider Pitch, }; +#include +struct layerData { + QString media; + Status status; + bool updated; + float vol; + float cursor; + int pan; + int pitch; + float duration; + int address; + unsigned int universe; + int device; +}; #endif // DEFINES_H - diff --git a/src/dmxPersonality.h b/src/dmxPersonality.h index 1e42df7..82f9468 100644 --- a/src/dmxPersonality.h +++ b/src/dmxPersonality.h @@ -1,39 +1,15 @@ #ifndef DMXPERSONALITY_H #define DMXPERSONALITY_H -/** Define the DMX personality to avoid dealing with - * numbers and change it easyly in case - * -1 - Volumen Coarse -2 - Pan -3 - Folder -4 - File -5 - Playback - 0-24 : Play once. - 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 -// 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 +#define VOLUME_COARSE 3 +#define PAN 6 +#define DMX_FOLDER 0 +#define DMX_FILE 1 +#define PLAYBACK 8 +#define VOLUME_FINE 2 +#define ENTRY_POINT_COARSE 5 +#define ENTRY_POINT_FINE 4 +#define PITCH 7 +#define LAYER_CHANNELS 9 #endif // DMXPERSONALITY_H diff --git a/src/dmxwidget.cpp b/src/dmxwidget.cpp index 60d4206..c5b4150 100644 --- a/src/dmxwidget.cpp +++ b/src/dmxwidget.cpp @@ -9,6 +9,8 @@ dmxWidget::dmxWidget(QWidget *parent) : QVBoxLayout *vbox = new QVBoxLayout; m_receiveDMX->setText("DMX Signal"); vbox->addWidget(m_receiveDMX); + vbox->setSpacing(1); + vbox->setContentsMargins(1, 1, 1, 1); this->setLayout(vbox); connect(m_watchDMX, SIGNAL(timeout()), this, SLOT(watchDMXExpired())); diff --git a/src/libremediaserver-audio-gui.cpp b/src/libremediaserver-audio-gui.cpp new file mode 100644 index 0000000..b769679 --- /dev/null +++ b/src/libremediaserver-audio-gui.cpp @@ -0,0 +1,56 @@ +/* + + Libre Media Server Audio - An Open source Media Server for arts and performing. + (c) Criptomart - Santiago Noreña 2012-2024 + 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 . +*/ + +#include "libremediaserver-audio-gui.h" + + +libreMediaServerAudioUi::libreMediaServerAudioUi(QWidget *parent) + : QMainWindow(parent) +{ + ui.setupUi(this); + this->setWindowTitle(VERSION); + m_aw = new AudioWidget(this); + setCentralWidget(m_aw); + m_dmxWidget = new dmxWidget(this); + QDockWidget *topWidget = new QDockWidget(tr("Master"), this); + topWidget->setAllowedAreas(Qt::TopDockWidgetArea); + topWidget->setWidget(m_dmxWidget); + topWidget->setContentsMargins(0, 0, 0, 0); + addDockWidget(Qt::TopDockWidgetArea, topWidget); + connect(ui.actionLaunch_OLA_Setup, SIGNAL(triggered()), this, SLOT(olasetup())); + this->setContentsMargins(5, 5, 5, 5); + this->setStyleSheet( + "color: white;" + "background-color: #4f4048;" + "selection-color: blue;" + "selection-background-color: green" + ); +} + +libreMediaServerAudioUi::~libreMediaServerAudioUi() +{ +} + +void libreMediaServerAudioUi::olasetup() +{ + QWebView *view = new QWebView(); + view->load(QUrl("http://localhost:9090/ola.html")); + view->show(); +} diff --git a/src/libremediaserver-audio-gui.h b/src/libremediaserver-audio-gui.h new file mode 100644 index 0000000..50b4d8b --- /dev/null +++ b/src/libremediaserver-audio-gui.h @@ -0,0 +1,48 @@ +/* + Libre Media Server Audio - An Open source Media Server for arts and performing. + (c) Criptomart - Santiago Noreña 2012-2024 + 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 . +*/ + +#ifndef LIBREMEDIASERVERAUDIOUI_H +#define LIBREMEDIASERVERAUDIOUI_H + +#include +#include + +#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 diff --git a/src/libremediaserver-audio-gui.ui b/src/libremediaserver-audio-gui.ui new file mode 100644 index 0000000..c0f11ad --- /dev/null +++ b/src/libremediaserver-audio-gui.ui @@ -0,0 +1,55 @@ + + + Santi Noreña lms@criptomart.net + LibreMediaServerAudio + + + + 0 + 0 + 500 + 400 + + + + + Unifont + 12 + 75 + true + + + + LibreMediaServer + + + + ../../../../criptomart/artwork/logo_v2_criptomart.net.png../../../../criptomart/artwork/logo_v2_criptomart.net.png + + + + + + 0 + 0 + 500 + 21 + + + + + File + + + + + + + + OLA Setup + + + + + + diff --git a/src/libremediaserver-audio.cpp b/src/libremediaserver-audio.cpp index 0d595ec..a9868fd 100644 --- a/src/libremediaserver-audio.cpp +++ b/src/libremediaserver-audio.cpp @@ -21,106 +21,239 @@ #include "libremediaserver-audio.h" -libreMediaServerAudio::libreMediaServerAudio(QStringList args, QWidget *parent) - : QMainWindow(parent) +libreMediaServerAudio::libreMediaServerAudio() { - Q_UNUSED(args); - qDebug() << VERSION; - qDebug() << COPYRIGHT; - qDebug() << LICENSE; - ui.setupUi(this); - this->setWindowTitle(VERSION); - 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(); - 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()); + for (int i = 0; i < MAX_LAYERS; i++) { + m_currentMedia[i] = ""; + m_currentStatus[i] = Status::Iddle; +#ifdef NOGUI + m_updateUi[i][0] = -1; + m_updateUi[i][1] = -1; + m_updateUi[i][2] = -1; + m_updateUi[i][3] = -1; +#endif + } + m_ola = new olaThread(this, m_settings->getLayersNumber()); Q_CHECK_PTR(m_ola); 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))); m_ola->registerUniverse(); - connect(ui.actionLaunch_OLA_Setup, SIGNAL(triggered()), this, SLOT(olasetup())); - m_aw->startEngine(); - qDebug("Init Complete."); + m_mae.startEngine(m_settings->getAudioDeviceId()); + qDebug("Core init Complete. Start reading DMX."); m_ola->blockSignals(false); +#ifdef NOGUI m_ola->start(QThread::TimeCriticalPriority ); +#endif } libreMediaServerAudio::~libreMediaServerAudio() { m_ola->stop(); - m_aw->stopEngine(); + m_mae.stopEngine(); } -void libreMediaServerAudio::olasetup() +void libreMediaServerAudio::loadMedia(int layer, int folder, int file) { - QWebView *view = new QWebView(); - view->load(QUrl("http://localhost:9090/ola.html")); - view->show(); + 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); + } + if (m_currentStatus[layer] == Status::PlayingFolder \ + || (m_currentStatus[layer] == Status::PlayingFolderLoop)\ + || (m_currentStatus[layer] == Status::PlayingFolderRandom)) { + m_played.append(file); + } else if (m_currentStatus[layer] == Status::PlayingOnce \ + || m_currentStatus[layer] == Status::PlayingLoop) { + m_played.clear(); + } } void libreMediaServerAudio::dmxInput(int layer, int channel, int value) { - if (layer > LAYER_CHANNELS) + if (layer >= MAX_LAYERS || channel >= LAYER_CHANNELS) return; QString mediaFile = NULL; int aux; - switch(channel){ - case DMX_FOLDER: - aux = m_ola->getValue(layer, DMX_FILE); - mediaFile = m_mediaLibrary->requestNewFile(value, aux); - if (QFile::exists(mediaFile)) - m_aw->mediaLoaded(layer, mediaFile); - break; - case DMX_FILE: - aux = m_ola->getValue(layer, DMX_FOLDER); - mediaFile = m_mediaLibrary->requestNewFile(aux, value); - if (QFile::exists(mediaFile)) - m_aw->mediaLoaded(layer, mediaFile); - break; - case VOLUME_COARSE: - case VOLUME_FINE: - m_aw->volChanged(layer, (value / 65025.0f)); - break; - case PAN: - m_aw->panChanged(layer, value); - break; - case PITCH: - m_aw->pitchChanged(layer, value); - break; - case ENTRY_POINT_COARSE: - case ENTRY_POINT_FINE: - m_aw->entryPointChanged(layer, value); - break; - case PLAYBACK: - if (value == 0) - break; + if (channel == VOLUME_COARSE || channel == VOLUME_FINE) { + float tmp = value / 65025.0f; + m_mae.volChanged(layer, tmp); + m_updateUi[layer][0] = tmp * 100.0f; + } else if (channel == PAN) { + m_mae.panChanged(layer, value); + m_updateUi[layer][1] = value; + } else if (channel == PITCH) { + m_mae.pitchChanged(layer, value); + m_updateUi[layer][2] = value; + } else if (channel == ENTRY_POINT_COARSE || channel == ENTRY_POINT_FINE) { + m_mae.setCursor(layer, value); + m_updateUi[layer][3] = value; + } else if (channel == PLAYBACK && value > 0) { aux = value / 25; - switch (aux) { - case 0 : - m_aw->playbackChanged(layer, PlayingOnce); - break; - case 1 : - m_aw->playbackChanged(layer, Stopped); - break; - case 2 : - m_aw->playbackChanged(layer, Paused); - break; - case 3 : - m_aw->playbackChanged(layer, PlayingLoop); - break; - default : - break; + Status s = m_currentStatus[layer]; + if (aux == 0) + s = Status::PlayingOnce; + else if (aux == 1) + s = Status::Stopped; + else if (aux == 2) + s = Status::Paused; + else if (aux == 3) + s = Status::PlayingLoop; + else if (aux == 4) + s = Status::PlayingFolder; + else if (aux == 5) + s = Status::PlayingFolderLoop; + else if (aux == 6) + s = Status::PlayingFolderRandom; + m_mae.playbackChanged(layer, s); + m_currentStatus[layer] = s; + qInfo() << "Layer" << layer << StatusStr[s]; +#ifndef NOGUI + if (m_ui) { + m_lmsUi->m_aw->playbackChanged(layer, s); + //m_lmsUi->m_aw->cursorChanged(layer, m_mae.getCursor(layer)); } - default: +#endif + } +} +#ifndef NOGUI +void libreMediaServerAudio::refreshUi() { + if (!m_ui) return; + 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; + } + if (m_updateUi[i][1] >= 0) { + m_lmsUi->m_aw->panChanged(i, m_updateUi[i][1]); + m_updateUi[i][1] = -1; + } + if (m_updateUi[i][2] >= 0) { + m_lmsUi->m_aw->pitchChanged(i, m_updateUi[i][2]); + m_updateUi[i][2] = -1; + } + if (m_updateUi[i][3] >= 0 \ + || m_currentStatus[i] == Status::PlayingOnce\ + || m_currentStatus[i] == Status::PlayingLoop\ + || m_currentStatus[i] == Status::PlayingFolder\ + || m_currentStatus[i] == Status::PlayingFolderLoop + || m_currentStatus[i] == Status::PlayingFolderRandom) { + m_lmsUi->m_aw->cursorChanged(i, m_mae.getCursor(i)); + m_updateUi[i][3] = -1; + } + if (m_mae.getAtEnd(i)) { + if (m_played.isEmpty()) + m_played.append(m_ola->getValue(i, DMX_FILE)); + if (m_currentStatus[i] == Status::PlayingOnce) { + m_currentStatus[i] = Status::Stopped; + } + if (m_currentStatus[i] == Status::PlayingFolder) { + uint last = m_played.last(); + int folder = m_ola->getValue(i, DMX_FOLDER); + last++; + if (last < m_mediaLibrary->getMediaFolderCount(folder)) { + this->loadMedia(i, folder, last); + m_mae.playbackChanged(i, Status::PlayingFolder); + } + else { + m_currentStatus[i] = Status::Stopped; + m_lmsUi->m_aw->playbackChanged(i, Status::Stopped); + } + } + if (m_currentStatus[i] == Status::PlayingFolderLoop) { + uint last = m_played.last(); + int folder = m_ola->getValue(i, DMX_FOLDER); + last++; + if (last >= m_mediaLibrary->getMediaFolderCount(folder)) { + this->loadMedia(i, folder, 0); + m_mae.playbackChanged(i, Status::PlayingFolderLoop); + } else { + this->loadMedia(i, folder, last); + m_mae.playbackChanged(i, Status::PlayingFolder); + } + } + if (m_currentStatus[i] == Status::PlayingFolderRandom) { + int last = -1; + int folder = m_ola->getValue(i, DMX_FOLDER); + if (uint(abs(m_played.size())) >= m_mediaLibrary->getMediaFolderCount(folder)) + m_played.clear(); + while (last == -1) { + last = rand() % m_mediaLibrary->getMediaFolderCount(folder); + if (m_played.contains(last)) + last = -1; + } + this->loadMedia(i, folder, last); + m_mae.playbackChanged(i, Status::PlayingFolderRandom); + } + } + } +} + +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))); + connect(m_lmsUi->m_aw, SIGNAL(uiLoadMedia(int, QString)), this, SLOT(uiLoadMedia(int, QString))); + m_refreshUi = new QTimer(this); + connect(m_refreshUi, SIGNAL(timeout()), this, SLOT(refreshUi())); + m_refreshUi->start(UI_REFRESH_TIME); + m_ola->start(QThread::TimeCriticalPriority ); +}; + +// From Ui widgets +void libreMediaServerAudio::uiSliderChanged(int layer, Slider s, int value) +{ + switch (s){ + case Slider::Volume: + m_mae.volChanged(layer, float((value / 100.0f))); + break; + case Slider::Pan: + m_mae.panChanged(layer, value); + break; + case Slider::Pitch: + m_mae.pitchChanged(layer, value); break; } } + +void libreMediaServerAudio::uiPlaybackChanged(int layer, Status s) +{ + ma_result result; + + result = m_mae.playbackChanged(layer, s); + if (result == MA_SUCCESS) { + m_currentStatus[layer] = s; + } else { + qWarning() << "ui playback change error" << result << "status" << s << "layer" << layer; + } +} + +void libreMediaServerAudio::uiLoadMedia(int layer, QString mediaFile) +{ + ma_result result; + + if (strcmp(mediaFile.toLatin1().constData(), m_currentMedia[layer].toLatin1().constData()) == 0) + return; + result = m_mae.loadMedia(layer, mediaFile.toLatin1().data()); + if (result == MA_SUCCESS) { + m_currentMedia[layer] = mediaFile; + m_lmsUi->m_aw->mediaLoaded(layer, mediaFile, m_mae.getDuration(layer)); + } else { + qWarning() << "ui load media error" << result << "file" << mediaFile << "layer" << layer; + } +} +#endif diff --git a/src/libremediaserver-audio.h b/src/libremediaserver-audio.h index 9cd154b..d83446a 100644 --- a/src/libremediaserver-audio.h +++ b/src/libremediaserver-audio.h @@ -20,35 +20,51 @@ #ifndef LIBREMEDIASERVERAUDIO_H #define LIBREMEDIASERVERAUDIO_H -#include -#include - -#include "audiowidget.h" #include "medialibrary.h" +#include "miniaudioengine.h" #include "olathread.h" #include "settings.h" -#include "dmxwidget.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 public: - libreMediaServerAudio (QStringList args, QWidget *parent = 0); + libreMediaServerAudio(); virtual ~libreMediaServerAudio(); - Ui::LibreMediaServerAudio ui; + 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 private: - AudioWidget *m_aw; - dmxWidget *m_dmxWidget; olaThread *m_ola; MediaLibrary *m_mediaLibrary; + MiniAudioEngine m_mae; + Settings *m_settings; + QString m_currentMedia[MAX_LAYERS]; + Status m_currentStatus[MAX_LAYERS]; + QList m_dmxSettings; + bool m_ui; + QList m_played; +#ifndef NOGUI + QTimer *m_refreshUi; + libreMediaServerAudioUi *m_lmsUi; + float m_updateUi[MAX_LAYERS][4]; private slots: - void olasetup(); - void dmxInput(int layer, int channel, int value); + void refreshUi(); + void uiSliderChanged(int layer, Slider s, int value); + void uiPlaybackChanged(int layer, Status s); + void uiLoadMedia(int layer, QString s); + +#endif }; #endif // LIBREMEDIASERVERAUDIO_H diff --git a/src/libremediaserver-audio.ui b/src/libremediaserver-audio.ui deleted file mode 100644 index 6ade744..0000000 --- a/src/libremediaserver-audio.ui +++ /dev/null @@ -1,81 +0,0 @@ - - - Santi Noreña belfegor@gmail.com - LibreMediaServerAudio - - - - 0 - 0 - 114 - 218 - - - - LibreMediaServer - - - - - - 0 - 0 - 114 - 22 - - - - - File - - - - - - - - Exit - - - - - Open Configuration... - - - - - Save Configuration... - - - - - Settings... - - - - - false - - - Init - - - - - IP Address - - - - - Make Thumbs - - - - - OLA Setup... - - - - - - diff --git a/src/main.cpp b/src/main.cpp index bfadafe..79b7a91 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,34 +18,28 @@ along with this program. If not, see . */ -#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[]) { QApplication app(argc, argv); - QStringList args = app.arguments(); - if (args.size() > 1) + libreMediaServerAudio lms; +#ifndef NOGUI + if (hasUi(argc, argv) || lms.getShowUi()) { - if (args.contains("-v")) - { - qDebug() << VERSION; - 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; - } + libreMediaServerAudioUi *lmsUi = new libreMediaServerAudioUi(); + lms.setUi(lmsUi); + lmsUi->show(); } - libreMediaServerAudio libreMediaServerAudio(args); - libreMediaServerAudio.show(); +#endif return app.exec(); } diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..1a96ff6 --- /dev/null +++ b/src/main.h @@ -0,0 +1,20 @@ +#ifndef MAIN_H +#define MAIN_H + +#include +#include + +#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 diff --git a/src/medialibrary.h b/src/medialibrary.h index 5a0b55b..ea92bca 100644 --- a/src/medialibrary.h +++ b/src/medialibrary.h @@ -27,14 +27,11 @@ class MediaLibrary : public QObject public: MediaLibrary(QObject *parent = 0); - /** - * @brief request a new file from the media library - * @param int folder - the folder required - * @param int layer - file required - * @return QString the file required with full path - */ QString requestNewFile(int folder, int layer); void initMediaLibrary(); + inline uint getMediaFolderCount(int folder) { + return m_media->at(folder).m_ElementCount; + } private: QList *m_media; diff --git a/src/miniaudioengine.cpp b/src/miniaudioengine.cpp index 6d5490c..9204636 100644 --- a/src/miniaudioengine.cpp +++ b/src/miniaudioengine.cpp @@ -1,8 +1,15 @@ #include "miniaudioengine.h" - +#include //enum macro 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; + } } void MiniAudioEngine::audioDataCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) @@ -20,25 +27,26 @@ void MiniAudioEngine::stopEngine() ma_resource_manager_uninit(&resourceManager); } -bool MiniAudioEngine::startEngine(int n) +bool MiniAudioEngine::startEngine(uint n) { ma_result result; result = this->startContext(); - if (result != MA_SUCCESS) { - return result; - } - this->getAllAudioDevices(); + if (result != MA_SUCCESS) return result; + result = this->getAllAudioDevices(); + if (result != MA_SUCCESS) return result; result = this->startDevice(n); return result; } -ma_result MiniAudioEngine::startDevice(int id) +ma_result MiniAudioEngine::startDevice(uint id) { ma_result result; ma_device_config deviceConfig; ma_engine_config engineConfig; + if (id >= playbackDeviceCount) + id = playbackDeviceCount - 1; deviceConfig = ma_device_config_init(ma_device_type_playback); deviceConfig.playback.pDeviceID = &pPlaybackDeviceInfos[id].id; deviceConfig.playback.format = resourceManager.config.decodedFormat; @@ -66,7 +74,7 @@ ma_result MiniAudioEngine::startDevice(int id) return result; } iChosenDevice = id; - qInfo("Initialized audio device %d: %s", id, pPlaybackDeviceInfos[id].name); + qInfo("Initialized audio device %d : %s", id, pPlaybackDeviceInfos[id].name); return result; } @@ -77,8 +85,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 = 4; result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager); if (result != MA_SUCCESS) { qCritical("Failed to initialize audio resource manager."); @@ -102,7 +110,7 @@ ma_result MiniAudioEngine::getAllAudioDevices() ma_context_uninit(&context); return result; } - printf("Audio devices detected in system:\n"); + printf("Audio devices available:\n"); for (ma_uint32 iAvailableDevice = 0; iAvailableDevice < playbackDeviceCount; iAvailableDevice += 1) { qInfo("%d: : %s", iAvailableDevice, pPlaybackDeviceInfos[iAvailableDevice].name); } @@ -115,21 +123,20 @@ ma_result MiniAudioEngine::loadMedia(int layer, char *file) if (m_mediaLoaded[layer] == true) { - qInfo("removing sound %i", layer); ma_sound_uninit(&m_currentSound[layer]); m_mediaLoaded[layer] = false; } result = ma_sound_init_from_file(&engine, file, \ MA_SOUND_FLAG_NO_SPATIALIZATION \ - /*| MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE \ - | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC \ - | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM \*/ + | MA_SOUND_FLAG_DECODE \ + /*| MA_SOUND_FLAG_NO_PITCH \*/ , NULL, NULL, &m_currentSound[layer]); if (result != MA_SUCCESS) qWarning("Failed to load file %s", file); else { m_mediaLoaded[layer] = true; - this->volChanged(layer, 0); + this->refreshValues(layer); + m_currentLayerValues[layer].media = file; } return result; } @@ -137,16 +144,21 @@ ma_result MiniAudioEngine::loadMedia(int layer, char *file) float MiniAudioEngine::getDuration(int layer) { ma_result result; - float ret = 0; + ma_uint64 lengthInPCMFrames; + ma_uint32 sampleRate; + float ret; if (m_mediaLoaded[layer] == false) return MA_DOES_NOT_EXIST; - result = ma_sound_get_length_in_seconds(&m_currentSound[layer], &ret); - if (result != MA_SUCCESS) - { - qWarning("Can not get duration %i", layer); - ret = 0; + result = ma_sound_get_length_in_pcm_frames(&m_currentSound[layer], &lengthInPCMFrames); + if (result != MA_SUCCESS) { + return result; } + result = ma_sound_get_data_format(&m_currentSound[layer], NULL, NULL, &sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return MA_ERROR; + } + ret = 1000.0f * (lengthInPCMFrames / float(sampleRate)); return ret; } @@ -160,8 +172,8 @@ float MiniAudioEngine::getCursor(int layer) result = ma_sound_get_cursor_in_seconds(&m_currentSound[layer], &ret); if (result != MA_SUCCESS) { - qWarning("Can not get cursor %i", layer); - ret = 0; + qWarning("%i can not get cursor error %i", layer, result); + ret = MA_ERROR; } return ret; } @@ -175,11 +187,10 @@ ma_result MiniAudioEngine::printFormatInfo(int layer) if (m_mediaLoaded[layer] == false) return MA_DOES_NOT_EXIST; ma_result result = ma_sound_get_data_format(&m_currentSound[layer], &format, &channels, &sampleRate, NULL, 0); - if (result != MA_SUCCESS) { - qWarning("Failed to get data format %i\n", layer); - return MA_INVALID_DATA; - } - qInfo("samples/sec: %u format: %u channels: %u", sampleRate, format, channels); + if (result != MA_SUCCESS) + qWarning("%i failed to get data format %i\n", layer, result); + else + qInfo() << "Layer:" << layer << m_currentLayerValues[layer].media << "samples/sec:" << sampleRate << "format:" << format << "channels:" << channels; return result; } @@ -188,7 +199,10 @@ void MiniAudioEngine::volChanged(int layer, float vol) { if (m_mediaLoaded[layer] == false) return; - 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) @@ -199,48 +213,90 @@ 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) { - float result; + float pitch; if (m_mediaLoaded[layer] == false) return; - result = value / 128.0; - ma_sound_group_set_pitch(&m_currentSound[layer], result); + pitch = value / 128.0; + ma_sound_group_set_pitch(&m_currentSound[layer], pitch); + m_currentLayerValues[layer].pitch = value; } -void MiniAudioEngine::playbackChanged(int layer, Status status) +ma_result MiniAudioEngine::playbackChanged(int layer, Status status) { + ma_result result = MA_SUCCESS; + if (m_mediaLoaded[layer] == false) - return; + return MA_DOES_NOT_EXIST; switch (status) { case Status::Paused: - ma_sound_stop(&m_currentSound[layer]); + result = ma_sound_stop_with_fade_in_milliseconds(&m_currentSound[layer], FADE_TIME); break; case Status::Stopped: - ma_sound_stop(&m_currentSound[layer]); - ma_sound_seek_to_pcm_frame(&m_currentSound[layer], 0); + result = ma_sound_stop_with_fade_in_milliseconds(&m_currentSound[layer], FADE_TIME); + result = this->seekToCursor(layer, m_currentLayerValues[layer].cursor); break; case Status::PlayingLoop: + ma_sound_set_stop_time_in_milliseconds(&m_currentSound[layer], ~(ma_uint64)0); ma_sound_set_looping(&m_currentSound[layer], true); - ma_sound_start(&m_currentSound[layer]); - break; + result = ma_sound_start(&m_currentSound[layer]); + break; case Status::PlayingOnce: + case Status::PlayingFolder: + case Status::PlayingFolderLoop: + case Status::PlayingFolderRandom: + ma_sound_set_stop_time_in_milliseconds(&m_currentSound[layer], ~(ma_uint64)0); ma_sound_set_looping(&m_currentSound[layer], false); - ma_sound_start(&m_currentSound[layer]); + result = ma_sound_start(&m_currentSound[layer]); + break; + default: break; } + if (result == MA_SUCCESS) + m_currentLayerValues[layer].status = status; + return result; } -void MiniAudioEngine::setCursor(int layer, int cursor) +ma_result MiniAudioEngine::seekToCursor(int layer, int cursor) { - ma_uint64 f; + ma_result result = MA_SUCCESS; + ma_uint64 end, start; 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); + return MA_DOES_NOT_EXIST; + result = ma_sound_get_length_in_pcm_frames(&m_currentSound[layer], &end); + if (result != MA_SUCCESS) { return result; } + start = (cursor * end) / 65025; + result = ma_sound_seek_to_pcm_frame(&m_currentSound[layer], start); + //if (result != MA_SUCCESS) { return result; } + //result = ma_data_source_set_loop_point_in_pcm_frames(&m_currentSound[layer], start, end); + return (result); +} + +ma_result MiniAudioEngine::setCursor(int layer, int cursor) +{ + ma_result result = MA_SUCCESS; + + m_currentLayerValues[layer].cursor = cursor; + result = this->seekToCursor(layer, cursor); + return (result); +} + +Status MiniAudioEngine::getStatus(int layer) +{ + return m_currentLayerValues[layer].status; +} + +void MiniAudioEngine::refreshValues(int layer) +{ + this->seekToCursor(layer, m_currentLayerValues[layer].cursor); + 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); } diff --git a/src/miniaudioengine.h b/src/miniaudioengine.h index 9549602..6e1393c 100644 --- a/src/miniaudioengine.h +++ b/src/miniaudioengine.h @@ -5,15 +5,16 @@ #include "miniaudio.h" #include "defines.h" // MAX_LAYERS #include // prints messages +#define MA_DEBUG_OUTPUT class MiniAudioEngine { - friend class AudioWidget; + friend class libreMediaServerAudio; public: MiniAudioEngine(); void stopEngine(); - bool startEngine(int id); + bool startEngine(uint id); static void audioDataCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); protected: @@ -21,11 +22,15 @@ protected: void volChanged(int layer, float vol); void panChanged(int layer, float pan); void pitchChanged(int layer, float pitch); - void playbackChanged(int layer, Status status); + ma_result playbackChanged(int layer, Status status); + ma_result setCursor(int layer, int cursor); + ma_result printFormatInfo(int layer); float getDuration(int layer); float getCursor(int layer); - void setCursor(int layer, int cursor); - ma_result printFormatInfo(int layer); + Status getStatus(int layer); + inline float getVol(int layer) { + return ma_sound_get_volume(&m_currentSound[layer]); } + inline bool getAtEnd(int layer) { return m_currentSound[layer].atEnd; } private: ma_resource_manager_config resourceManagerConfig; @@ -38,10 +43,13 @@ 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 startDevice(uint id); ma_result startContext(); + void refreshValues(int layer); + ma_result seekToCursor(int layer, int cursor); }; #endif // MINIAUDIOENGINE_H diff --git a/src/olathread.cpp b/src/olathread.cpp index ed0998b..6ca9325 100644 --- a/src/olathread.cpp +++ b/src/olathread.cpp @@ -1,11 +1,10 @@ -#include "olathread.h" - +#include "libremediaserver-audio.h" olaThread::olaThread(QObject *parent, int layers) : m_counter(0) , m_layers(layers) { - Q_UNUSED(parent); + this->setParent(parent); gettimeofday(&m_last_data, NULL); for (int i=0; i < MAX_LAYERS; i++) { @@ -63,42 +62,65 @@ void olaThread::stop() void olaThread::NewDmx(const ola::client::DMXMetadata &data, const ola::DmxBuffer &buffer) { - bool volSent = false; - bool entrySent = false; - foreach (const dmxSetting &i, m_dmxSettings) { if(i.universe == data.universe && i.address > -1) { + 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 VOLUME_COARSE: - value = (value * 0x100) + buffer.Get(i.address + VOLUME_FINE); - emit dmxOutput(i.layer,j,value); - volSent = true; + case DMX_FOLDER: + aux = buffer.Get(i.address + DMX_FILE); + qobject_cast(parent())->loadMedia(i.layer, value, aux); + m_dmx[i.layer][DMX_FILE] = aux; + fileSent = true; break; - case ENTRY_POINT_COARSE: - value = (value * 0x100) + buffer.Get(i.address + ENTRY_POINT_FINE); - emit dmxOutput(i.layer,j,value); - entrySent = true; + case DMX_FILE: + if (fileSent) + break; + aux = buffer.Get(i.address + DMX_FOLDER); + qobject_cast(parent())->loadMedia(i.layer, aux, value); + m_dmx[i.layer][DMX_FOLDER] = aux; + fileSent = true; break; case VOLUME_FINE: - if (volSent == false) - { - value = (buffer.Get(i.address + VOLUME_COARSE) * 0x100) + value; - emit dmxOutput(i.layer,j,value); - } + aux = buffer.Get(i.address + VOLUME_COARSE); + value += (aux * 0x100); + qobject_cast(parent())->dmxInput(i.layer, j, value); + m_dmx[i.layer][VOLUME_COARSE] = aux; + volSent = true; + break; + case VOLUME_COARSE: + if (volSent) + break; + value = (value * 0x100) + buffer.Get(i.address + VOLUME_FINE); + qobject_cast(parent())->dmxInput(i.layer, j, value); + m_dmx[i.layer][VOLUME_FINE] = buffer.Get(i.address + VOLUME_FINE); + volSent = true; + break; + case PLAYBACK: + qobject_cast(parent())->dmxInput(i.layer, j, value); break; 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; + qobject_cast(parent())->dmxInput(i.layer, j, value); + m_dmx[i.layer][ENTRY_POINT_COARSE] = buffer.Get(i.address + ENTRY_POINT_COARSE); + entrySent = true; + break; + case ENTRY_POINT_COARSE: + if (entrySent) + break; + value = (value * 0x100) + buffer.Get(i.address + ENTRY_POINT_FINE); + qobject_cast(parent())->dmxInput(i.layer, j, value); + m_dmx[i.layer][ENTRY_POINT_FINE] = buffer.Get(i.address + ENTRY_POINT_FINE); + entrySent = true; break; default: - emit dmxOutput(i.layer,j,value); + qobject_cast(parent())->dmxInput(i.layer, j, value); break; } } @@ -127,7 +149,7 @@ bool olaThread::CheckDataLoss() { void olaThread::socketClosed() { - qWarning("ola closed connection. Try reopening it... "); + qWarning("ola daemon closed connection, reopening it... "); m_clientWrapper->GetSelectServer()->Terminate(); m_client = NULL; m_clientWrapper = NULL; diff --git a/src/olathread.h b/src/olathread.h index 91d78b6..ed2735a 100644 --- a/src/olathread.h +++ b/src/olathread.h @@ -20,10 +20,17 @@ class olaThread : public QThread Q_OBJECT public: + QList m_dmxSettings; + int m_dmx[MAX_LAYERS][LAYER_CHANNELS]; + ola::client::OlaClientWrapper *m_clientWrapper; + ola::client::OlaClient *m_client; + unsigned int m_counter; + struct timeval m_last_data; // Last DMX frame received + int m_layers; olaThread(QObject *parent = 0, int layers = 0); virtual ~olaThread(); - + void run (); /** Retorna el valor de un canal *@param int layer the layer for we want the channel *@param int channel the channel for the value wanted @@ -35,14 +42,6 @@ public: void resendDmx(); private: - void run (); - ola::client::OlaClientWrapper *m_clientWrapper; - ola::client::OlaClient *m_client; - unsigned int m_counter; - struct timeval m_last_data; // Last DMX frame received - int m_layers; - int m_dmx[MAX_LAYERS][LAYER_CHANNELS]; - QList m_dmxSettings; /** * @brief Callback from ola. Control de errores en el registro de Universos en OLA * typedef SingleUseCallback1 ola::client::SetCallback @@ -53,7 +52,7 @@ private: if (error.Success()) { qDebug("Register Universe success"); } else { - qWarning("Register command failed: %s", error.Error().c_str()); + qCritical("Register command failed: %s", error.Error().c_str()); } } /** @@ -67,7 +66,7 @@ private: * This is called one for second if there is not updated in the DMX frame. * emit only the channels that has been changed. */ - void NewDmx(const ola::client::DMXMetadata &dmx_meta, const ola::DmxBuffer &buffer); // + void NewDmx(const ola::client::DMXMetadata &dmx_meta, const ola::DmxBuffer &buffer); /** * @brief Sometimes the ola server closes the connection. This is a callback to handle this event an reconect to ola */ diff --git a/src/settings.cpp b/src/settings.cpp index f664bb0..9d06394 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -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 readed:\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 internal id:" << i << "system 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() { diff --git a/src/settings.h b/src/settings.h index 8cd55bb..f27da6a 100644 --- a/src/settings.h +++ b/src/settings.h @@ -5,6 +5,7 @@ #include #include #include +#include #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 getUniverses() { return m_universe; } inline QString getPathMedia() { return m_pathmedia; } inline QList 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 m_settings; QString m_pathmedia; - uint m_audioDeviceId; + uint m_audioDeviceId[MAX_AUDIODEVICES]; + uint m_audioDeviceQty; QSet m_universe; int m_layersNumber; - - explicit Settings(QObject *parent = 0); - void readFromFile(QString file); - + bool m_ui; }; #endif // SETTINGS_H diff --git a/src/slidergroup.cpp b/src/slidergroup.cpp index e80a03f..9ef9680 100644 --- a/src/slidergroup.cpp +++ b/src/slidergroup.cpp @@ -1,45 +1,81 @@ #include "slidergroup.h" - -SliderGroup::SliderGroup(const QString &title, \ +#include +SliderGroup::SliderGroup(QString name, int min, int max, int decimals, QWidget *parent) - : QGroupBox(title, parent) + : QWidget(parent) { - this->setFlat(true); - this->setTitle(title); + QVBoxLayout *layout = new QVBoxLayout; + layout->setAlignment(Qt::AlignHCenter); + layout->setContentsMargins(0, 0, 0, 0); + //this->setMaximumWidth(40); slider = new QSlider(Qt::Orientation::Vertical); slider->setFocusPolicy(Qt::StrongFocus); slider->setTickPosition(QSlider::TicksBothSides); slider->setTickInterval((max - min) / 11); + slider->setMinimumHeight(0); slider->setSingleStep(1); slider->setRange(min, max); + slider->setValue(0); + slider->setMinimumWidth(50); + slider->setToolTip(name); + slider->setStyleSheet("QSlider {" + "border: 1px solid #5a4855;" + "margin: 0px;" + "height: 200px;" + "width: 50px;}" + ); + slider->setContentsMargins(0, 0, 0, 0); valueBox = new QDoubleSpinBox(); valueBox->setFocusPolicy(Qt::NoFocus); valueBox->setButtonSymbols(QAbstractSpinBox::NoButtons); - valueBox->setMaximumWidth(45); + 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))); - QVBoxLayout *slidersLayout = new QVBoxLayout(); - slidersLayout->addWidget(valueBox); - slidersLayout->addWidget(slider); - setLayout(slidersLayout); + //connect(slider, SIGNAL(mousePressEvent(QMouseEvent)), this, SLOT(mousePressEvent(QMouseEvent *))); + layout->addWidget(slider); + layout->addWidget(valueBox); + this->setStyleSheet("border: 1px solid #5a4855;" + "width: 50px;" + "margin: 0px;" + "background-color: #383034;" + ); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); + this->setLayout(layout); } void SliderGroup::sliderValueChanged(int value) { - if (valueBox->decimals() > 1) - value /= 100.0f; - emit valueChanged(value); + valueBox->blockSignals(true); valueBox->setValue(value); + valueBox->blockSignals(false); + emit valueChanged(value); }; void SliderGroup::setValue(float value) { - if (valueBox->decimals() > 1) - value *= 100.0f; - slider->setValue(value); + slider->blockSignals(true); + valueBox->blockSignals(true); + if (int(value) != slider->value()) + slider->setValue(value); valueBox->setValue(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); } diff --git a/src/slidergroup.h b/src/slidergroup.h index f5bae60..5bfeeb8 100644 --- a/src/slidergroup.h +++ b/src/slidergroup.h @@ -6,19 +6,19 @@ #include #include -class SliderGroup : public QGroupBox +class SliderGroup : public QWidget { Q_OBJECT public: - SliderGroup(const QString &title, - int min, - int max, - int decimals, - QWidget *parent = nullptr); + SliderGroup(QString name, + int min, + int max, + int decimals, + QWidget *parent = nullptr); signals: - void valueChanged(float value); + void valueChanged(int value); public slots: void setValue(float value); @@ -27,6 +27,8 @@ public slots: private: QSlider *slider; QDoubleSpinBox *valueBox; + + void mousePressEvent(QMouseEvent* event); }; #endif