/* * Copyright (C) 2006-2010 Paul Davis * Copyright (C) 2010-2011 Carl Hetherington * Copyright (C) 2014-2016 John Emmas * Copyright (C) 2014-2017 Robin Gareus * * 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 * (at your option) 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. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #define fst_error(...) fprintf(stderr, __VA_ARGS__) #ifdef PLATFORM_WINDOWS #include static UINT_PTR idle_timer_id = 0; #else /* linux + wine */ #include // PATH_MAX #include #include #include static int gui_quit = 0; static unsigned int idle_id = 0; #endif #ifndef COMPILER_MSVC extern char * strdup (const char *); #endif #include #include "fst.h" struct ERect { short top; short left; short bottom; short right; }; static pthread_mutex_t plugin_mutex; static VSTState* fst_first = NULL; /**< Head of linked list of all FSTs */ static int host_initialized = 0; static const char magic[] = "FST Plugin State v002"; static LRESULT WINAPI vstedit_wndproc (HWND w, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_KEYUP: case WM_KEYDOWN: break; case WM_SIZE: #ifdef PLATFORM_WINDOWS { LRESULT rv = DefWindowProcA (w, msg, wp, lp); RECT rect; GetClientRect(w, &rect); #ifndef NDEBUG printf("VST WM_SIZE.. %ld %ld %ld %ld\n", rect.top, rect.left, (rect.right - rect.left), (rect.bottom - rect.top)); #endif VSTState* fst = (VSTState*) GetProp (w, "fst_ptr"); if (fst) { int32_t width = (rect.right - rect.left); int32_t height = (rect.bottom - rect.top); if (width > 0 && height > 0) { fst->amc (fst->plugin, 15 /*audioMasterSizeWindow */, width, height, NULL, 0); } } return rv; } #endif break; case WM_CLOSE: /* we don't care about windows closing ... * WM_CLOSE is used for minimizing the window. * Our window has no frame so it shouldn't ever * get sent - but if it does, we don't want our * window to get minimized! */ return 0; break; case WM_DESTROY: case WM_NCDESTROY: /* we don't care about windows being destroyed ... */ return 0; break; default: break; } return DefWindowProcA (w, msg, wp, lp); } static VOID CALLBACK idle_hands( HWND hwnd, // handle to window for timer messages UINT message, // WM_TIMER message UINT idTimer, // timer identifier DWORD dwTime) // current system time { VSTState* fst; pthread_mutex_lock (&plugin_mutex); for (fst = fst_first; fst; fst = fst->next) { if (fst->gui_shown) { // this seems insane, but some plugins will not draw their meters if you don't // call this every time. Example Ambience by Magnus @ Smartelectron:x fst->plugin->dispatcher (fst->plugin, effEditIdle, 0, 0, NULL, 0); if (fst->wantIdle) { fst->wantIdle = fst->plugin->dispatcher (fst->plugin, effIdle, 0, 0, NULL, 0); } } pthread_mutex_lock (&fst->lock); #ifndef PLATFORM_WINDOWS /* linux + wine */ /* Dispatch messages to send keypresses to the plugin */ int i; for (i = 0; i < fst->n_pending_keys; ++i) { MSG msg; /* I'm not quite sure what is going on here; it seems * `special' keys must be delivered with WM_KEYDOWN, * but that alphanumerics etc. must use WM_CHAR or * they will be ignored. Ours is not to reason why ... */ if (fst->pending_keys[i].special != 0) { msg.message = WM_KEYDOWN; msg.wParam = fst->pending_keys[i].special; } else { msg.message = WM_CHAR; msg.wParam = fst->pending_keys[i].character; } msg.hwnd = GetFocus (); msg.lParam = 0; DispatchMessageA (&msg); } fst->n_pending_keys = 0; #endif /* See comment for call below */ vststate_maybe_set_program (fst); fst->want_program = -1; fst->want_chunk = 0; /* If we don't have an editor window yet, we still need to * set up the program, otherwise when we load a plugin without * opening its window it will sound wrong. However, it seems * that if you don't also load the program after opening the GUI, * the GUI does not reflect the program properly. So we'll not * mark that we've done this (ie we won't set want_program to -1) * and so it will be done again if and when the GUI arrives. */ if (fst->program_set_without_editor == 0) { vststate_maybe_set_program (fst); fst->program_set_without_editor = 1; } pthread_mutex_unlock (&fst->lock); } pthread_mutex_unlock (&plugin_mutex); } static void fst_idle_timer_add_plugin (VSTState* fst) { pthread_mutex_lock (&plugin_mutex); if (fst_first == NULL) { fst_first = fst; } else { VSTState* p = fst_first; while (p->next) { p = p->next; } p->next = fst; } pthread_mutex_unlock (&plugin_mutex); } static void fst_idle_timer_remove_plugin (VSTState* fst) { VSTState* p; VSTState* prev; pthread_mutex_lock (&plugin_mutex); for (p = fst_first, prev = NULL; p; prev = p, p = p->next) { if (p == fst) { if (prev) { prev->next = p->next; } break; } if (!p->next) { break; } } if (fst_first == fst) { fst_first = fst_first->next; } pthread_mutex_unlock (&plugin_mutex); } static VSTState* fst_new (void) { VSTState* fst = (VSTState*) calloc (1, sizeof (VSTState)); vststate_init (fst); #ifdef PLATFORM_WINDOWS fst->voffset = 45; fst->hoffset = 0; #else /* linux + wine */ fst->voffset = 24; fst->hoffset = 6; #endif return fst; } static void fst_delete (VSTState* fst) { if (fst) { free((void*)fst); fst = NULL; } } static VSTHandle* fst_handle_new (void) { VSTHandle* fst = (VSTHandle*) calloc (1, sizeof (VSTHandle)); return fst; } #ifndef PLATFORM_WINDOWS /* linux + wine */ static gboolean g_idle_call (gpointer ignored) { if (gui_quit) return FALSE; MSG msg; if (PeekMessageA (&msg, NULL, 0, 0, 1)) { TranslateMessage (&msg); DispatchMessageA (&msg); } idle_hands(NULL, 0, 0, 0); g_main_context_iteration(NULL, FALSE); return gui_quit ? FALSE : TRUE; } #endif int fst_init (void* possible_hmodule) { if (host_initialized) return 0; HMODULE hInst; if (possible_hmodule) { #ifdef PLATFORM_WINDOWS fst_error ("Error in fst_init(): (module handle is unnecessary for Win32 build)"); return -1; #else /* linux + wine */ hInst = (HMODULE) possible_hmodule; #endif } else if ((hInst = GetModuleHandleA (NULL)) == NULL) { fst_error ("can't get module handle"); return -1; } if (!hInst) { fst_error ("Cannot initialise VST host"); return -1; } WNDCLASSEX wclass; wclass.cbSize = sizeof(WNDCLASSEX); #ifdef PLATFORM_WINDOWS wclass.style = (CS_HREDRAW | CS_VREDRAW); wclass.hIcon = NULL; wclass.hCursor = LoadCursor(0, IDC_ARROW); #else /* linux + wine */ wclass.style = 0; wclass.hIcon = LoadIcon(hInst, "FST"); wclass.hCursor = LoadCursor(0, IDI_APPLICATION); #endif wclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wclass.lpfnWndProc = vstedit_wndproc; wclass.cbClsExtra = 0; wclass.cbWndExtra = 0; wclass.hInstance = hInst; wclass.lpszMenuName = "MENU_FST"; wclass.lpszClassName = "FST"; wclass.hIconSm = 0; pthread_mutex_init (&plugin_mutex, NULL); host_initialized = -1; if (!RegisterClassExA(&wclass)){ fst_error ("Error in fst_init(): (class registration failed"); return -1; } return 0; } void fst_start_threading(void) { #ifndef PLATFORM_WINDOWS /* linux + wine */ if (idle_id == 0) { gui_quit = 0; idle_id = g_idle_add (g_idle_call, NULL); } #endif } void fst_stop_threading(void) { #ifndef PLATFORM_WINDOWS /* linux + wine */ if (idle_id != 0) { gui_quit = 1; PostQuitMessage (0); g_main_context_iteration(NULL, FALSE); //g_source_remove(idle_id); idle_id = 0; } #endif } void fst_exit (void) { if (!host_initialized) return; VSTState* fst; // If any plugins are still open at this point, close them! while ((fst = fst_first)) fst_close (fst); #ifdef PLATFORM_WINDOWS if (idle_timer_id != 0) { KillTimer(NULL, idle_timer_id); } #else /* linux + wine */ if (idle_id) { gui_quit = 1; PostQuitMessage (0); } #endif host_initialized = FALSE; pthread_mutex_destroy (&plugin_mutex); } int fst_run_editor (VSTState* fst, void* window_parent) { /* For safety, remove any pre-existing editor window */ fst_destroy_editor (fst); if (fst->windows_window == NULL) { HMODULE hInst; HWND window; struct ERect* er = NULL; if (!(fst->plugin->flags & effFlagsHasEditor)) { fst_error ("Plugin \"%s\" has no editor", fst->handle->name); return -1; } if ((hInst = GetModuleHandleA (NULL)) == NULL) { fst_error ("fst_create_editor() can't get module handle"); return 1; } if ((window = CreateWindowExA (0, "FST", fst->handle->name, window_parent ? WS_CHILD : (WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX), CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, (HWND)window_parent, NULL, hInst, NULL) ) == NULL) { fst_error ("fst_create_editor() cannot create editor window"); return 1; } if (!SetPropA (window, "fst_ptr", fst)) { fst_error ("fst_create_editor() cannot set fst_ptr on window"); } fst->windows_window = window; if (window_parent) { // This is requiredv for some reason. Note the parent is set above when the window // is created. Without this extra call the actual plugin window will draw outside // of our plugin window. SetParent((HWND)fst->windows_window, (HWND)window_parent); fst->xid = 0; #ifndef PLATFORM_WINDOWS /* linux + wine */ } else { SetWindowPos (fst->windows_window, 0, 9999, 9999, 2, 2, 0); ShowWindow (fst->windows_window, SW_SHOWNA); fst->xid = (int) GetPropA (fst->windows_window, "__wine_x11_whole_window"); #endif } // This is the suggested order of calls. fst->plugin->dispatcher (fst->plugin, effEditGetRect, 0, 0, &er, 0 ); fst->plugin->dispatcher (fst->plugin, effEditOpen, 0, 0, fst->windows_window, 0 ); fst->plugin->dispatcher (fst->plugin, effEditGetRect, 0, 0, &er, 0 ); if (er != NULL) { fst->width = er->right - er->left; fst->height = er->bottom - er->top; } fst->been_activated = TRUE; } if (fst->windows_window) { #ifdef PLATFORM_WINDOWS if (idle_timer_id == 0) { // Init the idle timer if needed, so that the main window calls us. idle_timer_id = SetTimer(NULL, idle_timer_id, 50, (TIMERPROC) idle_hands); } #endif fst_idle_timer_add_plugin (fst); } return fst->windows_window == NULL ? -1 : 0; } void fst_destroy_editor (VSTState* fst) { if (fst->windows_window) { fprintf (stderr, "%s destroying edit window\n", fst->handle->name); fst_idle_timer_remove_plugin (fst); fst->plugin->dispatcher( fst->plugin, effEditClose, 0, 0, NULL, 0.0 ); DestroyWindow ((HWND)(fst->windows_window)); fst->windows_window = NULL; } fst->been_activated = FALSE; } void fst_move_window_into_view (VSTState* fst) { if (fst->windows_window) { #ifdef PLATFORM_WINDOWS SetWindowPos ((HWND)(fst->windows_window), HWND_TOP /*0*/, fst->hoffset, fst->voffset, fst->width, fst->height, SWP_NOACTIVATE|SWP_NOOWNERZORDER); #else /* linux + wine */ SetWindowPos ((HWND)(fst->windows_window), 0, 0, 0, fst->width + fst->hoffset, fst->height + fst->voffset, 0); #endif ShowWindow ((HWND)(fst->windows_window), SW_SHOWNA); UpdateWindow ((HWND)(fst->windows_window)); } } static HMODULE fst_load_vst_library(const char * path) { char legalized_path[PATH_MAX]; strcpy (legalized_path, g_locale_from_utf8(path, -1, NULL, NULL, NULL)); return ( LoadLibraryA (legalized_path) ); } VSTHandle * fst_load (const char *path) { VSTHandle* fhandle = NULL; if ((strlen(path)) && (NULL != (fhandle = fst_handle_new ()))) { char* period; fhandle->path = strdup (path); fhandle->name = g_path_get_basename(path); if ((period = strrchr (fhandle->name, '.'))) { *period = '\0'; } // See if we can load the plugin DLL if ((fhandle->dll = (HMODULE)fst_load_vst_library (path)) == NULL) { fst_unload (&fhandle); return NULL; } fhandle->main_entry = (main_entry_t) GetProcAddress ((HMODULE)fhandle->dll, "VSTPluginMain"); if (fhandle->main_entry == 0) { fhandle->main_entry = (main_entry_t) GetProcAddress ((HMODULE)fhandle->dll, "main"); } if (fhandle->main_entry == 0) { fst_unload (&fhandle); return NULL; } } return fhandle; } int fst_unload (VSTHandle** fhandle) { if (!(*fhandle)) { return -1; } if ((*fhandle)->plugincnt) { return -1; } if ((*fhandle)->dll) { FreeLibrary ((HMODULE)(*fhandle)->dll); (*fhandle)->dll = NULL; } if ((*fhandle)->path) { free ((*fhandle)->path); (*fhandle)->path = NULL; } if ((*fhandle)->name) { free ((*fhandle)->name); (*fhandle)->name = NULL; } free (*fhandle); *fhandle = NULL; return 0; } VSTState* fst_instantiate (VSTHandle* fhandle, audioMasterCallback amc, void* userptr) { VSTState* fst = NULL; if( fhandle == NULL ) { fst_error( "fst_instantiate(): (the handle was NULL)\n" ); return NULL; } fst = fst_new (); fst->amc = amc; if ((fst->plugin = fhandle->main_entry (amc)) == NULL) { fst_error ("fst_instantiate: %s could not be instantiated\n", fhandle->name); free (fst); return NULL; } fst->handle = fhandle; fst->plugin->ptr1 = userptr; if (fst->plugin->magic != kEffectMagic) { fst_error ("fst_instantiate: %s is not a vst plugin\n", fhandle->name); fst_close(fst); return NULL; } if (!userptr) { /* scanning.. or w/o master-callback userptr == 0, open now. * * Session::vst_callback needs a pointer to the AEffect * ((VSTPlugin*)userptr)->_plugin = vstfx->plugin * before calling effOpen, because effOpen may call back */ fst->plugin->dispatcher (fst->plugin, effOpen, 0, 0, 0, 0); fst->vst_version = fst->plugin->dispatcher (fst->plugin, effGetVstVersion, 0, 0, 0, 0); } fst->handle->plugincnt++; fst->wantIdle = 0; return fst; } void fst_audio_master_idle(void) { while(g_main_context_iteration(NULL, FALSE)) ; } void fst_close (VSTState* fst) { if (fst != NULL) { fst_destroy_editor (fst); if (fst->plugin) { fst->plugin->dispatcher (fst->plugin, effMainsChanged, 0, 0, NULL, 0); fst->plugin->dispatcher (fst->plugin, effClose, 0, 0, 0, 0); fst->plugin = NULL; } if (fst->handle) { if (fst->handle->plugincnt && --fst->handle->plugincnt == 0) { fst->handle->main_entry = NULL; fst_unload (&fst->handle); // XXX } } /* It might be good for this to be in it's own cleanup function since it will free the memory for the fst leaving the caller with an invalid pointer. Caller beware */ fst_delete(fst); } } #if 0 // ?? who needs this, where? float htonf (float v) { float result; char * fin = (char*)&v; char * fout = (char*)&result; fout[0] = fin[3]; fout[1] = fin[2]; fout[2] = fin[1]; fout[3] = fin[0]; return result; } #endif