diff --git a/docs/changelog.txt b/docs/changelog.txt index c324e44..ef76b53 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -29,24 +29,21 @@ v 0.2.0 Antígona (26/05/2024) - Play all medias found in one folder. - Play all medias in one folder consecutevily. - Play all medias in one folder randomly. ++ Multi audio devices output. v 0.1.3 Leúcade (19/04/2024) - + Ubuntu 22.04 jammy. + Qt 5.15.3. + Pitch. + Loop. v 0.1.2 Mayordomo (12/08/2015) - -- GUI config. -- Several bugs tested in real world. -- Variable layers. -- SFML as audio engine. ++ GUI config. ++ Variable layers. ++ SFML as audio engine. v 0.1.1 Pascual (24/09/2014) - + First Version: 4 layers playing .ogg. + Needs Open Lighting Arquitecture => 0.9.0. + Pure Data as audio engine. - ++ Qt4 diff --git a/docs/roadmap.txt b/docs/roadmap.txt index 9385090..154fd78 100644 --- a/docs/roadmap.txt +++ b/docs/roadmap.txt @@ -25,7 +25,6 @@ v 0.2.2 - Octopus Sound Card support (6 outputs - 8 inputs). v 0.2.1 -- Multi devices output. - mute/panic on layer. - Master Bus Layer: - each layer will have one "Gain" prefader that acts in source, "Vol" in v 1.3. @@ -46,6 +45,7 @@ v 0.2.1 - ¿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 v0.2.0: - Vumeter or indicator about audio output in layer and master, add to sliderGroup. diff --git a/src/libremediaserver-audio.cpp b/src/libremediaserver-audio.cpp index 10cef51..39c700d 100644 --- a/src/libremediaserver-audio.cpp +++ b/src/libremediaserver-audio.cpp @@ -43,11 +43,16 @@ libreMediaServerAudio::libreMediaServerAudio() Q_CHECK_PTR(m_ola); m_ola->blockSignals(true); m_ola->registerUniverse(); - m_mae.startEngine(); + if (!m_mae.startEngine()) { + cout << "Can not start Audio Engine!" << endl; + this->~libreMediaServerAudio(); + } uint *audioDevList = m_settings->getAudioDeviceId(); - for (uint i = 0; i < m_settings->getAudioDeviceQty(); i++) - m_mae.startDevice(audioDevList[i], i); - qDebug("Core init Complete. Start reading DMX."); + if (!m_mae.startDevice(audioDevList, m_settings->getAudioDeviceQty())) { + cout << "Can not start Audio Device!" << audioDevList << endl; + this->~libreMediaServerAudio(); + } + cout << "Core init Complete. Start reading DMX." << endl; m_ola->blockSignals(false); #ifdef NOGUI m_ola->start(QThread::TimeCriticalPriority ); @@ -58,6 +63,9 @@ libreMediaServerAudio::~libreMediaServerAudio() { m_ola->stop(); m_mae.stopEngine(); + sleep(1); + cout << "bye!" << endl; + exit(0); } void libreMediaServerAudio::loadMedia(int layer, int folder, int file) diff --git a/src/libremediaserver-audio.h b/src/libremediaserver-audio.h index d83446a..aed7e45 100644 --- a/src/libremediaserver-audio.h +++ b/src/libremediaserver-audio.h @@ -20,6 +20,10 @@ #ifndef LIBREMEDIASERVERAUDIO_H #define LIBREMEDIASERVERAUDIO_H + +#include +using namespace std; + #include "medialibrary.h" #include "miniaudioengine.h" #include "olathread.h" diff --git a/src/miniaudioengine.cpp b/src/miniaudioengine.cpp index 6051105..a0445c9 100644 --- a/src/miniaudioengine.cpp +++ b/src/miniaudioengine.cpp @@ -1,5 +1,15 @@ #include "miniaudioengine.h" -#include //enum macro +#define LPF_BIAS 0.9f /* Higher values means more bias towards the low pass filter (the low pass filter will be more audible). Lower values means more bias towards the echo. Must be between 0 and 1. */ +#define LPF_CUTOFF_FACTOR 80 /* High values = more filter. */ +#define LPF_ORDER 8 + +//static filterBank m_filterBank[MAX_LAYERS]; +//static ma_node_graph m_nodeGraph[MAX_LAYERS]; +//static soundNode m_soundNode[MAX_LAYERS]; + +static ma_lpf_node g_lpfNode[MAX_AUDIODEVICES]; +static ma_engine m_engine[MAX_AUDIODEVICES]; + MiniAudioEngine::MiniAudioEngine() { for (int i =0; i < MAX_LAYERS; i++) { @@ -12,19 +22,37 @@ MiniAudioEngine::MiniAudioEngine() } } -void MiniAudioEngine::audioDataCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +void MiniAudioEngine::audioDataCallback1(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) { (void)pInput; - //Do master audio processing before sending to device. - ma_engine_read_pcm_frames((ma_engine*)pDevice->pUserData, pOutput, frameCount, NULL); + (void)pDevice; + ma_result result; + result = ma_engine_read_pcm_frames((ma_engine*)pDevice->pUserData, pOutput, frameCount, NULL); + if (result != MA_SUCCESS) { + cout << "1"; + } +} + +void MiniAudioEngine::audioDataCallback2(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + (void)pInput; + (void)pDevice; + ma_result result; + + result = ma_engine_read_pcm_frames((ma_engine*)pDevice->pUserData, pOutput, frameCount, NULL); + if (result != MA_SUCCESS) { + cout << "2"; + } } void MiniAudioEngine::stopEngine() { - ma_engine_uninit(&engine[0]); - ma_device_uninit(&device[0]); - ma_context_uninit(&context); - ma_resource_manager_uninit(&resourceManager); + for (uint i = 0; i < m_devicesSelected; i++) { + ma_engine_uninit(&m_engine[i]); + ma_device_uninit(&m_device[i]); + } + ma_context_uninit(&m_context); + ma_resource_manager_uninit(&m_resourceManager); } bool MiniAudioEngine::startEngine() @@ -32,66 +60,144 @@ bool MiniAudioEngine::startEngine() ma_result result; result = this->startContext(); - if (result != MA_SUCCESS) return result; + if (result != MA_SUCCESS) return false; result = this->getAllAudioDevices(); - return result; + if (result != MA_SUCCESS) return false; + return true; } -ma_result MiniAudioEngine::startDevice(uint systemId, uint internalId) -{ +ma_result MiniAudioEngine::setNodeGraph(int id) { ma_result result; + + //ma_node_graph_config nodeGraphConfig = ma_node_graph_config_init(CHANNELS); + //result = ma_node_graph_init(&nodeGraphConfig, NULL, &m_nodeGraph[id]); + //if (result != MA_SUCCESS) { + // cout << "ERROR " << result << ": Failed to initialize node graph."; + // return MA_ERROR; + //} +/* + ma_loshelf2_config losConfig = ma_loshelf2_config_init(FORMAT, CHANNELS, SAMPLE_RATE, 1, 0.5, 83.3); // double gainDB, double shelfSlope, double frequency) + result = ma_loshelf2_init(&losConfig, NULL, &m_filterBank[id].loShelfNode); + if (result != MA_SUCCESS) { + cout << "Error " << result << ": Can not init loShelf node." << endl; + return result; + } + ma_engine m; + ma_node_graph *ng = (ma_node_graph *)(&m_engine[id]); + ma_node *node = ma_node_graph_get_endpoint(ng); + node = ma_engine_get_endpoint(&m_engine[id]); + ma_node_base nodeBase = ng->endpoint; + result = ma_node_attach_output_bus(&m_filterBank[id].loShelfNode, 0, &nodeBase, 0); + //result = ma_node_attach_output_bus(&m_filterBank[id].loShelfNode, 0, ma_engine_get_endpoint(engine), 0); + if (result != MA_SUCCESS) { + cout << "Error " << result << ": Can not attach filter to graph endpoint." << endl; + return result; + }*/ +/* + ma_splitter_node_config splitterConfig = ma_splitter_node_config_init(ma_engine_get_channels(&engine[0])); + result = ma_splitter_node_init(ng, &splitterConfig, NULL, &m_filterBank[layer].outputNode); + if (result != MA_SUCCESS) { + cout << "Can not init splitter node." << endl; + return result; + } + result = ma_node_attach_output_bus(&m_filterBank[layer].loShelfNode, 0, &m_filterBank[layer].outputNode, 0); + if (result != MA_SUCCESS) { + cout << "Can not attach loShelf to output." << endl; + return result; + } + result = ma_node_attach_output_bus(&m_filterBank[layer].outputNode, 0, ma_node_graph_get_endpoint(ng), 0); + if (result != MA_SUCCESS) { + cout << "Can not attach splitter to graph endpoint." << endl; + return result; + } + }*/ + /* Low Pass Filter. */ + ma_lpf_node_config lpfNodeConfig = ma_lpf_node_config_init(CHANNELS, SAMPLE_RATE, SAMPLE_RATE / LPF_CUTOFF_FACTOR, LPF_ORDER); + ma_node_graph *ng = ma_engine_get_node_graph(&m_engine[id]); + result = ma_lpf_node_init(ng, &lpfNodeConfig, NULL, &g_lpfNode[id]); + if (result != MA_SUCCESS) { + cout << "ERROR " << result << ": Failed to initialize low pass filter node." << endl; + return result; + } + ma_node *endpoint = ma_engine_get_endpoint(&m_engine[id]); + result = ma_node_attach_output_bus(&g_lpfNode[id], 0, endpoint, 0); + if (result != MA_SUCCESS) { + cout << "ERROR " << result << ": Failed to attach low pass filter node." << endl; + return result; + } + result = ma_node_set_output_bus_volume(&g_lpfNode[id], 0, LPF_BIAS); + if (result != MA_SUCCESS) { + cout << "ERROR " << result << ": Failed to set volume low pass filter node." << endl; + return result; + } + ma_node_set_state(&g_lpfNode[id], ma_node_state::ma_node_state_started); + return (result); +} + +bool MiniAudioEngine::startDevice(uint *systemId, uint nb) +{ + ma_result result = MA_SUCCESS; ma_device_config deviceConfig; ma_engine_config engineConfig; - if (systemId >= playbackDeviceCount) - systemId = playbackDeviceCount - 1; - deviceConfig = ma_device_config_init(ma_device_type_playback); - deviceConfig.playback.pDeviceID = &pPlaybackDeviceInfos[systemId].id; - deviceConfig.playback.format = resourceManager.config.decodedFormat; - deviceConfig.playback.channels = 0; - deviceConfig.sampleRate = resourceManager.config.decodedSampleRate; - deviceConfig.dataCallback = audioDataCallback; - deviceConfig.pUserData = &engine[internalId]; - result = ma_device_init(&context, &deviceConfig, &device[internalId]); - if (result != MA_SUCCESS) { - qCritical("Failed to initialize audio device %s.", pPlaybackDeviceInfos[systemId].name); - return result; + m_devicesSelected = nb; + for (uint internalId = 0; internalId < nb; internalId++) { + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.pDeviceID = &m_pPlaybackDeviceInfos[systemId[internalId]].id; + deviceConfig.playback.format = m_resourceManager.config.decodedFormat; + deviceConfig.playback.channels = 0; + deviceConfig.sampleRate = m_resourceManager.config.decodedSampleRate; + if (internalId == 0) + deviceConfig.dataCallback = audioDataCallback1; + else + deviceConfig.dataCallback = audioDataCallback2; + deviceConfig.pUserData = &m_engine[internalId]; + result = ma_device_init(&m_context, &deviceConfig, &m_device[internalId]); + if (result != MA_SUCCESS) { + cout << "Error " << result << ": Failed to initialize audio device " << m_pPlaybackDeviceInfos[*systemId].name << endl; + return false; + } + engineConfig = ma_engine_config_init(); + engineConfig.pDevice = &m_device[internalId]; + engineConfig.pResourceManager = &m_resourceManager; + engineConfig.noAutoStart = MA_TRUE; + result = ma_engine_init(&engineConfig, &m_engine[internalId]); + if (result != MA_SUCCESS) { + cout << "Error " << result << ": Failed to initialize audio engine" << endl; + return false; + } + result = this->setNodeGraph(internalId); + if (result != MA_SUCCESS) { + cout << "Error " << result << ": Failed to set node graph " << systemId[internalId] << endl; + return false; + } + result = ma_engine_start(&m_engine[internalId]); + if (result != MA_SUCCESS) { + cout << "Error " << result << ": Failed to start audio engine" << systemId[internalId] << endl; + return false; + } + cout << "Initialized Audio Device. internalId: " << internalId << " systemId: " << systemId[internalId] << " " << m_pPlaybackDeviceInfos[systemId[internalId]].name << endl; } - engineConfig = ma_engine_config_init(); - engineConfig.pDevice = &device[internalId]; - engineConfig.pResourceManager = &resourceManager; - engineConfig.noAutoStart = MA_TRUE; - result = ma_engine_init(NULL, &engine[internalId]); - if (result != MA_SUCCESS) { - qCritical("Failed to initialize audio engine."); - return result; - } - result = ma_engine_start(&engine[internalId]); - if (result != MA_SUCCESS) { - qCritical("Failed to start audio engine %i.", systemId); - return result; - } - qInfo("Initialized audio device internal: %ui system: %d %s", internalId, systemId, pPlaybackDeviceInfos[systemId].name); - return result; + return true; } ma_result MiniAudioEngine::startContext() { ma_result result; - resourceManagerConfig = ma_resource_manager_config_init(); - resourceManagerConfig.decodedFormat = ma_format_f32; /* ma_format_f32 should almost always be used as that's what the engine (and most everything else) uses for mixing. */ - resourceManagerConfig.decodedChannels = 0; - resourceManagerConfig.decodedSampleRate = ma_standard_sample_rate_44100; - resourceManagerConfig.jobThreadCount = 4; - result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager); + m_resourceManagerConfig = ma_resource_manager_config_init(); + m_resourceManagerConfig.decodedFormat = FORMAT; + m_resourceManagerConfig.decodedChannels = CHANNELS; + m_resourceManagerConfig.decodedSampleRate = SAMPLE_RATE; + m_resourceManagerConfig.jobThreadCount = 4; + result = ma_resource_manager_init(&m_resourceManagerConfig, &m_resourceManager); if (result != MA_SUCCESS) { - qCritical("Failed to initialize audio resource manager."); + cout << "Error " << result << ": Failed to initialize audio resource manager." << endl; return result; } - result = ma_context_init(NULL, 0, NULL, &context); + result = ma_context_init(NULL, 0, NULL, &m_context); if (result != MA_SUCCESS) { - qCritical("Failed to initialize audio context."); + cout << "Error " << result << ": Failed to initialize audio context." << endl; } return result; } @@ -101,15 +207,15 @@ ma_result MiniAudioEngine::getAllAudioDevices() { ma_result result; - result = ma_context_get_devices(&context, &pPlaybackDeviceInfos, &playbackDeviceCount, NULL, NULL); + result = ma_context_get_devices(&m_context, &m_pPlaybackDeviceInfos, &m_playbackDeviceCount, NULL, NULL); if (result != MA_SUCCESS) { - qWarning("Failed to enumerate playback devices.\n"); - ma_context_uninit(&context); + cout << "Error" << result << ": Failed to enumerate playback devices." << endl; + ma_context_uninit(&m_context); return result; } - printf("Audio devices available:\n"); - for (ma_uint32 iAvailableDevice = 0; iAvailableDevice < playbackDeviceCount; iAvailableDevice += 1) { - qInfo("%d: : %s", iAvailableDevice, pPlaybackDeviceInfos[iAvailableDevice].name); + cout << "Audio devices available:" << endl; + for (ma_uint32 iAvailableDevice = 0; iAvailableDevice < m_playbackDeviceCount; iAvailableDevice += 1) { + cout << iAvailableDevice << " : " << m_pPlaybackDeviceInfos[iAvailableDevice].name << endl; } return result; } @@ -123,19 +229,49 @@ ma_result MiniAudioEngine::loadMedia(int layer, char *file, uint audioDevice) ma_sound_uninit(&m_currentSound[layer]); m_mediaLoaded[layer] = false; } - result = ma_sound_init_from_file(&engine[audioDevice], file, \ +/* + ma_sound_config soundConfig = ma_sound_config_init(); + soundConfig = ma_sound_config_init(); + soundConfig.pFilePath = file; + soundConfig.pInitialAttachment = &m_filterBank[layer].loShelfNode; + soundConfig.initialAttachmentInputBusIndex = 0; + soundConfig.channelsIn = 0; + soundConfig.channelsOut = CHANNELS; + //soundConfig.monoExpansionMode; + soundConfig.flags = MA_SOUND_FLAG_NO_SPATIALIZATION; + // | MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT; + //| MA_SOUND_FLAG_STREAM | MA_SOUND_FLAG_NO_PITCH + soundConfig.volumeSmoothTimeInPCMFrames = 480; + //soundConfig.initialSeekPointInPCMFrames; + //soundConfig.rangeBegInPCMFrames; + //soundConfig.rangeEndInPCMFrames; + //soundConfig.loopPointBegInPCMFrames; + //soundConfig.loopPointEndInPCMFrames; + //soundConfig.isLooping; + //soundConfig.endCallback; + //soundConfig.pEndCallbackUserData; + + result = ma_sound_init_ex(&m_engine[audioDevice], &soundConfig, &m_currentSound[layer]); + if (result != MA_SUCCESS) { + cout << "Error" << result << ": Failed to load file " << file << endl; + return result; + }*/ + result = ma_sound_init_from_file(&m_engine[audioDevice], file, \ MA_SOUND_FLAG_NO_SPATIALIZATION \ - | MA_SOUND_FLAG_DECODE \ - | MA_SOUND_FLAG_STREAM \ - /*| MA_SOUND_FLAG_NO_PITCH \*/ + // | MA_SOUND_FLAG_DECODE // | MA_SOUND_FLAG_STREAM // | MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT , NULL, NULL, &m_currentSound[layer]); - if (result != MA_SUCCESS) - qWarning("Failed to load file %s", file); - else { - m_mediaLoaded[layer] = true; - this->refreshValues(layer); - m_currentLayerValues[layer].media = file; + if (result != MA_SUCCESS) { + cout << "Error " << result << ": Failed to load file " << file << endl; + return result; } + result = ma_node_attach_output_bus(&m_currentSound[layer], 0, &g_lpfNode[audioDevice], 0); + if (result != MA_SUCCESS) { + cout << "Error " << result << ": Failed to attach output bus " << audioDevice << endl; + //return result; + } + m_mediaLoaded[layer] = true; + this->refreshValues(layer); + m_currentLayerValues[layer].media = file; return result; } @@ -163,7 +299,7 @@ float MiniAudioEngine::getCursor(int layer) result = ma_sound_get_cursor_in_seconds(&m_currentSound[layer], &ret); if (result != MA_SUCCESS) { - qWarning("%i can not get cursor error %i", layer, result); + cout << "Error" << result << ": Can not get cursor " << layer << endl; ret = MA_ERROR; } return ret; @@ -177,11 +313,17 @@ ma_result MiniAudioEngine::printFormatInfo(int layer) if (m_mediaLoaded[layer] == false) return MA_DOES_NOT_EXIST; - ma_result result = ma_sound_get_data_format(&m_currentSound[layer], &format, &channels, &sampleRate, NULL, 0); - if (result != MA_SUCCESS) - qWarning("%i failed to get data format %i\n", layer, result); - else - qInfo() << "Layer:" << layer << m_currentLayerValues[layer].media << "samples/sec:" << sampleRate << "format:" << format << "channels:" << channels; + ma_result result = ma_sound_get_data_format(&m_currentSound[layer], \ + &format, &channels, &sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + cout << "Error" << result << ": Failed to get data format " << layer; + cout << endl; + } else { + cout << "Layer: " << layer; + cout << m_currentLayerValues[layer].media.toLatin1().data(); + cout << " samples/sec:" << sampleRate << " format:" << format; + cout << " channels:" << channels << endl; + } return result; } diff --git a/src/miniaudioengine.h b/src/miniaudioengine.h index b91d718..4f24ce7 100644 --- a/src/miniaudioengine.h +++ b/src/miniaudioengine.h @@ -9,7 +9,32 @@ #define MINIAUDIO_IMPLEMENTATION #include "miniaudio.h" #include "defines.h" // MAX_LAYERS -#include // prints messages +#include +using namespace std; + +/* Data Format */ +#define FORMAT ma_format_f32 /* Must always be f32. */ +#define CHANNELS 2 +#define SAMPLE_RATE 48000 + +typedef struct +{ + ma_node_base node; + ma_loshelf2 loShelfNode; + ma_peak2 midLowNode; + ma_peak2 midHighNode; + ma_hishelf2 hiShelfNode; + ma_splitter_node outputNode; +} filterBank; + +typedef struct +{ + ma_node_base input; + ma_data_source_node node; + ma_decoder decoder; + filterBank filters; +} soundNode; + class MiniAudioEngine { @@ -19,8 +44,13 @@ public: MiniAudioEngine(); void stopEngine(); bool startEngine(); - ma_result startDevice(uint id, uint internalId); - static void audioDataCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); + bool startDevice(uint *id, uint nb); + static void audioDataCallback1(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); + static void audioDataCallback2(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); + + ma_device m_device[MAX_AUDIODEVICES]; + ma_sound m_currentSound[MAX_LAYERS]; + ma_bool8 m_mediaLoaded[MAX_LAYERS]; protected: ma_result loadMedia(int layer, char *media, uint audioDevice); @@ -38,22 +68,19 @@ protected: inline bool getAtEnd(int layer) { return m_currentSound[layer].atEnd; } private: - ma_resource_manager_config resourceManagerConfig; - ma_resource_manager resourceManager; - ma_device_info* pPlaybackDeviceInfos; - ma_uint32 playbackDeviceCount; - ma_uint32 iChosenDevice; - ma_engine engine[MAX_AUDIODEVICES]; - ma_device device[MAX_AUDIODEVICES]; - ma_context context; - ma_sound m_currentSound[MAX_LAYERS]; - ma_bool8 m_mediaLoaded[MAX_LAYERS]; + ma_resource_manager_config m_resourceManagerConfig; + ma_resource_manager m_resourceManager; + ma_context m_context; + ma_device_info* m_pPlaybackDeviceInfos; + ma_uint32 m_playbackDeviceCount; + ma_uint32 m_devicesSelected; layerData m_currentLayerValues[MAX_LAYERS]; ma_result getAllAudioDevices(); ma_result startContext(); void refreshValues(int layer); ma_result seekToCursor(int layer, int cursor); + ma_result setNodeGraph(int id); }; #endif // MINIAUDIOENGINE_H