Browse Source

Initial SDL engine implementation

Signed-off-by: falkTX <falktx@falktx.com>
tags/v2.5.0
falkTX 2 years ago
parent
commit
96e4fa5719
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
11 changed files with 535 additions and 4 deletions
  1. +5
    -0
      Makefile.print.mk
  2. +12
    -0
      source/Makefile.deps.mk
  3. +6
    -0
      source/Makefile.mk
  4. +8
    -3
      source/backend/CarlaEngine.hpp
  5. +4
    -0
      source/backend/CarlaEngineInit.hpp
  6. +1
    -0
      source/backend/Makefile
  7. +42
    -1
      source/backend/engine/CarlaEngine.cpp
  8. +5
    -0
      source/backend/engine/CarlaEngineNative.cpp
  9. +440
    -0
      source/backend/engine/CarlaEngineSDL.cpp
  10. +10
    -0
      source/backend/engine/Makefile
  11. +2
    -0
      source/utils/CarlaEngineUtils.hpp

+ 5
- 0
Makefile.print.mk View File

@@ -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"



+ 12
- 0
source/Makefile.deps.mk View File

@@ -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)


+ 6
- 0
source/Makefile.mk View File

@@ -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


+ 8
- 3
source/backend/CarlaEngine.hpp View File

@@ -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
};

/*!


+ 4
- 0
source/backend/CarlaEngineInit.hpp View File

@@ -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();

}

// -----------------------------------------------------------------------


+ 1
- 0
source/backend/Makefile View File

@@ -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)


+ 42
- 1
source/backend/engine/CarlaEngine.cpp View File

@@ -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);


+ 5
- 0
source/backend/engine/CarlaEngineNative.cpp View File

@@ -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


+ 440
- 0
source/backend/engine/CarlaEngineSDL.cpp View File

@@ -0,0 +1,440 @@
/*
* Carla Plugin Host
* Copyright (C) 2011-2022 Filipe Coelho <falktx@falktx.com>
*
* 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; i<numDevices; ++i)
gDeviceNames.append(SDL_GetAudioDeviceName(i, 0));
#endif
}

// -------------------------------------------------------------------------------------------------------------------
// RtAudio Engine

class CarlaEngineSDL : public CarlaEngine
{
public:
CarlaEngineSDL()
: CarlaEngine(),
fDeviceId(0),
fDeviceName(),
fAudioOutCount(0),
fAudioIntBufOut(nullptr)
{
carla_debug("CarlaEngineSDL::CarlaEngineSDL()");

// just to make sure
pData->options.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<int>(pData->options.audioSampleRate);
requested.samples = static_cast<Uint16>(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; i<fAudioOutCount; ++i)
fAudioIntBufOut[i] = new float[received.samples];

pData->graph.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<PatchbayGraph>(pData->graph.getPatchbayGraph(), false, false);

callback(true, true,
ENGINE_CALLBACK_ENGINE_STARTED,
0,
pData->options.processMode,
pData->options.transportMode,
static_cast<int>(pData->bufferSize),
static_cast<float>(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<fAudioOutCount; ++i)
delete[] fAudioIntBufOut[i];
delete[] fAudioIntBufOut;
fAudioIntBufOut = nullptr;
}

fAudioOutCount = 0;
fDeviceName.clear();

return false;
}

bool isRunning() const noexcept override
{
return fDeviceId != 0 /*&& SDL_GetAudioDeviceStatus(fDeviceId) == SDL_AUDIO_PLAYING*/;
}

bool isOffline() const noexcept override
{
return false;
}

EngineType getType() const noexcept override
{
return kEngineTypeSDL;
}

const char* getCurrentDriverName() const noexcept override
{
return "SDL";
}

// -------------------------------------------------------------------
// Patchbay

template<class Graph>
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<RackGraph>(pData->graph.getRackGraph(), sendHost, sendOSC);

if (sendHost)
pData->graph.setUsingExternalHost(external);
if (sendOSC)
pData->graph.setUsingExternalOSC(external);

if (external)
return refreshExternalGraphPorts<PatchbayGraph>(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<uint>(static_cast<uint>(len) / sizeof(float) / fAudioOutCount);

const PendingRtEventsRunner prt(this, ulen, true);

// init our deinterleaved audio buffers
for (uint i=0, count=fAudioOutCount; i<count; ++i)
carla_zeroFloats(fAudioIntBufOut[i], ulen);

// initialize events
carla_zeroStructs(pData->events.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

+ 10
- 0
source/backend/engine/Makefile View File

@@ -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)


+ 2
- 0
source/utils/CarlaEngineUtils.hpp View File

@@ -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:


Loading…
Cancel
Save