Antigona Release #1
12 changed files with 165 additions and 23 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -75,6 +75,7 @@ struct layerData {
|
|||
int device;
|
||||
int bus1Vol;
|
||||
int bus2Vol;
|
||||
float level;
|
||||
};
|
||||
#endif // __cplusplus
|
||||
#endif // DEFINES_H
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue