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