diff --git a/docs/changelog.txt b/docs/changelog.txt index 5cd2ca3..9a2f61b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -1,10 +1,10 @@ ******************************************************************************* -LibreMediaServer Audio - An open source media server for arts and performing. +Libre Media Server Audio - An Open source Media Server for arts and performing. (c) Criptomart - Santiago Noreña 2012-2024 https://git.criptomart.net/libremediaserver ******************************************************************************* -LibreMediaServer Changelog +Libre Media Server ChangeLog 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. @@ -20,7 +20,9 @@ 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. + Terminal mode without graphical interface. All audio methods has been refactorized out of QWidget world. + 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); ++ 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. + Load media files from ui clicking in the media labels. + New Play Modes: @@ -29,7 +31,6 @@ v 0.2.0 Antígona (26/05/2024) - Play all medias in one folder randomly. + Multi audio devices output. + Vumeter for each layer -+ Show device name on Ui and ouput bus slider. v 0.1.3 Leúcade (19/04/2024) + Ubuntu 22.04 jammy. diff --git a/docs/roadmap.txt b/docs/roadmap.txt index bc786a9..bc06163 100644 --- a/docs/roadmap.txt +++ b/docs/roadmap.txt @@ -6,32 +6,38 @@ https://git.criptomart.net/libremediaserver Libre Media Server Roadmap -v 0.3.0 -- 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 +v 0.2.x +- skin, UI/UX - live input. -- remove ola and use sACN directly. +- insertar/bypass/eliminar audio procesadores sin reiniciar por capa y master. (compresor, equs). +- 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. + hay que empaquetar OLA, incluirlo en el binario, o implementar sACN y linkarlo estáticamente. + https://github.com/ETCLabs/sACN - Qt6. -- CIPT/MSex. +- Audio processing (eq, rev, compresor, ...) by master and layer. +- CIPT/MSex, send icons play/pause/stop. - Rasp build. - Octopus Sound Card support (6 outputs - 8 inputs). + +v 0.2.1 +- mute/panic on 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. - fader + value - - pan - - magicq personality .hed + - pan. + - magicq .hed - audio device linked, outputs will be redirected there. - dmx address + universe settings. - - compresor/limiter. -- Layer: - - audio procesadores (compresor, reveb, delay). - - mute/panic. -- Rose noise and sine generator. +- Rose noise and sine generator in menu to test system. +- 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. - Logs, verbosity, timestamp. - New play mode without pitch control, it saves resources. MA_SOUND_FLAG_NO_PITCH - SettingsDialog. @@ -39,4 +45,8 @@ v 0.3.0 - ¿Exit Point? is it needed? - Hardening: check return errors, try/catch exceptions, i'm too happy.... - 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 + +v0.2.0: +- mostrad información de envíos y dispositivos en ui diff --git a/src/audiolayerwidget.cpp b/src/audiolayerwidget.cpp index b1eb8b7..646cb96 100644 --- a/src/audiolayerwidget.cpp +++ b/src/audiolayerwidget.cpp @@ -91,39 +91,9 @@ AudioLayerWidget::AudioLayerWidget(QWidget *parent, int layer): volumeBox->setSpacing(0); volumeBox->setContentsMargins(0, 0, 0, 0); layout->addLayout(volumeBox); - QHBoxLayout *labelsBox = new QHBoxLayout; - m_level = new QLabel("-inf"); - m_level->setStyleSheet("border: 1px solid #5a4855;" - "margin: 0px;" - "background-color: #180014;" - "color: white;" - "width:70px;" - "text-align: center;" - ); - m_level->setMinimumWidth(70); - m_level->setAlignment(Qt::AlignHCenter); - labelsBox->addWidget(m_level); - m_bus1Label = new QLabel("dummy"); - m_bus1Label->setMinimumWidth(80); - m_bus1Label->setStyleSheet("border: 1px solid #5a4855;" - "margin: 0px;" - "background-color: #383034;" - "width:70px;" - ); - m_bus1Label->setAlignment(Qt::AlignHCenter); - labelsBox->addWidget(m_bus1Label); - m_bus2Label = new QLabel("dummy"); - m_bus2Label->setMinimumWidth(80); - m_bus2Label->setStyleSheet("border: 1px solid #5a4855;" - "margin: 0px;" - "background-color: #383034;" - "width: 70px;" - ); - m_bus2Label->setAlignment(Qt::AlignHCenter); - labelsBox->addWidget(m_bus2Label); - labelsBox->setSpacing(0); - labelsBox->setContentsMargins(0, 0, 0, 0); - layout->addLayout(labelsBox); + 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); @@ -198,7 +168,6 @@ void AudioLayerWidget::openMediaDialog() fileNames = dialog.selectedFiles(); emit uiLoadMedia(m_layer, fileNames.at(0)); this->setMediaFile(fileNames.at(0)); - this->setPlaybackStatus(Status::Stopped); } // from DMX signals @@ -268,40 +237,27 @@ void AudioLayerWidget::setCurrentTime(float progress) void AudioLayerWidget::setFilterParam(int channel, int value) { - if (channel <= FILTER_BANK_GAIN - HP_FREQ){ + if (channel <= FILTER_BANK_GAIN - FILTER_CHANNELS){ m_filterBank->blockSignals(true); m_filterBank->setValue(channel, value); m_filterBank->blockSignals(false); } else if (channel == SEND1 - HP_FREQ) { - m_bus1->blockSignals(true); - m_bus1->setValue((value * 256) + 255); m_bus1->blockSignals(false); + m_bus1->setValue((value * 256) + 255); + m_bus1->blockSignals(true); } else if (channel == SEND2 - HP_FREQ) { - m_bus2->blockSignals(true); - m_bus2->setValue(value * 256 + 255); m_bus2->blockSignals(false); + m_bus2->setValue(value * 256 + 255); + m_bus2->blockSignals(true); } } void AudioLayerWidget::setLevel(float db) { m_level->blockSignals(true); - if (db > -150) + if (db > -200) m_level->setText(QString::number(db)); else m_level->setText("-inf"); m_level->blockSignals(false); } - -void AudioLayerWidget::setBusName(uint bus, char *name) -{ - if (bus == 0) { - m_bus1Label->blockSignals(true); - m_bus1Label->setText(name); - m_bus1Label->blockSignals(false); - } else if (bus == 1) { - m_bus2Label->blockSignals(true); - m_bus2Label->setText(name); - m_bus2Label->blockSignals(false); - } -} diff --git a/src/audiolayerwidget.h b/src/audiolayerwidget.h index bc7010f..0f9517c 100644 --- a/src/audiolayerwidget.h +++ b/src/audiolayerwidget.h @@ -29,7 +29,6 @@ public: void setPitch(int pitch); void setFilterParam(int channel, int value); void setLevel(float db); - void setBusName(uint bus, char *name); private: Status m_status; @@ -38,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; @@ -47,9 +47,8 @@ private: QTimeEdit *m_totalTimeValue; QProgressBar *m_progress; FilterBankWidget *m_filterBank; - QLabel *m_level; - QLabel *m_bus1Label; - QLabel *m_bus2Label; + +//public slots: // From Ui private slots: diff --git a/src/audiowidget.cpp b/src/audiowidget.cpp index 8d43127..98b3a32 100644 --- a/src/audiowidget.cpp +++ b/src/audiowidget.cpp @@ -123,12 +123,3 @@ void AudioWidget::levelChanged(int layer, float db) m_layerUpdate[layer].level = db; m_layerUpdate[layer].updated = true; } - -void AudioWidget::busNameChanged(uint bus, char* name) -{ - for (uint i = 0; i < m_layers; i++) { - QLayoutItem *item = m_layout->itemAt(i); - AudioLayerWidget *alw = dynamic_cast(item->widget()); - alw->setBusName(bus, name); - } -} diff --git a/src/audiowidget.h b/src/audiowidget.h index 238ead2..ddb5767 100644 --- a/src/audiowidget.h +++ b/src/audiowidget.h @@ -16,7 +16,6 @@ public: AudioWidget(QWidget *parent = nullptr); void filterParamChanged(int layer, int channel, int value); void levelChanged(int layer, float db); - void busNameChanged(uint bus, char *name); private: QHBoxLayout *m_layout; diff --git a/src/libremediaserver-audio.cpp b/src/libremediaserver-audio.cpp index dd6cedd..56b016a 100644 --- a/src/libremediaserver-audio.cpp +++ b/src/libremediaserver-audio.cpp @@ -70,7 +70,8 @@ void libreMediaServerAudio::loadMedia(int layer, int folder, int file) if (strcmp(mediaFile.toLatin1().constData(), m_currentMedia[layer].toLatin1().constData()) == 0) return; 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; #ifndef NOGUI if (m_ui) @@ -134,7 +135,7 @@ void libreMediaServerAudio::dmxInput(int layer, int channel, int value) } #endif } else if (channel >= HP_FREQ) { - m_mae.filterParamChanged(layer, channel, value); + m_mae.filterParamChanged(layer, m_dmxSettings.at(layer).audioDevice, channel, value); #ifndef NOGUI if (m_ui) { m_lmsUi->m_aw->filterParamChanged(layer, channel, value); @@ -228,10 +229,6 @@ void libreMediaServerAudio::setUi(libreMediaServerAudioUi *lmsUi) connect(m_refreshUi, SIGNAL(timeout()), this, SLOT(refreshUi())); m_refreshUi->start(UI_REFRESH_TIME); m_ola->start(QThread::TimeCriticalPriority ); - for (uint i = 0; i < m_settings->getAudioDeviceQty(); i++) { - char *name = m_mae.getDeviceName(i); - m_lmsUi->m_aw->busNameChanged(i, name); - } }; // From Ui widgets @@ -251,10 +248,10 @@ void libreMediaServerAudio::uiSliderChanged(int layer, Slider s, int value) m_mae.setBypass(m_dmxSettings.at(layer).audioDevice, layer, value); break; case Slider::Bus1: - m_mae.filterParamChanged(layer, SEND1, value / 255.0f); + m_mae.filterParamChanged(layer, m_dmxSettings.at(layer).audioDevice, SEND1, value / 255); break; case Slider::Bus2: - m_mae.filterParamChanged(layer, SEND2, value / 255.0f); + m_mae.filterParamChanged(layer, m_dmxSettings.at(layer).audioDevice, SEND2, value / 255); break; } } @@ -277,7 +274,7 @@ void libreMediaServerAudio::uiLoadMedia(int layer, QString mediaFile) if (strcmp(mediaFile.toLatin1().constData(), m_currentMedia[layer].toLatin1().constData()) == 0) return; - result = m_mae.loadMedia(layer, mediaFile.toLatin1().data()); + result = m_mae.loadMedia(layer, mediaFile.toLatin1().data(), m_dmxSettings[layer].audioDevice); if (result == MA_SUCCESS) { m_currentMedia[layer] = mediaFile; m_lmsUi->m_aw->mediaLoaded(layer, mediaFile, m_mae.getDuration(layer)); diff --git a/src/ma_writer_node.c b/src/ma_writer_node.c index 5b20f66..2845649 100644 --- a/src/ma_writer_node.c +++ b/src/ma_writer_node.c @@ -38,6 +38,8 @@ static ma_node_vtable g_ma_writer_node_vtable = 2, 1, 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) @@ -176,7 +178,7 @@ void ma_data_source_rb_uninit(ma_data_source_rb* pMyDataSource) * vumeter */ -MA_API ma_vumeter_node_config ma_vumeter_node_config_init(ma_uint32 channels, ma_uint32 format, ma_uint32 sampleRate) +MA_API ma_vumeter_node_config ma_vumeter_node_config_init(ma_uint32 channels, ma_uint32 sampleRate) { ma_vumeter_node_config config; @@ -184,7 +186,6 @@ MA_API ma_vumeter_node_config ma_vumeter_node_config_init(ma_uint32 channels, ma config.nodeConfig = ma_node_config_init(); config.channels = channels; config.sampleRate = sampleRate; - config.format = format; return config; } @@ -200,7 +201,7 @@ static void ma_vumeter_node_process_pcm_frames(ma_node* pNode, const float** ppF float input = fabsf(ppFramesIn[0][i]); pVumeterNode->level += pVumeterNode->alpha * (input - pVumeterNode->level); } - ma_copy_pcm_frames(ppFramesOut[0], ppFramesIn[0], *pFrameCountOut, pVumeterNode->format, pVumeterNode->channels); + ma_copy_pcm_frames(ppFramesOut[0], ppFramesIn[0], *pFrameCountOut, ma_format_f32, pVumeterNode->channels); } static ma_node_vtable g_ma_vumeter_node_vtable = @@ -237,9 +238,8 @@ MA_API ma_result ma_vumeter_node_init(ma_node_graph* pNodeGraph, const ma_vumete } pVumeterNode->sampleRate = pConfig->sampleRate; pVumeterNode->channels = pConfig->channels; - pVumeterNode->format = pConfig->format; pVumeterNode->level = 0; - pVumeterNode->TC = 0.250f; + pVumeterNode->TC = 10000.0f; pVumeterNode->alpha = 1.0 - expf( (-2.0 * M_PI) / (pVumeterNode->TC * pConfig->sampleRate)); return MA_SUCCESS; } diff --git a/src/ma_writer_node.h b/src/ma_writer_node.h index 3e03df5..ac592c7 100644 --- a/src/ma_writer_node.h +++ b/src/ma_writer_node.h @@ -59,17 +59,15 @@ typedef struct ma_node_config nodeConfig; ma_uint32 channels; ma_uint32 sampleRate; - ma_uint32 format; } ma_vumeter_node_config; -MA_API ma_vumeter_node_config ma_vumeter_node_config_init(ma_uint32 channels, ma_uint32 format, ma_uint32 sampleRate); +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; - ma_uint32 format; float level; float TC; float alpha; diff --git a/src/miniaudioengine.cpp b/src/miniaudioengine.cpp index 1d0d5b3..3ea5c1b 100644 --- a/src/miniaudioengine.cpp +++ b/src/miniaudioengine.cpp @@ -132,7 +132,7 @@ 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, SAMPLE_RATE); + 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; @@ -278,7 +278,7 @@ ma_result MiniAudioEngine::startDevices() deviceConfig.dataCallback = audioDataCallback; engineConfig = ma_engine_config_init(); engineConfig.pResourceManager = &m_mae.resourceManager; - engineConfig.defaultVolumeSmoothTimeInPCMFrames = SAMPLE_RATE / 20; + engineConfig.gainSmoothTimeInMilliseconds = SAMPLE_RATE / 25; engineConfig.noAutoStart = MA_TRUE; for (uint internalId = 0; internalId < m_mae.audioDevicesQty; internalId++) { @@ -340,34 +340,25 @@ ma_result MiniAudioEngine::getAllAudioDevices() return result; } -char* MiniAudioEngine::getDeviceName(uint id) -{ - return m_mae.pPlaybackDeviceInfos[m_mae.audioDevicesId[id]].name; - -} - -ma_result MiniAudioEngine::loadMedia(int layer, char *file) +ma_result MiniAudioEngine::loadMedia(int layer, char *file, uint audioDevice) { ma_result result; 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]); m_mae.mediaLoaded[layer] = false; } - ma_sound_config soundConfig = ma_sound_config_init(); - soundConfig = ma_sound_config_init(); - 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]); + result = ma_sound_init_from_file(&m_mae.engines[0], file, \ + MA_SOUND_FLAG_NO_SPATIALIZATION \ + , NULL, NULL, &m_mae.sounds[layer]); 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; } m_mae.mediaLoaded[layer] = true; @@ -438,7 +429,7 @@ void MiniAudioEngine::volChanged(int layer, int vol) db = 0; } else db = ma_volume_db_to_linear(db); - ma_sound_set_fade_in_milliseconds(&m_mae.sounds[layer], -1, db, FADE_TIME); + ma_sound_group_set_fade_in_milliseconds(&m_mae.sounds[layer], -1, db, FADE_TIME); m_mae.currentStatus[layer].vol = vol; } @@ -488,13 +479,12 @@ 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_looping(&m_mae.sounds[layer], loop); result = ma_sound_start(&m_mae.sounds[layer]); - //this->volChanged(layer, m_mae.currentStatus[layer].vol); - float db = (m_mae.currentStatus[layer].vol / 771.0f) - 85.0f; - if (db <= -85.0f) { - db = 0; - } else - db = ma_volume_db_to_linear(db); - ma_sound_set_fade_in_milliseconds(&m_mae.sounds[layer], -1, db, 0); + if (m_mae.currentStatus[layer].cursor != 0) { + usleep(1000 * 50); // Avoid small glitch at start, how to flush the cached buffers in audio pipe line? + } + this->volChanged(layer, m_mae.currentStatus[layer].vol); + default: + break; } if (result == MA_SUCCESS) m_mae.currentStatus[layer].status = status; @@ -510,7 +500,7 @@ ma_result MiniAudioEngine::seekToCursor(int layer, int cursor) return MA_DOES_NOT_EXIST; result = ma_sound_get_length_in_pcm_frames(&m_mae.sounds[layer], &end); if (result != MA_SUCCESS) { return result; } - start = (cursor * end) / 65535; + 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? @@ -535,14 +525,14 @@ void MiniAudioEngine::refreshValues(int layer) { this->seekToCursor(layer, m_mae.currentStatus[layer].cursor); this->panChanged(layer, m_mae.currentStatus[layer].pan); - 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->volChanged(layer, m_mae.currentStatus[layer].vol); + this->pitchChanged(layer, m_mae.currentStatus[layer].pitch); + this->playbackChanged(layer, m_mae.currentStatus[layer].status); } -ma_result MiniAudioEngine::filterParamChanged(int layer, int channel, int value) +ma_result MiniAudioEngine::filterParamChanged(int layer, int audioDevice, int channel, int value) { + (void)audioDevice; ma_result result = MA_SUCCESS; filterBank *fb = &m_mae.filters[layer]; diff --git a/src/miniaudioengine.h b/src/miniaudioengine.h index 38107d8..89095e6 100644 --- a/src/miniaudioengine.h +++ b/src/miniaudioengine.h @@ -43,7 +43,6 @@ typedef struct ma_resource_manager resourceManager; ma_context context; ma_device_info* pPlaybackDeviceInfos; - ma_device_info pSelectedPlaybackDeviceInfos[MAX_AUDIODEVICES]; ma_uint32 playbackDeviceCount; ma_uint32 devicesSelected; ma_bool8 mediaLoaded[MAX_LAYERS]; @@ -59,13 +58,13 @@ class MiniAudioEngine friend class libreMediaServerAudio; public: - static void audioDataCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); - -protected: MiniAudioEngine(); void stopEngine(); bool startEngine(uint layersQty, uint* audioDevicesID, uint audioDevicesQty); - ma_result loadMedia(int layer, char *media); + static void audioDataCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); + +protected: + ma_result loadMedia(int layer, char *media, uint audioDevice); void volChanged(int layer, int vol); void panChanged(int layer, float pan); void pitchChanged(int layer, float pitch); @@ -79,13 +78,12 @@ protected: 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 channel, int value); + 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) - 4.0f; + return ma_volume_linear_to_db(level); }; - char* getDeviceName(uint id); private: MAE m_mae;