From 53bcb38455cb9aae86219910cd8b4b7505ebf3b2 Mon Sep 17 00:00:00 2001 From: snt Date: Wed, 22 May 2024 20:52:13 +0200 Subject: [PATCH] =?UTF-8?q?vumeter=20funcionando,=20hay=20que=20comparar?= =?UTF-8?q?=20la=20salida=20con=20un=20vumeter=20que=20sepa=20que=20funcio?= =?UTF-8?q?na=20bien=20y=20definir=20los=20par=C3=A1metros=20de=20ventanas?= =?UTF-8?q?,=20picos=20y=20dem=C3=A1s.=20Se=20insertan=20en=20la=20cadena?= =?UTF-8?q?=20de=20audio=20porque=20no=20veo=20la=20forma=20de=20hacerlo?= =?UTF-8?q?=20en=20paralelo=20https://github.com/mackron/miniaudio/issues/?= =?UTF-8?q?850?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog.txt | 1 + docs/roadmap.txt | 3 -- src/audiolayerwidget.cpp | 13 ++++++ src/audiolayerwidget.h | 2 + src/audiowidget.cpp | 21 ++++++--- src/audiowidget.h | 1 + src/defines.h | 1 + src/libremediaserver-audio.cpp | 1 + src/ma_writer_node.c | 79 ++++++++++++++++++++++++++++++++-- src/ma_writer_node.h | 31 +++++++++++++ src/miniaudioengine.cpp | 25 +++++++---- src/miniaudioengine.h | 10 +++-- 12 files changed, 165 insertions(+), 23 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index ef76b53..9a2f61b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -30,6 +30,7 @@ v 0.2.0 Antígona (26/05/2024) - Play all medias in one folder consecutevily. - Play all medias in one folder randomly. + Multi audio devices output. ++ Vumeter for each layer v 0.1.3 Leúcade (19/04/2024) + Ubuntu 22.04 jammy. diff --git a/docs/roadmap.txt b/docs/roadmap.txt index 9e5ec0c..bc06163 100644 --- a/docs/roadmap.txt +++ b/docs/roadmap.txt @@ -49,7 +49,4 @@ v 0.2.1 - ampliar writer para recibir un número n de entradas y escribirlas cada una en un buffer v0.2.0: -- Vumeter or indicator about audio output in layer and master, add to sliderGroup. - --> en master se puede hacer con jack, solo en capas. - --> en las capas, hace falta otro nodo atacado en los buses de input que analice la entrada. - mostrad información de envíos y dispositivos en ui diff --git a/src/audiolayerwidget.cpp b/src/audiolayerwidget.cpp index f37b67d..646cb96 100644 --- a/src/audiolayerwidget.cpp +++ b/src/audiolayerwidget.cpp @@ -91,6 +91,9 @@ AudioLayerWidget::AudioLayerWidget(QWidget *parent, int layer): volumeBox->setSpacing(0); volumeBox->setContentsMargins(0, 0, 0, 0); layout->addLayout(volumeBox); + m_level = new QLabel(); + m_level->setText("0"); + layout->addWidget(m_level); layout->setAlignment(Qt::AlignHCenter); layout->setSpacing(0); layout->setContentsMargins(1, 1, 1, 1); @@ -248,3 +251,13 @@ void AudioLayerWidget::setFilterParam(int channel, int value) m_bus2->blockSignals(true); } } + +void AudioLayerWidget::setLevel(float db) +{ + m_level->blockSignals(true); + if (db > -200) + m_level->setText(QString::number(db)); + else + m_level->setText("-inf"); + m_level->blockSignals(false); +} diff --git a/src/audiolayerwidget.h b/src/audiolayerwidget.h index cb446a9..0f9517c 100644 --- a/src/audiolayerwidget.h +++ b/src/audiolayerwidget.h @@ -28,6 +28,7 @@ public: void setPan(int pan); void setPitch(int pitch); void setFilterParam(int channel, int value); + void setLevel(float db); private: Status m_status; @@ -36,6 +37,7 @@ private: QPushButton *m_suspendResumeButton; ClickableLabel *m_fileValue; ClickableLabel * m_folderValue; + QLabel *m_level; SliderGroup *m_volume; SliderGroup *m_pan; SliderGroup *m_pitch; diff --git a/src/audiowidget.cpp b/src/audiowidget.cpp index da7c33f..98b3a32 100644 --- a/src/audiowidget.cpp +++ b/src/audiowidget.cpp @@ -13,12 +13,13 @@ AudioWidget::AudioWidget(QWidget *parent) : 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].duration = -1; 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_layerUpdate[i].vol = -1; + m_layerUpdate[i].pan = -1; + m_layerUpdate[i].pitch = -1; + m_layerUpdate[i].cursor = -1; + m_layerUpdate[i].level = 100; for (int j = 0; j < FILTER_CHANNELS; j++) m_filtersUpdate[i][j] = -1; } @@ -102,6 +103,10 @@ void AudioWidget::refreshUi() alw->setFilterParam(j, m_filtersUpdate[i][j]); m_filtersUpdate[i][j] = -1; } + if (m_layerUpdate[i].level < 100) { + alw->setLevel(m_layerUpdate[i].level); + m_layerUpdate[i].level = 100; + } m_layerUpdate[i].updated = false; } } @@ -112,3 +117,9 @@ void AudioWidget::filterParamChanged(int layer, int channel, int value) m_filtersUpdate[layer][channel - HP_FREQ] = value; m_layerUpdate[layer].updated = true; } + +void AudioWidget::levelChanged(int layer, float db) +{ + m_layerUpdate[layer].level = db; + m_layerUpdate[layer].updated = true; +} diff --git a/src/audiowidget.h b/src/audiowidget.h index 6849f5e..ddb5767 100644 --- a/src/audiowidget.h +++ b/src/audiowidget.h @@ -15,6 +15,7 @@ class AudioWidget : public QWidget public: AudioWidget(QWidget *parent = nullptr); void filterParamChanged(int layer, int channel, int value); + void levelChanged(int layer, float db); private: QHBoxLayout *m_layout; diff --git a/src/defines.h b/src/defines.h index 8d81a59..6751fd0 100644 --- a/src/defines.h +++ b/src/defines.h @@ -75,6 +75,7 @@ struct layerData { int device; int bus1Vol; int bus2Vol; + float level; }; #endif // __cplusplus #endif // DEFINES_H diff --git a/src/libremediaserver-audio.cpp b/src/libremediaserver-audio.cpp index 07e3b0e..56b016a 100644 --- a/src/libremediaserver-audio.cpp +++ b/src/libremediaserver-audio.cpp @@ -170,6 +170,7 @@ void libreMediaServerAudio::refreshUi() { m_lmsUi->m_aw->cursorChanged(i, m_mae.getCursor(i)); m_updateUi[i][3] = -1; } + m_lmsUi->m_aw->levelChanged(i, m_mae.getLevel(i)); if (m_mae.getAtEnd(i)) { if (m_currentStatus[i] == Status::PlayingOnce) { m_currentStatus[i] = Status::Stopped; diff --git a/src/ma_writer_node.c b/src/ma_writer_node.c index 82e057c..2845649 100644 --- a/src/ma_writer_node.c +++ b/src/ma_writer_node.c @@ -46,7 +46,7 @@ MA_API ma_result ma_writer_node_init(ma_node_graph* pNodeGraph, const ma_writer_ { ma_result result; ma_node_config baseConfig; - ma_uint32 inputChannels[2]; // Equal in size to the number of input channels specified in the vtable. + ma_uint32 inputChannels[2]; ma_uint32 outputChannels[1]; if (pWriteNode == NULL || pConfig == NULL || pConfig->pBuffer == NULL \ @@ -82,14 +82,12 @@ MA_API void ma_writer_node_uninit(ma_writer_node* pWriteNode, const ma_allocatio * Data Source Ring Buffer */ - ma_result ma_data_source_rb_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { ma_data_source_rb* ds = (ma_data_source_rb*)pDataSource; ma_uint32 pcmFramesAvailableInRB = 0; ma_uint32 pcmFramesProcessed = 0; - // lo mismo que en el callback, va el doble de rápido y con glitches. while (pcmFramesProcessed < frameCount) { pcmFramesAvailableInRB = ma_pcm_rb_available_read(ds->rb); if (pcmFramesAvailableInRB == 0) { @@ -175,3 +173,78 @@ void ma_data_source_rb_uninit(ma_data_source_rb* pMyDataSource) { ma_data_source_uninit(&pMyDataSource->base); } + +/* + * vumeter + */ + +MA_API ma_vumeter_node_config ma_vumeter_node_config_init(ma_uint32 channels, ma_uint32 sampleRate) +{ + ma_vumeter_node_config config; + + MA_ZERO_OBJECT(&config); + config.nodeConfig = ma_node_config_init(); + config.channels = channels; + config.sampleRate = sampleRate; + + return config; +} + +static void ma_vumeter_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_vumeter_node* pVumeterNode = (ma_vumeter_node*)pNode; + + MA_ASSERT(pVumeterNode != NULL); + MA_ASSERT(ma_node_get_input_bus_count(&pVumeterNode->baseNode) == 1); + + for (uint i = 0; i < *pFrameCountIn; i++) { + float input = fabsf(ppFramesIn[0][i]); + pVumeterNode->level += pVumeterNode->alpha * (input - pVumeterNode->level); + } + ma_copy_pcm_frames(ppFramesOut[0], ppFramesIn[0], *pFrameCountOut, ma_format_f32, pVumeterNode->channels); +} + +static ma_node_vtable g_ma_vumeter_node_vtable = +{ + ma_vumeter_node_process_pcm_frames, + NULL, + 1, + 1, + 0 +}; + +MA_API ma_result ma_vumeter_node_init(ma_node_graph* pNodeGraph, const ma_vumeter_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_vumeter_node* pVumeterNode) +{ + ma_result result; + ma_node_config baseConfig; + ma_uint32 inputChannels[1]; + ma_uint32 outputChannels[1]; + + if (pVumeterNode == NULL || pConfig == NULL \ + || (pConfig->channels > MA_MAX_NODE_BUS_COUNT) ) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pVumeterNode); + inputChannels[0] = pConfig->channels; + outputChannels[0] = pConfig->channels; + baseConfig = pConfig->nodeConfig; + baseConfig.vtable = &g_ma_vumeter_node_vtable; + baseConfig.pInputChannels = inputChannels; + baseConfig.pOutputChannels = outputChannels; + + result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pVumeterNode->baseNode); + if (result != MA_SUCCESS) { return result; + } + pVumeterNode->sampleRate = pConfig->sampleRate; + pVumeterNode->channels = pConfig->channels; + pVumeterNode->level = 0; + pVumeterNode->TC = 10000.0f; + pVumeterNode->alpha = 1.0 - expf( (-2.0 * M_PI) / (pVumeterNode->TC * pConfig->sampleRate)); + return MA_SUCCESS; +} + +MA_API void ma_vumeter_node_uninit(ma_vumeter_node* pVumeterNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_node_uninit(&pVumeterNode->baseNode, pAllocationCallbacks); +} diff --git a/src/ma_writer_node.h b/src/ma_writer_node.h index 1b5d259..ac592c7 100644 --- a/src/ma_writer_node.h +++ b/src/ma_writer_node.h @@ -8,6 +8,10 @@ extern "C" { #include "miniaudio.h" +/* + * writer + */ + typedef struct { ma_node_config nodeConfig; @@ -46,6 +50,33 @@ ma_result ma_data_source_rb_get_length(ma_data_source* pDataSource, ma_uint64* p ma_result ma_data_source_rb_init(ma_data_source_rb* pMyDataSource, ma_pcm_rb *ringBuffer); void ma_data_source_rb_uninit(ma_data_source_rb* pMyDataSource); + +/* + * VU meter + */ +typedef struct +{ + ma_node_config nodeConfig; + ma_uint32 channels; + ma_uint32 sampleRate; +} ma_vumeter_node_config; + +MA_API ma_vumeter_node_config ma_vumeter_node_config_init(ma_uint32 channels, ma_uint32 sampleRate); + +typedef struct +{ + ma_node_base baseNode; + ma_uint32 channels; + ma_uint32 sampleRate; + float level; + float TC; + float alpha; +} ma_vumeter_node; + +MA_API ma_result ma_vumeter_node_init(ma_node_graph* pNodeGraph, const ma_vumeter_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_vumeter_node* pVumeterNode); +MA_API void ma_vumeter_node_uninit(ma_vumeter_node* pVumeterNode, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API inline float ma_vumeter_node_get_level(ma_vumeter_node* pVumeterNode) { return 5 * pVumeterNode->level; }; + #ifdef __cplusplus } #endif diff --git a/src/miniaudioengine.cpp b/src/miniaudioengine.cpp index 328e738..3ea5c1b 100644 --- a/src/miniaudioengine.cpp +++ b/src/miniaudioengine.cpp @@ -24,7 +24,7 @@ void MiniAudioEngine::stopEngine() ma_sound_uninit(&m_mae.sounds[i]); } for (uint i = 0; i < m_mae.layersQty; i++) { - ma_node_uninit(&m_mae.filters[i].input, NULL); + ma_splitter_node_uninit(&m_mae.filters[i].input, NULL); ma_hpf_node_uninit(&m_mae.filters[i].hpf, NULL); ma_loshelf_node_uninit(&m_mae.filters[i].loshelf, NULL); ma_peak_node_uninit(&m_mae.filters[i].mLow, NULL); @@ -91,6 +91,7 @@ ma_result MiniAudioEngine::createFilterBank(uint layer) filterBank *fb = &m_mae.filters[layer]; ma_splitter_node_config splitterConfig = ma_splitter_node_config_init(CHANNELS); + splitterConfig.outputBusCount= 3; result = ma_splitter_node_init(ng, &splitterConfig, NULL, &fb->input); if (result != MA_SUCCESS) { cout << "ERROR " << result << ": Failed to init input node." << endl; @@ -131,6 +132,12 @@ ma_result MiniAudioEngine::createFilterBank(uint layer) cout << "ERROR " << result << ": Failed to init hi shelf filter node." << endl; return result; } + ma_vumeter_node_config vuc = ma_vumeter_node_config_init(CHANNELS, FORMAT); + ma_vumeter_node_init(ng, &vuc, NULL, &fb->vumeter); + if (result != MA_SUCCESS) { + cout << "ERROR " << result << ": Failed to init vumeter node." << endl; + return result; + } splitterConfig.outputBusCount = m_mae.audioDevicesQty; result = ma_splitter_node_init(ng, &splitterConfig, NULL, &fb->output); if (result != MA_SUCCESS) { @@ -142,7 +149,7 @@ ma_result MiniAudioEngine::createFilterBank(uint layer) cout << "ERROR " << result << ": Failed to attach input node." << endl; return result; } - result = ma_node_attach_output_bus(&fb->input, 1, &fb->output, 0); + result = ma_node_attach_output_bus(&fb->input, 1, &fb->vumeter, 0); if (result != MA_SUCCESS) { cout << "ERROR " << result << ": Failed to attach bypass connection." << endl; return result; @@ -168,7 +175,12 @@ ma_result MiniAudioEngine::createFilterBank(uint layer) cout << "ERROR " << result << ": Failed to attach high peaks filter node." << endl; return result; } - result = ma_node_attach_output_bus(&fb->hishelf, 0, &fb->output, 0); + result = ma_node_attach_output_bus(&fb->hishelf, 0, &fb->vumeter, 0); + if (result != MA_SUCCESS) { + cout << "ERROR " << result << ": Failed to attach high shelf filter node." << endl; + return result; + } + result = ma_node_attach_output_bus(&fb->vumeter, 0, &fb->output, 0); if (result != MA_SUCCESS) { cout << "ERROR " << result << ": Failed to attach high shelf filter node." << endl; return result; @@ -219,12 +231,6 @@ ma_result MiniAudioEngine::setNodeGraph() { cout << "ERROR " << result << ": Failed to init writer node." << endl; return result; } - // esto va a dar problemas al sumar en el envío 0 una vez por cad envío extra. - // writer_node puede ser silencioso - // así ya estamos en el caso de disparar varios bang por cada envío en el mismo nodegraph - // es mejor que writer node tenga varias entradas, una por cada envío - // que se dispara con un único engine y proporciona un único stream de audio de vuelta a ese engine - // en vez de un puntero hay que pasarle un array de rb result = ma_node_attach_output_bus(&m_mae.sendAuxNode[i], 0, ma_engine_get_endpoint(&m_mae.engines[0]), 0); if (result != MA_SUCCESS) { cout << "ERROR " << result << ": Failed to attach writer node." << endl; @@ -495,6 +501,7 @@ ma_result MiniAudioEngine::seekToCursor(int layer, int cursor) result = ma_sound_get_length_in_pcm_frames(&m_mae.sounds[layer], &end); if (result != MA_SUCCESS) { return result; } start = (cursor * end) / 65025; + Status oldStatus = m_mae.currentStatus[layer].status; result = ma_sound_seek_to_pcm_frame(&m_mae.sounds[layer], start); //result = ma_data_source_set_loop_point_in_pcm_frames(&m_mae.sounds[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 870dc42..89095e6 100644 --- a/src/miniaudioengine.h +++ b/src/miniaudioengine.h @@ -5,8 +5,6 @@ #define MA_ENABLE_JACK #define MA_NO_GENERATION #define MA_DEBUG_OUTPUT -#define MA_DISABLE_PULSEAUDIO -#define MA_DEBUG_OUTPUT #define MA_LOG_LEVEL_DEBUG DEBUG #define MINIAUDIO_IMPLEMENTATION #include "miniaudio.h" @@ -28,6 +26,7 @@ typedef struct ma_peak_node_config mHighConfig; ma_hishelf_node hishelf; ma_hishelf_node_config hishelfConfig; + ma_vumeter_node vumeter; ma_splitter_node output; } filterBank; @@ -76,10 +75,15 @@ protected: float getCursor(int layer); Status getStatus(int layer); inline float getVol(int layer) { - return ma_sound_get_volume(&m_mae.sounds[layer]); } + return ma_sound_get_volume(&m_mae.sounds[layer]); + }; inline bool getAtEnd(int layer) { return m_mae.sounds[layer].atEnd; } ma_result filterParamChanged(int layer, int audioDevice, int channel, int value); bool setBypass(int audioDevice, int layer, bool bypass); + inline float getLevel(int layer) { + float level = ma_vumeter_node_get_level(&m_mae.filters[layer].vumeter); + return ma_volume_linear_to_db(level); + }; private: MAE m_mae;