From 96e4fa5719f685398ae55b34c4ae0096900c993e Mon Sep 17 00:00:00 2001 From: falkTX Date: Sat, 2 Jul 2022 22:04:46 +0100 Subject: [PATCH] Initial SDL engine implementation Signed-off-by: falkTX --- Makefile.print.mk | 5 + source/Makefile.deps.mk | 12 + source/Makefile.mk | 6 + source/backend/CarlaEngine.hpp | 11 +- source/backend/CarlaEngineInit.hpp | 4 + source/backend/Makefile | 1 + source/backend/engine/CarlaEngine.cpp | 43 +- source/backend/engine/CarlaEngineNative.cpp | 5 + source/backend/engine/CarlaEngineSDL.cpp | 440 ++++++++++++++++++++ source/backend/engine/Makefile | 10 + source/utils/CarlaEngineUtils.hpp | 2 + 11 files changed, 535 insertions(+), 4 deletions(-) create mode 100644 source/backend/engine/CarlaEngineSDL.cpp diff --git a/Makefile.print.mk b/Makefile.print.mk index 7068181c1..6094cdcb3 100644 --- a/Makefile.print.mk +++ b/Makefile.print.mk @@ -101,6 +101,11 @@ else @printf -- "ASIO: $(ANS_NO) $(mZ)Windows only$(mE)\n" @printf -- "DirectSound: $(ANS_NO) $(mZ)Windows only$(mE)\n" @printf -- "WASAPI: $(ANS_NO) $(mZ)Windows only$(mE)\n" +endif +ifeq ($(HAVE_SDL),true) + @printf -- "SDL: $(ANS_YES)\n" +else + @printf -- "SDL: $(ANS_NO) $(mS)Missing SDL$(mE)\n" endif @printf -- "\n" diff --git a/source/Makefile.deps.mk b/source/Makefile.deps.mk index 3490d62fb..db7ed11a6 100644 --- a/source/Makefile.deps.mk +++ b/source/Makefile.deps.mk @@ -194,6 +194,8 @@ HAVE_QT4 = $(shell $(PKG_CONFIG) --exists QtCore QtGui && echo true) HAVE_QT5 = $(shell $(PKG_CONFIG) --exists Qt5Core Qt5Gui Qt5Widgets && \ $(PKG_CONFIG) --variable=qt_config Qt5Core | grep -q -v "static" && echo true) HAVE_QT5PKG = $(shell $(PKG_CONFIG) --silence-errors --variable=prefix Qt5OpenGLExtensions 1>/dev/null && echo true) +HAVE_SDL1 = $(shell $(PKG_CONFIG) --exists sdl && echo true) +HAVE_SDL2 = $(shell $(PKG_CONFIG) --exists sdl2 && echo true) HAVE_SNDFILE = $(shell $(PKG_CONFIG) --exists sndfile && echo true) ifeq ($(HAVE_FLUIDSYNTH),true) @@ -381,6 +383,16 @@ QT5_FLAGS = $(shell $(PKG_CONFIG) --cflags Qt5Core Qt5Gui Qt5Widgets) QT5_LIBS = $(shell $(PKG_CONFIG) --libs Qt5Core Qt5Gui Qt5Widgets) endif +ifeq ($(HAVE_SDL2),true) +HAVE_SDL = true +SDL_FLAGS = $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --cflags sdl2) +SDL_LIBS = $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --libs sdl2) +else ifeq ($(HAVE_SDL1),true) +HAVE_SDL = true +SDL_FLAGS = $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --cflags sdl) +SDL_LIBS = $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --libs sdl) +endif + ifeq ($(HAVE_SNDFILE),true) SNDFILE_FLAGS = $(shell $(PKG_CONFIG) --cflags sndfile) SNDFILE_LIBS = $(shell $(PKG_CONFIG) --libs sndfile) diff --git a/source/Makefile.mk b/source/Makefile.mk index dce83fe21..70477119c 100644 --- a/source/Makefile.mk +++ b/source/Makefile.mk @@ -217,6 +217,12 @@ ifeq ($(HAVE_PYQT),true) BASE_FLAGS += -DHAVE_PYQT endif +ifeq ($(HAVE_SDL2),true) +BASE_FLAGS += -DHAVE_SDL -DHAVE_SDL2 +else ifeq ($(HAVE_SDL1),true) +BASE_FLAGS += -DHAVE_SDL -DHAVE_SDL1 +endif + ifeq ($(HAVE_SNDFILE),true) BASE_FLAGS += -DHAVE_SNDFILE endif diff --git a/source/backend/CarlaEngine.hpp b/source/backend/CarlaEngine.hpp index 839d49277..998bd4ff7 100644 --- a/source/backend/CarlaEngine.hpp +++ b/source/backend/CarlaEngine.hpp @@ -62,20 +62,25 @@ enum EngineType { */ kEngineTypeRtAudio = 3, + /*! + * SDL engine type, used to provide Native Audio support. + */ + kEngineTypeSDL = 4, + /*! * Plugin engine type, used to export the engine as a plugin. */ - kEngineTypePlugin = 4, + kEngineTypePlugin = 5, /*! * Bridge engine type, used in BridgePlugin class. */ - kEngineTypeBridge = 5, + kEngineTypeBridge = 6, /*! * Dummy engine type, does not send audio or MIDI anywhere. */ - kEngineTypeDummy = 6 + kEngineTypeDummy = 7 }; /*! diff --git a/source/backend/CarlaEngineInit.hpp b/source/backend/CarlaEngineInit.hpp index 7a9cb28d8..3718b2418 100644 --- a/source/backend/CarlaEngineInit.hpp +++ b/source/backend/CarlaEngineInit.hpp @@ -75,6 +75,10 @@ const char* getRtAudioApiName(uint index); const char* const* getRtAudioApiDeviceNames(uint index); const EngineDriverDeviceInfo* getRtAudioDeviceInfo(uint index, const char* deviceName); +// SDL +CarlaEngine* newSDL(); +const char* const* getSDLDeviceNames(); + } // ----------------------------------------------------------------------- diff --git a/source/backend/Makefile b/source/backend/Makefile index 4c08b4a68..770a9a50e 100644 --- a/source/backend/Makefile +++ b/source/backend/Makefile @@ -82,6 +82,7 @@ STANDALONE_LINK_FLAGS += $(WATER_LIBS) STANDALONE_LINK_FLAGS += $(LIBLO_LIBS) STANDALONE_LINK_FLAGS += $(MAGIC_LIBS) STANDALONE_LINK_FLAGS += $(FLUIDSYNTH_LIBS) +STANDALONE_LINK_FLAGS += $(SDL_LIBS) STANDALONE_LINK_FLAGS += $(X11_LIBS) ifeq ($(USING_JUCE),true) diff --git a/source/backend/engine/CarlaEngine.cpp b/source/backend/engine/CarlaEngine.cpp index d579b83a0..e925350fa 100644 --- a/source/backend/engine/CarlaEngine.cpp +++ b/source/backend/engine/CarlaEngine.cpp @@ -107,6 +107,9 @@ uint CarlaEngine::getDriverCount() # ifdef USING_RTAUDIO count += getRtAudioApiCount(); # endif +# ifdef HAVE_SDL + ++count; +# endif #endif return count; @@ -138,8 +141,14 @@ const char* CarlaEngine::getDriverName(const uint index2) { if (index < count) return getRtAudioApiName(index); + index -= count; } # endif +# ifdef HAVE_SDL + if (index == 0) + return "SDL"; + --index; +# endif #endif carla_stderr("CarlaEngine::getDriverName(%i) - invalid index", index2); @@ -175,8 +184,14 @@ const char* const* CarlaEngine::getDriverDeviceNames(const uint index2) { if (index < count) return getRtAudioApiDeviceNames(index); + index -= count; } # endif +# ifdef HAVE_SDL + if (index == 0) + return getSDLDeviceNames(); + --index; +# endif #endif carla_stderr("CarlaEngine::getDriverDeviceNames(%i) - invalid index", index2); @@ -215,11 +230,25 @@ const EngineDriverDeviceInfo* CarlaEngine::getDriverDeviceInfo(const uint index2 { if (index < count) return getRtAudioDeviceInfo(index, deviceName); + index -= count; } # endif +# ifdef HAVE_SDL + if (index == 0) + { + static uint32_t sdlBufferSizes[] = { 512, 1024, 2048, 4096, 8192, 0 }; + static double sdlSampleRates[] = { 44100.0, 48000.0, 88200.0, 96000.0, 0.0 }; + static EngineDriverDeviceInfo devInfo; + devInfo.hints = 0x0; + devInfo.bufferSizes = sdlBufferSizes; + devInfo.sampleRates = sdlSampleRates; + return &devInfo; + } + --index; +# endif #endif - carla_stderr("CarlaEngine::getDriverDeviceNames(%i, \"%s\") - invalid index", index2, deviceName); + carla_stderr("CarlaEngine::getDriverDeviceInfo(%i, \"%s\") - invalid index", index2, deviceName); return nullptr; } @@ -251,8 +280,14 @@ bool CarlaEngine::showDriverDeviceControlPanel(const uint index2, const char* co { if (index < count) return false; + index -= count; } # endif +# ifdef HAVE_SDL + if (index == 0) + return false; + --index; +# endif #endif carla_stderr("CarlaEngine::showDriverDeviceControlPanel(%i, \"%s\") - invalid index", index2, deviceName); @@ -297,6 +332,7 @@ CarlaEngine* CarlaEngine::newDriverByName(const char* const driverName) if (std::strcmp(driverName, "WASAPI") == 0 || std::strcmp(driverName, "Windows Audio") == 0) return newJuce(AUDIO_API_WASAPI); # endif + # ifdef USING_RTAUDIO // ------------------------------------------------------------------- // common @@ -330,6 +366,11 @@ CarlaEngine* CarlaEngine::newDriverByName(const char* const driverName) if (std::strcmp(driverName, "WASAPI") == 0) return newRtAudio(AUDIO_API_WASAPI); # endif + +# ifdef HAVE_SDL + if (std::strcmp(driverName, "SDL") == 0) + return newSDL(); +# endif #endif carla_stderr("CarlaEngine::newDriverByName(\"%s\") - invalid driver name", driverName); diff --git a/source/backend/engine/CarlaEngineNative.cpp b/source/backend/engine/CarlaEngineNative.cpp index 9f3e672aa..905437b28 100644 --- a/source/backend/engine/CarlaEngineNative.cpp +++ b/source/backend/engine/CarlaEngineNative.cpp @@ -2953,6 +2953,11 @@ const char* const* getRtAudioApiDeviceNames(const uint) { return nullptr; } const EngineDriverDeviceInfo* getRtAudioDeviceInfo(const uint, const char* const) { return nullptr; } #endif +#ifdef HAVE_SDL +CarlaEngine* newSDL() { return nullptr; } +const char* const* getSDLDeviceNames() { return nullptr; } +#endif + } CARLA_BACKEND_END_NAMESPACE diff --git a/source/backend/engine/CarlaEngineSDL.cpp b/source/backend/engine/CarlaEngineSDL.cpp new file mode 100644 index 000000000..4da621275 --- /dev/null +++ b/source/backend/engine/CarlaEngineSDL.cpp @@ -0,0 +1,440 @@ +/* + * Carla Plugin Host + * Copyright (C) 2011-2022 Filipe Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the doc/GPL.txt file. + */ + +#include "CarlaEngineGraph.hpp" +#include "CarlaEngineInit.hpp" +#include "CarlaEngineInternal.hpp" +#include "CarlaStringList.hpp" + +#include "SDL.h" + +#ifndef HAVE_SDL2 +typedef int SDL_AudioDeviceID; +#endif + +CARLA_BACKEND_START_NAMESPACE + +// ------------------------------------------------------------------------------------------------------------------- +// Global static data + +static CarlaStringList gDeviceNames; + +// ------------------------------------------------------------------------------------------------------------------- + +static void initAudioDevicesIfNeeded() +{ + static bool needsInit = true; + + if (! needsInit) + return; + + needsInit = false; + +#ifdef HAVE_SDL2 + SDL_InitSubSystem(SDL_INIT_AUDIO); + + const int numDevices = SDL_GetNumAudioDevices(0); + + for (int i=0; ioptions.transportMode = ENGINE_TRANSPORT_MODE_INTERNAL; + } + + ~CarlaEngineSDL() override + { + CARLA_SAFE_ASSERT(fAudioOutCount == 0); + carla_debug("CarlaEngineSDL::~CarlaEngineSDL()"); + } + + // ------------------------------------- + + bool init(const char* const clientName) override + { + CARLA_SAFE_ASSERT_RETURN(fDeviceId == 0, false); + CARLA_SAFE_ASSERT_RETURN(fAudioOutCount == 0, false); + CARLA_SAFE_ASSERT_RETURN(clientName != nullptr && clientName[0] != '\0', false); + carla_debug("CarlaEngineSDL::init(\"%s\")", clientName); + + if (pData->options.processMode != ENGINE_PROCESS_MODE_CONTINUOUS_RACK && pData->options.processMode != ENGINE_PROCESS_MODE_PATCHBAY) + { + setLastError("Invalid process mode"); + return false; + } + + SDL_AudioSpec requested, received; + carla_zeroStruct(requested); +#ifdef HAVE_SDL2 + requested.format = AUDIO_F32SYS; +#else + requested.format = AUDIO_S16SYS; +#endif + requested.channels = 2; + requested.freq = static_cast(pData->options.audioSampleRate); + requested.samples = static_cast(pData->options.audioBufferSize); + requested.callback = carla_sdl_process_callback; + requested.userdata = this; + +#ifdef HAVE_SDL2 + const char* const deviceName = pData->options.audioDevice != nullptr && pData->options.audioDevice[0] != '\0' + ? pData->options.audioDevice + : nullptr; + + int flags = SDL_AUDIO_ALLOW_FREQUENCY_CHANGE; + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + flags |= SDL_AUDIO_ALLOW_CHANNELS_CHANGE; + + fDeviceId = SDL_OpenAudioDevice(deviceName, 0, &requested, &received, flags); +#else + fDeviceId = SDL_OpenAudio(&requested, &received) == 0 ? 1 : 0; +#endif + + if (fDeviceId == 0) + { + setLastError(SDL_GetError()); + return false; + } + + if (received.channels == 0) + { +#ifdef HAVE_SDL2 + SDL_CloseAudioDevice(fDeviceId); +#else + SDL_CloseAudio(); +#endif + fDeviceId = 0; + setLastError("No output channels available"); + return false; + } + + if (! pData->init(clientName)) + { + close(); + setLastError("Failed to init internal data"); + return false; + } + + pData->bufferSize = received.samples; + pData->sampleRate = received.freq; + pData->initTime(pData->options.transportExtra); + + fAudioOutCount = received.channels; + fAudioIntBufOut = new float*[fAudioOutCount]; + for (uint i=0; igraph.create(0, fAudioOutCount, 0, 0); + +#ifdef HAVE_SDL2 + SDL_PauseAudioDevice(fDeviceId, 0); +#else + SDL_PauseAudio(0); +#endif + + patchbayRefresh(true, false, false); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY) + refreshExternalGraphPorts(pData->graph.getPatchbayGraph(), false, false); + + callback(true, true, + ENGINE_CALLBACK_ENGINE_STARTED, + 0, + pData->options.processMode, + pData->options.transportMode, + static_cast(pData->bufferSize), + static_cast(pData->sampleRate), + getCurrentDriverName()); + return true; + } + + bool close() override + { + carla_debug("CarlaEngineSDL::close()"); + + // close device + if (fDeviceId != 0) + { + // SDL_PauseAudioDevice(fDeviceId, 1); +#ifdef HAVE_SDL2 + SDL_CloseAudioDevice(fDeviceId); +#else + SDL_CloseAudio(); +#endif + fDeviceId = 0; + } + + // clear engine data + CarlaEngine::close(); + + pData->graph.destroy(); + + // cleanup + if (fAudioIntBufOut != nullptr) + { + for (uint i=0; i + bool refreshExternalGraphPorts(Graph* const graph, const bool sendHost, const bool sendOSC) + { + CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false); + + char strBuf[STR_MAX+1U]; + strBuf[STR_MAX] = '\0'; + + ExternalGraph& extGraph(graph->extGraph); + + // --------------------------------------------------------------- + // clear last ports + + extGraph.clear(); + + // --------------------------------------------------------------- + // fill in new ones + + // Audio Out + for (uint i=0; i < fAudioOutCount; ++i) + { + std::snprintf(strBuf, STR_MAX, "playback_%i", i+1); + + PortNameToId portNameToId; + portNameToId.setData(kExternalGraphGroupAudioOut, i+1, strBuf, ""); + + extGraph.audioPorts.outs.append(portNameToId); + } + + // --------------------------------------------------------------- + // now refresh + + if (sendHost || sendOSC) + graph->refresh(sendHost, sendOSC, true, fDeviceName.buffer()); + + return true; + } + + bool patchbayRefresh(const bool sendHost, const bool sendOSC, const bool external) override + { + CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), false); + + if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK) + return refreshExternalGraphPorts(pData->graph.getRackGraph(), sendHost, sendOSC); + + if (sendHost) + pData->graph.setUsingExternalHost(external); + if (sendOSC) + pData->graph.setUsingExternalOSC(external); + + if (external) + return refreshExternalGraphPorts(pData->graph.getPatchbayGraph(), sendHost, sendOSC); + + return CarlaEngine::patchbayRefresh(sendHost, sendOSC, false); + } + + // ------------------------------------------------------------------- + +protected: + void handleAudioProcessCallback(uchar* const stream, const int len) + { + // safety checks + CARLA_SAFE_ASSERT_RETURN(stream != nullptr,); + CARLA_SAFE_ASSERT_RETURN(len > 0,); + +#ifdef HAVE_SDL2 + // direct float type + float* const fstream = (float*)stream; +#else + // signed 16bit int + int16_t* const istream = (int16_t*)stream; +#endif + + const uint ulen = static_cast(static_cast(len) / sizeof(float) / fAudioOutCount); + + const PendingRtEventsRunner prt(this, ulen, true); + + // init our deinterleaved audio buffers + for (uint i=0, count=fAudioOutCount; ievents.in, kMaxEngineEventInternalCount); + carla_zeroStructs(pData->events.out, kMaxEngineEventInternalCount); + + pData->graph.process(pData, nullptr, fAudioIntBufOut, ulen); + + // interleave audio back + for (uint i=0; i < fAudioOutCount; ++i) + { + for (uint j=0; j < ulen; ++j) + { +#ifdef HAVE_SDL2 + // direct float type + fstream[j * fAudioOutCount + i] = fAudioIntBufOut[i][j]; +#else + // signed 16bit int + istream[j * fAudioOutCount + i] = lrintf(carla_fixedValue(-1.0f, 1.0f, fAudioIntBufOut[i][j]) * 32767.0f); +#endif + } + } + } + + // ------------------------------------------------------------------- + + bool connectExternalGraphPort(const uint connectionType, const uint portId, const char* const portName) override + { + CARLA_SAFE_ASSERT_RETURN(connectionType != 0 || (portName != nullptr && portName[0] != '\0'), false); + carla_debug("CarlaEngineSDL::connectExternalGraphPort(%u, %u, \"%s\")", connectionType, portId, portName); + + switch (connectionType) + { + case kExternalGraphConnectionAudioIn1: + case kExternalGraphConnectionAudioIn2: + case kExternalGraphConnectionAudioOut1: + case kExternalGraphConnectionAudioOut2: + return CarlaEngine::connectExternalGraphPort(connectionType, portId, portName); + + case kExternalGraphConnectionMidiInput: + case kExternalGraphConnectionMidiOutput: + break; + } + + return false; + } + + bool disconnectExternalGraphPort(const uint connectionType, const uint portId, const char* const portName) override + { + CARLA_SAFE_ASSERT_RETURN(connectionType != 0 || (portName != nullptr && portName[0] != '\0'), false); + carla_debug("CarlaEngineSDL::disconnectExternalGraphPort(%u, %u, \"%s\")", connectionType, portId, portName); + + switch (connectionType) + { + case kExternalGraphConnectionAudioIn1: + case kExternalGraphConnectionAudioIn2: + case kExternalGraphConnectionAudioOut1: + case kExternalGraphConnectionAudioOut2: + return CarlaEngine::disconnectExternalGraphPort(connectionType, portId, portName); + + case kExternalGraphConnectionMidiInput: + case kExternalGraphConnectionMidiOutput: + break; + } + + return false; + } + + // ------------------------------------------------------------------- + +private: + SDL_AudioDeviceID fDeviceId; + + // current device name + CarlaString fDeviceName; + + // deinterleaved buffers + uint fAudioOutCount; + float** fAudioIntBufOut; + + #define handlePtr ((CarlaEngineSDL*)userData) + + static void carla_sdl_process_callback(void* userData, uchar* stream, int len) + { + handlePtr->handleAudioProcessCallback(stream, len); + } + + #undef handlePtr + + CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaEngineSDL) +}; + +// ----------------------------------------- + +namespace EngineInit { + +CarlaEngine* newSDL() +{ + initAudioDevicesIfNeeded(); + return new CarlaEngineSDL(); +} + +const char* const* getSDLDeviceNames() +{ + initAudioDevicesIfNeeded(); + + if (gDeviceNames.count() == 0) + { + static const char* deviceNames[] = { "Default", nullptr }; + return deviceNames; + } + + static const CharStringListPtr deviceNames = gDeviceNames.toCharStringListPtr(); + return deviceNames; +} + +} + +// ----------------------------------------- + +CARLA_BACKEND_END_NAMESPACE diff --git a/source/backend/engine/Makefile b/source/backend/engine/Makefile index 2756282ca..706e2b7b7 100644 --- a/source/backend/engine/Makefile +++ b/source/backend/engine/Makefile @@ -59,6 +59,11 @@ OBJSa += \ $(OBJDIR)/CarlaEngineRtAudio.cpp.o endif +ifeq ($(HAVE_SDL),true) +OBJSa += \ + $(OBJDIR)/CarlaEngineSDL.cpp.o +endif + OBJSp = $(OBJS) \ $(OBJDIR)/CarlaEngineNative.cpp.exp.o @@ -99,6 +104,11 @@ $(OBJDIR)/CarlaEngineRtAudio.cpp.o: CarlaEngineRtAudio.cpp @echo "Compiling CarlaEngineRtAudio.cpp" $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(RTAUDIO_FLAGS) $(RTMIDI_FLAGS) -c -o $@ +$(OBJDIR)/CarlaEngineSDL.cpp.o: CarlaEngineSDL.cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling CarlaEngineSDL.cpp" + @$(CXX) $< $(BUILD_CXX_FLAGS) $(SDL_FLAGS) -c -o $@ + ifeq ($(MACOS),true) $(OBJDIR)/CarlaEngineNative.cpp.exp.o: CarlaEngineNative.cpp -@mkdir -p $(OBJDIR) diff --git a/source/utils/CarlaEngineUtils.hpp b/source/utils/CarlaEngineUtils.hpp index 4262efec6..8a59eafd8 100644 --- a/source/utils/CarlaEngineUtils.hpp +++ b/source/utils/CarlaEngineUtils.hpp @@ -46,6 +46,8 @@ const char* EngineType2Str(const EngineType type) noexcept return "kEngineTypeJuce"; case kEngineTypeRtAudio: return "kEngineTypeRtAudio"; + case kEngineTypeSDL: + return "kEngineTypeSDL"; case kEngineTypePlugin: return "kEngineTypePlugin"; case kEngineTypeBridge: