vumeter funcionando, hay que comparar la salida con un vumeter que sepa

que funciona bien y definir los parámetros de ventanas, picos y demás.
Se insertan en la cadena de audio porque no veo la forma de hacerlo en paralelo https://github.com/mackron/miniaudio/issues/850
This commit is contained in:
snt 2024-05-22 20:52:13 +02:00
parent 200dcf86d4
commit 53bcb38455
12 changed files with 165 additions and 23 deletions

View file

@ -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 consecutevily.
- Play all medias in one folder randomly. - Play all medias in one folder randomly.
+ Multi audio devices output. + Multi audio devices output.
+ Vumeter for each layer
v 0.1.3 Leúcade (19/04/2024) v 0.1.3 Leúcade (19/04/2024)
+ Ubuntu 22.04 jammy. + Ubuntu 22.04 jammy.

View file

@ -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 - ampliar writer para recibir un número n de entradas y escribirlas cada una en un buffer
v0.2.0: 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 - mostrad información de envíos y dispositivos en ui

View file

@ -91,6 +91,9 @@ AudioLayerWidget::AudioLayerWidget(QWidget *parent, int layer):
volumeBox->setSpacing(0); volumeBox->setSpacing(0);
volumeBox->setContentsMargins(0, 0, 0, 0); volumeBox->setContentsMargins(0, 0, 0, 0);
layout->addLayout(volumeBox); layout->addLayout(volumeBox);
m_level = new QLabel();
m_level->setText("0");
layout->addWidget(m_level);
layout->setAlignment(Qt::AlignHCenter); layout->setAlignment(Qt::AlignHCenter);
layout->setSpacing(0); layout->setSpacing(0);
layout->setContentsMargins(1, 1, 1, 1); layout->setContentsMargins(1, 1, 1, 1);
@ -248,3 +251,13 @@ void AudioLayerWidget::setFilterParam(int channel, int value)
m_bus2->blockSignals(true); 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);
}

View file

@ -28,6 +28,7 @@ public:
void setPan(int pan); void setPan(int pan);
void setPitch(int pitch); void setPitch(int pitch);
void setFilterParam(int channel, int value); void setFilterParam(int channel, int value);
void setLevel(float db);
private: private:
Status m_status; Status m_status;
@ -36,6 +37,7 @@ private:
QPushButton *m_suspendResumeButton; QPushButton *m_suspendResumeButton;
ClickableLabel *m_fileValue; ClickableLabel *m_fileValue;
ClickableLabel * m_folderValue; ClickableLabel * m_folderValue;
QLabel *m_level;
SliderGroup *m_volume; SliderGroup *m_volume;
SliderGroup *m_pan; SliderGroup *m_pan;
SliderGroup *m_pitch; SliderGroup *m_pitch;

View file

@ -13,12 +13,13 @@ AudioWidget::AudioWidget(QWidget *parent) :
connect(alw, SIGNAL(uiPlaybackChanged(int, Status)), this, SIGNAL(uiPlaybackChanged(int, Status))); connect(alw, SIGNAL(uiPlaybackChanged(int, Status)), this, SIGNAL(uiPlaybackChanged(int, Status)));
connect(alw, SIGNAL(uiLoadMedia(int, QString)), this, SIGNAL(uiLoadMedia(int, QString))); connect(alw, SIGNAL(uiLoadMedia(int, QString)), this, SIGNAL(uiLoadMedia(int, QString)));
m_layerUpdate[i].status = Status::Iddle; m_layerUpdate[i].status = Status::Iddle;
m_layerUpdate[i].duration = 0; m_layerUpdate[i].duration = -1;
m_layerUpdate[i].media = ""; m_layerUpdate[i].media = "";
m_layerUpdate[i].vol = 0; m_layerUpdate[i].vol = -1;
m_layerUpdate[i].pan = 128; m_layerUpdate[i].pan = -1;
m_layerUpdate[i].pitch = 128; m_layerUpdate[i].pitch = -1;
m_layerUpdate[i].cursor = 0; m_layerUpdate[i].cursor = -1;
m_layerUpdate[i].level = 100;
for (int j = 0; j < FILTER_CHANNELS; j++) for (int j = 0; j < FILTER_CHANNELS; j++)
m_filtersUpdate[i][j] = -1; m_filtersUpdate[i][j] = -1;
} }
@ -102,6 +103,10 @@ void AudioWidget::refreshUi()
alw->setFilterParam(j, m_filtersUpdate[i][j]); alw->setFilterParam(j, m_filtersUpdate[i][j]);
m_filtersUpdate[i][j] = -1; 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; 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_filtersUpdate[layer][channel - HP_FREQ] = value;
m_layerUpdate[layer].updated = true; m_layerUpdate[layer].updated = true;
} }
void AudioWidget::levelChanged(int layer, float db)
{
m_layerUpdate[layer].level = db;
m_layerUpdate[layer].updated = true;
}

View file

@ -15,6 +15,7 @@ class AudioWidget : public QWidget
public: public:
AudioWidget(QWidget *parent = nullptr); AudioWidget(QWidget *parent = nullptr);
void filterParamChanged(int layer, int channel, int value); void filterParamChanged(int layer, int channel, int value);
void levelChanged(int layer, float db);
private: private:
QHBoxLayout *m_layout; QHBoxLayout *m_layout;

View file

@ -75,6 +75,7 @@ struct layerData {
int device; int device;
int bus1Vol; int bus1Vol;
int bus2Vol; int bus2Vol;
float level;
}; };
#endif // __cplusplus #endif // __cplusplus
#endif // DEFINES_H #endif // DEFINES_H

View file

@ -170,6 +170,7 @@ void libreMediaServerAudio::refreshUi() {
m_lmsUi->m_aw->cursorChanged(i, m_mae.getCursor(i)); m_lmsUi->m_aw->cursorChanged(i, m_mae.getCursor(i));
m_updateUi[i][3] = -1; m_updateUi[i][3] = -1;
} }
m_lmsUi->m_aw->levelChanged(i, m_mae.getLevel(i));
if (m_mae.getAtEnd(i)) { if (m_mae.getAtEnd(i)) {
if (m_currentStatus[i] == Status::PlayingOnce) { if (m_currentStatus[i] == Status::PlayingOnce) {
m_currentStatus[i] = Status::Stopped; m_currentStatus[i] = Status::Stopped;

View file

@ -46,7 +46,7 @@ MA_API ma_result ma_writer_node_init(ma_node_graph* pNodeGraph, const ma_writer_
{ {
ma_result result; ma_result result;
ma_node_config baseConfig; 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]; ma_uint32 outputChannels[1];
if (pWriteNode == NULL || pConfig == NULL || pConfig->pBuffer == NULL \ 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 * Data Source Ring Buffer
*/ */
ma_result ma_data_source_rb_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) 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_data_source_rb* ds = (ma_data_source_rb*)pDataSource;
ma_uint32 pcmFramesAvailableInRB = 0; ma_uint32 pcmFramesAvailableInRB = 0;
ma_uint32 pcmFramesProcessed = 0; ma_uint32 pcmFramesProcessed = 0;
// lo mismo que en el callback, va el doble de rápido y con glitches.
while (pcmFramesProcessed < frameCount) { while (pcmFramesProcessed < frameCount) {
pcmFramesAvailableInRB = ma_pcm_rb_available_read(ds->rb); pcmFramesAvailableInRB = ma_pcm_rb_available_read(ds->rb);
if (pcmFramesAvailableInRB == 0) { if (pcmFramesAvailableInRB == 0) {
@ -175,3 +173,78 @@ void ma_data_source_rb_uninit(ma_data_source_rb* pMyDataSource)
{ {
ma_data_source_uninit(&pMyDataSource->base); 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);
}

View file

@ -8,6 +8,10 @@ extern "C" {
#include "miniaudio.h" #include "miniaudio.h"
/*
* writer
*/
typedef struct typedef struct
{ {
ma_node_config nodeConfig; 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); 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); 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 #ifdef __cplusplus
} }
#endif #endif

View file

@ -24,7 +24,7 @@ void MiniAudioEngine::stopEngine()
ma_sound_uninit(&m_mae.sounds[i]); ma_sound_uninit(&m_mae.sounds[i]);
} }
for (uint i = 0; i < m_mae.layersQty; 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_hpf_node_uninit(&m_mae.filters[i].hpf, NULL);
ma_loshelf_node_uninit(&m_mae.filters[i].loshelf, NULL); ma_loshelf_node_uninit(&m_mae.filters[i].loshelf, NULL);
ma_peak_node_uninit(&m_mae.filters[i].mLow, 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]; filterBank *fb = &m_mae.filters[layer];
ma_splitter_node_config splitterConfig = ma_splitter_node_config_init(CHANNELS); ma_splitter_node_config splitterConfig = ma_splitter_node_config_init(CHANNELS);
splitterConfig.outputBusCount= 3;
result = ma_splitter_node_init(ng, &splitterConfig, NULL, &fb->input); result = ma_splitter_node_init(ng, &splitterConfig, NULL, &fb->input);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to init input node." << endl; 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; cout << "ERROR " << result << ": Failed to init hi shelf filter node." << endl;
return result; 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; splitterConfig.outputBusCount = m_mae.audioDevicesQty;
result = ma_splitter_node_init(ng, &splitterConfig, NULL, &fb->output); result = ma_splitter_node_init(ng, &splitterConfig, NULL, &fb->output);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
@ -142,7 +149,7 @@ ma_result MiniAudioEngine::createFilterBank(uint layer)
cout << "ERROR " << result << ": Failed to attach input node." << endl; cout << "ERROR " << result << ": Failed to attach input node." << endl;
return result; 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) { if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach bypass connection." << endl; cout << "ERROR " << result << ": Failed to attach bypass connection." << endl;
return result; return result;
@ -168,7 +175,12 @@ ma_result MiniAudioEngine::createFilterBank(uint layer)
cout << "ERROR " << result << ": Failed to attach high peaks filter node." << endl; cout << "ERROR " << result << ": Failed to attach high peaks filter node." << endl;
return result; 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) { if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach high shelf filter node." << endl; cout << "ERROR " << result << ": Failed to attach high shelf filter node." << endl;
return result; return result;
@ -219,12 +231,6 @@ ma_result MiniAudioEngine::setNodeGraph() {
cout << "ERROR " << result << ": Failed to init writer node." << endl; cout << "ERROR " << result << ": Failed to init writer node." << endl;
return result; 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); result = ma_node_attach_output_bus(&m_mae.sendAuxNode[i], 0, ma_engine_get_endpoint(&m_mae.engines[0]), 0);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to attach writer node." << endl; 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); result = ma_sound_get_length_in_pcm_frames(&m_mae.sounds[layer], &end);
if (result != MA_SUCCESS) { return result; } if (result != MA_SUCCESS) { return result; }
start = (cursor * end) / 65025; 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_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? //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); return (result);

View file

@ -5,8 +5,6 @@
#define MA_ENABLE_JACK #define MA_ENABLE_JACK
#define MA_NO_GENERATION #define MA_NO_GENERATION
#define MA_DEBUG_OUTPUT #define MA_DEBUG_OUTPUT
#define MA_DISABLE_PULSEAUDIO
#define MA_DEBUG_OUTPUT
#define MA_LOG_LEVEL_DEBUG DEBUG #define MA_LOG_LEVEL_DEBUG DEBUG
#define MINIAUDIO_IMPLEMENTATION #define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h" #include "miniaudio.h"
@ -28,6 +26,7 @@ typedef struct
ma_peak_node_config mHighConfig; ma_peak_node_config mHighConfig;
ma_hishelf_node hishelf; ma_hishelf_node hishelf;
ma_hishelf_node_config hishelfConfig; ma_hishelf_node_config hishelfConfig;
ma_vumeter_node vumeter;
ma_splitter_node output; ma_splitter_node output;
} filterBank; } filterBank;
@ -76,10 +75,15 @@ protected:
float getCursor(int layer); float getCursor(int layer);
Status getStatus(int layer); Status getStatus(int layer);
inline float getVol(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; } inline bool getAtEnd(int layer) { return m_mae.sounds[layer].atEnd; }
ma_result filterParamChanged(int layer, int audioDevice, int channel, int value); ma_result filterParamChanged(int layer, int audioDevice, int channel, int value);
bool setBypass(int audioDevice, int layer, bool bypass); 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: private:
MAE m_mae; MAE m_mae;