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