diff --git a/libremediaserver-audio.pro b/libremediaserver-audio.pro index 5e1f73f..d86932e 100644 --- a/libremediaserver-audio.pro +++ b/libremediaserver-audio.pro @@ -4,6 +4,7 @@ QT += webkitwidgets widgets HEADERS += src/libremediaserver-audio.h \ src/miniaudio.h \ src/medialibrary.h \ + src/miniaudioengine.h \ src/olathread.h \ src/audiolayerwidget.h \ src/dmxPersonality.h \ @@ -17,6 +18,7 @@ SOURCES += src/main.cpp \ src/miniaudio.c \ src/libremediaserver-audio.cpp \ src/medialibrary.cpp \ + src/miniaudioengine.cpp \ src/olathread.cpp \ src/audiolayerwidget.cpp \ src/audiowidget.cpp \ diff --git a/src/audiolayerwidget.cpp b/src/audiolayerwidget.cpp index 90735c4..d62c165 100644 --- a/src/audiolayerwidget.cpp +++ b/src/audiolayerwidget.cpp @@ -4,59 +4,50 @@ AudioLayerWidget::AudioLayerWidget(QWidget *parent, QString name): QGroupBox(parent) , m_suspendResumeButton(0) - , m_refreshGUI(new QTimer(this)) - , m_currentMedia("") - , m_mediaLoaded(false) + , m_volumeIndicator(new QSpinBox) + , m_panIndicator(new QSpinBox) + , m_pitchIndicator(new QSpinBox) { this->setTitle(name); QVBoxLayout *layout = new QVBoxLayout; - QHBoxLayout *status = new QHBoxLayout; + QGridLayout *status = new QGridLayout; m_statusLabel = new QLabel; m_statusLabel->setText(STATUS_LABEL); m_statusValue = new QLabel; - status->addWidget(m_statusLabel); - status->addWidget(m_statusValue); - m_loopCheck = new QCheckBox(); - m_loopCheckLabel = new QLabel; - m_loopCheckLabel->setText("Loop"); - connect(m_loopCheck, SIGNAL(stateChanged(int)), this, SLOT(loopChanged(int))); - status->addWidget(m_loopCheck); - status->addWidget(m_loopCheckLabel); - layout->addLayout(status); - - QHBoxLayout *folderLoaded = new QHBoxLayout; + status->addWidget(m_statusLabel, 0, 0); + status->addWidget(m_statusValue, 0, 2); m_folderLabel = new QLabel; m_folderLabel->setText(FOLDER_LABEL); m_folderValue = new QLabel; m_folderValue->setMaximumWidth(200); - folderLoaded->addWidget(m_folderLabel); - folderLoaded->addWidget(m_folderValue); - layout->addLayout(folderLoaded); - - QHBoxLayout *fileLoaded = new QHBoxLayout; + status->addWidget(m_folderLabel, 1, 0); + status->addWidget(m_folderValue, 1, 1); m_fileLabel = new QLabel; m_fileLabel->setText(FILE_LABEL); m_fileValue = new QLabel; m_fileValue->setMaximumWidth(200); - fileLoaded->addWidget(m_fileLabel); - fileLoaded->addWidget(m_fileValue); - layout->addLayout(fileLoaded); + status->addWidget(m_fileLabel, 1, 2); + status->addWidget(m_fileValue, 1, 3); + layout->addLayout(status); QGridLayout *volumeBox = new QGridLayout; m_volumeLabel = new QLabel; m_volumeLabel->setText(tr(VOLUME_LABEL)); m_volumeSlider = new QSlider(Qt::Horizontal); - m_volumeSlider->setMinimum(-100); + m_volumeSlider->setMinimum(0); m_volumeSlider->setMaximum(100); m_volumeSlider->setSingleStep(1); - m_volumeIndicator = new QLabel; + m_volumeIndicator->setRange(0, 100); + m_volumeIndicator->setValue(0); + m_volumeIndicator->setMaximumWidth(40); + m_volumeIndicator->setButtonSymbols(QAbstractSpinBox::NoButtons); volumeBox->addWidget(m_volumeLabel, 0, 0); volumeBox->addWidget(m_volumeSlider, 0, 1); volumeBox->addWidget(m_volumeIndicator, 0, 2); connect(m_volumeSlider, SIGNAL(valueChanged(int)), this, SLOT(volumeChanged(int))); connect(m_volumeSlider, &QSlider::valueChanged, this, [=] () { - m_volumeIndicator->setText(QString::number(m_volumeSlider->value())); + m_volumeIndicator->setValue(m_volumeSlider->value()); }); m_panLabel = new QLabel; m_panLabel->setText("Pan"); @@ -64,18 +55,34 @@ AudioLayerWidget::AudioLayerWidget(QWidget *parent, QString name): m_panSlider->setMinimum(0); m_panSlider->setMaximum(255); m_panSlider->setSingleStep(1); + m_panIndicator->setRange(0, 255); + m_panIndicator->setValue(128); + m_panIndicator->setMaximumWidth(40); + m_panIndicator->setButtonSymbols(QAbstractSpinBox::NoButtons); + connect(m_panSlider, &QSlider::valueChanged, this, [=] () { + m_panIndicator->setValue(m_panSlider->value()); + }); connect(m_panSlider, SIGNAL(valueChanged(int)), this, SLOT(panChanged(int))); volumeBox->addWidget(m_panLabel, 1, 0); volumeBox->addWidget(m_panSlider, 1, 1); + volumeBox->addWidget(m_panIndicator, 1, 2); m_pitchLabel = new QLabel; m_pitchLabel->setText("Pitch"); m_pitchSlider = new QSlider(Qt::Horizontal); m_pitchSlider->setMinimum(0); m_pitchSlider->setMaximum(255); m_pitchSlider->setSingleStep(1); + m_pitchIndicator->setRange(0, 255); + m_pitchIndicator->setValue(128); + m_pitchIndicator->setMaximumWidth(40); + m_pitchIndicator->setButtonSymbols(QAbstractSpinBox::NoButtons); + connect(m_pitchSlider, &QSlider::valueChanged, this, [=] () { + m_pitchIndicator->setValue(m_pitchSlider->value()); + }); connect(m_pitchSlider, SIGNAL(valueChanged(int)), this, SLOT(pitchChanged(int))); volumeBox->addWidget(m_pitchLabel, 2, 0); volumeBox->addWidget(m_pitchSlider, 2, 1); + volumeBox->addWidget(m_pitchIndicator, 2, 2); layout->addLayout(volumeBox); QHBoxLayout *progressTime = new QHBoxLayout; @@ -102,15 +109,12 @@ AudioLayerWidget::AudioLayerWidget(QWidget *parent, QString name): m_progressSlider = new QSlider(Qt::Horizontal); layout->addWidget(m_progressSlider); - m_suspendResumeButton = new QPushButton(this); m_suspendResumeButton->setText(tr(SUSPEND_LABEL)); connect(m_suspendResumeButton, SIGNAL(clicked()), SLOT(toggleSuspendResume())); layout->addWidget(m_suspendResumeButton); this->setLayout(layout); - connect(m_refreshGUI, SIGNAL(timeout()), this, SLOT(refreshGUI())); - m_refreshGUI->start(100); } AudioLayerWidget::~AudioLayerWidget() @@ -118,123 +122,69 @@ AudioLayerWidget::~AudioLayerWidget() } +// From UI. void AudioLayerWidget::volumeChanged(int value) { - float result; - - if (m_mediaLoaded == false) - return; - result = ma_volume_linear_to_db(value); - ma_sound_group_set_volume(¤tSound, result); + (void)value; + // ToDo: call the audio engine } void AudioLayerWidget::panChanged(int value) { - float result; - if (m_mediaLoaded == false) - return; - result = (value / 128.0) - 128; - ma_sound_group_set_pan(¤tSound, result); + (void)value; + // ToDo: call the audio engine } void AudioLayerWidget::pitchChanged(int value) { - float result; - if (m_mediaLoaded == false) - return; - result = (value / 128.0) - 128; - ma_sound_group_set_pitch(¤tSound, result); + (void)value; + // ToDo: call the audio engine } void AudioLayerWidget::loopChanged(int value) { - if (m_mediaLoaded == false) - return; - ma_sound_set_looping(¤tSound, value); + (void)value; + // ToDo: call the audio engine } +void AudioLayerWidget::toggleSuspendResume() +{ + switch (m_status) { + case Status::PlayingLoop: + case Status::PlayingOnce: + this->setPlaybackStatus(Status::Paused); + case Status::Paused: + case Status::Stopped: + this->setPlaybackStatus(Status::PlayingOnce); + } + // ToDo: call the audio engine +} + +// from DMX signals void AudioLayerWidget::setVol(qreal vol) { - this->volumeChanged(vol); m_volumeSlider->blockSignals(true); m_volumeSlider->setValue(vol); - m_volumeIndicator->setText(QString::number(vol)); + m_volumeIndicator->setValue(vol); m_volumeSlider->blockSignals(false); } void AudioLayerWidget::setPan(qreal pan) { - this->panChanged(pan); m_panSlider->blockSignals(true); m_panSlider->setValue(pan); + m_panIndicator->setValue(pan); m_panSlider->blockSignals(false); } void AudioLayerWidget::setPitch(qreal pitch) { - this->pitchChanged(pitch); m_pitchSlider->blockSignals(true); m_pitchSlider->setValue(pitch); + m_pitchIndicator->setValue(pitch); m_pitchSlider->blockSignals(false); } -void AudioLayerWidget::loadMedia(QString file) -{ - ma_result result; - ma_format *format = 0; - ma_uint32 *channels = 0; - ma_uint32 *sampleRate = 0; - - - if (m_currentMedia.compare(file) == 0 ) { - return; - } - if (!QFile::exists(file)) { - qWarning("Can not access to file %s", file.toLatin1().constData()); - return; - } - ma_engine engine = AudioWidget::getInstance()->getEngine(); - if (currentSound.ownsDataSource == true) - { - ma_sound_uninit(¤tSound); - } - result = ma_sound_init_from_file(&engine, file.toLatin1(), 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, NULL, NULL, ¤tSound); - if (result != MA_SUCCESS) { - qWarning("WARNING: Failed to load sound %s", file.toLatin1().constData()); - return; - } - m_currentMedia = file; - float pLength = this->getDuration(); - result = ma_sound_get_data_format(¤tSound, format, channels, sampleRate, NULL, 0); - if (result != MA_SUCCESS) { - qWarning("WARNING: Failed to get data format %s", file.toLatin1().constData()); - return; - } - m_mediaLoaded = true; - durationChanged(pLength * 1000); - fileLoaded(file); - // Display music informations - std::cout << "File loaded: " << file.toLatin1().constData() << " : " << std::endl; - std::cout << " " << pLength << " seconds"; - std::cout << " -- " << sampleRate << " samples/sec"; - std::cout << " -- " << format << " format"; - std::cout << " -- " << channels << " channels" << std::endl; -} - -float AudioLayerWidget::getDuration() -{ - float pLength; - - if (m_mediaLoaded == false) - return 0; - ma_result result = ma_sound_get_length_in_seconds(¤tSound, &pLength); - if (result != MA_SUCCESS) { - qWarning("WARNING: Failed to get total duration %s", m_currentMedia.toLatin1().constData()); - return 0; - } - return pLength; -} - void AudioLayerWidget::fileLoaded(QString file) { QStringList list = file.split("/"); @@ -245,74 +195,40 @@ void AudioLayerWidget::fileLoaded(QString file) } } +void AudioLayerWidget::setPlaybackStatus(Status status) +{ + m_status = status; + if (status == Status::Stopped) + m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(0)); + QString tmp; + switch (status) { + case Status::Paused: + tmp.append("Paused"); + break; + case Status::PlayingLoop: + tmp.append("Playing Loop"); + break; + case Status::PlayingOnce: + tmp.append("Playing one"); + break; + case Status::Stopped: + tmp.append("Stopped"); + break; + } + m_statusValue->setText(tmp); + m_suspendResumeButton->setText(tmp); +} + void AudioLayerWidget::durationChanged(qint64 dur) { + dur *= 1000; m_progressSlider->setMaximum(dur); m_totalTimeValue->setTime(QTime::fromMSecsSinceStartOfDay(dur)); } -void AudioLayerWidget::play(bool loop) +void AudioLayerWidget::refreshUi(float progress) { - if (m_mediaLoaded == false) - return; - ma_sound_set_looping(¤tSound, loop); - ma_sound_start(¤tSound); - m_loopCheck->blockSignals(true); - m_loopCheck->setChecked(loop); - m_loopCheck->blockSignals(false); -} - -void AudioLayerWidget::pause() -{ - if (m_mediaLoaded == false) - return; - ma_sound_stop(¤tSound); -} - -void AudioLayerWidget::stop() -{ - if (m_mediaLoaded == false) - return; - ma_sound_stop(¤tSound); - ma_sound_seek_to_pcm_frame(¤tSound, 0); - m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(0)); - m_statusValue->setText(STOP_LABEL); - m_suspendResumeButton->setText(tr(STOP_LABEL)); - m_progressSlider->setValue(0); -} - -void AudioLayerWidget::refreshGUI() { - float progress; - - if (m_mediaLoaded == false) - return; - if (currentSound.ownsDataSource == 0) - return; - switch (ma_sound_is_playing(¤tSound)) { - case true: - ma_sound_get_cursor_in_seconds(¤tSound, &progress); - progress *= 1000; - m_progressSlider->setValue(progress); - m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(progress)); - m_statusValue->setText(PLAY_LABEL); - m_suspendResumeButton->setText(tr(SUSPEND_LABEL)); - break; - case false: - m_statusValue->setText(PAUSE_LABEL); - m_suspendResumeButton->setText(tr(RESUME_LABEL)); - break; - } -} - -void AudioLayerWidget::toggleSuspendResume() -{ - if (m_mediaLoaded == false) - return; - if (ma_sound_is_playing(¤tSound)) { - this->pause(); - } else { - if (ma_sound_at_end(¤tSound)) - ma_sound_seek_to_pcm_frame(¤tSound, 0); - ma_sound_start(¤tSound); - } + progress *= 1000; + m_progressSlider->setValue(progress); + m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(progress)); } diff --git a/src/audiolayerwidget.h b/src/audiolayerwidget.h index 7362bd6..e5270d5 100644 --- a/src/audiolayerwidget.h +++ b/src/audiolayerwidget.h @@ -4,9 +4,7 @@ #include #include -#include #include -#include #include #include #include @@ -16,117 +14,63 @@ #include #include #include +#include #include "defines.h" -#include "audiowidget.h" -#include "miniaudio.h" class AudioLayerWidget : public QGroupBox { Q_OBJECT public: - explicit AudioLayerWidget(QWidget *parent = 0, QString name = "Layer"); ~AudioLayerWidget(); - - /** - * @brief load a new media file - * @param file name with full path - */ - void loadMedia(QString file); - void play(bool loop); - void stop(); - void pause(); - - /** - * @brief set the Volume - * @param vol volume range 0 -100 - */ void setVol(qreal vol); void resume(); void setPan(qreal pan); void setPitch(qreal pitch); void setLoop(bool on); - //void setEntryPoint(qreal entry); - //void setEndPoint(qreal end); - -public slots: - - /** - * @brief connected with the button - */ - void toggleSuspendResume(); - - /** - * @brief Connected with the slider - * @param volume 0 -100 range - */ - void volumeChanged(int vol); - void panChanged(int vol); - void pitchChanged(int vol); - void loopChanged(int vol); + void setPlaybackStatus(Status status); + inline Status getPlaybackStatus() { return m_status; } private: - QPushButton *m_suspendResumeButton; QLabel *m_statusLabel; QLabel * m_statusValue; - - QLabel *m_volumeLabel; - QSlider *m_volumeSlider; - QLabel *m_volumeIndicator; - - QLabel *m_panLabel; - QSlider *m_panSlider; - - QLabel *m_pitchLabel; - QSlider *m_pitchSlider; - - QLabel *m_loopCheckLabel; - QCheckBox *m_loopCheck; - - QLabel * m_progressLabel; - QSlider *m_progressSlider; - - QLabel *m_progressTimeLabel; - QTimeEdit *m_progressTime; - - QLabel *m_totalTimeLabel; - QTimeEdit *m_totalTimeValue; - QLabel *m_fileLabel; QLabel *m_fileValue; - QLabel * m_folderLabel; QLabel * m_folderValue; - QTimer *m_refreshGUI; - QString m_currentMedia; - ma_sound currentSound; - ma_bool8 m_mediaLoaded; + QLabel *m_volumeLabel; + QSlider *m_volumeSlider; + QSpinBox *m_volumeIndicator; + QLabel *m_panLabel; + QSlider *m_panSlider; + QSpinBox *m_panIndicator; + QLabel *m_pitchLabel; + QSlider *m_pitchSlider; + QSpinBox *m_pitchIndicator; - float getDuration(); + QLabel * m_progressLabel; + QSlider *m_progressSlider; + QLabel *m_progressTimeLabel; + QTimeEdit *m_progressTime; + QLabel *m_totalTimeLabel; + QTimeEdit *m_totalTimeValue; -private slots: + Status m_status; - /** - * @brief Update the GUI elements with the name of the new file loaded - * @param file - */ +public slots: + void toggleSuspendResume(); + void volumeChanged(int vol); + void panChanged(int vol); + void pitchChanged(int vol); + void loopChanged(int vol); void fileLoaded(QString file); - - /** - * @brief Update the GUI elements with the duration of the new file loaded - * @param dur The duration of the track in miliseconds - */ void durationChanged(qint64 dur); - - /** - * @brief Update the variable elements in GUI - */ - void refreshGUI(); + void refreshUi(float progress); }; #endif // AUDIOLAYERWIDGET_H diff --git a/src/audiomasterwidget.h b/src/audiomasterwidget.h index bdac3e5..51218c8 100644 --- a/src/audiomasterwidget.h +++ b/src/audiomasterwidget.h @@ -8,14 +8,8 @@ #include #include -//#include "ui_audiomasterwidget.h" -/* -namespace Ui { -class AudioMasterWidget; -}*/ - -class AudioMasterWidget : public QGroupBox //, public Ui::AudioMasterWidget +class AudioMasterWidget : public QGroupBox { Q_OBJECT @@ -28,11 +22,6 @@ public slots: void updateWatchDMX(); private: - //QLabel *m_file; - //QLabel *m_folder; - //QSlider *m_vol; - //QCheckBox *m_mute; - //QLabel *m_status; QCheckBox *m_receiveDMX; QTimer *m_watchDMX; diff --git a/src/audiowidget.cpp b/src/audiowidget.cpp index f4cf44f..160182d 100644 --- a/src/audiowidget.cpp +++ b/src/audiowidget.cpp @@ -1,172 +1,91 @@ #include "audiowidget.h" -#include "miniaudio.c" -AudioWidget *AudioWidget::_instance = 0; - -AudioWidget *AudioWidget::getInstance() { - - if (_instance == 0) { - _instance = new AudioWidget(); - Q_CHECK_PTR(_instance); - } - return _instance; -} AudioWidget::AudioWidget() : - engineCount(0) + m_layout(new QHBoxLayout()) + , m_refreshUi(new QTimer(this)) { - this->startEngine(); - layout = new QHBoxLayout(); + for (int i= 0; i < Settings::getInstance()->getLayersNumber(); i++ ) { - layout->insertWidget(i, new AudioLayerWidget(this, tr("Layer %1").arg(i + 1))); + m_layout->insertWidget(i, new AudioLayerWidget(this, tr("Layer %1").arg(i + 1))); } - setLayout(layout); + setLayout(m_layout); + connect(m_refreshUi, SIGNAL(timeout()), this, SLOT(refreshUi())); + m_refreshUi->start(UI_REFRESH_TIME); } -void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +bool AudioWidget::startEngine(int id) { - (void)pInput; - //Do master audio processing before sending to device. - ma_engine_read_pcm_frames((ma_engine*)pDevice->pUserData, pOutput, frameCount, NULL); + return (m_mae.startEngine(id)); } -void AudioWidget::startEngine(int n) +bool AudioWidget::startEngine() { - this->startContext(); - this->startDevice(n); -} - -void AudioWidget::startDevice(int id) -{ - ma_result result; - ma_device_config deviceConfig; - ma_engine_config engineConfig; - - deviceConfig = ma_device_config_init(ma_device_type_playback); - deviceConfig.playback.pDeviceID = &pPlaybackDeviceInfos[id].id; - deviceConfig.playback.format = resourceManager.config.decodedFormat; - deviceConfig.playback.channels = 0; - deviceConfig.sampleRate = resourceManager.config.decodedSampleRate; - deviceConfig.dataCallback = data_callback; - deviceConfig.pUserData = &engines[engineCount]; - result = ma_device_init(&context, &deviceConfig, &devices[engineCount]); - if (result != MA_SUCCESS) { - qCritical("Failed to initialize audio device %s.", pPlaybackDeviceInfos[id].name); - return; - } - engineConfig = ma_engine_config_init(); - engineConfig.pDevice = &devices[engineCount]; - engineConfig.pResourceManager = &resourceManager; - engineConfig.noAutoStart = MA_TRUE; - result = ma_engine_init(NULL, &engines[engineCount]); - if (result != MA_SUCCESS) { - qCritical("Failed to initialize audio engine."); - return; - } - result = ma_engine_start(&engines[engineCount]); - if (result != MA_SUCCESS) { - qCritical("Failed to start audio engine %d.", engineCount); - } - //engineCount +=1; - iChosenDevice = id; - qInfo("Initialized audio device %d: %s", id, pPlaybackDeviceInfos[id].name); -} - -void AudioWidget::startContext() -{ - ma_result result; - - 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; - result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager); - if (result != MA_SUCCESS) { - qCritical("Failed to initialize audio resource manager."); - return; - } - result = ma_context_init(NULL, 0, NULL, &context); - if (result != MA_SUCCESS) { - qCritical("Failed to initialize audio context."); - return; - } -} - -void AudioWidget::startEngine() -{ - this->startContext(); - this->getAllAudioDevices(); - iChosenDevice = Settings::getInstance()->getAudioDeviceId(); - this->startDevice(iChosenDevice); -} - -ma_engine AudioWidget::getEngine() -{ - return(engines[engineCount]); + return (m_mae.startEngine(Settings::getInstance()->getAudioDeviceId())); } void AudioWidget::stopEngine() { - ma_engine_uninit(&engines[engineCount]); - ma_device_uninit(&devices[engineCount]); - ma_context_uninit(&context); - ma_resource_manager_uninit(&resourceManager); + m_mae.stopEngine(); } -void AudioWidget::mediaLoaded(int layer, QString media) +void AudioWidget::mediaLoaded(int layer, QString file) { - QLayoutItem * const item = layout->itemAt(layer); - dynamic_cast(item->widget())->loadMedia(media); + 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); } void AudioWidget::volChanged(int layer, qreal vol) { - QLayoutItem * const item = layout->itemAt(layer); + m_mae.volChanged(layer, vol); + QLayoutItem * const item = m_layout->itemAt(layer); dynamic_cast(item->widget())->setVol(vol); } -void AudioWidget::panChanged(int layer, qreal vol) { - QLayoutItem * const item = layout->itemAt(layer); - dynamic_cast(item->widget())->setPan(vol); +void AudioWidget::panChanged(int layer, qreal pan) { + m_mae.panChanged(layer, pan); + QLayoutItem * const item = m_layout->itemAt(layer); + dynamic_cast(item->widget())->setPan(pan); } -void AudioWidget::pitchChanged(int layer, qreal vol) { - QLayoutItem * const item = layout->itemAt(layer); - dynamic_cast(item->widget())->setPitch(vol); +void AudioWidget::pitchChanged(int layer, qreal pitch) { + m_mae.pitchChanged(layer, pitch); + QLayoutItem * const item = m_layout->itemAt(layer); + dynamic_cast(item->widget())->setPitch(pitch); } void AudioWidget::playbackChanged(int layer, Status status) { - QLayoutItem * const item = layout->itemAt(layer); - switch (status) { - case PlayingOnce: - dynamic_cast(item->widget())->play(false); - break; - case Stopped: - dynamic_cast(item->widget())->stop(); - break; - case Paused: - dynamic_cast(item->widget())->pause(); - break; - case PlayingLoop: - dynamic_cast(item->widget())->play(true); - break; - } + m_mae.playbackChanged(layer, status); + QLayoutItem * const item = m_layout->itemAt(layer); + dynamic_cast(item->widget())->setPlaybackStatus(status); } -// enum all audio devices in system -void AudioWidget::getAllAudioDevices() -{ - ma_result result; - - result = ma_context_get_devices(&context, &pPlaybackDeviceInfos, &playbackDeviceCount, NULL, NULL); - if (result != MA_SUCCESS) { - qCritical("Failed to enumerate playback devices."); - ma_context_uninit(&context); - return; +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)); + } } - for (ma_uint32 iAvailableDevice = 0; iAvailableDevice < playbackDeviceCount; iAvailableDevice += 1) { - qInfo("%d: : %s", iAvailableDevice, pPlaybackDeviceInfos[iAvailableDevice].name); - } - return ; } diff --git a/src/audiowidget.h b/src/audiowidget.h index bf3c7c9..b35ecbd 100644 --- a/src/audiowidget.h +++ b/src/audiowidget.h @@ -9,7 +9,7 @@ #include "audiomasterwidget.h" #include "audiolayerwidget.h" #include "settings.h" -#include "miniaudio.h" +#include "miniaudioengine.h" #include "defines.h" @@ -20,43 +20,26 @@ class AudioWidget : public QWidget Q_OBJECT public: - - static AudioWidget *getInstance(); - ma_engine getEngine(); + AudioWidget(); + bool startEngine(); + bool startEngine(int id); void stopEngine(); - void startEngine(int id); - ma_device_info* pPlaybackDeviceInfos; - ma_uint32 playbackDeviceCount; - ma_uint32 iChosenDevice; protected: - void mediaLoaded(int layer, QString media ); void volChanged(int layer, qreal vol); void panChanged(int layer, qreal pan); void pitchChanged(int layer, qreal pitch); void playbackChanged(int layer, Status status); - void startEngine(); private: + MiniAudioEngine m_mae; + QString m_currentMedia[MAX_LAYERS]; + QHBoxLayout *m_layout; + QTimer *m_refreshUi; - static AudioWidget *_instance; - AudioWidget(); - QHBoxLayout *layout; - ma_engine engines[MAX_DEVICES]; - ma_uint32 engineCount; - ma_device devices[MAX_DEVICES]; - ma_context context; - ma_resource_manager_config resourceManagerConfig; - ma_resource_manager resourceManager; - void getAllAudioDevices(); - void startDevice(int id); - void startContext(); - -signals: - -public slots: - +private slots: + void refreshUi(); }; #endif // AUDIOWIDGET_H diff --git a/src/defines.h b/src/defines.h index bc57975..b34cf0c 100644 --- a/src/defines.h +++ b/src/defines.h @@ -1,40 +1,40 @@ #ifndef DEFINES_H #define DEFINES_H +#include +#include +#include + #define VERSION "LibreMediaServer-Audio 0.1.4" #define COPYRIGHT "(C) 2014-2024 Santi Norena lms@criptomart.net" #define LICENSE "GPL 3 License. See LICENSE.txt and credits.txt for details" - #define LAYERS_NUMBER 4 // esto tiene que desaparecer - #define DEFAULT_FILE "lms-audio.xlm" - -#define SUSPEND_LABEL "Pause playback" -#define RESUME_LABEL "Resume playback" - -#define PLAY_LABEL "Playing" -#define STOP_LABEL "Stopped" +#define SUSPEND_LABEL "Pause" +#define RESUME_LABEL "Resume" +#define PLAY_LABEL "Play" +#define STOP_LABEL "Stop" #define PAUSE_LABEL "Pause" #define IDDLE_LABEL "Iddle playback" - #define VOLUME_LABEL "Volume" #define PROGRESS_LABEL "Progress" #define PROGRESS_TIME_LABEL "Current" -#define REMAINING_TIME "Remaining Time: " +#define REMAINING_TIME "Remaining" #define TOTAL_TIME_LABEL "Total" -#define FILE_LABEL "File:" -#define FOLDER_LABEL "Folder:" +#define FILE_LABEL "File: " +#define FOLDER_LABEL "Folder: " #define STATUS_LABEL "Status: " - #define NOTIFY_INTERVAL 150 #define PULL_TIMER_INTERVAL 10 #define MAX_DEVICES 16 #define MAX_SOUNDS 4096 +#define MAX_LAYERS 16 +#define UI_REFRESH_TIME 100 // struct where save the DMX settings for each layer struct dmxSetting { int address; - uint universe; + quint8 universe; bool updated; int layer; }; diff --git a/src/libremediaserver-audio.cpp b/src/libremediaserver-audio.cpp index 0c223bc..a458db6 100644 --- a/src/libremediaserver-audio.cpp +++ b/src/libremediaserver-audio.cpp @@ -30,6 +30,7 @@ libreMediaServerAudio::libreMediaServerAudio(QStringList args, QWidget *parent) Settings *set = Settings::getInstance(); set->readFile(); + connect(set, SIGNAL(audioDeviceChanged(int)), this, SLOT(audioDeviceChanged(int))); /* if (args.contains("-log")) { // Inicia el widget Terminal @@ -57,7 +58,8 @@ libreMediaServerAudio::libreMediaServerAudio(QStringList args, QWidget *parent) // start audio engine MediaLibrary::getInstance()->initMediaLibrary(); - setCentralWidget(AudioWidget::getInstance()); + aw = new AudioWidget; + setCentralWidget(aw); amw = new AudioMasterWidget(this); QDockWidget *topWidget = new QDockWidget(tr("Master"), this); topWidget->setAllowedAreas(Qt::TopDockWidgetArea); @@ -66,20 +68,20 @@ libreMediaServerAudio::libreMediaServerAudio(QStringList args, QWidget *parent) // ola setup ola = new olaThread(); Q_CHECK_PTR(ola); - connect(set, SIGNAL(registerUniverse(int)), ola, SLOT(registerUniverse(int))); - ola->registerUniverse(); // register now all the universes ola->blockSignals(true); + connect(set, SIGNAL(registerUniverse(int)), ola, SLOT(registerUniverse(int))); connect(ola, SIGNAL (layerReceived()), amw, SLOT(updateWatchDMX())); connect(ola, SIGNAL(dmxOutput(int, int, int)), this, SLOT(dmxInput(int, int, int))); connect(ui.actionLaunch_OLA_Setup, SIGNAL(triggered()), this, SLOT(olasetup())); + ola->registerUniverse(); ola->start(QThread::TimeCriticalPriority ); - ola->blockSignals(false); // menus connect(ui.actionOpen_conf, SIGNAL(triggered()), this, SLOT(openFile())); connect(ui.actionSave_conf, SIGNAL(triggered()), this, SLOT(saveFile())); connect(ui.action_Settings, SIGNAL(triggered()), this, SLOT(settings())); - connect(set, SIGNAL(audioDeviceChanged(int)), this, SLOT(audioDeviceChanged(int))); - qDebug("Init Complete"); + aw->startEngine(); + qDebug("Init Complete."); + ola->blockSignals(false); } /////////////////////////////////////////////////////////////////// @@ -89,7 +91,7 @@ libreMediaServerAudio::libreMediaServerAudio(QStringList args, QWidget *parent) libreMediaServerAudio::~libreMediaServerAudio() { ola->stop(); - AudioWidget::getInstance()->stopEngine(); + aw->stopEngine(); } /////////////////////////////////////////////////////////////////// @@ -151,27 +153,27 @@ void libreMediaServerAudio::dmxInput(int layer, int channel, int value) aux = ola->getValue(layer, DMX_FILE); mediaFile = MediaLibrary::getInstance()->requestNewFile(value, aux); if (QFile::exists(mediaFile)) - AudioWidget::getInstance()->mediaLoaded(layer, mediaFile); + aw->mediaLoaded(layer, mediaFile); break; case DMX_FILE:// File aux = ola->getValue(layer, DMX_FOLDER); mediaFile = MediaLibrary::getInstance()->requestNewFile(aux, value); if (QFile::exists(mediaFile)) - AudioWidget::getInstance()->mediaLoaded(layer, mediaFile); + aw->mediaLoaded(layer, mediaFile); break; case VOLUME_COARSE: f = ( value * 0x100 ) + ola->getValue(layer, VOLUME_FINE); - AudioWidget::getInstance()->volChanged(layer, (f / 655.35)); + aw->volChanged(layer, (f / 655.35)); break; case VOLUME_FINE: f = ( ola->getValue(layer, VOLUME_COARSE) * 0x100 ) + value; - AudioWidget::getInstance()->volChanged(layer, (f / 655.35)); + aw->volChanged(layer, (f / 655.35)); break; case PAN: - AudioWidget::getInstance()->panChanged(layer, value); + aw->panChanged(layer, value); break; case PITCH: - AudioWidget::getInstance()->pitchChanged(layer, value); + aw->pitchChanged(layer, value); break; case PLAYBACK: if (value == 0) @@ -179,16 +181,16 @@ void libreMediaServerAudio::dmxInput(int layer, int channel, int value) aux = value / 25; switch (aux) { case 0 : - AudioWidget::getInstance()->playbackChanged(layer, PlayingOnce); + aw->playbackChanged(layer, PlayingOnce); break; case 1 : - AudioWidget::getInstance()->playbackChanged(layer, Stopped); + aw->playbackChanged(layer, Stopped); break; case 2 : - AudioWidget::getInstance()->playbackChanged(layer, Paused); + aw->playbackChanged(layer, Paused); break; case 3 : - AudioWidget::getInstance()->playbackChanged(layer, PlayingLoop); + aw->playbackChanged(layer, PlayingLoop); break; } default: @@ -198,6 +200,6 @@ void libreMediaServerAudio::dmxInput(int layer, int channel, int value) void libreMediaServerAudio::audioDeviceChanged(int id) { - AudioWidget::getInstance()->stopEngine(); - AudioWidget::getInstance()->startEngine(id); + aw->stopEngine(); + aw->startEngine(id); } diff --git a/src/libremediaserver-audio.h b/src/libremediaserver-audio.h index ea88afe..751ffcd 100644 --- a/src/libremediaserver-audio.h +++ b/src/libremediaserver-audio.h @@ -20,6 +20,7 @@ #ifndef LIBREMEDIASERVERAUDIO_H #define LIBREMEDIASERVERAUDIO_H + #include #include #include @@ -29,7 +30,9 @@ #include #include #include -#include +#include +#include +#include #include "audiowidget.h" #include "medialibrary.h" @@ -48,58 +51,29 @@ class libreMediaServerAudio : public QMainWindow Q_OBJECT public: - libreMediaServerAudio (QStringList args, QWidget *parent = 0); virtual ~libreMediaServerAudio(); - Ui::LibreMediaServerAudio ui; -// static QTextEdit *textEdit; // Terminal de feedback - -protected: - private: - // void MessageHandler(QtMsgType type, const QMessageLogContext &logcontext, const QString &msg); - AudioMasterWidget *amw; + AudioWidget *aw; + AudioMasterWidget *amw; + olaThread *ola; - olaThread *ola; - - void open_start(); - void save_finish(); - void open(QFile *file); - void save(QFile *file); + void open_start(); + void save_finish(); + void open(QFile *file); + void save(QFile *file); public slots: void audioDeviceChanged(int id); private slots: - - /** - * @brief Shows the OLA web setup page - */ void olasetup(); - - /** - * @brief Parser for new dmx data arriving - * @param layer - * @param channel - * @param value - */ void dmxInput(int layer, int channel, int value); - - // Menu File - /** - * @brief REad from dis a configuration file - */ void openFile(); - /** - * @brief Write to disk a configuration file - */ void saveFile(); - /** - * @brief OPen the settings dialog - */ void settings(); }; diff --git a/src/miniaudioengine.cpp b/src/miniaudioengine.cpp new file mode 100644 index 0000000..ed010a6 --- /dev/null +++ b/src/miniaudioengine.cpp @@ -0,0 +1,238 @@ +#include "miniaudioengine.h" + +MiniAudioEngine::MiniAudioEngine() +{ + +} + +void MiniAudioEngine::audioDataCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + (void)pInput; + //Do master audio processing before sending to device. + ma_engine_read_pcm_frames((ma_engine*)pDevice->pUserData, pOutput, frameCount, NULL); +} + +void MiniAudioEngine::stopEngine() +{ + ma_engine_uninit(&engine); + ma_device_uninit(&device); + ma_context_uninit(&context); + ma_resource_manager_uninit(&resourceManager); +} + +bool MiniAudioEngine::startEngine(int n) +{ + ma_result result; + + result = this->startContext(); + if (result != MA_SUCCESS) { + return result; + } + this->getAllAudioDevices(); + result = this->startDevice(n); + return result; +} + +ma_result MiniAudioEngine::startDevice(int id) +{ + ma_result result; + ma_device_config deviceConfig; + ma_engine_config engineConfig; + + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.pDeviceID = &pPlaybackDeviceInfos[id].id; + deviceConfig.playback.format = resourceManager.config.decodedFormat; + deviceConfig.playback.channels = 0; + deviceConfig.sampleRate = resourceManager.config.decodedSampleRate; + deviceConfig.dataCallback = audioDataCallback; + deviceConfig.pUserData = &engine; + result = ma_device_init(&context, &deviceConfig, &device); + if (result != MA_SUCCESS) { + printf("Failed to initialize audio device %s.", pPlaybackDeviceInfos[id].name); + return result; + } + engineConfig = ma_engine_config_init(); + engineConfig.pDevice = &device; + engineConfig.pResourceManager = &resourceManager; + engineConfig.noAutoStart = MA_TRUE; + result = ma_engine_init(NULL, &engine); + if (result != MA_SUCCESS) { + printf("Failed to initialize audio engine."); + return result; + } + result = ma_engine_start(&engine); + if (result != MA_SUCCESS) { + printf("Failed to start audio engine %i.", id); + return result; + } + iChosenDevice = id; + printf("Initialized audio device %d: %s", id, pPlaybackDeviceInfos[id].name); + return result; +} + +ma_result MiniAudioEngine::startContext() +{ + ma_result result; + + 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; + result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager); + if (result != MA_SUCCESS) { + printf("Failed to initialize audio resource manager."); + return result; + } + result = ma_context_init(NULL, 0, NULL, &context); + if (result != MA_SUCCESS) { + printf("Failed to initialize audio context."); + } + return result; +} + +// enum all audio devices in system +ma_result MiniAudioEngine::getAllAudioDevices() +{ + ma_result result; + + result = ma_context_get_devices(&context, &pPlaybackDeviceInfos, &playbackDeviceCount, NULL, NULL); + if (result != MA_SUCCESS) { + printf("Failed to enumerate playback devices.\n"); + ma_context_uninit(&context); + return result; + } + printf("Audio devices detected in system:\n"); + for (ma_uint32 iAvailableDevice = 0; iAvailableDevice < playbackDeviceCount; iAvailableDevice += 1) { + qInfo("%d: : %s", iAvailableDevice, pPlaybackDeviceInfos[iAvailableDevice].name); + } + return result; +} + +ma_result MiniAudioEngine::loadMedia(int layer, char *file) +{ + ma_result result; + + 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 \*/ + , 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); + } + return result; +} + +float MiniAudioEngine::getDuration(int layer) +{ + ma_result result; + float ret = 0; + + 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; + } + return ret; +} + +float MiniAudioEngine::getCursor(int layer) +{ + ma_result result; + float ret = 0; + + if (m_mediaLoaded[layer] == false) + return MA_DOES_NOT_EXIST; + result = ma_sound_get_cursor_in_seconds(&m_currentSound[layer], &ret); + if (result != MA_SUCCESS) + { + qWarning("Can not get cursor %i", layer); + ret = 0; + } + return ret; +} + +ma_result MiniAudioEngine::printFormatInfo(int layer) +{ + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + + 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); + return result; +} + +// Expects between 0 and 100 vol value in db +void MiniAudioEngine::volChanged(int layer, float vol) +{ + float result; + + if (m_mediaLoaded[layer] == false) + return; + result = ma_volume_linear_to_db(1.00000000 + (vol / 800.0)); + ma_sound_group_set_volume(&m_currentSound[layer], result); +} + +void MiniAudioEngine::panChanged(int layer, float value) +{ + float result; + + if (m_mediaLoaded[layer] == false) + return; + result = (value / 128.0) - 1.0; + ma_sound_group_set_pan(&m_currentSound[layer], result); +} + +void MiniAudioEngine::pitchChanged(int layer, float value) +{ + float result; + + if (m_mediaLoaded[layer] == false) + return; + result = value / 128.0; + ma_sound_group_set_pitch(&m_currentSound[layer], result); +} + +void MiniAudioEngine::playbackChanged(int layer, Status status) +{ + if (m_mediaLoaded[layer] == false) + return; + switch (status) { + case Status::Paused: + ma_sound_stop(&m_currentSound[layer]); + break; + case Status::Stopped: + ma_sound_stop(&m_currentSound[layer]); + ma_sound_seek_to_pcm_frame(&m_currentSound[layer], 0); + break; + case Status::PlayingLoop: + ma_sound_set_looping(&m_currentSound[layer], true); + ma_sound_start(&m_currentSound[layer]); + break; + case Status::PlayingOnce: + ma_sound_set_looping(&m_currentSound[layer], false); + ma_sound_start(&m_currentSound[layer]); + break; + } +} diff --git a/src/miniaudioengine.h b/src/miniaudioengine.h new file mode 100644 index 0000000..8cda15b --- /dev/null +++ b/src/miniaudioengine.h @@ -0,0 +1,48 @@ +#ifndef MINIAUDIOENGINE_H +#define MINIAUDIOENGINE_H + +#define MINIAUDIO_IMPLEMENTATION +#include "miniaudio.h" + +#include "defines.h" //MAX_LAYERS +#include // for printf + +class MiniAudioEngine +{ + friend class AudioWidget; + +public: + MiniAudioEngine(); + void stopEngine(); + bool startEngine(int id); + static void audioDataCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); + +protected: + // slots for DMX input + ma_result loadMedia(int layer, char *media ); + void volChanged(int layer, float vol); + void panChanged(int layer, float pan); + void pitchChanged(int layer, float pitch); + void playbackChanged(int layer, Status status); + float getDuration(int layer); + float getCursor(int layer); + ma_result printFormatInfo(int layer); + +private: + ma_resource_manager_config resourceManagerConfig; + ma_resource_manager resourceManager; + ma_device_info* pPlaybackDeviceInfos; + ma_uint32 playbackDeviceCount; + ma_uint32 iChosenDevice; + ma_engine engine; + ma_device device; + ma_context context; + ma_sound m_currentSound[MAX_LAYERS]; + ma_bool8 m_mediaLoaded[MAX_LAYERS]; + + ma_result getAllAudioDevices(); + ma_result startDevice(int id); + ma_result startContext(); +}; + +#endif // MINIAUDIOENGINE_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index e5b8de7..cbe19a5 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -53,7 +53,7 @@ void SettingsDialog::changeAudioDevice() { m_deviceDialog = new QDialog( this ); } - QLabel *msgLabel = new QLabel; +/* QLabel *msgLabel = new QLabel; AudioWidget *aw = AudioWidget::getInstance(); QString *msg = new QString; for (uint iAvailableDevice = 0; iAvailableDevice < aw->playbackDeviceCount; iAvailableDevice += 1) { @@ -72,7 +72,7 @@ void SettingsDialog::changeAudioDevice() layout->addWidget(closeButton); m_deviceDialog->setLayout(layout); m_deviceDialog->setModal(true); - m_deviceDialog->show(); + m_deviceDialog->show();*/ } void SettingsDialog::closeAudioDeviceDialog()