/* * 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 */ #include "carla_bridge_client.h" #include "carla_plugin.h" #include #include #ifndef __WINE__ //#include # include //#include #endif static HINSTANCE hInstG = nullptr; CARLA_BRIDGE_START_NAMESPACE // ------------------------------------------------------------------------- // client class CarlaPluginClient : public CarlaClient { public: CarlaPluginClient(CarlaToolkit* const toolkit) : CarlaClient(toolkit) { engine = nullptr; plugin = nullptr; } ~CarlaPluginClient() { } void setStuff(CarlaBackend::CarlaEngine* const engine_, CarlaBackend::CarlaPlugin* const plugin_) { engine = engine_; plugin = plugin_; } // --------------------------------------------------------------------- // processing void setParameter(const int32_t rindex, const double value) { Q_ASSERT(plugin); if (! plugin) return; plugin->setParameterValueByRIndex(rindex, value, true, true, false); } void setProgram(const uint32_t index) { Q_ASSERT(plugin && index < plugin->programCount()); if (! plugin) return; if (index >= plugin->programCount()) return; plugin->setProgram(index, true, true, false, true); } void setMidiProgram(const uint32_t bank, const uint32_t program) { Q_ASSERT(plugin); if (! plugin) return; plugin->setMidiProgramById(bank, program, true, true, false, true); } void noteOn(const uint8_t channel, const uint8_t note, const uint8_t velo) { Q_ASSERT(plugin); Q_ASSERT(velo > 0); if (! plugin) return; plugin->sendMidiSingleNote(channel, note, velo, true, true, false); } void noteOff(const uint8_t channel, const uint8_t note) { Q_ASSERT(plugin); if (! plugin) return; plugin->sendMidiSingleNote(channel, note, 0, true, true, false); } // --------------------------------------------------------------------- // plugin void saveNow() { Q_ASSERT(plugin); if (! plugin) return; plugin->prepareForSave(); #if 0 for (uint32_t i=0; i < CARLA_PLUGIN->customDataCount(); i++) { const CustomData* const cdata = CARLA_PLUGIN->customData(i); osc_send_bridge_custom_data(customdatatype2str(cdata->type), cdata->key, cdata->value); } if (CARLA_PLUGIN->hints() & PLUGIN_USES_CHUNKS) { void* data = nullptr; int32_t dataSize = CARLA_PLUGIN->chunkData(&data); if (data && dataSize >= 4) { QString filePath; filePath += "/tmp/.CarlaChunk_"; filePath += CARLA_PLUGIN->name(); QFile file(filePath); if (file.open(QIODevice::WriteOnly)) { QByteArray chunk((const char*)data, dataSize); file.write(chunk); file.close(); osc_send_bridge_chunk_data(filePath.toUtf8().constData()); } } } osc_send_configure(CARLA_BRIDGE_MSG_SAVED, ""); #endif } void setCustomData(const char* const type, const char* const key, const char* const value) { Q_ASSERT(plugin); if (! plugin) return; plugin->setCustomData(CarlaBackend::getCustomDataStringType(type), key, value, true); } void setChunkData(const char* const filePath) { Q_ASSERT(plugin); if (! plugin) return; Q_UNUSED(filePath); #if 0 nextChunkFilePath = strdup(filePath); while (nextChunkFilePath) carla_msleep(25); #endif } // --------------------------------------------------------------------- // idle void idle() { Q_ASSERT(plugin); if (! plugin) return; plugin->idleGui(); } // --------------------------------------------------------------------- // callback void handleCallback(const CarlaBackend::CallbackType action, const int value1, const int value2, const double value3) { switch (action) { case CarlaBackend::CALLBACK_PARAMETER_VALUE_CHANGED: //osc_send_control(value1, value3); break; case CarlaBackend::CALLBACK_PROGRAM_CHANGED: //osc_send_program(value1); break; case CarlaBackend::CALLBACK_MIDI_PROGRAM_CHANGED: //osc_send_midi_program(value1, value2, false); 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) //sendOscConfigure(CarlaBackend::CARLA_BRIDGE_MSG_HIDE_GUI, ""); //quequeMessage(MESSAGE_QUIT, 0, 0, 0.0); break; case CarlaBackend::CALLBACK_RESIZE_GUI: //quequeMessage(MESSAGE_RESIZE_GUI, value1, value2, 0.0); //m_toolkit->resize(value1, 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: //quequeMessage(MESSAGE_QUIT, 0, 0, 0.0); break; default: break; } Q_UNUSED(value1); Q_UNUSED(value2); Q_UNUSED(value3); } // --------------------------------------------------------------------- static void callback(void* const ptr, CarlaBackend::CallbackType const action, const unsigned short, const int value1, const int value2, const double value3) { Q_ASSERT(ptr); if (! ptr) return; CarlaPluginClient* const client = (CarlaPluginClient*)ptr; client->handleCallback(action, value1, value2, value3); } private: CarlaBackend::CarlaEngine* engine; CarlaBackend::CarlaPlugin* plugin; }; // ------------------------------------------------------------------------- // toolkit class CarlaToolkitPlugin : public CarlaToolkit { public: CarlaToolkitPlugin() : CarlaToolkit("carla-bridge-plugin") { qDebug("CarlaToolkitPlugin::CarlaToolkitPlugin()"); #ifdef __WINE__ closeNow = false; hwnd = nullptr; #else app = nullptr; dialog = nullptr; #endif } ~CarlaToolkitPlugin() { qDebug("CarlaToolkitPlugin::~CarlaToolkitPlugin()"); } void init() { qDebug("CarlaToolkitPlugin::init()"); #ifdef __WINE__ Q_ASSERT(! closeNow); WNDCLASSEXA wc; wc.cbSize = sizeof(WNDCLASSEXA); wc.style = 0; wc.lpfnWndProc = windowProcA; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstG; //nullptr; wc.hIcon = LoadIconA(nullptr, IDI_APPLICATION); wc.hCursor = LoadCursorA(nullptr, IDC_ARROW); wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND; wc.lpszMenuName = "MENU_CARLA_BRIDGE"; wc.lpszClassName = "CLASS_CARLA_BRIDGE"; wc.hIconSm = nullptr; RegisterClassExA(&wc); #else Q_ASSERT(! app); static int argc = 0; static char* argv[] = { nullptr }; app = new QApplication(argc, argv, true); #endif } void exec(CarlaClient* const client) { qDebug("CarlaToolkitPlugin::exec(%p)", client); Q_ASSERT(client); m_client = client; #ifdef QTCREATOR_TEST show(); #endif #ifdef __WINE__ Q_ASSERT(! closeNow); MSG msg; CarlaPluginClient* const pluginClient = (CarlaPluginClient*)client; while (! closeNow) { //pluginClient->runMessages(); //pluginClient->idle(); //if (closeNow) // break; while (GetMessageA(&msg, hwnd, 0, 0) > 0) { //if (PeekMessageA(&msg, hwnd, 0, 0, PM_REMOVE)) //{ //TranslateMessage(&msg); DispatchMessageA(&msg); pluginClient->runMessages(); pluginClient->idle(); } //if (closeNow) // break; //carla_msleep(50); } #else Q_ASSERT(app); app->exec(); #endif } void quit() { qDebug("CarlaToolkitPlugin::quit()"); #ifdef __WINE__ if (closeNow && hwnd) { DestroyWindow(hwnd); hwnd = nullptr; } closeNow = true; #else Q_ASSERT(app); if (app && dialog) { delete dialog; dialog = nullptr; } if (! app) return; if (! app->closingDown()) app->quit(); delete app; #endif } void show() { qDebug("CarlaToolkitPlugin::show()"); ShowWindow(hwnd, SW_SHOWNORMAL); UpdateWindow(hwnd); } void hide() { qDebug("CarlaToolkitPlugin::hide()"); ShowWindow(hwnd, SW_HIDE); } void resize(int width, int height) { qDebug("CarlaToolkitPlugin::resize(%i, %i)", width, height); SetWindowPos(hwnd, 0, 0, 0, width + 6, height + 25, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER); } void createWindow(const char* const pluginName) { #ifdef __WINE__ hwnd = CreateWindowA("CLASS_CARLA_BRIDGE", pluginName, WS_OVERLAPPEDWINDOW &~ WS_THICKFRAME &~ WS_MAXIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, nullptr, hInstG, nullptr); if (! hwnd) return; SetWindowLongPtrA(hwnd, GWLP_USERDATA, (LONG_PTR)this); SetWindowPos(hwnd, 0, 0, 0, 1100 + 6, 600 + 25, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER); #else dialog = new QDialog(nullptr); dialog->resize(10, 10); dialog->setWindowTitle(pluginName); #endif } CarlaBackend::GuiDataHandle getWindowHandle() const { #ifdef __WINE__ return hwnd; #else return dialog; #endif } private: #ifdef __WINE__ bool closeNow; HWND hwnd; void handleWindowCloseMessageA() { //m_client->quequeMessage(MESSAGE_QUIT, 0, 0, 0.0); //m_client->quequeMessage(MESSAGE_SHOW_GUI, 0, 0, 0.0); closeNow = true; m_client->sendOscConfigure(CarlaBackend::CARLA_BRIDGE_MSG_HIDE_GUI, ""); } static LRESULT CALLBACK windowProcA(HWND _hwnd, UINT message, WPARAM wParam, LPARAM lParam) { qDebug("windowProcA(%p, %i, %li, %li)", _hwnd, message, wParam, lParam); if (message == WM_CLOSE || message == WM_DESTROY) { CarlaToolkitPlugin* const toolkit = (CarlaToolkitPlugin*)GetWindowLongPtrA(_hwnd, GWLP_USERDATA); Q_ASSERT(toolkit); if (toolkit) { toolkit->handleWindowCloseMessageA(); return TRUE; } } return DefWindowProcA(_hwnd, message, wParam, lParam); } #else QApplication* app; QDialog* dialog; #endif }; CarlaToolkit* CarlaToolkit::createNew(const char* const) { return new CarlaToolkitPlugin; } // ------------------------------------------------------------------------- CARLA_BRIDGE_END_NAMESPACE #ifdef __WINE__ int WINAPI WinMain(HINSTANCE hInstX, HINSTANCE, LPSTR, int) { hInstG = hInstX; qWarning("test %li %p", hInstG, hInstG); #define MAXCMDTOKENS 128 int argc; LPSTR argv[MAXCMDTOKENS]; LPSTR p = GetCommandLine(); char command[256]; char *args; char *d, *e; argc = 0; args = (char *)malloc(lstrlen(p)+1); if (args == (char *)NULL) { qCritical("Insufficient memory in WinMain()"); return 1; } // Parse command line handling quotes. d = args; while (*p) { // for each argument if (argc >= MAXCMDTOKENS - 1) break; e = d; while ((*p) && (*p != ' ')) { if (*p == '\042') { // Remove quotes, skipping over embedded spaces. // Doesn't handle embedded quotes. p++; while ((*p) && (*p != '\042')) *d++ =*p++; } else *d++ = *p; if (*p) p++; } *d++ = '\0'; argv[argc++] = e; while ((*p) && (*p == ' ')) p++; // Skip over trailing spaces } argv[argc] = 0; if (strlen(argv[0]) == 0) { GetModuleFileName(hInstG, command, sizeof(command)-1); argv[0] = command; } #else int main(int argc, char* argv[]) { #endif //if (argc != 6) //{ //qWarning("%s :: bad arguments", argv[0]); //return 1; //} //const char* const oscUrl = argv[1]; //const char* const stype = argv[2]; //const char* const filename = argv[3]; //const char* name = argv[4]; //const char* const label = argv[5]; const char* const oscUrl = "osc.udp://null"; const char* const stype = "VST"; const char* const filename = "/home/falktx/.wine/drive_c/Program Files (x86)/VstPlugins/IL Harmless.dll"; const char* name = "(none)"; const char* const label = "DemoBuild"; if (strcmp(name, "(none)") == 0) name = nullptr; CarlaBackend::PluginType itype; if (strcmp(stype, "LADSPA") == 0) itype = CarlaBackend::PLUGIN_LADSPA; else if (strcmp(stype, "DSSI") == 0) itype = CarlaBackend::PLUGIN_DSSI; else if (strcmp(stype, "LV2") == 0) itype = CarlaBackend::PLUGIN_LV2; else if (strcmp(stype, "VST") == 0) itype = CarlaBackend::PLUGIN_VST; else { itype = CarlaBackend::PLUGIN_NONE; qWarning("Invalid plugin type '%s'", stype); return 1; } // Init toolkit CarlaBridge::CarlaToolkitPlugin toolkit; toolkit.init(); // Init client CarlaBridge::CarlaPluginClient client(&toolkit); // Init OSC if (! client.oscInit(oscUrl)) { toolkit.quit(); return -1; } // Init backend engine CarlaBackend::CarlaEngineJack engine; engine.setCallback(client.callback, &client); // bridge client <-> engine client.registerOscEngine(&engine); // Init engine QString engName = QString("%1 (master)").arg(label); engName.truncate(engine.maxClientNameSize()); engine.init(engName.toUtf8().constData()); /// Init plugin short id = engine.addPlugin(itype, filename, name, label); if (id >= 0 && id < CarlaBackend::MAX_PLUGINS) { CarlaBackend::CarlaPlugin* const plugin = engine.getPlugin(id); client.setStuff(&engine, plugin); plugin->setEnabled(true); plugin->setActive(true, false, false); toolkit.createWindow(plugin->name()); plugin->setGuiData(0, toolkit.getWindowHandle()); plugin->showGui(true); } else { qWarning("Plugin failed to load, error was:\n%s", CarlaBackend::getLastError()); return 1; } toolkit.exec(&client); engine.removeAllPlugins(); engine.close(); // Close OSC client.sendOscExiting(); client.oscClose(); // Close client //client.close(); // Close toolkit toolkit.quit(); return 0; }