/* * 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 "CarlaBackendUtils.hpp" #include #ifndef HAVE_SDL2 typedef Uint32 SDL_AudioDeviceID; #endif #ifndef SDL_HINT_AUDIO_DEVICE_APP_NAME # define SDL_HINT_AUDIO_DEVICE_APP_NAME "SDL_AUDIO_DEVICE_APP_NAME" #endif #ifndef SDL_HINT_AUDIO_DEVICE_STREAM_NAME # define SDL_HINT_AUDIO_DEVICE_STREAM_NAME "SDL_AUDIO_DEVICE_STREAM_NAME" #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 SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, clientName); // SDL_SetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME, ); SDL_SetHint(SDL_HINT_AUDIO_RESAMPLING_MODE, "2"); 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 carla_stdout("open fAudioOutCount %d %d %d | %d vs %d", fAudioOutCount, received.samples, received.freq, received.format, requested.format); 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; const uint ulen = static_cast(static_cast(len) / sizeof(float) / fAudioOutCount); #else // signed 16bit int int16_t* const istream = (int16_t*)stream; const uint ulen = static_cast(static_cast(len) / sizeof(int16_t) / fAudioOutCount); #endif 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