quita el glitch al resproducir después de cargar un fichero.

varias optimizaciones y comprobaciones
This commit is contained in:
snt 2024-05-24 01:49:21 +02:00
parent db86987b6a
commit 0d29dda4c1
8 changed files with 66 additions and 71 deletions

View file

@ -1,10 +1,10 @@
******************************************************************************* *******************************************************************************
Libre Media Server Audio - An Open source Media Server for arts and performing. LibreMediaServer Audio - An open source media server for arts and performing.
(c) Criptomart - Santiago Noreña 2012-2024 <lms@criptomart.net> (c) Criptomart - Santiago Noreña 2012-2024 <lms@criptomart.net>
https://git.criptomart.net/libremediaserver https://git.criptomart.net/libremediaserver
******************************************************************************* *******************************************************************************
Libre Media Server ChangeLog LibreMediaServer Changelog
v 0.2.0 Antígona (26/05/2024) v 0.2.0 Antígona (26/05/2024)
+ Change audio engine to miniaudio because is imposible pan in SFML and it has not access to low API and audio processing. + Change audio engine to miniaudio because is imposible pan in SFML and it has not access to low API and audio processing.
@ -20,9 +20,7 @@ v 0.2.0 Antígona (26/05/2024)
+ OlaThread send double channels (volume, entry point, load media) only once for each dmx frame buffer. + OlaThread send double channels (volume, entry point, load media) only once for each dmx frame buffer.
+ Terminal mode without graphical interface. All audio methods has been refactorized out of QWidget world. + Terminal mode without graphical interface. All audio methods has been refactorized out of QWidget world.
+ Compilation without GUI (-DNOGUI). + Compilation without GUI (-DNOGUI).
+ 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); + 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). Signals are used only from Ui to libreMediaServer to notify user interactions and Qtimers. + 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. + Load media files from ui clicking in the media labels.
+ New Play Modes: + New Play Modes:
@ -31,6 +29,7 @@ v 0.2.0 Antígona (26/05/2024)
- 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 + Vumeter for each layer
+ Show device name on Ui and ouput bus slider.
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

@ -6,38 +6,32 @@ https://git.criptomart.net/libremediaserver
Libre Media Server Roadmap Libre Media Server Roadmap
v 0.2.x v 0.3.0
- skin, UI/UX - Ui/Ux: skin, style.
- Ui/Ux; Keyboards strokes.
- Ui/Ux: Dar la opción clickeando en el widget de tiempo de poner una cuenta atrás en vez de hacia delante.
- Ui/Ux: seek cursor playback
- live input. - live input.
- insertar/bypass/eliminar audio procesadores sin reiniciar por capa y master. (compresor, equs). - remove ola and use sACN directly.
- FX en capas master para que se puedan usar como envíos de auxiliar.
- Enroutado de masters en otros masters (retorno de efectos).
v 0.2.2
- Use sACN directly.
+ la instalación de OLA es mediante compilación, el repo de paquetes no está actualizado, nada user-friendly. + la instalación de OLA es mediante compilación, el repo de paquetes no está actualizado, nada user-friendly.
+ hay que empaquetar OLA, incluirlo en el binario, o implementar sACN y linkarlo estáticamente. + hay que empaquetar OLA, incluirlo en el binario, o implementar sACN y linkarlo estáticamente.
+ https://github.com/ETCLabs/sACN + https://github.com/ETCLabs/sACN
- Qt6. - Qt6.
- Audio processing (eq, rev, compresor, ...) by master and layer. - CIPT/MSex.
- CIPT/MSex, send icons play/pause/stop.
- Rasp build. - Rasp build.
- Octopus Sound Card support (6 outputs - 8 inputs). - Octopus Sound Card support (6 outputs - 8 inputs).
v 0.2.1
- mute/panic on layer.
- Master Bus Layer: - Master Bus Layer:
- each layer will have one "Gain" prefader that acts in source, "Vol" in v 1.3.
- each layer will have one volume dmx channel for each bus layer. One aux "Send" prefader.
- mute/panic. - mute/panic.
- fader + value - fader + value
- pan. - pan
- magicq .hed - magicq personality .hed
- audio device linked, outputs will be redirected there. - audio device linked, outputs will be redirected there.
- dmx address + universe settings. - dmx address + universe settings.
- Rose noise and sine generator in menu to test system. - compresor/limiter.
- Ui/Ux; Keyboards strokes. - Layer:
- Ui/Ux: Dar la opción clickeando en el widget de tiempo de poner una cuenta atrás en vez de hacia delante. - audio procesadores (compresor, reveb, delay).
- mute/panic.
- Rose noise and sine generator.
- Logs, verbosity, timestamp. - Logs, verbosity, timestamp.
- New play mode without pitch control, it saves resources. MA_SOUND_FLAG_NO_PITCH - New play mode without pitch control, it saves resources. MA_SOUND_FLAG_NO_PITCH
- SettingsDialog. - SettingsDialog.
@ -45,8 +39,4 @@ v 0.2.1
- ¿Exit Point? is it needed? - ¿Exit Point? is it needed?
- Hardening: check return errors, try/catch exceptions, i'm too happy.... - Hardening: check return errors, try/catch exceptions, i'm too happy....
- Tests: errors on wrong conf file. - Tests: errors on wrong conf file.
- Ui/Ux: seek cursor playback
- 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:
- mostrad información de envíos y dispositivos en ui

View file

@ -198,6 +198,7 @@ void AudioLayerWidget::openMediaDialog()
fileNames = dialog.selectedFiles(); fileNames = dialog.selectedFiles();
emit uiLoadMedia(m_layer, fileNames.at(0)); emit uiLoadMedia(m_layer, fileNames.at(0));
this->setMediaFile(fileNames.at(0)); this->setMediaFile(fileNames.at(0));
this->setPlaybackStatus(Status::Stopped);
} }
// from DMX signals // from DMX signals
@ -267,18 +268,18 @@ void AudioLayerWidget::setCurrentTime(float progress)
void AudioLayerWidget::setFilterParam(int channel, int value) void AudioLayerWidget::setFilterParam(int channel, int value)
{ {
if (channel <= FILTER_BANK_GAIN - FILTER_CHANNELS){ if (channel <= FILTER_BANK_GAIN - HP_FREQ){
m_filterBank->blockSignals(true); m_filterBank->blockSignals(true);
m_filterBank->setValue(channel, value); m_filterBank->setValue(channel, value);
m_filterBank->blockSignals(false); m_filterBank->blockSignals(false);
} else if (channel == SEND1 - HP_FREQ) { } else if (channel == SEND1 - HP_FREQ) {
m_bus1->blockSignals(false);
m_bus1->setValue((value * 256) + 255);
m_bus1->blockSignals(true); m_bus1->blockSignals(true);
m_bus1->setValue((value * 256) + 255);
m_bus1->blockSignals(false);
} else if (channel == SEND2 - HP_FREQ) { } else if (channel == SEND2 - HP_FREQ) {
m_bus2->blockSignals(false);
m_bus2->setValue(value * 256 + 255);
m_bus2->blockSignals(true); m_bus2->blockSignals(true);
m_bus2->setValue(value * 256 + 255);
m_bus2->blockSignals(false);
} }
} }

View file

@ -70,8 +70,7 @@ void libreMediaServerAudio::loadMedia(int layer, int folder, int file)
if (strcmp(mediaFile.toLatin1().constData(), m_currentMedia[layer].toLatin1().constData()) == 0) if (strcmp(mediaFile.toLatin1().constData(), m_currentMedia[layer].toLatin1().constData()) == 0)
return; return;
if (QFile::exists(mediaFile)){ if (QFile::exists(mediaFile)){
m_mae.loadMedia(layer, mediaFile.toLatin1().data(),\ m_mae.loadMedia(layer, mediaFile.toLatin1().data());
m_dmxSettings.at(layer).audioDevice);
m_currentMedia[layer] = mediaFile; m_currentMedia[layer] = mediaFile;
#ifndef NOGUI #ifndef NOGUI
if (m_ui) if (m_ui)
@ -135,7 +134,7 @@ void libreMediaServerAudio::dmxInput(int layer, int channel, int value)
} }
#endif #endif
} else if (channel >= HP_FREQ) { } else if (channel >= HP_FREQ) {
m_mae.filterParamChanged(layer, m_dmxSettings.at(layer).audioDevice, channel, value); m_mae.filterParamChanged(layer, channel, value);
#ifndef NOGUI #ifndef NOGUI
if (m_ui) { if (m_ui) {
m_lmsUi->m_aw->filterParamChanged(layer, channel, value); m_lmsUi->m_aw->filterParamChanged(layer, channel, value);
@ -252,10 +251,10 @@ void libreMediaServerAudio::uiSliderChanged(int layer, Slider s, int value)
m_mae.setBypass(m_dmxSettings.at(layer).audioDevice, layer, value); m_mae.setBypass(m_dmxSettings.at(layer).audioDevice, layer, value);
break; break;
case Slider::Bus1: case Slider::Bus1:
m_mae.filterParamChanged(layer, m_dmxSettings.at(layer).audioDevice, SEND1, value / 255); m_mae.filterParamChanged(layer, SEND1, value / 255.0f);
break; break;
case Slider::Bus2: case Slider::Bus2:
m_mae.filterParamChanged(layer, m_dmxSettings.at(layer).audioDevice, SEND2, value / 255); m_mae.filterParamChanged(layer, SEND2, value / 255.0f);
break; break;
} }
} }
@ -278,7 +277,7 @@ void libreMediaServerAudio::uiLoadMedia(int layer, QString mediaFile)
if (strcmp(mediaFile.toLatin1().constData(), m_currentMedia[layer].toLatin1().constData()) == 0) if (strcmp(mediaFile.toLatin1().constData(), m_currentMedia[layer].toLatin1().constData()) == 0)
return; return;
result = m_mae.loadMedia(layer, mediaFile.toLatin1().data(), m_dmxSettings[layer].audioDevice); result = m_mae.loadMedia(layer, mediaFile.toLatin1().data());
if (result == MA_SUCCESS) { if (result == MA_SUCCESS) {
m_currentMedia[layer] = mediaFile; m_currentMedia[layer] = mediaFile;
m_lmsUi->m_aw->mediaLoaded(layer, mediaFile, m_mae.getDuration(layer)); m_lmsUi->m_aw->mediaLoaded(layer, mediaFile, m_mae.getDuration(layer));

View file

@ -38,8 +38,6 @@ static ma_node_vtable g_ma_writer_node_vtable =
2, 2,
1, 1,
0 0
// MA_NODE_FLAG_CONTINUOUS_PROCESSING
// MA_NODE_FLAG_SILENT_OUTPUT
}; };
MA_API ma_result ma_writer_node_init(ma_node_graph* pNodeGraph, const ma_writer_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_writer_node* pWriteNode) MA_API ma_result ma_writer_node_init(ma_node_graph* pNodeGraph, const ma_writer_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_writer_node* pWriteNode)
@ -178,7 +176,7 @@ void ma_data_source_rb_uninit(ma_data_source_rb* pMyDataSource)
* vumeter * vumeter
*/ */
MA_API ma_vumeter_node_config ma_vumeter_node_config_init(ma_uint32 channels, ma_uint32 sampleRate) MA_API ma_vumeter_node_config ma_vumeter_node_config_init(ma_uint32 channels, ma_uint32 format, ma_uint32 sampleRate)
{ {
ma_vumeter_node_config config; ma_vumeter_node_config config;
@ -186,6 +184,7 @@ MA_API ma_vumeter_node_config ma_vumeter_node_config_init(ma_uint32 channels, ma
config.nodeConfig = ma_node_config_init(); config.nodeConfig = ma_node_config_init();
config.channels = channels; config.channels = channels;
config.sampleRate = sampleRate; config.sampleRate = sampleRate;
config.format = format;
return config; return config;
} }
@ -201,7 +200,7 @@ static void ma_vumeter_node_process_pcm_frames(ma_node* pNode, const float** ppF
float input = fabsf(ppFramesIn[0][i]); float input = fabsf(ppFramesIn[0][i]);
pVumeterNode->level += pVumeterNode->alpha * (input - pVumeterNode->level); pVumeterNode->level += pVumeterNode->alpha * (input - pVumeterNode->level);
} }
ma_copy_pcm_frames(ppFramesOut[0], ppFramesIn[0], *pFrameCountOut, ma_format_f32, pVumeterNode->channels); ma_copy_pcm_frames(ppFramesOut[0], ppFramesIn[0], *pFrameCountOut, pVumeterNode->format, pVumeterNode->channels);
} }
static ma_node_vtable g_ma_vumeter_node_vtable = static ma_node_vtable g_ma_vumeter_node_vtable =
@ -238,8 +237,9 @@ MA_API ma_result ma_vumeter_node_init(ma_node_graph* pNodeGraph, const ma_vumete
} }
pVumeterNode->sampleRate = pConfig->sampleRate; pVumeterNode->sampleRate = pConfig->sampleRate;
pVumeterNode->channels = pConfig->channels; pVumeterNode->channels = pConfig->channels;
pVumeterNode->format = pConfig->format;
pVumeterNode->level = 0; pVumeterNode->level = 0;
pVumeterNode->TC = 10000.0f; pVumeterNode->TC = 0.250f;
pVumeterNode->alpha = 1.0 - expf( (-2.0 * M_PI) / (pVumeterNode->TC * pConfig->sampleRate)); pVumeterNode->alpha = 1.0 - expf( (-2.0 * M_PI) / (pVumeterNode->TC * pConfig->sampleRate));
return MA_SUCCESS; return MA_SUCCESS;
} }

View file

@ -59,15 +59,17 @@ typedef struct
ma_node_config nodeConfig; ma_node_config nodeConfig;
ma_uint32 channels; ma_uint32 channels;
ma_uint32 sampleRate; ma_uint32 sampleRate;
ma_uint32 format;
} ma_vumeter_node_config; } ma_vumeter_node_config;
MA_API ma_vumeter_node_config ma_vumeter_node_config_init(ma_uint32 channels, ma_uint32 sampleRate); MA_API ma_vumeter_node_config ma_vumeter_node_config_init(ma_uint32 channels, ma_uint32 format, ma_uint32 sampleRate);
typedef struct typedef struct
{ {
ma_node_base baseNode; ma_node_base baseNode;
ma_uint32 channels; ma_uint32 channels;
ma_uint32 sampleRate; ma_uint32 sampleRate;
ma_uint32 format;
float level; float level;
float TC; float TC;
float alpha; float alpha;

View file

@ -132,7 +132,7 @@ 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_config vuc = ma_vumeter_node_config_init(CHANNELS, FORMAT, SAMPLE_RATE);
ma_vumeter_node_init(ng, &vuc, NULL, &fb->vumeter); ma_vumeter_node_init(ng, &vuc, NULL, &fb->vumeter);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
cout << "ERROR " << result << ": Failed to init vumeter node." << endl; cout << "ERROR " << result << ": Failed to init vumeter node." << endl;
@ -278,7 +278,7 @@ ma_result MiniAudioEngine::startDevices()
deviceConfig.dataCallback = audioDataCallback; deviceConfig.dataCallback = audioDataCallback;
engineConfig = ma_engine_config_init(); engineConfig = ma_engine_config_init();
engineConfig.pResourceManager = &m_mae.resourceManager; engineConfig.pResourceManager = &m_mae.resourceManager;
engineConfig.gainSmoothTimeInMilliseconds = SAMPLE_RATE / 25; engineConfig.defaultVolumeSmoothTimeInPCMFrames = SAMPLE_RATE / 20;
engineConfig.noAutoStart = MA_TRUE; engineConfig.noAutoStart = MA_TRUE;
for (uint internalId = 0; internalId < m_mae.audioDevicesQty; internalId++) { for (uint internalId = 0; internalId < m_mae.audioDevicesQty; internalId++) {
@ -346,25 +346,28 @@ char* MiniAudioEngine::getDeviceName(uint id)
} }
ma_result MiniAudioEngine::loadMedia(int layer, char *file, uint audioDevice) ma_result MiniAudioEngine::loadMedia(int layer, char *file)
{ {
ma_result result; ma_result result;
if (m_mae.mediaLoaded[layer] == true) if (m_mae.mediaLoaded[layer] == true)
{ {
ma_sound_set_volume(&m_mae.sounds[layer], 0.0f);
ma_sound_stop(&m_mae.sounds[layer]);
ma_sound_uninit(&m_mae.sounds[layer]); ma_sound_uninit(&m_mae.sounds[layer]);
m_mae.mediaLoaded[layer] = false; m_mae.mediaLoaded[layer] = false;
} }
result = ma_sound_init_from_file(&m_mae.engines[0], file, \ ma_sound_config soundConfig = ma_sound_config_init();
MA_SOUND_FLAG_NO_SPATIALIZATION \ soundConfig = ma_sound_config_init();
, NULL, NULL, &m_mae.sounds[layer]); soundConfig.pFilePath = file;
soundConfig.pInitialAttachment = &m_mae.filters[layer].input;
soundConfig.initialAttachmentInputBusIndex = 0;
soundConfig.channelsIn = 0;
soundConfig.channelsOut = CHANNELS;
soundConfig.flags = MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT | MA_SOUND_FLAG_STREAM; //| MA_SOUND_FLAG_NO_PITCH
result = ma_sound_init_ex(&m_mae.engines[0], &soundConfig, &m_mae.sounds[layer]);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
cout << "Error " << result << ": Failed to load file " << file << endl; cout << "Error" << result << ": Failed to load file " << file << endl;
return result;
}
result = ma_node_attach_output_bus(&m_mae.sounds[layer], 0, &m_mae.filters[layer].input, 0);
if (result != MA_SUCCESS) {
cout << "Error " << result << ": Failed to attach sound output bus " << audioDevice << endl;
return result; return result;
} }
m_mae.mediaLoaded[layer] = true; m_mae.mediaLoaded[layer] = true;
@ -435,7 +438,7 @@ void MiniAudioEngine::volChanged(int layer, int vol)
db = 0; db = 0;
} else } else
db = ma_volume_db_to_linear(db); db = ma_volume_db_to_linear(db);
ma_sound_group_set_fade_in_milliseconds(&m_mae.sounds[layer], -1, db, FADE_TIME); ma_sound_set_fade_in_milliseconds(&m_mae.sounds[layer], -1, db, FADE_TIME);
m_mae.currentStatus[layer].vol = vol; m_mae.currentStatus[layer].vol = vol;
} }
@ -485,12 +488,13 @@ ma_result MiniAudioEngine::playbackChanged(int layer, Status status)
ma_sound_set_stop_time_in_milliseconds(&m_mae.sounds[layer], ~(ma_uint64)0); ma_sound_set_stop_time_in_milliseconds(&m_mae.sounds[layer], ~(ma_uint64)0);
ma_sound_set_looping(&m_mae.sounds[layer], loop); ma_sound_set_looping(&m_mae.sounds[layer], loop);
result = ma_sound_start(&m_mae.sounds[layer]); result = ma_sound_start(&m_mae.sounds[layer]);
if (m_mae.currentStatus[layer].cursor != 0) { //this->volChanged(layer, m_mae.currentStatus[layer].vol);
usleep(1000 * 50); // Avoid small glitch at start, how to flush the cached buffers in audio pipe line? float db = (m_mae.currentStatus[layer].vol / 771.0f) - 85.0f;
} if (db <= -85.0f) {
this->volChanged(layer, m_mae.currentStatus[layer].vol); db = 0;
default: } else
break; db = ma_volume_db_to_linear(db);
ma_sound_set_fade_in_milliseconds(&m_mae.sounds[layer], -1, db, 0);
} }
if (result == MA_SUCCESS) if (result == MA_SUCCESS)
m_mae.currentStatus[layer].status = status; m_mae.currentStatus[layer].status = status;
@ -506,7 +510,7 @@ ma_result MiniAudioEngine::seekToCursor(int layer, int cursor)
return MA_DOES_NOT_EXIST; return MA_DOES_NOT_EXIST;
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) / 65535;
Status oldStatus = m_mae.currentStatus[layer].status; 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?
@ -531,14 +535,14 @@ void MiniAudioEngine::refreshValues(int layer)
{ {
this->seekToCursor(layer, m_mae.currentStatus[layer].cursor); this->seekToCursor(layer, m_mae.currentStatus[layer].cursor);
this->panChanged(layer, m_mae.currentStatus[layer].pan); this->panChanged(layer, m_mae.currentStatus[layer].pan);
this->volChanged(layer, m_mae.currentStatus[layer].vol);
this->pitchChanged(layer, m_mae.currentStatus[layer].pitch); this->pitchChanged(layer, m_mae.currentStatus[layer].pitch);
ma_sound_group_set_fade_in_milliseconds(&m_mae.sounds[layer], -1, 0, 0.0f);
this->playbackChanged(layer, m_mae.currentStatus[layer].status); this->playbackChanged(layer, m_mae.currentStatus[layer].status);
this->volChanged(layer, m_mae.currentStatus[layer].vol);
} }
ma_result MiniAudioEngine::filterParamChanged(int layer, int audioDevice, int channel, int value) ma_result MiniAudioEngine::filterParamChanged(int layer, int channel, int value)
{ {
(void)audioDevice;
ma_result result = MA_SUCCESS; ma_result result = MA_SUCCESS;
filterBank *fb = &m_mae.filters[layer]; filterBank *fb = &m_mae.filters[layer];

View file

@ -65,7 +65,7 @@ protected:
MiniAudioEngine(); MiniAudioEngine();
void stopEngine(); void stopEngine();
bool startEngine(uint layersQty, uint* audioDevicesID, uint audioDevicesQty); bool startEngine(uint layersQty, uint* audioDevicesID, uint audioDevicesQty);
ma_result loadMedia(int layer, char *media, uint audioDevice); ma_result loadMedia(int layer, char *media);
void volChanged(int layer, int vol); void volChanged(int layer, int vol);
void panChanged(int layer, float pan); void panChanged(int layer, float pan);
void pitchChanged(int layer, float pitch); void pitchChanged(int layer, float pitch);
@ -79,11 +79,11 @@ protected:
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 channel, int value);
bool setBypass(int audioDevice, int layer, bool bypass); bool setBypass(int audioDevice, int layer, bool bypass);
inline float getLevel(int layer) { inline float getLevel(int layer) {
float level = ma_vumeter_node_get_level(&m_mae.filters[layer].vumeter); float level = ma_vumeter_node_get_level(&m_mae.filters[layer].vumeter);
return ma_volume_linear_to_db(level); return ma_volume_linear_to_db(level) - 4.0f;
}; };
char* getDeviceName(uint id); char* getDeviceName(uint id);