/* * Carla Bridge Plugin * Copyright (C) 2011-2013 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 GPL.txt file */ #include "CarlaPluginInternal.hpp" #ifndef BUILD_BRIDGE #include "CarlaBridgeUtils.hpp" #include "CarlaShmUtils.hpp" #include #include #ifdef CARLA_OS_WIN # include #endif #include #include #include #include #define CARLA_BRIDGE_CHECK_OSC_TYPES(/* argc, types, */ argcToCompare, typesToCompare) \ /* check argument count */ \ if (argc != argcToCompare) \ { \ carla_stderr("BridgePlugin::%s() - argument count mismatch: %i != %i", __FUNCTION__, argc, argcToCompare); \ return 1; \ } \ if (argc > 0) \ { \ /* check for nullness */ \ if (! (types && typesToCompare)) \ { \ carla_stderr("BridgePlugin::%s() - argument types are null", __FUNCTION__); \ return 1; \ } \ /* check argument types */ \ if (std::strcmp(types, typesToCompare) != 0) \ { \ carla_stderr("BridgePlugin::%s() - argument types mismatch: '%s' != '%s'", __FUNCTION__, types, typesToCompare); \ return 1; \ } \ } CARLA_BACKEND_START_NAMESPACE // ------------------------------------------------------------------------------------------------------------------- // Engine Helpers extern void registerEnginePlugin(CarlaEngine* const engine, const unsigned int id, CarlaPlugin* const plugin); // ------------------------------------------------------------------------------------------------------------------- shm_t shm_mkstemp(char* const fileBase) { static const char charSet[] = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"; const int size = (fileBase != nullptr) ? std::strlen(fileBase) : 0; shm_t shm; carla_shm_init(shm); if (size < 6) { errno = EINVAL; return shm; } if (std::strcmp(fileBase + size - 6, "XXXXXX") != 0) { errno = EINVAL; return shm; } while (true) { for (int c = size - 6; c < size; c++) { // Note the -1 to avoid the trailing '\0' in charSet. fileBase[c] = charSet[std::rand() % (sizeof(charSet) - 1)]; } shm_t shm = carla_shm_create(fileBase); if (carla_is_shm_valid(shm) || errno != EEXIST) return shm; } } // ------------------------------------------------------------------------------------------------------------------- struct BridgeParamInfo { float value; CarlaString name; CarlaString unit; BridgeParamInfo() : value(0.0f) {} }; class BridgePlugin : public CarlaPlugin { public: BridgePlugin(CarlaEngine* const engine, const unsigned int id, const BinaryType btype, const PluginType ptype) : CarlaPlugin(engine, id), fBinaryType(btype), fPluginType(ptype), fInitiated(false), fInitError(false), fSaved(false), fParams(nullptr) { carla_debug("BridgePlugin::BridgePlugin(%p, %i, %s, %s)", engine, id, BinaryType2Str(btype), PluginType2Str(ptype)); kData->osc.thread.setMode(CarlaPluginThread::PLUGIN_THREAD_BRIDGE); } ~BridgePlugin() { carla_debug("BridgePlugin::~BridgePlugin()"); kData->singleMutex.lock(); kData->masterMutex.lock(); _cleanup(); if (kData->osc.data.target != nullptr) { osc_send_hide(&kData->osc.data); osc_send_quit(&kData->osc.data); } kData->osc.data.free(); // Wait a bit first, then force kill if (kData->osc.thread.isRunning() && ! kData->osc.thread.wait(1000)) // kData->engine->getOptions().oscUiTimeout { carla_stderr("Failed to properly stop Plugin Bridge thread"); kData->osc.thread.terminate(); } //info.chunk.clear(); } // ------------------------------------------------------------------- // Information (base) PluginType type() const { return fPluginType; } PluginCategory category() { return fInfo.category; } long uniqueId() const { return fInfo.uniqueId; } // ------------------------------------------------------------------- // Information (count) uint32_t audioInCount() { return fInfo.aIns; } uint32_t audioOutCount() { return fInfo.aOuts; } uint32_t midiInCount() { return fInfo.mIns; } uint32_t midiOutCount() { return fInfo.mOuts; } #if 0 // ------------------------------------------------------------------- // Information (current data) int32_t chunkData(void** const dataPtr) { CARLA_ASSERT(dataPtr); if (! info.chunk.isEmpty()) { *dataPtr = info.chunk.data(); return info.chunk.size(); } return 0; } // ------------------------------------------------------------------- // Information (per-plugin data) double getParameterValue(const uint32_t parameterId) { CARLA_ASSERT(parameterId < param.count); return params[parameterId].value; } #endif void getLabel(char* const strBuf) { std::strncpy(strBuf, (const char*)fInfo.label, STR_MAX); } void getMaker(char* const strBuf) { std::strncpy(strBuf, (const char*)fInfo.maker, STR_MAX); } void getCopyright(char* const strBuf) { std::strncpy(strBuf, (const char*)fInfo.copyright, STR_MAX); } void getRealName(char* const strBuf) { std::strncpy(strBuf, (const char*)fInfo.name, STR_MAX); } #if 0 void getParameterName(const uint32_t parameterId, char* const strBuf) { CARLA_ASSERT(parameterId < param.count); strncpy(strBuf, params[parameterId].name.toUtf8().constData(), STR_MAX); } void getParameterUnit(const uint32_t parameterId, char* const strBuf) { CARLA_ASSERT(parameterId < param.count); strncpy(strBuf, params[parameterId].unit.toUtf8().constData(), STR_MAX); } #endif // ------------------------------------------------------------------- // Set data (internal stuff) int setOscPluginBridgeInfo(const PluginBridgeInfoType type, const int argc, const lo_arg* const* const argv, const char* const types) { carla_stdout("setOscPluginBridgeInfo(%i, %i, %p, \"%s\")", type, argc, argv, types); switch (type) { case kPluginBridgeAudioCount: { CARLA_BRIDGE_CHECK_OSC_TYPES(3, "iii"); const int32_t aIns = argv[0]->i; const int32_t aOuts = argv[1]->i; const int32_t aTotal = argv[2]->i; CARLA_ASSERT(aIns >= 0); CARLA_ASSERT(aOuts >= 0); CARLA_ASSERT(aIns + aOuts == aTotal); fInfo.aIns = aIns; fInfo.aOuts = aOuts; break; // unused (void)aTotal; } case kPluginBridgeMidiCount: { CARLA_BRIDGE_CHECK_OSC_TYPES(3, "iii"); const int32_t mIns = argv[0]->i; const int32_t mOuts = argv[1]->i; const int32_t mTotal = argv[2]->i; CARLA_ASSERT(mIns >= 0); CARLA_ASSERT(mOuts >= 0); CARLA_ASSERT(mIns + mOuts == mTotal); fInfo.mIns = mIns; fInfo.mOuts = mOuts; break; // unused (void)mTotal; } #if 0 case PluginBridgeParameterCount: { CARLA_BRIDGE_CHECK_OSC_TYPES(3, "iii"); const int32_t pIns = argv[0]->i; const int32_t pOuts = argv[1]->i; const int32_t pTotal = argv[2]->i; CARLA_ASSERT(pIns >= 0); CARLA_ASSERT(pOuts >= 0); CARLA_ASSERT(pIns + pOuts <= pTotal); // delete old data if (param.count > 0) { delete[] param.data; delete[] param.ranges; delete[] params; } // create new if needed const int32_t maxParams = x_engine->getOptions().maxParameters; param.count = (pTotal < maxParams) ? pTotal : 0; if (param.count > 0) { param.data = new ParameterData[param.count]; param.ranges = new ParameterRanges[param.count]; params = new BridgeParamInfo[param.count]; } else { param.data = nullptr; param.ranges = nullptr; params = nullptr; } break; Q_UNUSED(pIns); Q_UNUSED(pOuts); } case PluginBridgeProgramCount: { CARLA_BRIDGE_CHECK_OSC_TYPES(1, "i"); const int32_t count = argv[0]->i; CARLA_ASSERT(count >= 0); // delete old programs if (prog.count > 0) { for (uint32_t i=0; i < prog.count; i++) { if (prog.names[i]) free((void*)prog.names[i]); } delete[] prog.names; } prog.count = count; // create new if needed if (prog.count > 0) { prog.names = new const char* [prog.count]; for (uint32_t i=0; i < prog.count; i++) prog.names[i] = nullptr; } else prog.names = nullptr; break; } case PluginBridgeMidiProgramCount: { CARLA_BRIDGE_CHECK_OSC_TYPES(1, "i"); const int32_t count = argv[0]->i; // delete old programs if (midiprog.count > 0) { for (uint32_t i=0; i < midiprog.count; i++) { if (midiprog.data[i].name) free((void*)midiprog.data[i].name); } delete[] midiprog.data; } // create new if needed midiprog.count = count; if (midiprog.count > 0) midiprog.data = new MidiProgramData[midiprog.count]; else midiprog.data = nullptr; break; } #endif case kPluginBridgePluginInfo: { CARLA_BRIDGE_CHECK_OSC_TYPES(7, "iissssh"); const int32_t category = argv[0]->i; const int32_t hints = argv[1]->i; const char* const name = (const char*)&argv[2]->s; const char* const label = (const char*)&argv[3]->s; const char* const maker = (const char*)&argv[4]->s; const char* const copyright = (const char*)&argv[5]->s; const int64_t uniqueId = argv[6]->h; CARLA_ASSERT(category >= 0); CARLA_ASSERT(hints >= 0); CARLA_ASSERT(name); CARLA_ASSERT(label); CARLA_ASSERT(maker); CARLA_ASSERT(copyright); fHints = hints | PLUGIN_IS_BRIDGE; fInfo.category = static_cast(category); fInfo.uniqueId = uniqueId; fInfo.name = name; fInfo.label = label; fInfo.maker = maker; fInfo.copyright = copyright; if (fName.isEmpty()) fName = kData->engine->getNewUniquePluginName(name); break; } #if 0 case PluginBridgeParameterInfo: { CARLA_BRIDGE_CHECK_OSC_TYPES(3, "iss"); const int32_t index = argv[0]->i; const char* const name = (const char*)&argv[1]->s; const char* const unit = (const char*)&argv[2]->s; CARLA_ASSERT(index >= 0 && index < (int32_t)param.count); CARLA_ASSERT(name); CARLA_ASSERT(unit); if (index >= 0 && index < (int32_t)param.count) { params[index].name = QString(name); params[index].unit = QString(unit); } break; } case PluginBridgeParameterData: { CARLA_BRIDGE_CHECK_OSC_TYPES(6, "iiiiii"); const int32_t index = argv[0]->i; const int32_t type = argv[1]->i; const int32_t rindex = argv[2]->i; const int32_t hints = argv[3]->i; const int32_t channel = argv[4]->i; const int32_t cc = argv[5]->i; CARLA_ASSERT(index >= 0 && index < (int32_t)param.count); CARLA_ASSERT(type >= 0); CARLA_ASSERT(rindex >= 0); CARLA_ASSERT(hints >= 0); CARLA_ASSERT(channel >= 0 && channel < 16); CARLA_ASSERT(cc >= -1); if (index >= 0 && index < (int32_t)param.count) { param.data[index].type = (ParameterType)type; param.data[index].index = index; param.data[index].rindex = rindex; param.data[index].hints = hints; param.data[index].midiChannel = channel; param.data[index].midiCC = cc; } break; } case PluginBridgeParameterRanges: { CARLA_BRIDGE_CHECK_OSC_TYPES(7, "idddddd"); const int32_t index = argv[0]->i; const double def = argv[1]->d; const double min = argv[2]->d; const double max = argv[3]->d; const double step = argv[4]->d; const double stepSmall = argv[5]->d; const double stepLarge = argv[6]->d; CARLA_ASSERT(index >= 0 && index < (int32_t)param.count); CARLA_ASSERT(min < max); CARLA_ASSERT(def >= min); CARLA_ASSERT(def <= max); if (index >= 0 && index < (int32_t)param.count) { param.ranges[index].def = def; param.ranges[index].min = min; param.ranges[index].max = max; param.ranges[index].step = step; param.ranges[index].stepSmall = stepSmall; param.ranges[index].stepLarge = stepLarge; params[index].value = def; } break; } case PluginBridgeProgramInfo: { CARLA_BRIDGE_CHECK_OSC_TYPES(2, "is"); const int32_t index = argv[0]->i; const char* const name = (const char*)&argv[1]->s; CARLA_ASSERT(index >= 0 && index < (int32_t)prog.count); CARLA_ASSERT(name); if (index >= 0 && index < (int32_t)prog.count) { if (prog.names[index]) free((void*)prog.names[index]); prog.names[index] = strdup(name); } break; } case PluginBridgeMidiProgramInfo: { CARLA_BRIDGE_CHECK_OSC_TYPES(4, "iiis"); const int32_t index = argv[0]->i; const int32_t bank = argv[1]->i; const int32_t program = argv[2]->i; const char* const name = (const char*)&argv[3]->s; CARLA_ASSERT(index >= 0 && index < (int32_t)midiprog.count); CARLA_ASSERT(bank >= 0); CARLA_ASSERT(program >= 0 && program < 128); CARLA_ASSERT(name); if (index >= 0 && index < (int32_t)midiprog.count) { if (midiprog.data[index].name) free((void*)midiprog.data[index].name); midiprog.data[index].bank = bank; midiprog.data[index].program = program; midiprog.data[index].name = strdup(name); } break; } case PluginBridgeConfigure: { CARLA_BRIDGE_CHECK_OSC_TYPES(2, "ss"); const char* const key = (const char*)&argv[0]->s; const char* const value = (const char*)&argv[1]->s; CARLA_ASSERT(key); CARLA_ASSERT(value); if (! (key && value)) { // invalid pass(); } else if (std::strcmp(key, CARLA_BRIDGE_MSG_HIDE_GUI) == 0) { x_engine->callback(CALLBACK_SHOW_GUI, m_id, 0, 0, 0.0, nullptr); } else if (std::strcmp(key, CARLA_BRIDGE_MSG_SAVED) == 0) { m_saved = true; } break; } case PluginBridgeSetParameterValue: { CARLA_BRIDGE_CHECK_OSC_TYPES(2, "id"); const int32_t index = argv[0]->i; const double value = argv[1]->d; CARLA_ASSERT(index != PARAMETER_NULL); setParameterValueByRIndex(index, value, false, true, true); break; } case PluginBridgeSetDefaultValue: { CARLA_BRIDGE_CHECK_OSC_TYPES(2, "id"); const int32_t index = argv[0]->i; const double value = argv[1]->d; CARLA_ASSERT(index >= 0 && index < (int32_t)param.count); if (index >= 0 && index < (int32_t)param.count) param.ranges[index].def = value; break; } case PluginBridgeSetProgram: { CARLA_BRIDGE_CHECK_OSC_TYPES(1, "i"); const int32_t index = argv[0]->i; CARLA_ASSERT(index < (int32_t)prog.count); setProgram(index, false, true, true, true); break; } case PluginBridgeSetMidiProgram: { CARLA_BRIDGE_CHECK_OSC_TYPES(1, "i"); const int32_t index = argv[0]->i; CARLA_ASSERT(index < (int32_t)midiprog.count); setMidiProgram(index, false, true, true, true); break; } case PluginBridgeSetCustomData: { CARLA_BRIDGE_CHECK_OSC_TYPES(3, "sss"); const char* const type = (const char*)&argv[0]->s; const char* const key = (const char*)&argv[1]->s; const char* const value = (const char*)&argv[2]->s; CARLA_ASSERT(type); CARLA_ASSERT(key); CARLA_ASSERT(value); setCustomData(type, key, value, false); break; } case PluginBridgeSetChunkData: { CARLA_BRIDGE_CHECK_OSC_TYPES(1, "s"); const char* const chunkFileChar = (const char*)&argv[0]->s; CARLA_ASSERT(chunkFileChar); QString chunkFileStr(chunkFileChar); #ifndef Q_OS_WIN // Using Wine, fix temp dir if (m_binary == BINARY_WIN32 || m_binary == BINARY_WIN64) { // Get WINEPREFIX QString wineDir; if (const char* const WINEPREFIX = getenv("WINEPREFIX")) wineDir = QString(WINEPREFIX); else wineDir = QDir::homePath() + "/.wine"; QStringList chunkFileStrSplit1 = chunkFileStr.split(":/"); QStringList chunkFileStrSplit2 = chunkFileStrSplit1.at(1).split("\\"); QString wineDrive = chunkFileStrSplit1.at(0).toLower(); QString wineTMP = chunkFileStrSplit2.at(0); QString baseName = chunkFileStrSplit2.at(1); chunkFileStr = wineDir; chunkFileStr += "/drive_"; chunkFileStr += wineDrive; chunkFileStr += "/"; chunkFileStr += wineTMP; chunkFileStr += "/"; chunkFileStr += baseName; chunkFileStr = QDir::toNativeSeparators(chunkFileStr); } #endif QFile chunkFile(chunkFileStr); if (chunkFile.open(QIODevice::ReadOnly)) { info.chunk = chunkFile.readAll(); chunkFile.close(); chunkFile.remove(); } break; } #endif case kPluginBridgeUpdateNow: { fInitiated = true; break; } case kPluginBridgeError: { CARLA_BRIDGE_CHECK_OSC_TYPES(1, "s"); const char* const error = (const char*)&argv[0]->s; CARLA_ASSERT(error != nullptr); kData->engine->setLastError(error); fInitError = true; fInitiated = true; break; } } return 0; } #if 0 // ------------------------------------------------------------------- // Set data (plugin-specific stuff) void setParameterValue(const uint32_t parameterId, double value, const bool sendGui, const bool sendOsc, const bool sendCallback) { CARLA_ASSERT(parameterId < param.count); params[parameterId].value = fixParameterValue(value, param.ranges[parameterId]); CarlaPlugin::setParameterValue(parameterId, value, sendGui, sendOsc, sendCallback); } void setCustomData(const char* const type, const char* const key, const char* const value, const bool sendGui) { CARLA_ASSERT(type); CARLA_ASSERT(key); CARLA_ASSERT(value); if (sendGui) { // TODO - if type is chunk|binary, store it in a file and send path instead QString cData; cData = type; cData += "·"; cData += key; cData += "·"; cData += value; osc_send_configure(&osc.data, CARLA_BRIDGE_MSG_SET_CUSTOM, cData.toUtf8().constData()); } CarlaPlugin::setCustomData(type, key, value, sendGui); } void setChunkData(const char* const stringData) { CARLA_ASSERT(m_hints & PLUGIN_USES_CHUNKS); CARLA_ASSERT(stringData); QString filePath; filePath = QDir::tempPath(); filePath += "/.CarlaChunk_"; filePath += m_name; filePath = QDir::toNativeSeparators(filePath); QFile file(filePath); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(&file); out << stringData; file.close(); osc_send_configure(&osc.data, CARLA_BRIDGE_MSG_SET_CHUNK, filePath.toUtf8().constData()); } } #endif // ------------------------------------------------------------------- // Set gui stuff void showGui(const bool yesNo) { if (yesNo) osc_send_show(&kData->osc.data); else osc_send_hide(&kData->osc.data); } void idleGui() { if (! kData->osc.thread.isRunning()) carla_stderr2("TESTING: Bridge has closed!"); CarlaPlugin::idleGui(); } // ------------------------------------------------------------------- // Plugin state void reload() { carla_debug("BridgePlugin::reload() - start"); CARLA_ASSERT(kData->engine != nullptr); if (kData->engine == nullptr) return; const ProcessMode processMode(kData->engine->getProccessMode()); // Safely disable plugin for reload const ScopedDisabler sd(this); deleteBuffers(); bool needsCtrlIn, needsCtrlOut; needsCtrlIn = needsCtrlOut = false; if (fInfo.aIns > 0) { kData->audioIn.createNew(fInfo.aIns); } if (fInfo.aOuts > 0) { kData->audioOut.createNew(fInfo.aOuts); needsCtrlIn = true; } if (fInfo.mIns > 0) needsCtrlIn = true; if (fInfo.mOuts > 0) needsCtrlOut = true; const uint portNameSize = kData->engine->maxPortNameSize(); CarlaString portName; // Audio Ins for (uint32_t j=0; j < fInfo.aIns; j++) { portName.clear(); if (processMode == PROCESS_MODE_SINGLE_CLIENT) { portName = fName; portName += ":"; } if (fInfo.aIns > 1) { portName += "input_"; portName += CarlaString(j+1); } else portName += "input"; portName.truncate(portNameSize); kData->audioIn.ports[j].port = (CarlaEngineAudioPort*)kData->client->addPort(kEnginePortTypeAudio, portName, true); kData->audioIn.ports[j].rindex = j; } // Audio Outs for (uint32_t j=0; j < fInfo.aOuts; j++) { portName.clear(); if (processMode == PROCESS_MODE_SINGLE_CLIENT) { portName = fName; portName += ":"; } if (fInfo.aOuts > 1) { portName += "output_"; portName += CarlaString(j+1); } else portName += "output"; portName.truncate(portNameSize); kData->audioOut.ports[j].port = (CarlaEngineAudioPort*)kData->client->addPort(kEnginePortTypeAudio, portName, false); kData->audioOut.ports[j].rindex = j; } if (needsCtrlIn) { portName.clear(); if (processMode == PROCESS_MODE_SINGLE_CLIENT) { portName = fName; portName += ":"; } portName += "event-in"; portName.truncate(portNameSize); kData->event.portIn = (CarlaEngineEventPort*)kData->client->addPort(kEnginePortTypeEvent, portName, true); } if (needsCtrlOut) { portName.clear(); if (processMode == PROCESS_MODE_SINGLE_CLIENT) { portName = fName; portName += ":"; } portName += "event-out"; portName.truncate(portNameSize); kData->event.portOut = (CarlaEngineEventPort*)kData->client->addPort(kEnginePortTypeEvent, portName, false); } //bufferSizeChanged(kData->engine->getBufferSize()); //reloadPrograms(true); carla_debug("BridgePlugin::reload() - end"); } #if 0 void prepareForSave() { m_saved = false; osc_send_configure(&osc.data, CARLA_BRIDGE_MSG_SAVE_NOW, ""); for (int i=0; i < 200; i++) { if (m_saved) break; carla_msleep(50); } if (! m_saved) carla_stderr("BridgePlugin::prepareForSave() - Timeout while requesting save state"); else carla_debug("BridgePlugin::prepareForSave() - success!"); } #endif // ------------------------------------------------------------------- // Plugin processing void process(float** const inBuffer, float** const outBuffer, const uint32_t frames) { uint32_t i, k; // -------------------------------------------------------------------------------------------------------- // Check if active if (! kData->active) { // disable any output sound for (i=0; i < kData->audioOut.count; i++) carla_zeroFloat(outBuffer[i], frames); //kData->activeBefore = kData->active; return; } for (i=0; i < fInfo.aIns; ++i) carla_copyFloat(fShmAudioPool.data + i * frames, inBuffer[i], frames); rdwr_writeOpcode(&fShmControl.data->ringBuffer, kPluginBridgeOpcodeProcess); rdwr_commitWrite(&fShmControl.data->ringBuffer); waitForServer(); for (i=0; i < fInfo.aOuts; ++i) carla_copyFloat(outBuffer[i], fShmAudioPool.data + (i+fInfo.aIns) * frames, frames); } // ------------------------------------------------------------------- // Post-poned events void uiParameterChange(const uint32_t index, const float value) { CARLA_ASSERT(index < kData->param.count); if (index >= kData->param.count) return; if (kData->osc.data.target == nullptr) return; osc_send_control(&kData->osc.data, kData->param.data[index].rindex, value); } void uiProgramChange(const uint32_t index) { CARLA_ASSERT(index < kData->prog.count); if (index >= kData->prog.count) return; if (kData->osc.data.target == nullptr) return; osc_send_program(&kData->osc.data, index); } void uiMidiProgramChange(const uint32_t index) { CARLA_ASSERT(index < kData->midiprog.count); if (index >= kData->midiprog.count) return; if (kData->osc.data.target == nullptr) return; osc_send_midi_program(&kData->osc.data, index); } void uiNoteOn(const uint8_t channel, const uint8_t note, const uint8_t velo) { CARLA_ASSERT(channel < MAX_MIDI_CHANNELS); CARLA_ASSERT(note < MAX_MIDI_NOTE); CARLA_ASSERT(velo > 0 && velo < MAX_MIDI_VALUE); if (channel >= MAX_MIDI_CHANNELS) return; if (note >= MAX_MIDI_NOTE) return; if (velo >= MAX_MIDI_VALUE) return; if (kData->osc.data.target == nullptr) return; uint8_t midiData[4] = { 0 }; midiData[1] = MIDI_STATUS_NOTE_ON + channel; midiData[2] = note; midiData[3] = velo; osc_send_midi(&kData->osc.data, midiData); } void uiNoteOff(const uint8_t channel, const uint8_t note) { CARLA_ASSERT(channel < MAX_MIDI_CHANNELS); CARLA_ASSERT(note < MAX_MIDI_NOTE); if (channel >= MAX_MIDI_CHANNELS) return; if (note >= MAX_MIDI_NOTE) return; if (kData->osc.data.target == nullptr) return; uint8_t midiData[4] = { 0 }; midiData[1] = MIDI_STATUS_NOTE_OFF + channel; midiData[2] = note; osc_send_midi(&kData->osc.data, midiData); } void bufferSizeChanged(const uint32_t newBufferSize) { resizeAudioPool(newBufferSize); } // ------------------------------------------------------------------- bool init(const char* const filename, const char* const name, const char* const label, const char* const bridgeBinary) { CARLA_ASSERT(kData->engine != nullptr); CARLA_ASSERT(kData->client == nullptr); CARLA_ASSERT(filename != nullptr); // --------------------------------------------------------------- // first checks if (kData->engine == nullptr) { return false; } if (kData->client != nullptr) { kData->engine->setLastError("Plugin client is already registered"); return false; } if (filename == nullptr) { kData->engine->setLastError("null filename"); return false; } // --------------------------------------------------------------- // set info if (name != nullptr) fName = kData->engine->getNewUniquePluginName(name); fFilename = filename; // --------------------------------------------------------------- // register client kData->client = kData->engine->addClient(this); if (kData->client == nullptr || ! kData->client->isOk()) { kData->engine->setLastError("Failed to register plugin client"); return false; } // --------------------------------------------------------------- // SHM Audio Pool { char tmpFileBase[60]; std::srand(std::time(NULL)); std::sprintf(tmpFileBase, "/carla-bridge_shm_XXXXXX"); fShmAudioPool.shm = shm_mkstemp(tmpFileBase); if (! carla_is_shm_valid(fShmAudioPool.shm)) { //_cleanup(); carla_stdout("Failed to open or create shared memory file #1"); return false; } fShmAudioPool.filename = tmpFileBase; } // --------------------------------------------------------------- // SHM Control { char tmpFileBase[60]; std::sprintf(tmpFileBase, "/carla-bridge_shc_XXXXXX"); fShmControl.shm = shm_mkstemp(tmpFileBase); if (! carla_is_shm_valid(fShmControl.shm)) { //_cleanup(); carla_stdout("Failed to open or create shared memory file #2"); return false; } fShmControl.filename = tmpFileBase; if (! carla_shm_map(fShmControl.shm, fShmControl.data)) { //_cleanup(); carla_stdout("Failed to mmap shared memory file"); return false; } CARLA_ASSERT(fShmControl.data != nullptr); std::memset(fShmControl.data, 0, sizeof(BridgeShmControl)); std::strcpy(fShmControl.data->ringBuffer.buf, "This thing is actually working!!!!"); if (sem_init(&fShmControl.data->runServer, 1, 0) != 0) { //_cleanup(); carla_stdout("Failed to initialize shared memory semaphore #1"); return false; } if (sem_init(&fShmControl.data->runClient, 1, 0) != 0) { //_cleanup(); carla_stdout("Failed to initialize shared memory semaphore #2"); return false; } } // initial values rdwr_writeOpcode(&fShmControl.data->ringBuffer, kPluginBridgeOpcodeBufferSize); rdwr_writeInt(&fShmControl.data->ringBuffer, kData->engine->getBufferSize()); rdwr_writeOpcode(&fShmControl.data->ringBuffer, kPluginBridgeOpcodeSampleRate); rdwr_writeFloat(&fShmControl.data->ringBuffer, kData->engine->getSampleRate()); rdwr_commitWrite(&fShmControl.data->ringBuffer); // register plugin now so we can receive OSC (and wait for it) fHints |= PLUGIN_IS_BRIDGE; registerEnginePlugin(kData->engine, fId, this); // init OSC { char shmIdStr[12+1] = { 0 }; std::strncpy(shmIdStr, &fShmAudioPool.filename[fShmAudioPool.filename.length()-6], 6); std::strncat(shmIdStr, &fShmControl.filename[fShmControl.filename.length()-6], 6); kData->osc.thread.setOscData(bridgeBinary, label, getPluginTypeAsString(fPluginType), shmIdStr); kData->osc.thread.start(); //kData->osc.thread.waitForStarted(); } for (int i=0; i < 200; i++) { if (fInitiated || ! kData->osc.thread.isRunning()) break; carla_msleep(50); } if (! fInitiated) { // unregister so it gets handled properly registerEnginePlugin(kData->engine, fId, nullptr); if (kData->osc.thread.isRunning()) kData->osc.thread.terminate(); kData->engine->setLastError("Timeout while waiting for a response from plugin-bridge\n(or the plugin crashed on initialization?)"); return false; } else if (fInitError) { // unregister so it gets handled properly registerEnginePlugin(kData->engine, fId, nullptr); kData->osc.thread.quit(); // last error was set before return false; } resizeAudioPool(kData->engine->getBufferSize()); rdwr_writeOpcode(&fShmControl.data->ringBuffer, kPluginBridgeOpcodeReadyWait); rdwr_writeInt(&fShmControl.data->ringBuffer, fShmAudioPool.size); rdwr_commitWrite(&fShmControl.data->ringBuffer); waitForServer(); return true; } private: const BinaryType fBinaryType; const PluginType fPluginType; bool fInitiated; bool fInitError; bool fSaved; struct BridgeAudioPool { CarlaString filename; float* data; size_t size; shm_t shm; BridgeAudioPool() : data(nullptr), size(0) { carla_shm_init(shm); } } fShmAudioPool; struct BridgeControl { CarlaString filename; BridgeShmControl* data; shm_t shm; BridgeControl() : data(nullptr) { carla_shm_init(shm); } } fShmControl; struct Info { uint32_t aIns, aOuts; uint32_t mIns, mOuts; PluginCategory category; long uniqueId; CarlaString name; CarlaString label; CarlaString maker; CarlaString copyright; //QByteArray chunk; Info() : aIns(0), aOuts(0), mIns(0), mOuts(0), category(PLUGIN_CATEGORY_NONE), uniqueId(0) {} } fInfo; BridgeParamInfo* fParams; void _cleanup() { if (fShmAudioPool.filename.isNotEmpty()) fShmAudioPool.filename.clear(); if (fShmControl.filename.isNotEmpty()) fShmControl.filename.clear(); if (fShmAudioPool.data != nullptr) { carla_shm_unmap(fShmAudioPool.shm, fShmAudioPool.data, fShmAudioPool.size); fShmAudioPool.data = nullptr; } fShmAudioPool.size = 0; // and again if (fShmControl.data != nullptr) { carla_shm_unmap(fShmControl.shm, fShmControl.data, sizeof(BridgeShmControl)); fShmControl.data = nullptr; } if (carla_is_shm_valid(fShmAudioPool.shm)) carla_shm_close(fShmAudioPool.shm); if (carla_is_shm_valid(fShmControl.shm)) carla_shm_close(fShmControl.shm); } void resizeAudioPool(const uint32_t bufferSize) { if (fShmAudioPool.data != nullptr) carla_shm_unmap(fShmAudioPool.shm, fShmAudioPool.data, fShmAudioPool.size); fShmAudioPool.size = (fInfo.aIns+fInfo.aOuts)*bufferSize*sizeof(float); if (fShmAudioPool.size > 0) fShmAudioPool.data = (float*)carla_shm_map(fShmAudioPool.shm, fShmAudioPool.size); else fShmAudioPool.data = nullptr; } void waitForServer() { sem_post(&fShmControl.data->runServer); timespec ts_timeout; #ifdef CARLA_OS_WIN timeval now; gettimeofday(&now, nullptr); ts_timeout.tv_sec = now.tv_sec; ts_timeout.tv_nsec = now.tv_usec * 1000; #else clock_gettime(CLOCK_REALTIME, &ts_timeout); #endif ts_timeout.tv_sec += 5; if (sem_timedwait(&fShmControl.data->runClient, &ts_timeout) != 0) kData->active = false; // TODO } CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(BridgePlugin) }; CARLA_BACKEND_END_NAMESPACE #endif // ! BUILD_BRIDGE CARLA_BACKEND_START_NAMESPACE CarlaPlugin* CarlaPlugin::newBridge(const Initializer& init, BinaryType btype, PluginType ptype, const char* const bridgeBinary) { carla_debug("CarlaPlugin::newBridge({%p, \"%s\", \"%s\", \"%s\"}, %s, %s, \"%s\")", init.engine, init.filename, init.name, init.label, BinaryType2Str(btype), PluginType2Str(ptype), bridgeBinary); #ifndef BUILD_BRIDGE if (bridgeBinary == nullptr) { init.engine->setLastError("Bridge not possible, bridge-binary not found"); return nullptr; } BridgePlugin* const plugin = new BridgePlugin(init.engine, init.id, btype, ptype); if (! plugin->init(init.filename, init.name, init.label, bridgeBinary)) { delete plugin; return nullptr; } plugin->reload(); return plugin; #else init.engine->setLastError("Plugin bridge support not available"); return nullptr; // unused (void)bridgeBinary; #endif } #ifndef BUILD_BRIDGE // ------------------------------------------------------------------- // Bridge Helper int CarlaPluginSetOscBridgeInfo(CarlaPlugin* const plugin, const PluginBridgeInfoType type, const int argc, const lo_arg* const* const argv, const char* const types) { return ((BridgePlugin*)plugin)->setOscPluginBridgeInfo(type, argc, argv, types); } #endif CARLA_BACKEND_END_NAMESPACE