From 0584579c1645a894d1f50f60669a0163cab61c19 Mon Sep 17 00:00:00 2001 From: falkTX Date: Fri, 15 Sep 2017 23:26:46 +0200 Subject: [PATCH] Import the super experimental libjack code --- source/backend/plugin/CarlaPluginJack.cpp | 1605 ++++++++++++++++++++- source/interposer/Makefile | 39 +- source/interposer/libjack.cpp | 1490 +++++++++++++++++++ source/tests/carla-plugin-jack.py | 5 +- 4 files changed, 3114 insertions(+), 25 deletions(-) create mode 100644 source/interposer/libjack.cpp diff --git a/source/backend/plugin/CarlaPluginJack.cpp b/source/backend/plugin/CarlaPluginJack.cpp index 08f7ea324..77e5545ed 100644 --- a/source/backend/plugin/CarlaPluginJack.cpp +++ b/source/backend/plugin/CarlaPluginJack.cpp @@ -1,6 +1,6 @@ /* - * Carla JACK Plugin - * Copyright (C) 2017 Filipe Coelho + * Carla Plugin JACK + * Copyright (C) 2016-2017 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 @@ -15,31 +15,1608 @@ * For a full copy of the GNU General Public License see the doc/GPL.txt file. */ -#include "CarlaPlugin.hpp" -#include "CarlaEngine.hpp" -#include "CarlaUtils.hpp" - #ifdef BUILD_BRIDGE # error This file should not be used under bridge mode #endif +#include "CarlaPluginInternal.hpp" + +#include "CarlaBackendUtils.hpp" +#include "CarlaBridgeUtils.hpp" +#include "CarlaEngineUtils.hpp" +#include "CarlaMathUtils.hpp" +#include "CarlaShmUtils.hpp" +#include "CarlaThread.hpp" + +#include "jackbridge/JackBridge.hpp" + +#include + +// ------------------------------------------------------------------------------------------------------------------- + +using juce::ChildProcess; +using juce::File; +using juce::ScopedPointer; +using juce::String; +using juce::StringArray; +using juce::Time; + CARLA_BACKEND_START_NAMESPACE // ------------------------------------------------------------------------------------------------------------------- +// Fallback data -CarlaPlugin* CarlaPlugin::newJackApp(const Initializer& init) +static const ExternalMidiNote kExternalMidiNoteFallback = { -1, 0, 0 }; + +// ------------------------------------------------------------------------------------------------------------------- + +class CarlaPluginJackThread : public CarlaThread { - carla_debug("CarlaPlugin::newVST3({%p, \"%s\", \"%s\", " P_INT64 "})", init.engine, init.filename, init.name, init.uniqueId); +public: + CarlaPluginJackThread(CarlaEngine* const engine, CarlaPlugin* const plugin) noexcept + : CarlaThread("CarlaPluginJackThread"), + kEngine(engine), + kPlugin(plugin), + fShmIds(), + fExtraArgs(), + fProcess() {} + + void setData(const char* const shmIds, const char* const args) noexcept + { + CARLA_SAFE_ASSERT_RETURN(shmIds != nullptr && shmIds[0] != '\0',); + CARLA_SAFE_ASSERT(! isThreadRunning()); + + fShmIds = shmIds; + fExtraArgs = args; + } + + uintptr_t getProcessID() const noexcept + { + CARLA_SAFE_ASSERT_RETURN(fProcess != nullptr, 0); + + return (uintptr_t)fProcess->getPID(); + } + +protected: + void run() + { + if (fProcess == nullptr) + { + fProcess = new ChildProcess(); + } + else if (fProcess->isRunning()) + { + carla_stderr("CarlaPluginJackThread::run() - already running"); + } + + String name(kPlugin->getName()); + String filename(kPlugin->getFilename()); + + if (name.isEmpty()) + name = "(none)"; + + CARLA_SAFE_ASSERT_RETURN(filename.isNotEmpty(),); + + StringArray arguments; + + // binary + arguments.add(filename); + arguments.addTokens(fExtraArgs, true); + + bool started; + + { + char strBuf[STR_MAX+1]; + strBuf[STR_MAX] = '\0'; + + const EngineOptions& options(kEngine->getOptions()); + const ScopedEngineEnvironmentLocker _seel(kEngine); #ifdef CARLA_OS_LINUX - init.engine->setLastError("JACK application support not implemented yet"); - return nullptr; -#else - init.engine->setLastError("JACK application support not available"); - return nullptr; + const char* const oldPreload(std::getenv("LD_PRELOAD")); + + if (oldPreload != nullptr) + ::unsetenv("LD_PRELOAD"); #endif -} + + std::snprintf(strBuf, STR_MAX, P_UINTPTR, options.frontendWinId); + carla_setenv("CARLA_FRONTEND_WIN_ID", strBuf); + carla_setenv("CARLA_SHM_IDS", fShmIds.toRawUTF8()); + carla_setenv("LD_LIBRARY_PATH", "/home/falktx/Personal/FOSS/GIT/falkTX/Carla/bin/jack"); + + started = fProcess->start(arguments); + +#ifdef CARLA_OS_LINUX + if (oldPreload != nullptr) + ::setenv("LD_PRELOAD", oldPreload, 1); + + ::unsetenv("LD_LIBRARY_PATH"); +#endif + } + + if (! started) + { + carla_stdout("failed!"); + fProcess = nullptr; + return; + } + + for (; fProcess->isRunning() && ! shouldThreadExit();) + carla_sleep(1); + + // we only get here if bridge crashed or thread asked to exit + if (fProcess->isRunning() && shouldThreadExit()) + { + fProcess->waitForProcessToFinish(2000); + + if (fProcess->isRunning()) + { + carla_stdout("CarlaPluginJackThread::run() - bridge refused to close, force kill now"); + fProcess->kill(); + } + else + { + carla_stdout("CarlaPluginJackThread::run() - bridge auto-closed successfully"); + } + } + else + { + // forced quit, may have crashed + if (fProcess->getExitCode() != 0 /*|| fProcess->exitStatus() == QProcess::CrashExit*/) + { + carla_stderr("CarlaPluginJackThread::run() - bridge crashed"); + + CarlaString errorString("Plugin '" + CarlaString(kPlugin->getName()) + "' has crashed!\n" + "Saving now will lose its current settings.\n" + "Please remove this plugin, and not rely on it from this point."); + kEngine->callback(CarlaBackend::ENGINE_CALLBACK_ERROR, kPlugin->getId(), 0, 0, 0.0f, errorString); + } + } + + fProcess = nullptr; + } + +private: + CarlaEngine* const kEngine; + CarlaPlugin* const kPlugin; + + String fShmIds; + String fExtraArgs; + + ScopedPointer fProcess; + + CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginJackThread) +}; // ------------------------------------------------------------------------------------------------------------------- +class CarlaPluginJack : public CarlaPlugin +{ +public: + CarlaPluginJack(CarlaEngine* const engine, const uint id) + : CarlaPlugin(engine, id), + fInitiated(false), + fInitError(false), + fTimedOut(false), + fTimedError(false), + fProcWaitTime(0), + fLastPongTime(-1), + fBridgeThread(engine, this), + fShmAudioPool(), + fShmRtClientControl(), + fShmNonRtClientControl(), + fShmNonRtServerControl(), + fInfo() + { + carla_debug("CarlaPluginJack::CarlaPluginJack(%p, %i, %s, %s)", engine, id, BinaryType2Str(btype), PluginType2Str(ptype)); + + pData->hints |= PLUGIN_IS_BRIDGE; + } + + ~CarlaPluginJack() override + { + carla_debug("CarlaPluginJack::~CarlaPluginJack()"); + + // close UI + if (pData->hints & PLUGIN_HAS_CUSTOM_UI) + pData->transientTryCounter = 0; + + pData->singleMutex.lock(); + pData->masterMutex.lock(); + + if (pData->client != nullptr && pData->client->isActive()) + pData->client->deactivate(); + + if (pData->active) + { + deactivate(); + pData->active = false; + } + + if (fBridgeThread.isThreadRunning()) + { + fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientQuit); + fShmNonRtClientControl.commitWrite(); + + fShmRtClientControl.writeOpcode(kPluginBridgeRtClientQuit); + fShmRtClientControl.commitWrite(); + + if (! fTimedOut) + waitForClient("stopping", 3000); + } + + fBridgeThread.stopThread(3000); + + fShmNonRtServerControl.clear(); + fShmNonRtClientControl.clear(); + fShmRtClientControl.clear(); + fShmAudioPool.clear(); + + clearBuffers(); + + fInfo.chunk.clear(); + } + + // ------------------------------------------------------------------- + // Information (base) + + PluginType getType() const noexcept override + { + return PLUGIN_JACK; + } + + PluginCategory getCategory() const noexcept override + { + return fInfo.category; + } + + // ------------------------------------------------------------------- + // Information (count) + + uint32_t getMidiInCount() const noexcept override + { + return fInfo.mIns; + } + + uint32_t getMidiOutCount() const noexcept override + { + return fInfo.mOuts; + } + + // ------------------------------------------------------------------- + // Information (current data) + + // ------------------------------------------------------------------- + // Information (per-plugin data) + + uint getOptionsAvailable() const noexcept override + { + return fInfo.optionsAvailable; + } + + void getLabel(char* const strBuf) const noexcept override + { + std::strncpy(strBuf, fInfo.label, STR_MAX); + } + + void getMaker(char* const strBuf) const noexcept override + { + nullStrBuf(strBuf); + } + + void getCopyright(char* const strBuf) const noexcept override + { + nullStrBuf(strBuf); + } + + void getRealName(char* const strBuf) const noexcept override + { + std::strncpy(strBuf, fInfo.name, STR_MAX); + } + + // ------------------------------------------------------------------- + // Set data (state) + + // ------------------------------------------------------------------- + // Set data (internal stuff) + + void setOption(const uint option, const bool yesNo, const bool sendCallback) override + { + { + const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); + + fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientSetOption); + fShmNonRtClientControl.writeUInt(option); + fShmNonRtClientControl.writeBool(yesNo); + fShmNonRtClientControl.commitWrite(); + } + + CarlaPlugin::setOption(option, yesNo, sendCallback); + } + + void setCtrlChannel(const int8_t channel, const bool sendOsc, const bool sendCallback) noexcept override + { + CARLA_SAFE_ASSERT_RETURN(sendOsc || sendCallback,); // never call this from RT + + { + const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); + + fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientSetCtrlChannel); + fShmNonRtClientControl.writeShort(channel); + fShmNonRtClientControl.commitWrite(); + } + + CarlaPlugin::setCtrlChannel(channel, sendOsc, sendCallback); + } + + // ------------------------------------------------------------------- + // Set data (plugin-specific stuff) + + // ------------------------------------------------------------------- + // Set ui stuff + + void idle() override + { + if (fBridgeThread.isThreadRunning()) + { + if (fInitiated && fTimedOut && pData->active) + setActive(false, true, true); + + { + const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); + + fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientPing); + fShmNonRtClientControl.commitWrite(); + } + + try { + handleNonRtData(); + } CARLA_SAFE_EXCEPTION("handleNonRtData"); + } + else if (fInitiated) + { + fTimedOut = true; + fTimedError = true; + fInitiated = false; + pData->engine->callback(ENGINE_CALLBACK_PLUGIN_UNAVAILABLE, pData->id, 0, 0, 0.0f, + "Plugin bridge has been stopped or crashed"); + } + + CarlaPlugin::idle(); + } + + // ------------------------------------------------------------------- + // Plugin state + + void reload() override + { + CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr,); + carla_debug("CarlaPluginJack::reload() - start"); + + const EngineProcessMode processMode(pData->engine->getProccessMode()); + + // Safely disable plugin for reload + const ScopedDisabler sd(this); + + // cleanup of previous data + pData->audioIn.clear(); + pData->audioOut.clear(); + pData->cvIn.clear(); + pData->cvOut.clear(); + pData->event.clear(); + + bool needsCtrlIn, needsCtrlOut; + needsCtrlIn = needsCtrlOut = false; + + if (fInfo.aIns > 0) + { + pData->audioIn.createNew(fInfo.aIns); + } + + if (fInfo.aOuts > 0) + { + pData->audioOut.createNew(fInfo.aOuts); + needsCtrlIn = true; + } + + if (fInfo.cvIns > 0) + { + pData->cvIn.createNew(fInfo.cvIns); + } + + if (fInfo.cvOuts > 0) + { + pData->cvOut.createNew(fInfo.cvOuts); + } + + if (fInfo.mIns > 0) + needsCtrlIn = true; + + if (fInfo.mOuts > 0) + needsCtrlOut = true; + + const uint portNameSize(pData->engine->getMaxPortNameSize()); + CarlaString portName; + + // Audio Ins + for (uint32_t j=0; j < fInfo.aIns; ++j) + { + portName.clear(); + + if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) + { + portName = pData->name; + portName += ":"; + } + + if (fInfo.aInNames != nullptr && fInfo.aInNames[j] != nullptr) + { + portName += fInfo.aInNames[j]; + } + else if (fInfo.aIns > 1) + { + portName += "input_"; + portName += CarlaString(j+1); + } + else + portName += "input"; + + portName.truncate(portNameSize); + + pData->audioIn.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, true, j); + pData->audioIn.ports[j].rindex = j; + } + + // Audio Outs + for (uint32_t j=0; j < fInfo.aOuts; ++j) + { + portName.clear(); + + if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) + { + portName = pData->name; + portName += ":"; + } + + if (fInfo.aOutNames != nullptr && fInfo.aOutNames[j] != nullptr) + { + portName += fInfo.aOutNames[j]; + } + else if (fInfo.aOuts > 1) + { + portName += "output_"; + portName += CarlaString(j+1); + } + else + portName += "output"; + + portName.truncate(portNameSize); + + pData->audioOut.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false, j); + pData->audioOut.ports[j].rindex = j; + } + + // TODO - MIDI + + // TODO - CV + + if (needsCtrlIn) + { + portName.clear(); + + if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) + { + portName = pData->name; + portName += ":"; + } + + portName += "event-in"; + portName.truncate(portNameSize); + + pData->event.portIn = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, true, 0); + } + + if (needsCtrlOut) + { + portName.clear(); + + if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) + { + portName = pData->name; + portName += ":"; + } + + portName += "event-out"; + portName.truncate(portNameSize); + + pData->event.portOut = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, false, 0); + } + + // extra plugin hints + pData->extraHints = 0x0; + + if (fInfo.mIns > 0) + pData->extraHints |= PLUGIN_EXTRA_HINT_HAS_MIDI_IN; + + if (fInfo.mOuts > 0) + pData->extraHints |= PLUGIN_EXTRA_HINT_HAS_MIDI_OUT; + + if (fInfo.aIns <= 2 && fInfo.aOuts <= 2 && (fInfo.aIns == fInfo.aOuts || fInfo.aIns == 0 || fInfo.aOuts == 0)) + pData->extraHints |= PLUGIN_EXTRA_HINT_CAN_RUN_RACK; + + bufferSizeChanged(pData->engine->getBufferSize()); + reloadPrograms(true); + + carla_debug("CarlaPluginJack::reload() - end"); + } + + // ------------------------------------------------------------------- + // Plugin processing + + void activate() noexcept override + { + CARLA_SAFE_ASSERT_RETURN(! fTimedError,); + + { + const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); + + fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientActivate); + fShmNonRtClientControl.commitWrite(); + } + + fTimedOut = false; + + try { + waitForClient("activate", 2000); + } CARLA_SAFE_EXCEPTION("activate - waitForClient"); + } + + void deactivate() noexcept override + { + CARLA_SAFE_ASSERT_RETURN(! fTimedError,); + + { + const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); + + fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientDeactivate); + fShmNonRtClientControl.commitWrite(); + } + + fTimedOut = false; + + try { + waitForClient("deactivate", 2000); + } CARLA_SAFE_EXCEPTION("deactivate - waitForClient"); + } + + void process(const float** const audioIn, float** const audioOut, const float** const cvIn, float** const cvOut, const uint32_t frames) override + { + // -------------------------------------------------------------------------------------------------------- + // Check if active + + if (fTimedOut || fTimedError || ! pData->active) + { + // disable any output sound + for (uint32_t i=0; i < pData->audioOut.count; ++i) + FloatVectorOperations::clear(audioOut[i], static_cast(frames)); + for (uint32_t i=0; i < pData->cvOut.count; ++i) + FloatVectorOperations::clear(cvOut[i], static_cast(frames)); + return; + } + + // -------------------------------------------------------------------------------------------------------- + // Check if needs reset + + if (pData->needsReset) + { + // TODO + + pData->needsReset = false; + } + + // -------------------------------------------------------------------------------------------------------- + // Event Input + + if (pData->event.portIn != nullptr) + { + // ---------------------------------------------------------------------------------------------------- + // MIDI Input (External) + + if (pData->extNotes.mutex.tryLock()) + { + for (RtLinkedList::Itenerator it = pData->extNotes.data.begin2(); it.valid(); it.next()) + { + const ExternalMidiNote& note(it.getValue(kExternalMidiNoteFallback)); + CARLA_SAFE_ASSERT_CONTINUE(note.channel >= 0 && note.channel < MAX_MIDI_CHANNELS); + + uint8_t data1, data2, data3; + data1 = uint8_t((note.velo > 0 ? MIDI_STATUS_NOTE_ON : MIDI_STATUS_NOTE_OFF) | (note.channel & MIDI_CHANNEL_BIT)); + data2 = note.note; + data3 = note.velo; + + fShmRtClientControl.writeOpcode(kPluginBridgeRtClientMidiEvent); + fShmRtClientControl.writeUInt(0); // time + fShmRtClientControl.writeByte(0); // port + fShmRtClientControl.writeByte(3); // size + fShmRtClientControl.writeByte(data1); + fShmRtClientControl.writeByte(data2); + fShmRtClientControl.writeByte(data3); + fShmRtClientControl.commitWrite(); + } + + pData->extNotes.data.clear(); + pData->extNotes.mutex.unlock(); + + } // End of MIDI Input (External) + + // ---------------------------------------------------------------------------------------------------- + // Event Input (System) + + bool allNotesOffSent = false; + + for (uint32_t i=0, numEvents=pData->event.portIn->getEventCount(); i < numEvents; ++i) + { + const EngineEvent& event(pData->event.portIn->getEvent(i)); + + // Control change + switch (event.type) + { + case kEngineEventTypeNull: + break; + + case kEngineEventTypeControl: { + const EngineControlEvent& ctrlEvent = event.ctrl; + + switch (ctrlEvent.type) + { + case kEngineControlEventTypeNull: + break; + + case kEngineControlEventTypeParameter: + // Control backend stuff + if (event.channel == pData->ctrlChannel) + { + float value; + + if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) != 0) + { + value = ctrlEvent.value; + setDryWet(value, false, false); + pData->postponeRtEvent(kPluginPostRtEventParameterChange, PARAMETER_DRYWET, 0, value); + } + + if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0) + { + value = ctrlEvent.value*127.0f/100.0f; + setVolume(value, false, false); + pData->postponeRtEvent(kPluginPostRtEventParameterChange, PARAMETER_VOLUME, 0, value); + } + + if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0) + { + float left, right; + value = ctrlEvent.value/0.5f - 1.0f; + + if (value < 0.0f) + { + left = -1.0f; + right = (value*2.0f)+1.0f; + } + else if (value > 0.0f) + { + left = (value*2.0f)-1.0f; + right = 1.0f; + } + else + { + left = -1.0f; + right = 1.0f; + } + + setBalanceLeft(left, false, false); + setBalanceRight(right, false, false); + pData->postponeRtEvent(kPluginPostRtEventParameterChange, PARAMETER_BALANCE_LEFT, 0, left); + pData->postponeRtEvent(kPluginPostRtEventParameterChange, PARAMETER_BALANCE_RIGHT, 0, right); + } + } + break; + + case kEngineControlEventTypeMidiBank: + if (pData->options & PLUGIN_OPTION_MAP_PROGRAM_CHANGES) + { + fShmRtClientControl.writeOpcode(kPluginBridgeRtClientControlEventMidiBank); + fShmRtClientControl.writeUInt(event.time); + fShmRtClientControl.writeByte(event.channel); + fShmRtClientControl.writeUShort(event.ctrl.param); + fShmRtClientControl.commitWrite(); + } + break; + + case kEngineControlEventTypeMidiProgram: + if (pData->options & PLUGIN_OPTION_MAP_PROGRAM_CHANGES) + { + fShmRtClientControl.writeOpcode(kPluginBridgeRtClientControlEventMidiProgram); + fShmRtClientControl.writeUInt(event.time); + fShmRtClientControl.writeByte(event.channel); + fShmRtClientControl.writeUShort(event.ctrl.param); + fShmRtClientControl.commitWrite(); + } + break; + + case kEngineControlEventTypeAllSoundOff: + if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF) + { + fShmRtClientControl.writeOpcode(kPluginBridgeRtClientControlEventAllSoundOff); + fShmRtClientControl.writeUInt(event.time); + fShmRtClientControl.writeByte(event.channel); + fShmRtClientControl.commitWrite(); + } + break; + + case kEngineControlEventTypeAllNotesOff: + if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF) + { + if (event.channel == pData->ctrlChannel && ! allNotesOffSent) + { + allNotesOffSent = true; + sendMidiAllNotesOffToCallback(); + } + + fShmRtClientControl.writeOpcode(kPluginBridgeRtClientControlEventAllNotesOff); + fShmRtClientControl.writeUInt(event.time); + fShmRtClientControl.writeByte(event.channel); + fShmRtClientControl.commitWrite(); + } + break; + } // switch (ctrlEvent.type) + break; + } // case kEngineEventTypeControl + + case kEngineEventTypeMidi: { + const EngineMidiEvent& midiEvent(event.midi); + + if (midiEvent.size == 0 || midiEvent.size >= MAX_MIDI_VALUE) + continue; + + const uint8_t* const midiData(midiEvent.size > EngineMidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data); + + uint8_t status = uint8_t(MIDI_GET_STATUS_FROM_DATA(midiData)); + + if (status == MIDI_STATUS_CHANNEL_PRESSURE && (pData->options & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE) == 0) + continue; + if (status == MIDI_STATUS_CONTROL_CHANGE && (pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) == 0) + continue; + if (status == MIDI_STATUS_POLYPHONIC_AFTERTOUCH && (pData->options & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH) == 0) + continue; + if (status == MIDI_STATUS_PITCH_WHEEL_CONTROL && (pData->options & PLUGIN_OPTION_SEND_PITCHBEND) == 0) + continue; + + // Fix bad note-off + if (status == MIDI_STATUS_NOTE_ON && midiData[2] == 0) + status = MIDI_STATUS_NOTE_OFF; + + fShmRtClientControl.writeOpcode(kPluginBridgeRtClientMidiEvent); + fShmRtClientControl.writeUInt(event.time); + fShmRtClientControl.writeByte(midiEvent.port); + fShmRtClientControl.writeByte(midiEvent.size); + + fShmRtClientControl.writeByte(uint8_t(midiData[0] | (event.channel & MIDI_CHANNEL_BIT))); + + for (uint8_t j=1; j < midiEvent.size; ++j) + fShmRtClientControl.writeByte(midiData[j]); + + fShmRtClientControl.commitWrite(); + + if (status == MIDI_STATUS_NOTE_ON) + pData->postponeRtEvent(kPluginPostRtEventNoteOn, event.channel, midiData[1], midiData[2]); + else if (status == MIDI_STATUS_NOTE_OFF) + pData->postponeRtEvent(kPluginPostRtEventNoteOff, event.channel, midiData[1], 0.0f); + } break; + } + } + + pData->postRtEvents.trySplice(); + + } // End of Event Input + + if (! processSingle(audioIn, audioOut, cvIn, cvOut, frames)) + return; + + // -------------------------------------------------------------------------------------------------------- + // MIDI Output + + if (pData->event.portOut != nullptr) + { + uint8_t size; + uint32_t time; + const uint8_t* midiData(fShmRtClientControl.data->midiOut); + + for (std::size_t read=0; readevent.portOut->writeMidiEvent(time, size, data); + + read += 1U /* size*/ + 4U /* time */ + size; + } + + } // End of Control and MIDI Output + } + + bool processSingle(const float** const audioIn, float** const audioOut, + const float** const cvIn, float** const cvOut, const uint32_t frames) + { + CARLA_SAFE_ASSERT_RETURN(! fTimedError, false); + CARLA_SAFE_ASSERT_RETURN(frames > 0, false); + + if (pData->audioIn.count > 0) + { + CARLA_SAFE_ASSERT_RETURN(audioIn != nullptr, false); + } + if (pData->audioOut.count > 0) + { + CARLA_SAFE_ASSERT_RETURN(audioOut != nullptr, false); + } + if (pData->cvIn.count > 0) + { + CARLA_SAFE_ASSERT_RETURN(cvIn != nullptr, false); + } + if (pData->cvOut.count > 0) + { + CARLA_SAFE_ASSERT_RETURN(cvOut != nullptr, false); + } + + const int iframes(static_cast(frames)); + + // -------------------------------------------------------------------------------------------------------- + // Try lock, silence otherwise + + if (pData->engine->isOffline()) + { + pData->singleMutex.lock(); + } + else if (! pData->singleMutex.tryLock()) + { + for (uint32_t i=0; i < pData->audioOut.count; ++i) + FloatVectorOperations::clear(audioOut[i], iframes); + for (uint32_t i=0; i < pData->cvOut.count; ++i) + FloatVectorOperations::clear(cvOut[i], iframes); + return false; + } + + // -------------------------------------------------------------------------------------------------------- + // Reset audio buffers + + for (uint32_t i=0; i < fInfo.aIns; ++i) + FloatVectorOperations::copy(fShmAudioPool.data + (i * frames), audioIn[i], iframes); + + // -------------------------------------------------------------------------------------------------------- + // TimeInfo + + const EngineTimeInfo& timeInfo(pData->engine->getTimeInfo()); + BridgeTimeInfo& bridgeTimeInfo(fShmRtClientControl.data->timeInfo); + + bridgeTimeInfo.playing = timeInfo.playing; + bridgeTimeInfo.frame = timeInfo.frame; + bridgeTimeInfo.usecs = timeInfo.usecs; + bridgeTimeInfo.valid = timeInfo.valid; + + if (timeInfo.valid & EngineTimeInfo::kValidBBT) + { + bridgeTimeInfo.bar = timeInfo.bbt.bar; + bridgeTimeInfo.beat = timeInfo.bbt.beat; + bridgeTimeInfo.tick = timeInfo.bbt.tick; + + bridgeTimeInfo.beatsPerBar = timeInfo.bbt.beatsPerBar; + bridgeTimeInfo.beatType = timeInfo.bbt.beatType; + + bridgeTimeInfo.ticksPerBeat = timeInfo.bbt.ticksPerBeat; + bridgeTimeInfo.beatsPerMinute = timeInfo.bbt.beatsPerMinute; + bridgeTimeInfo.barStartTick = timeInfo.bbt.barStartTick; + } + + // -------------------------------------------------------------------------------------------------------- + // Run plugin + + { + fShmRtClientControl.writeOpcode(kPluginBridgeRtClientProcess); + fShmRtClientControl.commitWrite(); + } + + waitForClient("process", fProcWaitTime); + + if (fTimedOut) + { + pData->singleMutex.unlock(); + return false; + } + + for (uint32_t i=0; i < fInfo.aOuts; ++i) + FloatVectorOperations::copy(audioOut[i], fShmAudioPool.data + ((i + fInfo.aIns) * frames), iframes); + + // -------------------------------------------------------------------------------------------------------- + // Post-processing (dry/wet, volume and balance) + + { + const bool doVolume = (pData->hints & PLUGIN_CAN_VOLUME) != 0 && carla_isNotEqual(pData->postProc.volume, 1.0f); + const bool doDryWet = (pData->hints & PLUGIN_CAN_DRYWET) != 0 && carla_isNotEqual(pData->postProc.dryWet, 1.0f); + const bool doBalance = (pData->hints & PLUGIN_CAN_BALANCE) != 0 && ! (carla_isEqual(pData->postProc.balanceLeft, -1.0f) && carla_isEqual(pData->postProc.balanceRight, 1.0f)); + const bool isMono = (pData->audioIn.count == 1); + + bool isPair; + float bufValue, oldBufLeft[doBalance ? frames : 1]; + + for (uint32_t i=0; i < pData->audioOut.count; ++i) + { + // Dry/Wet + if (doDryWet) + { + const uint32_t c = isMono ? 0 : i; + + for (uint32_t k=0; k < frames; ++k) + { + bufValue = audioIn[c][k]; + audioOut[i][k] = (audioOut[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); + } + } + + // Balance + if (doBalance) + { + isPair = (i % 2 == 0); + + if (isPair) + { + CARLA_ASSERT(i+1 < pData->audioOut.count); + FloatVectorOperations::copy(oldBufLeft, audioOut[i], iframes); + } + + float balRangeL = (pData->postProc.balanceLeft + 1.0f)/2.0f; + float balRangeR = (pData->postProc.balanceRight + 1.0f)/2.0f; + + for (uint32_t k=0; k < frames; ++k) + { + if (isPair) + { + // left + audioOut[i][k] = oldBufLeft[k] * (1.0f - balRangeL); + audioOut[i][k] += audioOut[i+1][k] * (1.0f - balRangeR); + } + else + { + // right + audioOut[i][k] = audioOut[i][k] * balRangeR; + audioOut[i][k] += oldBufLeft[k] * balRangeL; + } + } + } + + // Volume (and buffer copy) + if (doVolume) + { + for (uint32_t k=0; k < frames; ++k) + audioOut[i][k] *= pData->postProc.volume; + } + } + + } // End of Post-processing + + // -------------------------------------------------------------------------------------------------------- + + pData->singleMutex.unlock(); + return true; + } + + void bufferSizeChanged(const uint32_t newBufferSize) override + { + resizeAudioPool(newBufferSize); + + { + const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); + fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientSetBufferSize); + fShmNonRtClientControl.writeUInt(newBufferSize); + fShmNonRtClientControl.commitWrite(); + } + + //fProcWaitTime = newBufferSize*1000/pData->engine->getSampleRate(); + fProcWaitTime = 1000; + + waitForClient("buffersize", 1000); + } + + void sampleRateChanged(const double newSampleRate) override + { + { + const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); + fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientSetSampleRate); + fShmNonRtClientControl.writeDouble(newSampleRate); + fShmNonRtClientControl.commitWrite(); + } + + //fProcWaitTime = pData->engine->getBufferSize()*1000/newSampleRate; + fProcWaitTime = 1000; + + waitForClient("samplerate", 1000); + } + + void offlineModeChanged(const bool isOffline) override + { + { + const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); + fShmNonRtClientControl.writeOpcode(isOffline ? kPluginBridgeNonRtClientSetOffline : kPluginBridgeNonRtClientSetOnline); + fShmNonRtClientControl.commitWrite(); + } + + waitForClient("offline", 1000); + } + + // ------------------------------------------------------------------- + // Post-poned UI Stuff + + // ------------------------------------------------------------------- + + void handleNonRtData() + { + for (; fShmNonRtServerControl.isDataAvailableForReading();) + { + const PluginBridgeNonRtServerOpcode opcode(fShmNonRtServerControl.readOpcode()); +//#ifdef DEBUG + if (opcode != kPluginBridgeNonRtServerPong) + { + carla_debug("CarlaPluginJack::handleNonRtData() - got opcode: %s", PluginBridgeNonRtServerOpcode2str(opcode)); + } +//#endif + if (opcode != kPluginBridgeNonRtServerNull && fLastPongTime > 0) + fLastPongTime = Time::currentTimeMillis(); + + switch (opcode) + { + case kPluginBridgeNonRtServerNull: + case kPluginBridgeNonRtServerPong: + break; + + case kPluginBridgeNonRtServerPluginInfo1: + // uint/category, uint/hints, uint/optionsAvailable, uint/optionsEnabled, long/uniqueId + fShmNonRtServerControl.readUInt(); + fShmNonRtServerControl.readUInt(); + fShmNonRtServerControl.readUInt(); + fShmNonRtServerControl.readUInt(); + fShmNonRtServerControl.readLong(); + break; + + case kPluginBridgeNonRtServerPluginInfo2: { + // uint/size, str[] (realName), uint/size, str[] (label), uint/size, str[] (maker), uint/size, str[] (copyright) + + // realName + const uint32_t realNameSize(fShmNonRtServerControl.readUInt()); + char realName[realNameSize+1]; + carla_zeroChars(realName, realNameSize+1); + fShmNonRtServerControl.readCustomData(realName, realNameSize); + + // label + const uint32_t labelSize(fShmNonRtServerControl.readUInt()); + char label[labelSize+1]; + carla_zeroChars(label, labelSize+1); + fShmNonRtServerControl.readCustomData(label, labelSize); + + // maker + const uint32_t makerSize(fShmNonRtServerControl.readUInt()); + char maker[makerSize+1]; + carla_zeroChars(maker, makerSize+1); + fShmNonRtServerControl.readCustomData(maker, makerSize); + + // copyright + const uint32_t copyrightSize(fShmNonRtServerControl.readUInt()); + char copyright[copyrightSize+1]; + carla_zeroChars(copyright, copyrightSize+1); + fShmNonRtServerControl.readCustomData(copyright, copyrightSize); + + fInfo.name = realName; + fInfo.label = label; + + if (pData->name == nullptr) + pData->name = pData->engine->getUniquePluginName(realName); + } break; + + case kPluginBridgeNonRtServerAudioCount: + // uint/ins, uint/outs + fInfo.aIns = fShmNonRtServerControl.readUInt(); + fInfo.aOuts = fShmNonRtServerControl.readUInt(); + + CARLA_SAFE_ASSERT(fInfo.aInNames == nullptr); + CARLA_SAFE_ASSERT(fInfo.aOutNames == nullptr); + + if (fInfo.aIns > 0) + { + fInfo.aInNames = new const char*[fInfo.aIns]; + carla_zeroPointers(fInfo.aInNames, fInfo.aIns); + } + + if (fInfo.aOuts > 0) + { + fInfo.aOutNames = new const char*[fInfo.aOuts]; + carla_zeroPointers(fInfo.aOutNames, fInfo.aOuts); + } + break; + + case kPluginBridgeNonRtServerMidiCount: + // uint/ins, uint/outs + fInfo.mIns = fShmNonRtServerControl.readUInt(); + fInfo.mOuts = fShmNonRtServerControl.readUInt(); + break; + + case kPluginBridgeNonRtServerCvCount: + // uint/ins, uint/outs + fInfo.cvIns = fShmNonRtServerControl.readUInt(); + fInfo.cvOuts = fShmNonRtServerControl.readUInt(); + break; + + case kPluginBridgeNonRtServerParameterCount: + // uint/count + fShmNonRtServerControl.readUInt(); + break; + + case kPluginBridgeNonRtServerProgramCount: + // uint/count + fShmNonRtServerControl.readUInt(); + break; + + case kPluginBridgeNonRtServerMidiProgramCount: + // uint/count + fShmNonRtServerControl.readUInt(); + break; + + case kPluginBridgeNonRtServerPortName: { + // byte/type, uint/index, uint/size, str[] (name) + const uint8_t portType = fShmNonRtServerControl.readByte(); + const uint32_t index = fShmNonRtServerControl.readUInt(); + + // name + const uint32_t nameSize(fShmNonRtServerControl.readUInt()); + char* const name = new char[nameSize+1]; + carla_zeroChars(name, nameSize+1); + fShmNonRtServerControl.readCustomData(name, nameSize); + + CARLA_SAFE_ASSERT_BREAK(portType > kPluginBridgePortNull && portType < kPluginBridgePortTypeCount); + + switch (portType) + { + case kPluginBridgePortAudioInput: + CARLA_SAFE_ASSERT_BREAK(index < fInfo.aIns); + fInfo.aInNames[index] = name; + break; + case kPluginBridgePortAudioOutput: + CARLA_SAFE_ASSERT_BREAK(index < fInfo.aOuts); + fInfo.aOutNames[index] = name; + break; + } + + } break; + + case kPluginBridgeNonRtServerParameterData1: + // uint/index, int/rindex, uint/type, uint/hints, int/cc + fShmNonRtServerControl.readUInt(); + fShmNonRtServerControl.readInt(); + fShmNonRtServerControl.readUInt(); + fShmNonRtServerControl.readUInt(); + fShmNonRtServerControl.readShort(); + break; + + case kPluginBridgeNonRtServerParameterData2: + // uint/index, uint/size, str[] (name), uint/size, str[] (unit) + fShmNonRtServerControl.readUInt(); + if (const uint32_t size = fShmNonRtServerControl.readUInt()) + { + char name[size]; + fShmNonRtServerControl.readCustomData(name, size); + } + if (const uint32_t size = fShmNonRtServerControl.readUInt()) + { + char symbol[size]; + fShmNonRtServerControl.readCustomData(symbol, size); + } + if (const uint32_t size = fShmNonRtServerControl.readUInt()) + { + char unit[size]; + fShmNonRtServerControl.readCustomData(unit, size); + } + break; + + case kPluginBridgeNonRtServerParameterRanges: + // uint/index, float/def, float/min, float/max, float/step, float/stepSmall, float/stepLarge + fShmNonRtServerControl.readUInt(); + fShmNonRtServerControl.readFloat(); + fShmNonRtServerControl.readFloat(); + fShmNonRtServerControl.readFloat(); + fShmNonRtServerControl.readFloat(); + fShmNonRtServerControl.readFloat(); + fShmNonRtServerControl.readFloat(); + break; + + case kPluginBridgeNonRtServerParameterValue: + // uint/index, float/value + fShmNonRtServerControl.readUInt(); + fShmNonRtServerControl.readFloat(); + break; + + case kPluginBridgeNonRtServerParameterValue2: + // uint/index, float/value + fShmNonRtServerControl.readUInt(); + fShmNonRtServerControl.readFloat(); + break; + + case kPluginBridgeNonRtServerDefaultValue: + // uint/index, float/value + fShmNonRtServerControl.readUInt(); + fShmNonRtServerControl.readFloat(); + break; + + case kPluginBridgeNonRtServerCurrentProgram: + // int/index + fShmNonRtServerControl.readInt(); + break; + + case kPluginBridgeNonRtServerCurrentMidiProgram: { + // int/index + fShmNonRtServerControl.readInt(); + } break; + + case kPluginBridgeNonRtServerProgramName: { + // uint/index, uint/size, str[] (name) + fShmNonRtServerControl.readUInt(); + const uint32_t nameSize(fShmNonRtServerControl.readUInt()); + char name[nameSize]; + fShmNonRtServerControl.readCustomData(name, nameSize); + } break; + + case kPluginBridgeNonRtServerMidiProgramData: { + // uint/index, uint/bank, uint/program, uint/size, str[] (name) + fShmNonRtServerControl.readUInt(); + fShmNonRtServerControl.readUInt(); + fShmNonRtServerControl.readUInt(); + const uint32_t nameSize(fShmNonRtServerControl.readUInt()); + char name[nameSize]; + fShmNonRtServerControl.readCustomData(name, nameSize); + } break; + + case kPluginBridgeNonRtServerSetCustomData: { + // uint/size, str[], uint/size, str[], uint/size, str[] + + // type + const uint32_t typeSize(fShmNonRtServerControl.readUInt()); + char type[typeSize]; + fShmNonRtServerControl.readCustomData(type, typeSize); + + // key + const uint32_t keySize(fShmNonRtServerControl.readUInt()); + char key[keySize]; + fShmNonRtServerControl.readCustomData(key, keySize); + + // value + const uint32_t valueSize(fShmNonRtServerControl.readUInt()); + char value[valueSize]; + fShmNonRtServerControl.readCustomData(value, valueSize); + } break; + + case kPluginBridgeNonRtServerSetChunkDataFile: { + // uint/size, str[] (filename) + const uint32_t chunkFilePathSize(fShmNonRtServerControl.readUInt()); + char chunkFilePath[chunkFilePathSize]; + fShmNonRtServerControl.readCustomData(chunkFilePath, chunkFilePathSize); + } break; + + case kPluginBridgeNonRtServerSetLatency: + // uint + fShmNonRtServerControl.readUInt(); + break; + + case kPluginBridgeNonRtServerReady: + fInitiated = true; + break; + + case kPluginBridgeNonRtServerSaved: + case kPluginBridgeNonRtServerUiClosed: + break; + + case kPluginBridgeNonRtServerError: { + // error + const uint32_t errorSize(fShmNonRtServerControl.readUInt()); + char error[errorSize+1]; + carla_zeroChars(error, errorSize+1); + fShmNonRtServerControl.readCustomData(error, errorSize); + + if (fInitiated) + { + pData->engine->callback(ENGINE_CALLBACK_ERROR, pData->id, 0, 0, 0.0f, error); + + // just in case + pData->engine->setLastError(error); + fInitError = true; + } + else + { + pData->engine->setLastError(error); + fInitError = true; + fInitiated = true; + } + } break; + } + } + } + + // ------------------------------------------------------------------- + + uintptr_t getUiBridgeProcessId() const noexcept override + { + return fBridgeThread.getProcessID(); + } + + // ------------------------------------------------------------------- + + bool init(const char* const filename, const char* const name, const char* const args) + { + CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr, false); + + // --------------------------------------------------------------- + // first checks + + if (pData->client != nullptr) + { + pData->engine->setLastError("Plugin client is already registered"); + return false; + } + + // --------------------------------------------------------------- + // set info + + if (name != nullptr && name[0] != '\0') + pData->name = pData->engine->getUniquePluginName(name); + + if (filename != nullptr && filename[0] != '\0') + pData->filename = carla_strdup(filename); + else + pData->filename = carla_strdup(""); + + std::srand(static_cast(std::time(nullptr))); + + // --------------------------------------------------------------- + // init sem/shm + + if (! fShmAudioPool.initializeServer()) + { + carla_stderr("Failed to initialize shared memory audio pool"); + return false; + } + + if (! fShmRtClientControl.initializeServer()) + { + carla_stderr("Failed to initialize RT client control"); + fShmAudioPool.clear(); + return false; + } + + if (! fShmNonRtClientControl.initializeServer()) + { + carla_stderr("Failed to initialize Non-RT client control"); + fShmRtClientControl.clear(); + fShmAudioPool.clear(); + return false; + } + + if (! fShmNonRtServerControl.initializeServer()) + { + carla_stderr("Failed to initialize Non-RT server control"); + fShmNonRtClientControl.clear(); + fShmRtClientControl.clear(); + fShmAudioPool.clear(); + return false; + } + + // --------------------------------------------------------------- + + // initial values + fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientNull); + fShmNonRtClientControl.writeUInt(static_cast(sizeof(BridgeRtClientData))); + fShmNonRtClientControl.writeUInt(static_cast(sizeof(BridgeNonRtClientData))); + fShmNonRtClientControl.writeUInt(static_cast(sizeof(BridgeNonRtServerData))); + + fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientSetBufferSize); + fShmNonRtClientControl.writeUInt(pData->engine->getBufferSize()); + + fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientSetSampleRate); + fShmNonRtClientControl.writeDouble(pData->engine->getSampleRate()); + + fShmNonRtClientControl.commitWrite(); + + // testing dummy message + fShmRtClientControl.writeOpcode(kPluginBridgeRtClientNull); + fShmRtClientControl.commitWrite(); + + // init bridge thread + { + char shmIdsStr[6*4+1]; + carla_zeroChars(shmIdsStr, 6*4+1); + + std::strncpy(shmIdsStr+6*0, &fShmAudioPool.filename[fShmAudioPool.filename.length()-6], 6); + std::strncpy(shmIdsStr+6*1, &fShmRtClientControl.filename[fShmRtClientControl.filename.length()-6], 6); + std::strncpy(shmIdsStr+6*2, &fShmNonRtClientControl.filename[fShmNonRtClientControl.filename.length()-6], 6); + std::strncpy(shmIdsStr+6*3, &fShmNonRtServerControl.filename[fShmNonRtServerControl.filename.length()-6], 6); + + fBridgeThread.setData(shmIdsStr, args); + fBridgeThread.startThread(); + } + + fInitiated = false; + fLastPongTime = Time::currentTimeMillis(); + CARLA_SAFE_ASSERT(fLastPongTime > 0); + + static bool sFirstInit = true; + + int64_t timeoutEnd = 5000; + + if (sFirstInit) + timeoutEnd *= 2; + sFirstInit = false; + + const bool needsEngineIdle = pData->engine->getType() != kEngineTypePlugin; + + for (; Time::currentTimeMillis() < fLastPongTime + timeoutEnd && fBridgeThread.isThreadRunning();) + { + pData->engine->callback(ENGINE_CALLBACK_IDLE, 0, 0, 0, 0.0f, nullptr); + + if (needsEngineIdle) + pData->engine->idle(); + + idle(); + + if (fInitiated) + break; + if (pData->engine->isAboutToClose()) + break; + + carla_msleep(20); + } + + fLastPongTime = -1; + + if (fInitError || ! fInitiated) + { + fBridgeThread.stopThread(6000); + + if (! fInitError) + pData->engine->setLastError("Timeout while waiting for a response from plugin-bridge\n(or the plugin crashed on initialization?)"); + + return false; + } + + // --------------------------------------------------------------- + // register client + + if (pData->name == nullptr) + pData->name = pData->engine->getUniquePluginName("unknown"); + + pData->client = pData->engine->addClient(this); + + if (pData->client == nullptr || ! pData->client->isOk()) + { + pData->engine->setLastError("Failed to register plugin client"); + return false; + } + + return true; + } + +private: + bool fInitiated; + bool fInitError; + bool fTimedOut; + bool fTimedError; + uint fProcWaitTime; + + int64_t fLastPongTime; + + CarlaPluginJackThread fBridgeThread; + + BridgeAudioPool fShmAudioPool; + BridgeRtClientControl fShmRtClientControl; + BridgeNonRtClientControl fShmNonRtClientControl; + BridgeNonRtServerControl fShmNonRtServerControl; + + struct Info { + uint32_t aIns, aOuts; + uint32_t cvIns, cvOuts; + uint32_t mIns, mOuts; + PluginCategory category; + uint optionsAvailable; + CarlaString name; + CarlaString label; + const char** aInNames; + const char** aOutNames; + std::vector chunk; + + Info() + : aIns(0), + aOuts(0), + cvIns(0), + cvOuts(0), + mIns(0), + mOuts(0), + category(PLUGIN_CATEGORY_NONE), + optionsAvailable(0), + name(), + label(), + aInNames(nullptr), + aOutNames(nullptr), + chunk() {} + + CARLA_DECLARE_NON_COPY_STRUCT(Info) + } fInfo; + + void resizeAudioPool(const uint32_t bufferSize) + { + fShmAudioPool.resize(bufferSize, fInfo.aIns+fInfo.aOuts, fInfo.cvIns+fInfo.cvOuts); + + fShmRtClientControl.writeOpcode(kPluginBridgeRtClientSetAudioPool); + fShmRtClientControl.writeULong(static_cast(fShmAudioPool.dataSize)); + fShmRtClientControl.commitWrite(); + + waitForClient("resize-pool", 5000); + } + + void waitForClient(const char* const action, const uint msecs) + { + CARLA_SAFE_ASSERT_RETURN(! fTimedOut,); + CARLA_SAFE_ASSERT_RETURN(! fTimedError,); + + if (fShmRtClientControl.waitForClient(msecs)) + return; + + fTimedOut = true; + carla_stderr("waitForClient(%s) timed out", action); + } + + CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginJack) +}; + CARLA_BACKEND_END_NAMESPACE + +// ------------------------------------------------------------------------------------------------------------------- + +CARLA_BACKEND_START_NAMESPACE + +CarlaPlugin* CarlaPlugin::newJackApp(const Initializer& init) +{ + carla_debug("CarlaPlugin::newJackApp({%p, \"%s\", \"%s\", \"%s\"}, %s, %s, \"%s\")", init.engine, init.filename, init.name, init.label); + + CarlaPluginJack* const plugin(new CarlaPluginJack(init.engine, init.id)); + + if (! plugin->init(init.filename, init.name, init.label)) + { + delete plugin; + return nullptr; + } + + return plugin; +} + +CARLA_BACKEND_END_NAMESPACE + +// ------------------------------------------------------------------------------------------------------------------- diff --git a/source/interposer/Makefile b/source/interposer/Makefile index 85d436dba..624a0b144 100644 --- a/source/interposer/Makefile +++ b/source/interposer/Makefile @@ -21,8 +21,12 @@ endif # ---------------------------------------------------------------------------------------------------------------------------- -BUILD_CXX_FLAGS += -I$(CWD)/includes -I$(CWD)/utils -LINK_FLAGS += -ldl +BUILD_CXX_FLAGS += -I$(CWD) -I$(CWD)/backend -I$(CWD)/includes -I$(CWD)/modules -I$(CWD)/utils + +INTERPOSER_SAFE_LIBS = -ldl +INTERPOSER_X11_LIBS = $(X11_LIBS) -ldl +LIBJACK_LIBS = $(MODULEDIR)/juce_core.a +LIBJACK_LIBS += $(JUCE_CORE_LIBS) # ---------------------------------------------------------------------------------------------------------------------------- @@ -31,10 +35,14 @@ TARGETS = ifeq ($(LINUX),true) OBJS += $(OBJDIR)/interposer-safe.cpp.o -TARGETS += $(BINDIR)/libcarla_interposer-safe$(LIB_EXT) +TARGETS += $(BINDIR)/libcarla_interposer-safe.so + +OBJS += $(OBJDIR)/libjack.cpp.o +TARGETS += $(BINDIR)/jack/libjack.so.0 + ifeq ($(HAVE_X11),true) OBJS += $(OBJDIR)/interposer-x11.cpp.o -TARGETS += $(BINDIR)/libcarla_interposer-x11$(LIB_EXT) +TARGETS += $(BINDIR)/libcarla_interposer-x11.so endif endif @@ -52,15 +60,20 @@ debug: # ---------------------------------------------------------------------------------------------------------------------------- -$(BINDIR)/libcarla_interposer-safe$(LIB_EXT): $(OBJDIR)/interposer-safe.cpp.o +$(BINDIR)/libcarla_interposer-safe.so: $(OBJDIR)/interposer-safe.cpp.o -@mkdir -p $(BINDIR) - @echo "Linking libcarla_interposer-safe$(LIB_EXT)" - @$(CXX) $< $(SHARED) $(LINK_FLAGS) -o $@ + @echo "Linking libcarla_interposer-safe.so" + @$(CXX) $< $(SHARED) $(LINK_FLAGS) $(INTERPOSER_SAFE_LIBS) -o $@ -$(BINDIR)/libcarla_interposer-x11$(LIB_EXT): $(OBJDIR)/interposer-x11.cpp.o +$(BINDIR)/libcarla_interposer-x11.so: $(OBJDIR)/interposer-x11.cpp.o -@mkdir -p $(BINDIR) - @echo "Linking libcarla_interposer-x11$(LIB_EXT)" - @$(CXX) $< $(SHARED) $(LINK_FLAGS) $(X11_LIBS) -o $@ + @echo "Linking libcarla_interposer-x11.so" + @$(CXX) $< $(SHARED) $(LINK_FLAGS) $(INTERPOSER_X11_LIBS) -o $@ + +$(BINDIR)/jack/libjack.so.0: $(OBJDIR)/libjack.cpp.o + -@mkdir -p $(BINDIR)/jack + @echo "Linking libjack.so.0" + @$(CXX) $< $(SHARED) $(LINK_FLAGS) $(LIBJACK_LIBS) -o $@ # ---------------------------------------------------------------------------------------------------------------------------- @@ -74,6 +87,12 @@ $(OBJDIR)/interposer-x11.cpp.o: interposer-x11.cpp @echo "Compiling $<" @$(CXX) $< $(BUILD_CXX_FLAGS) $(X11_FLAGS) -c -o $@ +$(OBJDIR)/libjack.cpp.o: libjack.cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling $<" + @$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ + # -Wno-missing-declarations + -include $(OBJS:%.o=%.d) # ---------------------------------------------------------------------------------------------------------------------------- diff --git a/source/interposer/libjack.cpp b/source/interposer/libjack.cpp new file mode 100644 index 000000000..822446832 --- /dev/null +++ b/source/interposer/libjack.cpp @@ -0,0 +1,1490 @@ +/* + * Carla JACK API for external applications + * Copyright (C) 2016-2017 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. + */ + +// need to include this first +#include "CarlaDefines.h" + +// then define this for bridge utils +#define BUILD_BRIDGE 1 + +// now include a bunch of stuff +#include "CarlaBackendUtils.hpp" +#include "CarlaBridgeUtils.hpp" +#include "CarlaMIDI.h" +#include "CarlaMutex.hpp" +#include "LinkedList.hpp" + +#include "AppConfig.h" +#include "juce_core/juce_core.h" + +#ifdef __SSE2_MATH__ +# include +#endif + +#if 0 +#include +#include +#include +#include +#include +#endif + +// must be last +#include "jackbridge/JackBridge.hpp" + +// small check to not hurt myself +#ifdef JACKBRIDGE_DIRECT +# error "Cannot create custom jack server while linking to libjack directly" +#endif + +using juce::File; +using juce::MemoryBlock; +using juce::String; +using juce::Time; +using juce::Thread; + +CARLA_BACKEND_START_NAMESPACE + +// -------------------------------------------------------------------------------------------------------------------- + +struct JackPortState { + char* name; + char* fullname; + void* buffer; + uint index; + uint flags; + + JackPortState() + : name(nullptr), + fullname(nullptr), + buffer(nullptr), + index(0), + flags(0) {} + + JackPortState(const char* const n, const uint i, const uint f) + : name(strdup(n)), + fullname(nullptr), + buffer(nullptr), + index(i), + flags(f) + { + char strBuf[STR_MAX+1]; + snprintf(strBuf, STR_MAX, "system:%s", n); + strBuf[STR_MAX] = '\0'; + + fullname = strdup(strBuf); + } + + ~JackPortState() + { + free(name); + free(fullname); + } +}; + +struct JackClientState { + bool activated; + + char* name; + + uint32_t bufferSize; + double sampleRate; + + bool playing; + jack_position_t position; + + LinkedList audioIns; + LinkedList audioOuts; + uint32_t fakeIns, fakeOuts; + + LinkedList midiIns; + LinkedList midiOuts; + + JackProcessCallback process; + void* processPtr; + + JackShutdownCallback shutdown; + void* shutdownPtr; + + JackClientState() + : activated(false), + name(nullptr), + bufferSize(0), + sampleRate(0.0), + playing(false), + audioIns(), + audioOuts(), + fakeIns(0), + fakeOuts(0), + midiIns(), + midiOuts(), + process(nullptr), + processPtr(nullptr), + shutdown(nullptr), + shutdownPtr(nullptr) + { + carla_zeroStruct(position); + } + + ~JackClientState() + { + free(name); + } +}; + +class CarlaJackClient : public Thread +{ +public: + JackClientState fState; + + CarlaJackClient(const char* const audioPoolBaseName, const char* const rtClientBaseName, const char* const nonRtClientBaseName, const char* const nonRtServerBaseName) + : Thread("CarlaJackClient"), + fState(), + fShmAudioPool(), + fShmRtClientControl(), + fShmNonRtClientControl(), + fShmNonRtServerControl(), + fBaseNameAudioPool(audioPoolBaseName), + fBaseNameRtClientControl(rtClientBaseName), + fBaseNameNonRtClientControl(nonRtClientBaseName), + fBaseNameNonRtServerControl(nonRtServerBaseName), + fIsOffline(false), + fFirstIdle(true), + fLastPingTime(-1), + fAudioIns(0), + fAudioOuts(0) + { + carla_debug("CarlaJackClient::CarlaJackClient(\"%s\", \"%s\", \"%s\", \"%s\")", audioPoolBaseName, rtClientBaseName, nonRtClientBaseName, nonRtServerBaseName); + } + + ~CarlaJackClient() noexcept override + { + carla_debug("CarlaJackClient::~CarlaJackClient()"); + + clear(); + } + + bool init(const char* const clientName) + { + carla_debug("CarlaJackClient::init(\"%s\")", clientName); + + if (! fShmAudioPool.attachClient(fBaseNameAudioPool)) + { + carla_stderr("Failed to attach to audio pool shared memory"); + return false; + } + + if (! fShmRtClientControl.attachClient(fBaseNameRtClientControl)) + { + clear(); + carla_stderr("Failed to attach to rt client control shared memory"); + return false; + } + + if (! fShmRtClientControl.mapData()) + { + clear(); + carla_stderr("Failed to map rt client control shared memory"); + return false; + } + + if (! fShmNonRtClientControl.attachClient(fBaseNameNonRtClientControl)) + { + clear(); + carla_stderr("Failed to attach to non-rt client control shared memory"); + return false; + } + + if (! fShmNonRtClientControl.mapData()) + { + clear(); + carla_stderr("Failed to map non-rt control client shared memory"); + return false; + } + + if (! fShmNonRtServerControl.attachClient(fBaseNameNonRtServerControl)) + { + clear(); + carla_stderr("Failed to attach to non-rt server control shared memory"); + return false; + } + + if (! fShmNonRtServerControl.mapData()) + { + clear(); + carla_stderr("Failed to map non-rt control server shared memory"); + return false; + } + + PluginBridgeNonRtClientOpcode opcode; + + opcode = fShmNonRtClientControl.readOpcode(); + CARLA_SAFE_ASSERT_INT(opcode == kPluginBridgeNonRtClientNull, opcode); + + const uint32_t shmRtClientDataSize = fShmNonRtClientControl.readUInt(); + CARLA_SAFE_ASSERT_INT2(shmRtClientDataSize == sizeof(BridgeRtClientData), shmRtClientDataSize, sizeof(BridgeRtClientData)); + + const uint32_t shmNonRtClientDataSize = fShmNonRtClientControl.readUInt(); + CARLA_SAFE_ASSERT_INT2(shmNonRtClientDataSize == sizeof(BridgeNonRtClientData), shmNonRtClientDataSize, sizeof(BridgeNonRtClientData)); + + const uint32_t shmNonRtServerDataSize = fShmNonRtClientControl.readUInt(); + CARLA_SAFE_ASSERT_INT2(shmNonRtServerDataSize == sizeof(BridgeNonRtServerData), shmNonRtServerDataSize, sizeof(BridgeNonRtServerData)); + + if (shmRtClientDataSize != sizeof(BridgeRtClientData) || shmNonRtClientDataSize != sizeof(BridgeNonRtClientData) || shmNonRtServerDataSize != sizeof(BridgeNonRtServerData)) + { + carla_stderr2("CarlaJackClient: data size mismatch"); + return false; + } + + opcode = fShmNonRtClientControl.readOpcode(); + CARLA_SAFE_ASSERT_INT(opcode == kPluginBridgeNonRtClientSetBufferSize, opcode); + fState.bufferSize = fShmNonRtClientControl.readUInt(); + + opcode = fShmNonRtClientControl.readOpcode(); + CARLA_SAFE_ASSERT_INT(opcode == kPluginBridgeNonRtClientSetSampleRate, opcode); + fState.sampleRate = fShmNonRtClientControl.readDouble(); + + if (fState.bufferSize == 0 || carla_isZero(fState.sampleRate)) + { + carla_stderr2("CarlaJackClient: invalid empty state"); + return false; + } + + fState.name = strdup(clientName); + + // tell backend we're live + { + const CarlaMutexLocker _cml(fShmNonRtServerControl.mutex); + + fShmNonRtServerControl.writeOpcode(kPluginBridgeNonRtServerPong); + fShmNonRtServerControl.commitWrite(); + } + + startThread(10); + + return true; + } + + bool close() + { + carla_debug("CarlaEnginePlugin::close()"); + fLastPingTime = -1; + + stopThread(5000); + clear(); + + for (LinkedList::Itenerator it = fState.audioIns.begin2(); it.valid(); it.next()) + { + if (JackPortState* const jport = it.getValue(nullptr)) + delete jport; + } + + for (LinkedList::Itenerator it = fState.audioOuts.begin2(); it.valid(); it.next()) + { + if (JackPortState* const jport = it.getValue(nullptr)) + delete jport; + } + + fState.audioIns.clear(); + fState.audioOuts.clear(); + + return true; + } + + void clear() noexcept + { + fShmAudioPool.clear(); + fShmRtClientControl.clear(); + fShmNonRtClientControl.clear(); + fShmNonRtServerControl.clear(); + } + + void activate() + { + const CarlaMutexLocker _cml(fShmNonRtServerControl.mutex); + const bool wasFirstIdle(fFirstIdle); + + if (wasFirstIdle) + { + fFirstIdle = false; + fLastPingTime = Time::currentTimeMillis(); + CARLA_SAFE_ASSERT(fLastPingTime > 0); + + if (fState.audioIns.count() == 0 && fState.audioOuts.count() == 0) + { + carla_stderr("Create 2 ins, 2 outs prematurely for the client"); + fState.audioIns.append(new JackPortState("in_1", 0, JackPortIsOutput)); + fState.audioIns.append(new JackPortState("in_2", 1, JackPortIsOutput)); + fState.fakeIns = 2; + + fState.audioOuts.append(new JackPortState("out_1", 0, JackPortIsInput)); + fState.audioOuts.append(new JackPortState("out_2", 1, JackPortIsInput)); + fState.fakeOuts = 2; + } + + char bufStr[STR_MAX+1]; + uint32_t bufStrSize; + + // kPluginBridgeNonRtServerPluginInfo1 + { + // uint/category, uint/hints, uint/optionsAvailable, uint/optionsEnabled, long/uniqueId + fShmNonRtServerControl.writeOpcode(kPluginBridgeNonRtServerPluginInfo1); + fShmNonRtServerControl.writeUInt(PLUGIN_CATEGORY_NONE); + fShmNonRtServerControl.writeUInt(0x0); + fShmNonRtServerControl.writeUInt(0x0); + fShmNonRtServerControl.writeUInt(0x0); + fShmNonRtServerControl.writeLong(0); + fShmNonRtServerControl.commitWrite(); + } + + // kPluginBridgeNonRtServerPluginInfo2 + { + // uint/size, str[] (realName), uint/size, str[] (label), uint/size, str[] (maker), uint/size, str[] (copyright) + fShmNonRtServerControl.writeOpcode(kPluginBridgeNonRtServerPluginInfo2); + + carla_zeroChars(bufStr, STR_MAX); + std::strncpy(bufStr, fState.name, 64); + bufStrSize = carla_fixedValue(1U, 64U, static_cast(std::strlen(bufStr))); + fShmNonRtServerControl.writeUInt(bufStrSize); + fShmNonRtServerControl.writeCustomData(bufStr, bufStrSize); + + carla_zeroChars(bufStr, STR_MAX); + + bufStrSize = 1; + fShmNonRtServerControl.writeUInt(bufStrSize); + fShmNonRtServerControl.writeCustomData(bufStr, bufStrSize); + + fShmNonRtServerControl.writeUInt(bufStrSize); + fShmNonRtServerControl.writeCustomData(bufStr, bufStrSize); + + fShmNonRtServerControl.writeUInt(bufStrSize); + fShmNonRtServerControl.writeCustomData(bufStr, bufStrSize); + + fShmNonRtServerControl.commitWrite(); + } + + // kPluginBridgeNonRtServerAudioCount + { + const uint32_t aIns = fState.audioIns.count(); + const uint32_t aOuts = fState.audioOuts.count(); + + // uint/ins, uint/outs + fShmNonRtServerControl.writeOpcode(kPluginBridgeNonRtServerAudioCount); + fShmNonRtServerControl.writeUInt(aIns); + fShmNonRtServerControl.writeUInt(aOuts); + fShmNonRtServerControl.commitWrite(); + + // kPluginBridgeNonRtServerPortName + for (uint32_t i=0; iname); + CARLA_SAFE_ASSERT_CONTINUE(portName != nullptr && portName[0] != '\0'); + + // byte/type, uint/index, uint/size, str[] (name) + fShmNonRtServerControl.writeOpcode(kPluginBridgeNonRtServerPortName); + fShmNonRtServerControl.writeByte(kPluginBridgePortAudioInput); + fShmNonRtServerControl.writeUInt(i); + + bufStrSize = static_cast(std::strlen(portName)); + fShmNonRtServerControl.writeUInt(bufStrSize); + fShmNonRtServerControl.writeCustomData(portName, bufStrSize); + } + + // kPluginBridgeNonRtServerPortName + for (uint32_t i=0; iname); + CARLA_SAFE_ASSERT_CONTINUE(portName != nullptr && portName[0] != '\0'); + + // byte/type, uint/index, uint/size, str[] (name) + fShmNonRtServerControl.writeOpcode(kPluginBridgeNonRtServerPortName); + fShmNonRtServerControl.writeByte(kPluginBridgePortAudioOutput); + fShmNonRtServerControl.writeUInt(i); + + bufStrSize = static_cast(std::strlen(portName)); + fShmNonRtServerControl.writeUInt(bufStrSize); + fShmNonRtServerControl.writeCustomData(portName, bufStrSize); + } + } + + fShmNonRtServerControl.waitIfDataIsReachingLimit(); + + // kPluginBridgeNonRtServerMidiCount + { + const uint32_t mIns = fState.midiIns.count(); + const uint32_t mOuts = fState.midiOuts.count(); + + // uint/ins, uint/outs + fShmNonRtServerControl.writeOpcode(kPluginBridgeNonRtServerMidiCount); + fShmNonRtServerControl.writeUInt(mIns); + fShmNonRtServerControl.writeUInt(mOuts); + fShmNonRtServerControl.commitWrite(); + } + + fShmNonRtServerControl.waitIfDataIsReachingLimit(); + + // ready! + fShmNonRtServerControl.writeOpcode(kPluginBridgeNonRtServerReady); + fShmNonRtServerControl.commitWrite(); + fShmNonRtServerControl.waitIfDataIsReachingLimit(); + + // must be static + fAudioIns = fState.audioIns.count(); + fAudioOuts = fState.audioOuts.count(); + + carla_stdout("Carla Jack Client Ready!"); + fLastPingTime = Time::currentTimeMillis(); + } + + fState.activated = true; + } + + void deactivate() + { + const CarlaMutexLocker _cml(fShmNonRtServerControl.mutex); + + fState.activated = false; + + for (LinkedList::Itenerator it = fState.audioIns.begin2(); it.valid(); it.next()) + { + if (JackPortState* const jport = it.getValue(nullptr)) + delete jport; + } + + for (LinkedList::Itenerator it = fState.audioOuts.begin2(); it.valid(); it.next()) + { + if (JackPortState* const jport = it.getValue(nullptr)) + delete jport; + } + + fState.audioIns.clear(); + fState.audioOuts.clear(); + } + + void handleNonRtData() + { + for (; fShmNonRtClientControl.isDataAvailableForReading();) + { + const PluginBridgeNonRtClientOpcode opcode(fShmNonRtClientControl.readOpcode()); + +// #ifdef DEBUG + if (opcode != kPluginBridgeNonRtClientPing) + { + carla_stdout("CarlaJackClient::handleNonRtData() - got opcode: %s", PluginBridgeNonRtClientOpcode2str(opcode)); + } +// #endif + + if (opcode != kPluginBridgeNonRtClientNull && opcode != kPluginBridgeNonRtClientPingOnOff && fLastPingTime > 0) + fLastPingTime = Time::currentTimeMillis(); + + switch (opcode) + { + case kPluginBridgeNonRtClientNull: + break; + + case kPluginBridgeNonRtClientPing: { + const CarlaMutexLocker _cml(fShmNonRtServerControl.mutex); + + fShmNonRtServerControl.writeOpcode(kPluginBridgeNonRtServerPong); + fShmNonRtServerControl.commitWrite(); + } break; + + case kPluginBridgeNonRtClientPingOnOff: { + const uint32_t onOff(fShmNonRtClientControl.readBool()); + + fLastPingTime = onOff ? Time::currentTimeMillis() : -1; + } break; + + case kPluginBridgeNonRtClientActivate: + case kPluginBridgeNonRtClientDeactivate: + break; + + case kPluginBridgeNonRtClientSetBufferSize: + fShmNonRtClientControl.readUInt(); + //bufferSizeChanged(); + break; + + case kPluginBridgeNonRtClientSetSampleRate: + fShmNonRtClientControl.readDouble(); + //sampleRateChanged(); + break; + + case kPluginBridgeNonRtClientSetOffline: + fIsOffline = true; + //offlineModeChanged(true); + break; + + case kPluginBridgeNonRtClientSetOnline: + fIsOffline = false; + //offlineModeChanged(false); + break; + + case kPluginBridgeNonRtClientSetParameterValue: + case kPluginBridgeNonRtClientSetParameterMidiChannel: + case kPluginBridgeNonRtClientSetParameterMidiCC: + case kPluginBridgeNonRtClientSetProgram: + case kPluginBridgeNonRtClientSetMidiProgram: + case kPluginBridgeNonRtClientSetCustomData: + case kPluginBridgeNonRtClientSetChunkDataFile: + case kPluginBridgeNonRtClientSetCtrlChannel: + case kPluginBridgeNonRtClientSetOption: + break; + + case kPluginBridgeNonRtClientPrepareForSave: + { + const CarlaMutexLocker _cml(fShmNonRtServerControl.mutex); + + fShmNonRtServerControl.writeOpcode(kPluginBridgeNonRtServerSaved); + fShmNonRtServerControl.commitWrite(); + } + break; + + case kPluginBridgeNonRtClientShowUI: + case kPluginBridgeNonRtClientHideUI: + case kPluginBridgeNonRtClientUiParameterChange: + case kPluginBridgeNonRtClientUiProgramChange: + case kPluginBridgeNonRtClientUiMidiProgramChange: + case kPluginBridgeNonRtClientUiNoteOn: + case kPluginBridgeNonRtClientUiNoteOff: + break; + + case kPluginBridgeNonRtClientQuit: + signalThreadShouldExit(); + break; + } + } + } + + // ------------------------------------------------------------------- + +protected: + void run() override + { + carla_stderr("CarlaJackClient run START"); + +#ifdef __SSE2_MATH__ + // Set FTZ and DAZ flags + _mm_setcsr(_mm_getcsr() | 0x8040); +#endif + + bool quitReceived = false; + + for (; ! quitReceived && ! threadShouldExit();) + { + handleNonRtData(); + + const BridgeRtClientControl::WaitHelper helper(fShmRtClientControl); + + if (! helper.ok) + continue; + + for (; fShmRtClientControl.isDataAvailableForReading();) + { + const PluginBridgeRtClientOpcode opcode(fShmRtClientControl.readOpcode()); +//#ifdef DEBUG + if (opcode != kPluginBridgeRtClientProcess && opcode != kPluginBridgeRtClientMidiEvent) + { + carla_stdout("CarlaJackClientRtThread::run() - got opcode: %s", PluginBridgeRtClientOpcode2str(opcode)); + } +//#endif + + switch (opcode) + { + case kPluginBridgeRtClientNull: + break; + + case kPluginBridgeRtClientSetAudioPool: { + if (fShmAudioPool.data != nullptr) + { + jackbridge_shm_unmap(fShmAudioPool.shm, fShmAudioPool.data); + fShmAudioPool.data = nullptr; + } + const uint64_t poolSize(fShmRtClientControl.readULong()); + CARLA_SAFE_ASSERT_BREAK(poolSize > 0); + fShmAudioPool.data = (float*)jackbridge_shm_map(fShmAudioPool.shm, static_cast(poolSize)); + break; + } + + case kPluginBridgeRtClientControlEventParameter: + case kPluginBridgeRtClientControlEventMidiBank: + case kPluginBridgeRtClientControlEventMidiProgram: + case kPluginBridgeRtClientControlEventAllSoundOff: + case kPluginBridgeRtClientControlEventAllNotesOff: + case kPluginBridgeRtClientMidiEvent: + break; + + case kPluginBridgeRtClientProcess: { + CARLA_SAFE_ASSERT_BREAK(fShmAudioPool.data != nullptr); + + const CarlaMutexTryLocker cmtl(fRealtimeThreadMutex); + + if (cmtl.wasLocked()) + { + float* fdata = fShmAudioPool.data; + + if (fState.process == nullptr || ! fState.activated) + { + for (uint32_t i=0; i 0) + carla_zeroFloats(fdata, fState.bufferSize*fAudioOuts); + } + else + { + const BridgeTimeInfo& bridgeTimeInfo(fShmRtClientControl.data->timeInfo); + + uint32_t i = 0; + for (LinkedList::Itenerator it = fState.audioIns.begin2(); it.valid(); it.next()) + { + CARLA_SAFE_ASSERT_BREAK(i++ < fAudioIns); + if (JackPortState* const jport = it.getValue(nullptr)) + jport->buffer = fdata; + fdata += fState.bufferSize; + } + + i=0; + for (LinkedList::Itenerator it = fState.audioOuts.begin2(); it.valid(); it.next()) + { + CARLA_SAFE_ASSERT_BREAK(i++ < fAudioOuts); + if (JackPortState* const jport = it.getValue(nullptr)) + jport->buffer = fdata; + fdata += fState.bufferSize; + } + + fState.playing = bridgeTimeInfo.playing; + fState.position.frame = bridgeTimeInfo.frame; + fState.position.usecs = bridgeTimeInfo.usecs; + + if (bridgeTimeInfo.valid & 0x1 /* kValidBBT */) + { + fState.position.valid = JackPositionBBT; + + fState.position.bar = bridgeTimeInfo.bar; + fState.position.beat = bridgeTimeInfo.beat; + fState.position.tick = bridgeTimeInfo.tick; + + fState.position.beats_per_bar = bridgeTimeInfo.beatsPerBar; + fState.position.beat_type = bridgeTimeInfo.beatType; + + fState.position.ticks_per_beat = bridgeTimeInfo.ticksPerBeat; + fState.position.beats_per_minute = bridgeTimeInfo.beatsPerMinute; + fState.position.bar_start_tick = bridgeTimeInfo.barStartTick; + } + else + { + fState.position.valid = static_cast(0); + } + + fState.process(fState.bufferSize, fState.processPtr); + } + } + else + { + carla_stderr2("CarlaJackClient: fRealtimeThreadMutex tryLock failed"); + } + + carla_zeroBytes(fShmRtClientControl.data->midiOut, kBridgeRtClientDataMidiOutSize); + break; + } + + case kPluginBridgeRtClientQuit: + quitReceived = true; + signalThreadShouldExit(); + break; + } + } + } + + carla_stderr("CarlaJackClient run END"); + + //callback(ENGINE_CALLBACK_ENGINE_STOPPED, 0, 0, 0, 0.0f, nullptr); + + if (! quitReceived) + { + const char* const message("Plugin bridge error, process thread has stopped"); + const std::size_t messageSize(std::strlen(message)); + + const CarlaMutexLocker _cml(fShmNonRtServerControl.mutex); + fShmNonRtServerControl.writeOpcode(kPluginBridgeNonRtServerError); + fShmNonRtServerControl.writeUInt(messageSize); + fShmNonRtServerControl.writeCustomData(message, messageSize); + fShmNonRtServerControl.commitWrite(); + } + + if (fState.shutdown != nullptr) + fState.shutdown(fState.shutdownPtr); + } + +private: + BridgeAudioPool fShmAudioPool; + BridgeRtClientControl fShmRtClientControl; + BridgeNonRtClientControl fShmNonRtClientControl; + BridgeNonRtServerControl fShmNonRtServerControl; + + CarlaString fBaseNameAudioPool; + CarlaString fBaseNameRtClientControl; + CarlaString fBaseNameNonRtClientControl; + CarlaString fBaseNameNonRtServerControl; + + bool fIsOffline; + bool fFirstIdle; + int64_t fLastPingTime; + + uint32_t fAudioIns; + uint32_t fAudioOuts; + + CarlaMutex fRealtimeThreadMutex; + + CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaJackClient) +}; + +CARLA_BACKEND_END_NAMESPACE + +// -------------------------------------------------------------------------------------------------------------------- + +CARLA_BACKEND_USE_NAMESPACE + +CarlaJackClient* global_client = nullptr; + +CARLA_EXPORT +jack_client_t* jack_client_open(const char* client_name, jack_options_t /*options*/, jack_status_t* status, ...) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + if (global_client != nullptr) + return (jack_client_t*)global_client; + + const char* const shmIds(std::getenv("CARLA_SHM_IDS")); + + if (shmIds == nullptr || std::strlen(shmIds) != 6*4) + { + if (status != nullptr) + *status = JackFailure; + return nullptr; + } + + char audioPoolBaseName[6+1]; + char rtClientBaseName[6+1]; + char nonRtClientBaseName[6+1]; + char nonRtServerBaseName[6+1]; + + std::memcpy(audioPoolBaseName, shmIds+6*0, 6); + std::memcpy(rtClientBaseName, shmIds+6*1, 6); + std::memcpy(nonRtClientBaseName, shmIds+6*2, 6); + std::memcpy(nonRtServerBaseName, shmIds+6*3, 6); + + audioPoolBaseName[6] = '\0'; + rtClientBaseName[6] = '\0'; + nonRtClientBaseName[6] = '\0'; + nonRtServerBaseName[6] = '\0'; + + CarlaJackClient* const client = new CarlaJackClient(audioPoolBaseName, rtClientBaseName, + nonRtClientBaseName, nonRtServerBaseName); + + if (! client->init(client_name)) + { + if (status != nullptr) + *status = JackServerError; + return nullptr; + } + + global_client = client; + return (jack_client_t*)client; +} + +CARLA_EXPORT +jack_client_t* jack_client_new(const char* client_name) +{ + return jack_client_open(client_name, JackNullOption, nullptr); +} + +CARLA_EXPORT +int jack_client_close(jack_client_t* client) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 1); + + JackClientState& jstate(jclient->fState); + + if (jstate.activated) + { + jclient->deactivate(); + } + + return 0; + + jclient->close(); + delete jclient; + + return 0; +} + +CARLA_EXPORT +jack_port_t* jack_port_register(jack_client_t* client, const char* port_name, const char* port_type, + unsigned long flags, unsigned long /*buffer_size*/) +{ + carla_stdout("CarlaJackClient :: %s | %s %s %lu", __FUNCTION__, port_name, port_type, flags); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, nullptr); + + JackClientState& jstate(jclient->fState); + const bool isActivated(jstate.activated); + + //CARLA_SAFE_ASSERT(! isActivated); + + CARLA_SAFE_ASSERT_RETURN(port_name != nullptr && port_name[0] != '\0', nullptr); + CARLA_SAFE_ASSERT_RETURN(port_type != nullptr && port_type[0] != '\0', nullptr); + + if (std::strcmp(port_type, JACK_DEFAULT_AUDIO_TYPE) == 0) + { + uint32_t index; + + /**/ if (flags & JackPortIsInput) + { + if (isActivated) + { + CARLA_SAFE_ASSERT_RETURN(jstate.fakeIns > 0, nullptr); + jstate.fakeIns -= 1; + index = jstate.audioIns.count() - jstate.fakeIns - 1; + } + else + { + index = jstate.audioIns.count(); + jstate.audioIns.append(new JackPortState(port_name, index, flags)); + } + + return (jack_port_t*)jstate.audioIns.getAt(index, nullptr); + } + else if (flags & JackPortIsOutput) + { + if (isActivated) + { + CARLA_SAFE_ASSERT_RETURN(jstate.fakeOuts > 0, nullptr); + jstate.fakeOuts -= 1; + index = jstate.audioOuts.count() - jstate.fakeOuts - 1; + } + else + { + index = jstate.audioOuts.count(); + jstate.audioOuts.append(new JackPortState(port_name, index, flags)); + } + + return (jack_port_t*)jstate.audioOuts.getAt(index, nullptr); + } + + carla_stderr2("Invalid port flags '%x'", flags); + return nullptr; + } + + carla_stderr2("Invalid port type '%s'", port_type); + return nullptr; +} + +CARLA_EXPORT +int jack_port_unregister(jack_client_t* client, jack_port_t* port) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 1); + + JackPortState* const jport = (JackPortState*)port; + CARLA_SAFE_ASSERT_RETURN(jport != nullptr, 1); + + JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(! jstate.activated, 1); + + if (jport->flags & JackPortIsOutput) + { + CARLA_SAFE_ASSERT_RETURN(jstate.audioIns.removeOne(jport), 1); + } + else + { + CARLA_SAFE_ASSERT_RETURN(jstate.audioOuts.removeOne(jport), 1); + } + + return 0; +} + +CARLA_EXPORT +void* jack_port_get_buffer(jack_port_t* port, jack_nframes_t) +{ + JackPortState* const jport = (JackPortState*)port; + CARLA_SAFE_ASSERT_RETURN(jport != nullptr, nullptr); + + return jport->buffer; +} + +CARLA_EXPORT +int jack_set_process_callback(jack_client_t* client, JackProcessCallback callback, void* arg) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 1); + + JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(! jstate.activated, 1); + + jstate.process = callback; + jstate.processPtr = arg; + + return 0; +} + +CARLA_EXPORT +void jack_on_shutdown(jack_client_t* client, JackShutdownCallback callback, void* arg) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr,); + + JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(! jstate.activated,); + + jstate.shutdown = callback; + jstate.shutdownPtr = arg; +} + +CARLA_EXPORT +int jack_activate(jack_client_t* client) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 1); + + const JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(! jstate.activated, 1); + + jclient->activate(); + return 0; +} + +CARLA_EXPORT +int jack_deactivate(jack_client_t* client) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 1); + + const JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(jstate.activated, 1); + + jclient->deactivate(); + return 0; +} + +CARLA_EXPORT +char* jack_get_client_name(jack_client_t* client) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, nullptr); + + const JackClientState& jstate(jclient->fState); + + return jstate.name; +} + +CARLA_EXPORT +jack_nframes_t jack_get_buffer_size(jack_client_t* client) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 0); + + const JackClientState& jstate(jclient->fState); + + return jstate.bufferSize; +} + +CARLA_EXPORT +jack_nframes_t jack_get_sample_rate(jack_client_t* client) +{ + carla_debug("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 0); + + const JackClientState& jstate(jclient->fState); + + return jstate.sampleRate; +} + +// -------------------------------------------------------------------------------------------------------------------- + +CARLA_EXPORT +void jack_set_error_function(void (*func)(const char *)) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + (void)func; +} + +CARLA_EXPORT +int jack_set_sync_callback(jack_client_t* client, JackSyncCallback /*callback*/, void* /*arg*/) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 1); + + const JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(! jstate.activated, 1); + + // TODO + + return 0; +} + +CARLA_EXPORT +int jack_set_timebase_callback(jack_client_t* client, int, JackTimebaseCallback, void*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 1); + + const JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(! jstate.activated, 1); + + // TODO + + return EBUSY; +} + +CARLA_EXPORT +int jack_set_buffer_size_callback(jack_client_t* client, JackBufferSizeCallback /*callback*/, void* /*arg*/) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 1); + + const JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(! jstate.activated, 1); + + // TODO + + return 0; +} + +CARLA_EXPORT +int jack_set_sample_rate_callback(jack_client_t* client, JackSampleRateCallback /*callback*/, void* /*arg*/) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 1); + + const JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(! jstate.activated, 1); + + // TODO + + return 0; +} + +CARLA_EXPORT +int jack_set_client_registration_callback(jack_client_t* client, JackClientRegistrationCallback, void*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 1); + + const JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(! jstate.activated, 1); + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + + return 0; +} + +CARLA_EXPORT +int jack_set_port_registration_callback(jack_client_t* client, JackPortRegistrationCallback, void*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 1); + + const JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(! jstate.activated, 1); + + return 0; +} + +CARLA_EXPORT +int jack_set_port_connect_callback(jack_client_t* client, JackPortConnectCallback, void*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 1); + + const JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(! jstate.activated, 1); + + return 0; +} + +CARLA_EXPORT +int jack_set_graph_order_callback(jack_client_t* client, JackGraphOrderCallback, void*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 1); + + const JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(! jstate.activated, 1); + + return 0; +} + +CARLA_EXPORT +int jack_is_realtime(jack_client_t*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + return 0; +} + +CARLA_EXPORT +int jack_transport_locate(jack_client_t*, jack_nframes_t) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + return 1; +} + +CARLA_EXPORT +jack_transport_state_t jack_transport_query(const jack_client_t* client, jack_position_t* pos) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, JackTransportStopped); + + const JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(jstate.activated, JackTransportStopped); + + if (pos != nullptr) + std::memcpy(pos, &jstate.position, sizeof(jack_position_t)); + + return jstate.playing ? JackTransportRolling : JackTransportStopped; +} + +CARLA_EXPORT +int jack_transport_reposition(jack_client_t*, const jack_position_t*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + return EINVAL; +} + +CARLA_EXPORT +void jack_transport_start(jack_client_t*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + +} + +CARLA_EXPORT +void jack_transport_stop (jack_client_t*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + +} + +CARLA_EXPORT +pthread_t jack_client_thread_id(jack_client_t* client) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 0); + + const JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(jstate.activated, 0); + + return (pthread_t)jclient->getThreadId(); +} + +CARLA_EXPORT +jack_nframes_t jack_frame_time(const jack_client_t* client) +{ + carla_debug("CarlaJackClient :: %s", __FUNCTION__); + + CarlaJackClient* const jclient = (CarlaJackClient*)client; + CARLA_SAFE_ASSERT_RETURN(jclient != nullptr, 0); + + const JackClientState& jstate(jclient->fState); + CARLA_SAFE_ASSERT_RETURN(jstate.activated, 0); + + return jstate.position.usecs; +} + +CARLA_EXPORT +void jack_free(void* ptr) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + free(ptr); +} + +CARLA_EXPORT +jack_nframes_t jack_midi_get_event_count(void*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + return 0; +} + + +CARLA_EXPORT +int jack_midi_event_get(jack_midi_event_t*, void*, uint32_t) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + return ENODATA; +} + +CARLA_EXPORT +const char* jack_port_short_name(const jack_port_t* port) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + JackPortState* const jport = (JackPortState*)port; + CARLA_SAFE_ASSERT_RETURN(jport != nullptr, nullptr); + + return jport->name; +} + +CARLA_EXPORT +const char* jack_port_name(const jack_port_t* port) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + JackPortState* const jport = (JackPortState*)port; + CARLA_SAFE_ASSERT_RETURN(jport != nullptr, nullptr); + + return jport->fullname; +} + +CARLA_EXPORT +int jack_port_flags(const jack_port_t* port) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + JackPortState* const jport = (JackPortState*)port; + CARLA_SAFE_ASSERT_RETURN(jport != nullptr, 0); + + return jport->flags; +} + +CARLA_EXPORT +const char* jack_port_type(const jack_port_t* port) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + JackPortState* const jport = (JackPortState*)port; + CARLA_SAFE_ASSERT_RETURN(jport != nullptr, nullptr); + + // TODO + + return JACK_DEFAULT_AUDIO_TYPE; +} + +CARLA_EXPORT +int jack_client_real_time_priority(jack_client_t*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + return -1; +} + +CARLA_EXPORT +int jack_connect(jack_client_t*, const char*, const char*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + return 0; +} + +CARLA_EXPORT +int jack_disconnect(jack_client_t*, const char*, const char*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + return 0; +} + +CARLA_EXPORT +const char** jack_get_ports(jack_client_t*, const char* a, const char* b, unsigned long flags) +{ + carla_stdout("CarlaJackClient :: %s | %s %s %li", __FUNCTION__, a, b, flags); + + static const char* capture_1 = "system:capture_1"; + static const char* capture_2 = "system:capture_2"; + static const char* playback_1 = "system:playback_1"; + static const char* playback_2 = "system:playback_2"; + + if (flags == 0 || (flags & (JackPortIsInput|JackPortIsOutput)) != 0) + { + if (const char** const ret = (const char**)calloc(5, sizeof(const char*))) + { + ret[0] = capture_1; + ret[1] = capture_2; + ret[2] = playback_1; + ret[3] = playback_2; + ret[4] = nullptr; + return ret; + } + } + + if (flags & JackPortIsInput) + { + if (const char** const ret = (const char**)calloc(3, sizeof(const char*))) + { + ret[0] = playback_1; + ret[1] = playback_2; + ret[2] = nullptr; + return ret; + } + } + + if (flags & JackPortIsOutput) + { + if (const char** const ret = (const char**)calloc(3, sizeof(const char*))) + { + ret[0] = capture_1; + ret[1] = capture_2; + ret[2] = nullptr; + return ret; + } + } + + return nullptr; +} + +CARLA_EXPORT +jack_port_t* jack_port_by_name(jack_client_t*, const char*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + return nullptr; +} + +CARLA_EXPORT +jack_port_t* jack_port_by_id(jack_client_t*, jack_port_id_t) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + return nullptr; +} + +CARLA_EXPORT +void jack_set_info_function(void (*func)(const char *)) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + (void)func; +} + +CARLA_EXPORT +int jack_set_freewheel(jack_client_t*, int) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + return 1; +} + +CARLA_EXPORT +int jack_set_buffer_size(jack_client_t*, jack_nframes_t) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + return 1; +} + +CARLA_EXPORT +int jack_engine_takeover_timebase(jack_client_t*) +{ + carla_stdout("CarlaJackClient :: %s", __FUNCTION__); + + return ENOSYS; +} + +CARLA_EXPORT +float jack_cpu_load(jack_client_t*) +{ + return 0.0f; +} + +CARLA_EXPORT +int jack_client_name_size(void) +{ + return STR_MAX; +} + +CARLA_EXPORT +void jack_port_get_latency_range(jack_port_t*, jack_latency_callback_mode_t, jack_latency_range_t* range) +{ + range->min = range->max = 0; +} + +CARLA_EXPORT +jack_nframes_t jack_port_get_latency(jack_port_t*) +{ + return 0; +} + +CARLA_EXPORT +int jack_set_xrun_callback(jack_client_t*, JackXRunCallback, void*) +{ + return 0; +} + +CARLA_EXPORT +int jack_set_thread_init_callback(jack_client_t*, JackThreadInitCallback, void*) +{ + return 0; +} + +// -------------------------------------------------------------------------------------------------------------------- + +#include "jackbridge/JackBridge2.cpp" +#include "CarlaBridgeUtils.cpp" + +// -------------------------------------------------------------------------------------------------------------------- diff --git a/source/tests/carla-plugin-jack.py b/source/tests/carla-plugin-jack.py index 3ffd558cb..503fa751f 100755 --- a/source/tests/carla-plugin-jack.py +++ b/source/tests/carla-plugin-jack.py @@ -32,7 +32,10 @@ if not host.engine_init("JACK", "Carla-Plugin-JACK"): print("Engine failed to initialize, possible reasons:\n%s" % host.get_last_error()) exit(1) -if not host.add_plugin(BINARY_NATIVE, PLUGIN_JACK, "/usr/bin/lmms", "", "", 0, None, 0): +fname = "/usr/bin/pulseaudio" +label = "--high-priority --realtime --exit-idle-time=-1 --file=/usr/share/cadence/pulse2jack/play.pa -n" + +if not host.add_plugin(BINARY_NATIVE, PLUGIN_JACK, fname, "", label, 0, None, 0): print("Failed to load plugin, possible reasons:\n%s" % host.get_last_error()) host.engine_close() exit(1)