b224220eea20510b0caa6994024b685d7b5e97dc
[ardour.git] / libs / ardour / linux_vst_info_file.cc
1 /***********************************************************/
2 /*vstfx infofile - module to manage info files             */
3 /*containing cached information about a plugin. e.g. its   */
4 /*name, creator etc etc                                    */
5 /***********************************************************/
6
7 #include <iostream>
8
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <unistd.h>
12 #include <errno.h>
13
14 #include <stdlib.h>
15 #include <stddef.h>
16 #include <stdio.h>
17 #include <string.h>
18 #include <libgen.h>
19
20 #include <glib.h>
21 #include <glib/gstdio.h>
22
23 #include "pbd/error.h"
24
25 #include "ardour/linux_vst_support.h"
26
27 #define MAX_STRING_LEN 256
28
29 static char* read_string(FILE *fp)
30 {
31     char buf[MAX_STRING_LEN];
32
33     if (!fgets( buf, MAX_STRING_LEN, fp )) {
34             return 0;
35     }
36     
37     if(strlen(buf) < MAX_STRING_LEN) {
38             if (strlen(buf)) {
39                     buf[strlen(buf)-1] = 0;
40             }
41             return strdup(buf);
42     } else {
43             return 0;
44     }
45 }
46
47 static VSTInfo *
48 load_vstfx_info_file (FILE* fp)
49 {
50         VSTInfo *info;
51         int i;
52         
53         if ((info = (VSTInfo*) malloc (sizeof (VSTInfo))) == 0) {
54                 return 0;
55         }
56
57         if((info->name = read_string(fp)) == 0) goto error;
58         if((info->creator = read_string(fp)) == 0) goto error;
59         if(1 != fscanf(fp, "%d\n", &info->UniqueID)) goto error;
60         if((info->Category = read_string(fp)) == 0) goto error;
61         if(1 != fscanf(fp, "%d\n", &info->numInputs)) goto error;
62         if(1 != fscanf(fp, "%d\n", &info->numOutputs)) goto error;
63         if(1 != fscanf(fp, "%d\n", &info->numParams)) goto error;
64         if(1 != fscanf(fp, "%d\n", &info->wantMidi)) goto error;
65         if(1 != fscanf(fp, "%d\n", &info->hasEditor)) goto error;
66         if(1 != fscanf(fp, "%d\n", &info->canProcessReplacing)) goto error;
67         
68         if((info->ParamNames = (char **) malloc(sizeof(char*)*info->numParams)) == 0) {
69                 goto error;
70         }
71
72         for (i=0; i<info->numParams; i++) {
73                 if((info->ParamNames[i] = read_string(fp)) == 0) goto error;
74         }
75
76         if ((info->ParamLabels = (char **) malloc(sizeof(char*)*info->numParams)) == 0) {
77                 goto error;
78         }
79         
80         for (i=0; i < info->numParams; i++) {
81                 if((info->ParamLabels[i] = read_string(fp)) == 0) goto error;
82         }
83         
84         return info;
85         
86   error:
87         free( info );
88         return 0;
89 }
90
91 static int
92 save_vstfx_info_file (VSTInfo *info, FILE* fp)
93 {
94     int i;
95
96     if (info == 0) {
97             vstfx_error("** ERROR ** VSTFXinfofile : info ptr is 0\n");
98             return -1;
99     }
100
101     if (fp == 0) {
102             vstfx_error("** ERROR ** VSTFXinfofile : file ptr is 0\n");
103             return -1;
104     }
105     
106     fprintf( fp, "%s\n", info->name );
107     fprintf( fp, "%s\n", info->creator );
108     fprintf( fp, "%d\n", info->UniqueID );
109     fprintf( fp, "%s\n", info->Category );
110     fprintf( fp, "%d\n", info->numInputs );
111     fprintf( fp, "%d\n", info->numOutputs );
112     fprintf( fp, "%d\n", info->numParams );
113     fprintf( fp, "%d\n", info->wantMidi );
114     fprintf( fp, "%d\n", info->hasEditor );
115     fprintf( fp, "%d\n", info->canProcessReplacing );
116
117     for (i=0; i < info->numParams; i++) {
118                 fprintf(fp, "%s\n", info->ParamNames[i]);
119     }
120         
121     for (i=0; i < info->numParams; i++) {
122                 fprintf(fp, "%s\n", info->ParamLabels[i]);
123     }
124         
125     return 0;
126 }
127
128 static char* vstfx_infofile_stat (char *dllpath, struct stat* statbuf, int personal)
129 {
130         char* path;
131         char* dir_path;
132         char* basename;
133         char* base;
134         size_t blen;
135
136         if (strstr (dllpath, ".so" ) == 0) {
137                 return 0;
138         }
139         
140         if (personal) {
141                 dir_path = g_build_filename (g_get_home_dir(), ".fst", NULL);
142         } else {
143                 dir_path = g_path_get_dirname (dllpath);
144         }
145         
146         base = g_path_get_basename (dllpath);
147         blen = strlen (base) + 2; // null char and '.'
148         basename = (char*) g_malloc (blen);
149         snprintf (basename, blen, ".%s.fsi", base);
150         g_free (base);
151         
152         path = g_build_filename (dir_path, basename, NULL);
153         
154         g_free (dir_path);
155         g_free (basename);
156
157
158         if (g_file_test (path, GFileTest (G_FILE_TEST_EXISTS|G_FILE_TEST_IS_REGULAR))) {
159
160                 /* info file exists in same location as the shared object, so
161                    check if its current and up to date
162                 */
163
164
165                 struct stat dllstat;
166                 
167                 if (stat (dllpath, &dllstat) == 0) {
168                         if (stat(path, statbuf) == 0) {
169                                 if (dllstat.st_mtime <= statbuf->st_mtime) {
170                                         /* plugin is older than info file */
171                                         return path;
172                                 }
173                         }
174                 } 
175         }
176
177         g_free (path);
178
179         return 0;
180 }
181
182
183 static FILE* vstfx_infofile_for_read (char* dllpath)
184 {
185         struct stat own_statbuf;
186         struct stat sys_statbuf;
187         char *own_info;
188         char *sys_info;
189         
190         own_info = vstfx_infofile_stat (dllpath, &own_statbuf, 1);
191         sys_info = vstfx_infofile_stat (dllpath, &sys_statbuf, 0);
192
193         if (own_info) {
194                 if (sys_info) {
195                         if (own_statbuf.st_mtime <= sys_statbuf.st_mtime) {
196                                 /* system info file is newer, use it */
197                                 return fopen (sys_info, "r");
198                         }
199                 } else {
200                         return fopen (own_info, "r");
201                 }
202         }
203
204         return 0;
205 }
206
207 static FILE* vstfx_infofile_create (char* dllpath, int personal)
208 {
209         char* path;
210         char* dir_path;
211         char* basename;
212         char* base;
213         size_t blen;
214
215         if (strstr (dllpath, ".so" ) == 0) {
216                 return 0;
217         }
218         
219         if (personal) {
220                 dir_path = g_build_filename (g_get_home_dir(), ".fst", NULL);
221
222                 /* if the directory doesn't yet exist, try to create it */
223
224                 if (!g_file_test (dir_path, G_FILE_TEST_IS_DIR)) {
225                         if (g_mkdir (dir_path, 0700)) {
226                                 return 0;
227                         }
228                 }
229
230         } else {
231                 dir_path = g_path_get_dirname (dllpath);
232         }
233         
234         base = g_path_get_basename (dllpath);
235         blen = strlen (base) + 2; // null char and '.'
236         basename = (char*) g_malloc (blen);
237         snprintf (basename, blen, ".%s.fsi", base);
238         g_free (base);
239
240         path = g_build_filename (dir_path, basename, NULL);
241
242         g_free (dir_path);
243         g_free (basename);
244
245         FILE* f = fopen (path, "w");
246         g_free (path);
247
248         return f;
249 }
250
251 static FILE* vstfx_infofile_for_write (char* dllpath)
252 {
253         FILE* f;
254
255         if ((f = vstfx_infofile_create (dllpath, 0)) == 0) {
256                 f = vstfx_infofile_create (dllpath, 1);
257         }
258         
259         return f;
260 }
261
262 static
263 int vstfx_can_midi (VSTState* vstfx)
264 {
265         AEffect *plugin = vstfx->plugin;
266         
267         int vst_version = plugin->dispatcher (plugin, effGetVstVersion, 0, 0, 0, 0.0f);
268
269         if (vst_version >= 2)
270         {
271                 /* should we send it VST events (i.e. MIDI) */
272                 
273                 if ((plugin->flags & effFlagsIsSynth) || (plugin->dispatcher (plugin, effCanDo, 0, 0,(void*) "receiveVstEvents", 0.0f) > 0))
274                     return -1;
275         }
276         return false;
277 }
278
279 static VSTInfo *
280 vstfx_info_from_plugin (VSTState* vstfx)
281 {
282         VSTInfo* info = (VSTInfo*) malloc (sizeof (VSTInfo));
283         
284         AEffect *plugin;
285         int i;
286         
287         /*We need to init the creator because some plugins
288           fail to implement getVendorString, and so won't stuff the
289           string with any name*/
290         
291         char creator[65] = "Unknown\0";
292         
293         if(!vstfx)
294         {
295                 vstfx_error( "** ERROR ** VSTFXinfofile : vstfx ptr is 0\n" );
296                 return 0;
297         }
298         
299         if(!info)
300                 return 0;
301         
302         plugin = vstfx->plugin;
303         
304         info->name = strdup(vstfx->handle->name ); 
305         
306         /*If the plugin doesn't bother to implement GetVendorString we will
307           have pre-stuffed the string with 'Unkown' */
308         
309         plugin->dispatcher (plugin, effGetVendorString, 0, 0, creator, 0);
310         
311         /*Some plugins DO implement GetVendorString, but DON'T put a name in it
312           so if its just a zero length string we replace it with 'Unknown' */
313         
314         if (strlen(creator) == 0) {
315                 info->creator = strdup("Unknown");
316         } else {
317                 info->creator = strdup (creator);
318         }
319         
320         info->UniqueID = plugin->uniqueID;
321         
322         info->Category = strdup("None");          // FIXME:  
323         info->numInputs = plugin->numInputs;
324         info->numOutputs = plugin->numOutputs;
325         info->numParams = plugin->numParams;
326         info->wantMidi = vstfx_can_midi(vstfx); 
327         info->hasEditor = plugin->flags & effFlagsHasEditor ? true : false;
328         info->canProcessReplacing = plugin->flags & effFlagsCanReplacing ? true : false;
329         info->ParamNames = (char **) malloc(sizeof(char*)*info->numParams);
330         info->ParamLabels = (char **) malloc(sizeof(char*)*info->numParams);
331
332         for(i=0; i < info->numParams; i++) {
333                 char name[64];
334                 char label[64];
335                 
336                 /*Not all plugins give parameters labels as well as names*/
337                 
338                 strcpy(name, "No Name");
339                 strcpy(label, "No Label");
340                 
341                 plugin->dispatcher (plugin, effGetParamName, i, 0, name, 0);
342                 info->ParamNames[i] = strdup(name);
343                 
344                 //NOTE: 'effGetParamLabel' is no longer defined in vestige headers
345                 //plugin->dispatcher (plugin, effGetParamLabel, i, 0, label, 0);
346                 info->ParamLabels[i] = strdup(label);
347         }
348         return info;
349 }
350
351 /* A simple 'dummy' audiomaster callback which should be ok,
352 we will only be instantiating the plugin in order to get its info*/
353
354 static intptr_t
355 simple_master_callback (AEffect *, int32_t opcode, int32_t, intptr_t, void *, float)
356 {
357         if (opcode == audioMasterVersion)
358                 return 2;
359         else
360                 return 0;
361 }
362
363 /*Try to get plugin info - first by looking for a .fsi cache of the
364 data, and if that doesn't exist, load the plugin, get its data and
365 then cache it for future ref*/
366
367 VSTInfo *
368 vstfx_get_info (char* dllpath)
369 {
370         FILE* infofile;
371         VSTHandle* h;
372         VSTState* vstfx;
373         VSTInfo* info;
374
375         if ((infofile = vstfx_infofile_for_read (dllpath)) != 0) {
376                 VSTInfo *info;
377                 info = load_vstfx_info_file (infofile);
378                 fclose (infofile);
379                 return info;
380         } 
381         
382         if(!(h = vstfx_load(dllpath))) {
383                 PBD::warning << "Cannot get LinuxVST information from " << dllpath << ": load failed." << endmsg;
384                 return 0;
385         }
386         
387         if(!(vstfx = vstfx_instantiate(h, simple_master_callback, 0))) {
388                 vstfx_unload(h);
389                 PBD::warning << "Cannot get LinuxVST information from " << dllpath << ": instantiation failed." << endmsg;
390                 return 0;
391         }
392         
393         infofile = vstfx_infofile_for_write (dllpath);
394         
395         if(!infofile) {
396                 vstfx_close(vstfx);
397                 vstfx_unload(h);
398                 PBD::warning << "Cannot get LinuxVST information from " << dllpath << ": cannot create new FST info file." << endmsg;
399                 return 0;
400         }
401         
402         info = vstfx_info_from_plugin(vstfx);
403         
404         save_vstfx_info_file(info, infofile);
405         fclose (infofile);
406         
407         vstfx_close(vstfx);
408         vstfx_unload(h);
409         
410         return info;
411 }
412
413 void
414 vstfx_free_info (VSTInfo *info)
415 {
416         for (int i = 0; i < info->numParams; i++) {
417                 free (info->ParamNames[i]);
418                 free (info->ParamLabels[i]);
419         }
420         
421         free (info->name);
422         free (info->creator);
423         free (info->Category);
424         free (info);
425 }
426
427