/* * Carla Plugin bridge code * Copyright (C) 2012 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 COPYING file */ #ifdef BUILD_BRIDGE_PLUGIN #include "carla_bridge_client.hpp" #include "carla_backend_utils.hpp" #include "carla_plugin.hpp" #include #include #include #include #include #include #include #include #ifdef Q_OS_UNIX # include #endif static int qargc = 0; static char** qargv = nullptr; static bool qCloseNow = false; static bool qSaveNow = false; #if defined(Q_OS_HAIKU) || defined(Q_OS_UNIX) void closeSignalHandler(int) { qCloseNow = true; } void saveSignalHandler(int) { qSaveNow = true; } #elif defined(Q_OS_WIN) BOOL WINAPI closeSignalHandler(DWORD dwCtrlType) { if (dwCtrlType == CTRL_C_EVENT) { qCloseNow = true; return TRUE; } return FALSE; } #endif void initSignalHandler() { #if defined(Q_OS_HAIKU) || defined(Q_OS_UNIX) struct sigaction sint; struct sigaction sterm; struct sigaction susr1; sint.sa_handler = closeSignalHandler; sint.sa_flags = SA_RESTART; sint.sa_restorer = nullptr; sigemptyset(&sint.sa_mask); sigaction(SIGINT, &sint, nullptr); sterm.sa_handler = closeSignalHandler; sterm.sa_flags = SA_RESTART; sterm.sa_restorer = nullptr; sigemptyset(&sterm.sa_mask); sigaction(SIGTERM, &sterm, nullptr); susr1.sa_handler = saveSignalHandler; susr1.sa_flags = SA_RESTART; susr1.sa_restorer = nullptr; sigemptyset(&susr1.sa_mask); sigaction(SIGUSR1, &susr1, nullptr); #elif defined(Q_OS_WIN) SetConsoleCtrlHandler(closeSignalHandler, TRUE); #endif } CARLA_BRIDGE_START_NAMESPACE // ------------------------------------------------------------------------- class BridgePluginGUI : public QMainWindow { public: class Callback { public: virtual ~Callback() {} virtual void guiClosedCallback() = 0; }; BridgePluginGUI(QWidget* const parent, Callback* const callback_) : QMainWindow(parent), callback(callback_) { qDebug("BridgePluginGUI::BridgePluginGUI(%p, %p", parent, callback); CARLA_ASSERT(callback); m_firstShow = true; m_resizable = true; container = new GuiContainer(this); setCentralWidget(container); setNewSize(50, 50); } ~BridgePluginGUI() { qDebug("BridgePluginGUI::~BridgePluginGUI()"); CARLA_ASSERT(container); delete container; } GuiContainer* getContainer() { return container; } void setResizable(bool resizable) { m_resizable = resizable; setNewSize(width(), height()); #ifdef Q_OS_WIN if (! resizable) setWindowFlags(windowFlags() | Qt::MSWindowsFixedSizeDialogHint); #endif } void setTitle(const char* title) { CARLA_ASSERT(title); setWindowTitle(QString("%1 (GUI)").arg(title)); } void setNewSize(int width, int height) { qDebug("BridgePluginGUI::setNewSize(%i, %i)", width, height); if (width < 30) width = 30; if (height < 30) height = 30; if (m_resizable) { resize(width, height); } else { setFixedSize(width, height); container->setFixedSize(width, height); } } void setVisible(const bool yesNo) { qDebug("BridgePluginGUI::setVisible(%s)", bool2str(yesNo)); if (yesNo) { if (m_firstShow) { m_firstShow = false; restoreGeometry(QByteArray()); } else if (! m_geometry.isNull()) restoreGeometry(m_geometry); } else m_geometry = saveGeometry(); QMainWindow::setVisible(yesNo); } protected: void hideEvent(QHideEvent* const event) { qDebug("BridgePluginGUI::hideEvent(%p)", event); event->accept(); close(); } void closeEvent(QCloseEvent* const event) { qDebug("BridgePluginGUI::closeEvent(%p)", event); if (event->spontaneous()) { callback->guiClosedCallback(); QMainWindow::closeEvent(event); return; } event->ignore(); } private: Callback* const callback; GuiContainer* container; bool m_firstShow; bool m_resizable; QByteArray m_geometry; }; // ------------------------------------------------------------------------- class BridgePluginClient : public CarlaToolkit, public CarlaClient, public BridgePluginGUI::Callback, public QApplication { public: BridgePluginClient() : CarlaToolkit("carla-bridge-plugin"), CarlaClient(this), QApplication(qargc, qargv, true) { qDebug("BridgePluginClient::BridgePluginClient()"); msgTimerGUI = 0; msgTimerOSC = 0; engine = nullptr; plugin = nullptr; pluginGui = nullptr; m_client = this; m_doQuit = false; m_hasUI = false; m_qt4UI = false; m_needsResize = false; m_nextSize[0] = 0; m_nextSize[1] = 0; } ~BridgePluginClient() { qDebug("BridgePluginClient::~BridgePluginClient()"); CARLA_ASSERT(msgTimerGUI == 0); CARLA_ASSERT(msgTimerOSC == 0); CARLA_ASSERT(! pluginGui); } void setStuff(CarlaBackend::CarlaEngine* const engine, CarlaBackend::CarlaPlugin* const plugin) { qDebug("BridgePluginClient::setStuff(%p, %p)", engine, plugin); CARLA_ASSERT(engine); CARLA_ASSERT(plugin); this->engine = engine; this->plugin = plugin; } // --------------------------------------------------------------------- // toolkit void init() { qDebug("BridgePluginClient::init()"); pluginGui = new BridgePluginGUI(nullptr, this); pluginGui->hide(); } void exec(CarlaClient* const, const bool showGui) { qDebug("BridgePluginClient::exec()"); if (showGui) { if (m_hasUI) show(); m_doQuit = true; } else { CarlaClient::sendOscUpdate(); CarlaClient::sendOscBridgeUpdate(); QApplication::setQuitOnLastWindowClosed(false); } msgTimerGUI = startTimer(50); msgTimerOSC = startTimer(25); QApplication::exec(); } void quit() { qDebug("BridgePluginClient::quit()"); if (msgTimerGUI != 0) { QApplication::killTimer(msgTimerGUI); msgTimerGUI = 0; } if (msgTimerOSC != 0) { QApplication::killTimer(msgTimerOSC); msgTimerOSC = 0; } if (pluginGui) { if (pluginGui->isVisible()) hide(); pluginGui->close(); delete pluginGui; pluginGui = nullptr; } if (! QApplication::closingDown()) QApplication::quit(); } void show() { qDebug("BridgePluginClient::show()"); CARLA_ASSERT(pluginGui); if (plugin) plugin->showGui(true); if (pluginGui && m_qt4UI) pluginGui->show(); } void hide() { qDebug("BridgePluginClient::hide()"); CARLA_ASSERT(pluginGui); if (pluginGui && m_qt4UI) pluginGui->hide(); if (plugin) plugin->showGui(false); } void resize(int width, int height) { qDebug("BridgePluginClient::resize(%i, %i)", width, height); CARLA_ASSERT(pluginGui); if (pluginGui) pluginGui->setNewSize(width, height); } // --------------------------------------------------------------------- void createWindow(const bool resizable) { qDebug("BridgePluginClient::createWindow(%s)", bool2str(resizable)); CARLA_ASSERT(plugin); CARLA_ASSERT(pluginGui); if (! (plugin && pluginGui)) return; m_hasUI = true; m_qt4UI = true; pluginGui->setResizable(resizable); pluginGui->setTitle(plugin->name()); plugin->setGuiContainer(pluginGui->getContainer()); } void enableUI() { m_hasUI = true; } // --------------------------------------------------------------------- // processing void setParameter(const int32_t rindex, const double value) { qDebug("CarlaPluginClient::setParameter(%i, %g)", rindex, value); CARLA_ASSERT(plugin); if (! plugin) return; plugin->setParameterValueByRIndex(rindex, value, true, true, false); } void setProgram(const uint32_t index) { qDebug("CarlaPluginClient::setProgram(%i)", index); CARLA_ASSERT(engine); CARLA_ASSERT(plugin); CARLA_ASSERT(index < plugin->programCount()); if (! (plugin && engine)) return; if (index >= plugin->programCount()) return; plugin->setProgram(index, true, true, false, true); double value; for (uint32_t i=0; i < plugin->parameterCount(); i++) { value = plugin->getParameterValue(i); engine->osc_send_bridge_set_parameter_value(i, value); engine->osc_send_bridge_set_default_value(i, value); } } void setMidiProgram(const uint32_t index) { qDebug("CarlaPluginClient::setMidiProgram(%i)", index); CARLA_ASSERT(engine); CARLA_ASSERT(plugin); if (! (plugin && engine)) return; plugin->setMidiProgram(index, true, true, false, true); double value; for (uint32_t i=0; i < plugin->parameterCount(); i++) { value = plugin->getParameterValue(i); engine->osc_send_bridge_set_parameter_value(i, value); engine->osc_send_bridge_set_default_value(i, value); } } void noteOn(const uint8_t channel, const uint8_t note, const uint8_t velo) { qDebug("CarlaPluginClient::noteOn(%i, %i, %i)", channel, note, velo); CARLA_ASSERT(plugin); CARLA_ASSERT(velo > 0); if (! plugin) return; plugin->sendMidiSingleNote(channel, note, velo, true, true, false); } void noteOff(const uint8_t channel, const uint8_t note) { qDebug("CarlaPluginClient::noteOff(%i, %i)", channel, note); CARLA_ASSERT(plugin); if (! plugin) return; plugin->sendMidiSingleNote(channel, note, 0, true, true, false); } // --------------------------------------------------------------------- // plugin void saveNow() { qDebug("CarlaPluginClient::saveNow()"); CARLA_ASSERT(plugin); CARLA_ASSERT(engine); if (! (plugin && engine)) return; plugin->prepareForSave(); for (uint32_t i=0; i < plugin->customDataCount(); i++) { const CarlaBackend::CustomData* const cdata = plugin->customData(i); engine->osc_send_bridge_set_custom_data(cdata->type, cdata->key, cdata->value); } if (plugin->hints() & CarlaBackend::PLUGIN_USES_CHUNKS) { void* data = nullptr; int32_t dataSize = plugin->chunkData(&data); if (data && dataSize >= 4) { QString filePath; filePath = QDir::tempPath(); #ifdef Q_OS_WIN filePath += "\\.CarlaChunk_"; #else filePath += "/.CarlaChunk_"; #endif filePath += plugin->name(); QFile file(filePath); if (file.open(QIODevice::WriteOnly)) { QByteArray chunk((const char*)data, dataSize); file.write(chunk); file.close(); engine->osc_send_bridge_set_chunk_data(filePath.toUtf8().constData()); } } } engine->osc_send_bridge_configure(CarlaBackend::CARLA_BRIDGE_MSG_SAVED, ""); } void setCustomData(const char* const type, const char* const key, const char* const value) { qDebug("CarlaPluginClient::setCustomData(\"%s\", \"%s\", \"%s\")", type, key, value); CARLA_ASSERT(plugin); if (! plugin) return; plugin->setCustomData(type, key, value, true); } void setChunkData(const char* const filePath) { qDebug("CarlaPluginClient::setChunkData(\"%s\")", filePath); CARLA_ASSERT(plugin); if (! plugin) return; QString chunkFilePath(filePath); #ifdef Q_OS_WIN if (chunkFilePath.startsWith("/")) { // running under Wine, posix host chunkFilePath = chunkFilePath.replace(0, 1, "Z:/"); chunkFilePath = QDir::toNativeSeparators(chunkFilePath); } #endif QFile chunkFile(chunkFilePath); if (plugin && chunkFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream in(&chunkFile); QString stringData(in.readAll()); chunkFile.close(); chunkFile.remove(); plugin->setChunkData(stringData.toUtf8().constData()); } } // --------------------------------------------------------------------- // callback void handleCallback(const CarlaBackend::CallbackType action, const int value1, const int value2, const double value3, const char* const valueStr) { qDebug("CarlaPluginClient::handleCallback(%s, %i, %i, %g \"%s\")", CarlaBackend::CallbackType2Str(action), value1, value2, value3, valueStr); if (! engine) return; switch (action) { case CarlaBackend::CALLBACK_PARAMETER_VALUE_CHANGED: #if 0 parametersToUpdate.insert(value1); #endif engine->osc_send_bridge_set_parameter_value(value1, value3); break; case CarlaBackend::CALLBACK_PROGRAM_CHANGED: engine->osc_send_bridge_set_program(value1); break; case CarlaBackend::CALLBACK_MIDI_PROGRAM_CHANGED: engine->osc_send_bridge_set_midi_program(value1); break; case CarlaBackend::CALLBACK_NOTE_ON: { //uint8_t mdata[4] = { 0, MIDI_STATUS_NOTE_ON, (uint8_t)value1, (uint8_t)value2 }; //osc_send_midi(mdata); break; } case CarlaBackend::CALLBACK_NOTE_OFF: { //uint8_t mdata[4] = { 0, MIDI_STATUS_NOTE_OFF, (uint8_t)value1, (uint8_t)value2 }; //osc_send_midi(mdata); break; } case CarlaBackend::CALLBACK_SHOW_GUI: if (value1 == 0) { engine->osc_send_bridge_configure(CarlaBackend::CARLA_BRIDGE_MSG_HIDE_GUI, ""); if (m_doQuit) qCloseNow = true; } break; case CarlaBackend::CALLBACK_RESIZE_GUI: CARLA_ASSERT(value1 > 0 && value2 > 0); CARLA_ASSERT(pluginGui); if (value1 > 0 && value2 > 0 && pluginGui) { m_needsResize = true; m_nextSize[0] = value1; m_nextSize[1] = value2; } break; case CarlaBackend::CALLBACK_RELOAD_PARAMETERS: //if (CARLA_PLUGIN) //{ // for (uint32_t i=0; i < CARLA_PLUGIN->parameterCount(); i++) // { // osc_send_control(i, CARLA_PLUGIN->getParameterValue(i)); // } //} break; case CarlaBackend::CALLBACK_QUIT: //QApplication::quit(); break; default: break; } } // --------------------------------------------------------------------- static void callback(void* const ptr, CarlaBackend::CallbackType const action, const unsigned short, const int value1, const int value2, const double value3, const char* const valueStr) { CARLA_ASSERT(ptr); if (! ptr) return; BridgePluginClient* const _this_ = (BridgePluginClient*)ptr; _this_->handleCallback(action, value1, value2, value3, valueStr); } protected: void guiClosedCallback() { if (engine) engine->osc_send_bridge_configure(CarlaBackend::CARLA_BRIDGE_MSG_HIDE_GUI, ""); } void timerEvent(QTimerEvent* const event) { if (qCloseNow) return quit(); if (qSaveNow) { // TODO qSaveNow = false; } if (event->timerId() == msgTimerGUI) { #if 0 if (parametersToUpdate.size() > 0) { for (auto it = parametersToUpdate.begin(); it != parametersToUpdate.end(); it++) { const int32_t paramId(*it); engine->osc_send_bridge_set_parameter_value(paramId, plugin->getParameterValue(paramId)); } parametersToUpdate.clear(); } #endif if (plugin) plugin->idleGui(); if (pluginGui && m_needsResize) { pluginGui->setNewSize(m_nextSize[0], m_nextSize[1]); m_needsResize = false; } } else if (event->timerId() == msgTimerOSC) { if (! CarlaClient::oscIdle()) { CARLA_ASSERT(msgTimerOSC == 0); msgTimerOSC = 0; return; } } QApplication::timerEvent(event); } // --------------------------------------------------------------------- private: int msgTimerGUI, msgTimerOSC; #if 0 std::set parametersToUpdate; #endif bool m_doQuit; bool m_hasUI; bool m_qt4UI; bool m_needsResize; int m_nextSize[2]; CarlaBackend::CarlaEngine* engine; CarlaBackend::CarlaPlugin* plugin; BridgePluginGUI* pluginGui; }; // ------------------------------------------------------------------------- CARLA_BRIDGE_END_NAMESPACE int main(int argc, char* argv[]) { if (argc != 6) { qWarning("usage: %s