diff --git a/docs/changelog.txt b/docs/changelog.txt index 5c7e0b6..c324e44 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -13,8 +13,8 @@ v 0.2.0 Antígona (26/05/2024) + Pan. + Show faders values. New SliderGroup class. + Entry Point 16 bits. -+ Refactor AudioMasterWidget to AudioDMXReceptionWidget. -+ Read mp3, flac, wav (mp3 has given some errors seeking cursor...). ++ Refactor AudioMasterWidget to dmxWidget. ++ Read mp3, flac, wav (mp3 has given some errors seeking cursors...). Audio Engine is working at 48Khz and 32 bits pcm float, if media files are encoding at same configuration, it saves a resampling operation later live. + 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. @@ -23,8 +23,12 @@ v 0.2.0 Antígona (26/05/2024) + 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). ++ 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). Signals are used only from Ui to libreMediaServer to notify user interactions and Qtimers. + Load media files from ui clicking in the media labels. ++ New Play Modes: + - Play all medias found in one folder. + - Play all medias in one folder consecutevily. + - Play all medias in one folder randomly. v 0.1.3 Leúcade (19/04/2024) diff --git a/docs/roadmap.txt b/docs/roadmap.txt index 0771257..9385090 100644 --- a/docs/roadmap.txt +++ b/docs/roadmap.txt @@ -26,9 +26,6 @@ v 0.2.2 v 0.2.1 - Multi devices output. -- Play Mode: - - Play all medias found in folder consecutevily or random, with loop. - - Play all medias, consecutevily and random, with loop. - mute/panic on layer. - Master Bus Layer: - each layer will have one "Gain" prefader that acts in source, "Vol" in v 1.3. diff --git a/src/audiolayerwidget.cpp b/src/audiolayerwidget.cpp index e8d22bd..2655938 100644 --- a/src/audiolayerwidget.cpp +++ b/src/audiolayerwidget.cpp @@ -46,11 +46,11 @@ AudioLayerWidget::AudioLayerWidget(QWidget *parent, int layer): m_progressTime = new QTimeEdit; m_progressTime->setToolTip("Current Time"); m_progressTime->setObjectName("Current Time"); - m_progressTime->setDisplayFormat("mm:ss:zz"); + m_progressTime->setDisplayFormat("mm:ss:zzz"); m_progressTime->setReadOnly(true); m_progressTime->setButtonSymbols(QAbstractSpinBox::NoButtons); m_progressTime->setMinimumWidth(80); - m_progressTime->setMaximumWidth(80); + //m_progressTime->setMaximumWidth(80); m_progressTime->setFocusPolicy(Qt::NoFocus); m_progressTime->setAlignment(Qt::AlignHCenter); m_progressTime->setContentsMargins(0,0,0,0); @@ -68,22 +68,22 @@ AudioLayerWidget::AudioLayerWidget(QWidget *parent, int layer): 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))); + QVBoxLayout *volumeBox = new QVBoxLayout; + m_pitch = new SliderGroup("Pitch", 0 , 255, 0, NULL); + volumeBox->addWidget(m_pitch); + connect(m_pitch, SIGNAL(valueChanged(int)), this, SLOT(pitchChanged(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); + m_volume = new SliderGroup("Vol", 0 , 100, 2, NULL); + volumeBox->addWidget(m_volume); + connect(m_volume, SIGNAL(valueChanged(int)), this, SLOT(volumeChanged(int))); 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); + layout->setContentsMargins(1, 1, 1, 1); this->setLayout(layout); } @@ -113,13 +113,17 @@ void AudioLayerWidget::toggleSuspendResume() switch (m_status) { case Status::PlayingLoop: case Status::PlayingOnce: + case Status::PlayingFolder: + case Status::PlayingFolderLoop: + case Status::PlayingFolderRandom: + m_oldStatus = m_status; this->setPlaybackStatus(Status::Paused); emit uiPlaybackChanged(m_layer, Status::Paused); break; case Status::Paused: case Status::Stopped: - this->setPlaybackStatus(Status::PlayingLoop); - emit uiPlaybackChanged(m_layer, Status::PlayingLoop); + this->setPlaybackStatus(m_oldStatus); + emit uiPlaybackChanged(m_layer, m_oldStatus); case Status::Iddle: break; } @@ -174,10 +178,9 @@ void AudioLayerWidget::setMediaFile(QString file) void AudioLayerWidget::setPlaybackStatus(Status s) { - Status status = static_cast(s); m_suspendResumeButton->blockSignals(true); - m_status = status; - m_suspendResumeButton->setText(StatusStr[status]); + m_status = s; + m_suspendResumeButton->setText(StatusStr[s]); m_suspendResumeButton->blockSignals(false); } diff --git a/src/audiolayerwidget.h b/src/audiolayerwidget.h index 9380b81..cae18d4 100644 --- a/src/audiolayerwidget.h +++ b/src/audiolayerwidget.h @@ -21,6 +21,7 @@ public: private: Status m_status; + Status m_oldStatus; int m_layer; QPushButton *m_suspendResumeButton; ClickableLabel *m_fileValue; diff --git a/src/audiowidget.cpp b/src/audiowidget.cpp index 8bd077c..e163da3 100644 --- a/src/audiowidget.cpp +++ b/src/audiowidget.cpp @@ -5,7 +5,8 @@ AudioWidget::AudioWidget(QWidget *parent) : QWidget(parent) , m_layout(new QHBoxLayout()) { - for (int i= 0; i < Settings::getInstance()->getLayersNumber(); i++ ) { + m_layers = Settings::getInstance()->getLayersNumber(); + for (uint i= 0; i < m_layers; i++ ) { AudioLayerWidget *alw = new AudioLayerWidget(this, i); m_layout->insertWidget(i, alw); connect(alw, SIGNAL(uiSliderChanged(int, Slider, int)), this, SIGNAL(uiSliderChanged(int, Slider, int))); @@ -64,7 +65,7 @@ void AudioWidget::cursorChanged(int layer, float cursor) void AudioWidget::refreshUi() { - for (int i = 0; i < MAX_LAYERS; i++) + for (uint i = 0; i < m_layers; i++) { if (m_layerUpdate[i].updated) { QLayoutItem * const item = m_layout->itemAt(i); diff --git a/src/audiowidget.h b/src/audiowidget.h index dd9b1f1..c314656 100644 --- a/src/audiowidget.h +++ b/src/audiowidget.h @@ -18,6 +18,7 @@ private: QHBoxLayout *m_layout; layerData m_layerUpdate[MAX_LAYERS]; QTimer *m_refreshUi; + uint m_layers; public slots: void volChanged(int layer, float vol); diff --git a/src/defines.h b/src/defines.h index ff299c1..2355059 100644 --- a/src/defines.h +++ b/src/defines.h @@ -7,7 +7,7 @@ #define DEFAULT_FILE "lms-audio.xlm" #define MAX_LAYERS 4 #define MAX_AUDIODEVICES 8 -#define UI_REFRESH_TIME 66 +#define UI_REFRESH_TIME 100 #define FADE_TIME 25 // DMX Frame time, 40 fps, avoid clicks struct dmxSetting { @@ -32,12 +32,12 @@ static const char* StatusStr[] = { "Stop", "Pause", - "Playing One", - "Playing One Loop", + "Play One", + "Play One Loop", "Iddle", - "Playing Folder", - "Playing Folder Loop", - "Playing Folder Random", + "Play Folder", + "Play Folder Loop", + "Play Folder Rand", 0x0 }; diff --git a/src/libremediaserver-audio.cpp b/src/libremediaserver-audio.cpp index a9868fd..8f16ebc 100644 --- a/src/libremediaserver-audio.cpp +++ b/src/libremediaserver-audio.cpp @@ -122,7 +122,8 @@ void libreMediaServerAudio::dmxInput(int layer, int channel, int value) #ifndef NOGUI if (m_ui) { m_lmsUi->m_aw->playbackChanged(layer, s); - //m_lmsUi->m_aw->cursorChanged(layer, m_mae.getCursor(layer)); + m_played.clear(); + m_played.append(m_ola->getValue(layer, DMX_FILE)); } #endif } @@ -153,8 +154,6 @@ void libreMediaServerAudio::refreshUi() { 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; } diff --git a/src/miniaudioengine.cpp b/src/miniaudioengine.cpp index 9204636..264871b 100644 --- a/src/miniaudioengine.cpp +++ b/src/miniaudioengine.cpp @@ -85,7 +85,7 @@ 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 = ma_standard_sample_rate_48000; + resourceManagerConfig.decodedSampleRate = ma_standard_sample_rate_44100; resourceManagerConfig.jobThreadCount = 4; result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager); if (result != MA_SUCCESS) { @@ -129,6 +129,7 @@ ma_result MiniAudioEngine::loadMedia(int layer, char *file) result = ma_sound_init_from_file(&engine, file, \ MA_SOUND_FLAG_NO_SPATIALIZATION \ | MA_SOUND_FLAG_DECODE \ + | MA_SOUND_FLAG_STREAM \ /*| MA_SOUND_FLAG_NO_PITCH \*/ , NULL, NULL, &m_currentSound[layer]); if (result != MA_SUCCESS) @@ -144,22 +145,15 @@ ma_result MiniAudioEngine::loadMedia(int layer, char *file) float MiniAudioEngine::getDuration(int layer) { ma_result result; - ma_uint64 lengthInPCMFrames; - ma_uint32 sampleRate; float ret; if (m_mediaLoaded[layer] == false) return MA_DOES_NOT_EXIST; - result = ma_sound_get_length_in_pcm_frames(&m_currentSound[layer], &lengthInPCMFrames); + result = ma_sound_get_length_in_seconds(&m_currentSound[layer], &ret); 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; + return (ret * 1000); } float MiniAudioEngine::getCursor(int layer) @@ -233,27 +227,25 @@ ma_result MiniAudioEngine::playbackChanged(int layer, Status status) if (m_mediaLoaded[layer] == false) return MA_DOES_NOT_EXIST; + bool loop = false; switch (status) { case Status::Paused: result = ma_sound_stop_with_fade_in_milliseconds(&m_currentSound[layer], FADE_TIME); break; case Status::Stopped: - result = ma_sound_stop_with_fade_in_milliseconds(&m_currentSound[layer], FADE_TIME); + 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); - result = ma_sound_start(&m_currentSound[layer]); - break; + loop = true; 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_set_looping(&m_currentSound[layer], loop); result = ma_sound_start(&m_currentSound[layer]); - break; + //this->volChanged(layer, m_currentLayerValues[layer].vol); // glitch when seek to cursor, how flush the audio buffer? default: break; } @@ -273,8 +265,7 @@ ma_result MiniAudioEngine::seekToCursor(int layer, int cursor) 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); + //result = ma_data_source_set_loop_point_in_pcm_frames(&m_currentSound[layer], start, end); // this do nothing here, it must be done after set_looping or start? return (result); } diff --git a/src/miniaudioengine.h b/src/miniaudioengine.h index 6e1393c..103126e 100644 --- a/src/miniaudioengine.h +++ b/src/miniaudioengine.h @@ -1,11 +1,15 @@ #ifndef MINIAUDIOENGINE_H #define MINIAUDIOENGINE_H +#define MA_ENABLE_ONLY_SPECIFIC_BACKENDS 1 +#define MA_ENABLE_JACK 1 +#define MA_NO_GENERATION 1 +#define MA_DEBUG_OUTPUT 1 +#define MA_DISABLE_PULSEAUDIO #define MINIAUDIO_IMPLEMENTATION #include "miniaudio.h" #include "defines.h" // MAX_LAYERS #include // prints messages -#define MA_DEBUG_OUTPUT class MiniAudioEngine { diff --git a/src/slidergroup.cpp b/src/slidergroup.cpp index 9ef9680..4395a8d 100644 --- a/src/slidergroup.cpp +++ b/src/slidergroup.cpp @@ -1,5 +1,17 @@ #include "slidergroup.h" #include +#include + +DoubleSpinBoxClickable::DoubleSpinBoxClickable(QWidget *parent) + : QDoubleSpinBox{parent} {} + +DoubleSpinBoxClickable::~DoubleSpinBoxClickable() {} + +SliderClickDisable::SliderClickDisable(QWidget *parent) + : QSlider{parent} {} + +SliderClickDisable::~SliderClickDisable() {} + SliderGroup::SliderGroup(QString name, int min, int max, @@ -7,42 +19,46 @@ SliderGroup::SliderGroup(QString name, QWidget *parent) : QWidget(parent) { - QVBoxLayout *layout = new QVBoxLayout; + QBoxLayout *layout; + if (decimals) { + layout = new QVBoxLayout; + slider.setOrientation(Qt::Vertical); + } + else { + layout = new QHBoxLayout; + slider.setOrientation(Qt::Horizontal); + slider.setMinimumHeight(10); + } 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 {" + 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;}" + "margin: 0px;}" ); - slider->setContentsMargins(0, 0, 0, 0); - valueBox = new QDoubleSpinBox(); - valueBox->setFocusPolicy(Qt::NoFocus); - 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); + slider.setContentsMargins(0, 0, 0, 0); + valueBox.setFocusPolicy(Qt::NoFocus); + 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(&valueBox, SIGNAL(enableSlider()), this, SLOT(enableSlider())); + layout->addWidget(&slider); + layout->addWidget(&valueBox); this->setStyleSheet("border: 1px solid #5a4855;" "width: 50px;" "margin: 0px;" @@ -55,27 +71,19 @@ SliderGroup::SliderGroup(QString name, void SliderGroup::sliderValueChanged(int value) { - valueBox->blockSignals(true); - valueBox->setValue(value); - valueBox->blockSignals(false); + valueBox.blockSignals(true); + valueBox.setValue(value); + valueBox.blockSignals(false); emit valueChanged(value); }; void SliderGroup::setValue(float 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); + slider.blockSignals(true); + valueBox.blockSignals(true); + if (int(value) != slider.value()) + slider.setValue(value); + valueBox.setValue(value); + slider.blockSignals(false); + valueBox.blockSignals(false); } diff --git a/src/slidergroup.h b/src/slidergroup.h index 5bfeeb8..b6940b1 100644 --- a/src/slidergroup.h +++ b/src/slidergroup.h @@ -5,6 +5,78 @@ #include #include #include +#include +#include +#include +/* +//slider->installEventFilter(new QSliderAnalyser); +class QSliderAnalyser + : public QObject +{ + public: + QSliderAnalyser() + { + } + + virtual ~QSliderAnalyser() + { + } + + protected: + bool eventFilter(QObject* object, QEvent* event) override + { + if (event->type() == QEvent::MouseButtonPress) { + qDebug() << event->type() << object->objectName(); + } + return QObject::eventFilter(object, event); + } +};*/ + +class DoubleSpinBoxClickable: public QDoubleSpinBox +{ + Q_OBJECT + +public: + DoubleSpinBoxClickable(QWidget *parent = 0); + ~DoubleSpinBoxClickable(); + +signals: + void enableSlider(); + +protected: + void mousePressEvent ( QMouseEvent * event ) + { + if (event->button() == Qt::LeftButton) { + qDebug() << "enabling slider"; + emit(enableSlider()); + } + event->accept(); + } +}; + +class SliderClickDisable + : public QSlider +{ + Q_OBJECT + +public: + explicit SliderClickDisable(QWidget *parent = Q_NULLPTR); + ~SliderClickDisable(); + +protected: + void mousePressEvent ( QMouseEvent * event ) + { + if (event->button() == Qt::RightButton) + { + if (this->isEnabled()) { + qDebug() << "disabling slider"; + this->setDisabled(true); + } + event->accept(); + } + QSlider::mousePressEvent(event); + } +}; class SliderGroup : public QWidget { @@ -25,10 +97,12 @@ public slots: void sliderValueChanged(int value); private: - QSlider *slider; - QDoubleSpinBox *valueBox; + SliderClickDisable slider; + DoubleSpinBoxClickable valueBox; + +private slots: + void enableSlider() { slider.setEnabled(true); } - void mousePressEvent(QMouseEvent* event); }; #endif