/* * 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.h" #include "carla_plugin.h" #include #ifndef __WINE__ #include #include #include #endif #define CARLA_PLUGIN CarlaBackend::CarlaPlugins[0] void toolkit_plugin_idle(); ClientData* client = nullptr; // ------------------------------------------------------------------------- // backend stuff CARLA_BACKEND_START_NAMESPACE short add_plugin_ladspa(const char* const filename, const char* const name, const char* const label, const void* const extra_stuff); short add_plugin_dssi(const char* const filename, const char* const name, const char* const label, const void* const extra_stuff); short add_plugin_lv2(const char* const filename, const char* const name, const char* const label); short add_plugin_vst(const char* const filename, const char* const name, const char* const label); CARLA_BACKEND_END_NAMESPACE using namespace CarlaBackend; // ------------------------------------------------------------------------- // toolkit classes #ifdef __WINE__ LRESULT WINAPI MainProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CLOSE: if (client) client->queque_message(BRIDGE_MESSAGE_SHOW_GUI, 0, 0, 0.0); osc_send_configure(CARLA_BRIDGE_MSG_HIDE_GUI, ""); return TRUE; } return DefWindowProc(hWnd, msg, wParam, lParam); } static bool close_now = false; static HINSTANCE hInst = nullptr; static HWND gui = nullptr; #else class PluginIdleTimer : public QTimer { public: PluginIdleTimer() {} void timerEvent(QTimerEvent*) { if (client) client->run_messages(); toolkit_plugin_idle(); } Q_SLOT void guiClosed() { //if (client) // client->queque_message(BRIDGE_MESSAGE_SHOW_GUI, 0, 0, 0.0); osc_send_configure(CARLA_BRIDGE_MSG_HIDE_GUI, ""); } }; static QApplication* app = nullptr; static QDialog* gui = nullptr; #endif #define nextShowMsgNULL 0 #define nextShowMsgFALSE 1 #define nextShowMsgTRUE 2 static int nextShowMsg = nextShowMsgNULL; static const char* nextChunkFilePath = nullptr; // ------------------------------------------------------------------------- // toolkit calls void toolkit_init() { #ifdef __WINE__ #else static int argc = 0; static char* argv[] = { nullptr }; app = new QApplication(argc, argv, true); #endif } void toolkit_plugin_idle() { if (nextShowMsg) { bool yesno = nextShowMsg - 1; CARLA_PLUGIN->showGui(yesno); if (gui) { #ifdef __WINE__ ShowWindow(gui, yesno ? SW_SHOWNORMAL : SW_HIDE); UpdateWindow(gui); #else gui->setVisible(yesno); #endif } nextShowMsg = nextShowMsgNULL; } if (nextChunkFilePath) { QFile file(nextChunkFilePath); free((void*)nextChunkFilePath); nextChunkFilePath = nullptr; if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QString stringData = file.readAll(); file.remove(); CARLA_PLUGIN->setChunkData(stringData.toUtf8().constData()); } } CARLA_PLUGIN->idleGui(); static PluginPostEvent postEvents[MAX_POST_EVENTS]; CARLA_PLUGIN->postEventsCopy(postEvents); for (uint32_t i=0; i < MAX_POST_EVENTS; i++) { if (postEvents[i].type == PluginPostEventNull) break; switch (postEvents[i].type) { case PluginPostEventParameterChange: callback_action(CALLBACK_PARAMETER_CHANGED, 0, postEvents[i].index, 0, postEvents[i].value); break; case PluginPostEventProgramChange: callback_action(CALLBACK_PROGRAM_CHANGED, 0, postEvents[i].index, 0, 0.0); break; case PluginPostEventMidiProgramChange: callback_action(CALLBACK_MIDI_PROGRAM_CHANGED, 0, postEvents[i].index, 0, 0.0); break; case PluginPostEventNoteOn: callback_action(CALLBACK_NOTE_ON, 0, postEvents[i].index, postEvents[i].value, 0.0); break; case PluginPostEventNoteOff: callback_action(CALLBACK_NOTE_OFF, 0, postEvents[i].index, 0, 0.0); break; default: break; } } const ParameterData* paramData; for (uint32_t i=0; i < CARLA_PLUGIN->parameterCount(); i++) { paramData = CARLA_PLUGIN->parameterData(i); if (paramData->type == PARAMETER_OUTPUT && (paramData->hints & PARAMETER_IS_AUTOMABLE) > 0) osc_send_control(paramData->rindex, CARLA_PLUGIN->getParameterValue(i)); } if (CARLA_PLUGIN->audioInCount() > 0) { osc_send_bridge_ains_peak(1, ains_peak[0]); osc_send_bridge_ains_peak(2, ains_peak[1]); } if (CARLA_PLUGIN->audioOutCount() > 0) { osc_send_bridge_aouts_peak(1, aouts_peak[0]); osc_send_bridge_aouts_peak(2, aouts_peak[1]); } } void toolkit_loop() { #ifdef __WINE__ MSG msg; while (! close_now) { while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessage(&msg); client->run_messages(); toolkit_plugin_idle(); carla_msleep(50); } #else PluginIdleTimer timer; timer.start(50); if (gui) timer.connect(gui, SIGNAL(finished(int)), &timer, SLOT(guiClosed())); app->setQuitOnLastWindowClosed(false); app->exec(); #endif } void toolkit_quit() { #ifdef __WINE__ close_now = true; #else if (app) app->quit(); #endif } void toolkit_window_show() { nextShowMsg = nextShowMsgTRUE; } void toolkit_window_hide() { nextShowMsg = nextShowMsgFALSE; } void toolkit_window_resize(int width, int height) { if (gui) { #ifdef __WINE__ SetWindowPos(gui, 0, 0, 0, width + 6, height + 25, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER); #else gui->setFixedSize(width, height); #endif } } // ------------------------------------------------------------------------- // client stuff class PluginData : public ClientData { public: PluginData() : ClientData("") { } ~PluginData() { } // --------------------------------------------------------------------- // processing void set_parameter(int32_t rindex, double value) { if (CARLA_PLUGIN) CARLA_PLUGIN->setParameterValueByRIndex(rindex, value, true, true, false); } void set_program(uint32_t index) { if (CARLA_PLUGIN && index < CARLA_PLUGIN->programCount()) CARLA_PLUGIN->setProgram(index, true, true, false, true); callback_action(CALLBACK_RELOAD_PARAMETERS, 0, 0, 0, 0.0); } void set_midi_program(uint32_t bank, uint32_t program) { if (CARLA_PLUGIN) CARLA_PLUGIN->setMidiProgramById(bank, program, true, true, false, true); callback_action(CALLBACK_RELOAD_PARAMETERS, 0, 0, 0, 0.0); } void note_on(uint8_t note, uint8_t velocity) { if (CARLA_PLUGIN) CARLA_PLUGIN->sendMidiSingleNote(note, velocity, true, true, false); } void note_off(uint8_t note) { if (CARLA_PLUGIN) CARLA_PLUGIN->sendMidiSingleNote(note, 0, true, true, false); } // plugin void save_now() { CARLA_PLUGIN->prepareForSave(); 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, ""); } void set_custom_data(const char* const type, const char* const key, const char* const value) { if (CARLA_PLUGIN) CARLA_PLUGIN->setCustomData(customdatastr2type(type), key, value, false); } void set_chunk_data(const char* const filePath) { nextChunkFilePath = strdup(filePath); while (nextChunkFilePath) carla_msleep(25); } }; // ------------------------------------------------------------------------- void plugin_bridge_callback(CallbackType action, unsigned short, int value1, int value2, double value3) { switch (action) { case CALLBACK_PARAMETER_CHANGED: osc_send_control(value1, value3); break; case CALLBACK_PROGRAM_CHANGED: osc_send_program(value1); break; case CALLBACK_MIDI_PROGRAM_CHANGED: osc_send_midi_program(value1, value2, false); break; case CALLBACK_NOTE_ON: { uint8_t mdata[4] = { 0, MIDI_STATUS_NOTE_ON, (uint8_t)value1, (uint8_t)value2 }; osc_send_midi(mdata); break; } case CALLBACK_NOTE_OFF: { uint8_t mdata[4] = { 0, MIDI_STATUS_NOTE_OFF, (uint8_t)value1, (uint8_t)value2 }; osc_send_midi(mdata); break; } case CALLBACK_SHOW_GUI: if (value1 == 0) osc_send_configure(CARLA_BRIDGE_MSG_HIDE_GUI, ""); break; case CALLBACK_RESIZE_GUI: if (client) client->queque_message(BRIDGE_MESSAGE_RESIZE_GUI, value1, value2, 0.0); break; case 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 CALLBACK_QUIT: if (client) client->queque_message(BRIDGE_MESSAGE_QUIT, 0, 0, 0.0); break; default: break; } } // ------------------------------------------------------------------------- #ifdef __WINE__ int WINAPI WinMain(HINSTANCE hInstX, HINSTANCE, LPSTR, int) { hInst = hInstX; #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(hInst, 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 osc_url = 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]; if (strcmp(name, "(none)") == 0) name = nullptr; short id; PluginType itype; if (strcmp(stype, "LADSPA") == 0) itype = PLUGIN_LADSPA; else if (strcmp(stype, "DSSI") == 0) itype = PLUGIN_DSSI; else if (strcmp(stype, "LV2") == 0) itype = PLUGIN_LV2; else if (strcmp(stype, "VST") == 0) itype = PLUGIN_VST; else { itype = PLUGIN_NONE; qWarning("Invalid plugin type '%s'", stype); return 1; } // Init backend set_callback_function(plugin_bridge_callback); set_last_error("no error"); // Init engine QString engName = QString("%1 (master)").arg(label); engName.truncate(CarlaEngine::maxClientNameSize()); CarlaEngine engine; engine.init(engName.toUtf8().constData()); // Init toolkit toolkit_init(); // Init plugin client client = new PluginData; // Init OSC osc_init(osc_url); osc_send_update(); // Get plugin type switch (itype) { case PLUGIN_LADSPA: id = add_plugin_ladspa(filename, name, label, nullptr); break; case PLUGIN_DSSI: id = add_plugin_dssi(filename, name, label, nullptr); break; case PLUGIN_LV2: id = add_plugin_lv2(filename, name, label); break; case PLUGIN_VST: id = add_plugin_vst(filename, name, label); break; default: id = -1; break; } // Init plugin if (id == 0 && CARLA_PLUGIN) { // Create gui if needed GuiInfo guiInfo; CARLA_PLUGIN->getGuiInfo(&guiInfo); QString guiTitle = QString("%1 (GUI)").arg(CARLA_PLUGIN->name()); #ifdef __WINE__ if (guiInfo.type == GUI_INTERNAL_HWND) { WNDCLASSEX wclass; wclass.cbSize = sizeof(WNDCLASSEX); wclass.style = 0; wclass.lpfnWndProc = MainProc; wclass.cbClsExtra = 0; wclass.cbWndExtra = 0; wclass.hInstance = hInst; wclass.hIcon = LoadIcon(hInst, "carla"); wclass.hCursor = LoadCursor(0, IDI_APPLICATION); wclass.lpszMenuName = "MENU_CARLA_BRIDGE"; wclass.lpszClassName = "CLASS_CARLA_BRIDGE"; wclass.hIconSm = 0; if (! RegisterClassEx(&wclass)) { qCritical("Failed to register Wine application"); return 1; } gui = CreateWindow("CLASS_CARLA_BRIDGE", guiTitle.toUtf8().constData(), WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInst, 0); SetWindowPos(gui, 0, 0, 0, 6, 25, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER); qDebug("Wine GUI created"); #else if (guiInfo.type == GUI_INTERNAL_QT4 || guiInfo.type == GUI_INTERNAL_X11) { gui = new QDialog(nullptr); gui->resize(10, 10); gui->setWindowTitle(guiTitle); #endif CARLA_PLUGIN->setGuiData(0, gui); } // Report OK to backend osc_send_bridge_update(); // Main loop toolkit_loop(); // Remove & delete plugin carla_proc_lock(); CARLA_PLUGIN->setEnabled(false); carla_proc_unlock(); delete CARLA_PLUGIN; // Cleanup #ifndef __WINE__ if (gui) { gui->close(); delete gui; } delete app; #endif } else { qWarning("Plugin failed to load, error was:\n%s", get_last_error()); return 1; } // delete old data if (nextChunkFilePath) { free((void*)nextChunkFilePath); nextChunkFilePath = nullptr; } // Close plugin client delete client; client = nullptr; // Close engine engine.close(); // Close OSC osc_send_exiting(); osc_close(); return 0; }