1 /*
2 cfgvar.c:
3
4 Copyright (C) 2005 Istvan Varga
5
6 This file is part of Csound.
7
8 The Csound Library is free software; you can redistribute it
9 and/or modify it under the terms of the GNU Lesser General Public
10 License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version.
12
13 Csound is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with Csound; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21 02110-1301 USA
22 */
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <ctype.h>
28 #include <math.h>
29
30 #include "csoundCore.h"
31 #include "cfgvar.h"
32 #include "namedins.h"
33
34 /* the global database */
35
36 /* list of error messages */
37
38 static const char *errmsg_list[] = {
39 Str_noop("(no error)"),
40 Str_noop("invalid variable name"),
41 Str_noop("invalid variable type"),
42 Str_noop("invalid flags specified"),
43 Str_noop("NULL pointer argument"),
44 Str_noop("the specified value is too high"),
45 Str_noop("the specified value is too low"),
46 Str_noop("the specified value is not an integer power of two"),
47 Str_noop("invalid boolean value; must be 0 or 1"),
48 Str_noop("memory allocation failed"),
49 Str_noop("string exceeds maximum allowed length"),
50 Str_noop("(unknown error)")
51 };
52
53 /* check if the specified name, and type and flags values are valid */
54
check_name(const char * name)55 static int check_name(const char *name)
56 {
57 int i, c;
58
59 if (UNLIKELY(name == NULL))
60 return CSOUNDCFG_INVALID_NAME;
61 if (UNLIKELY(name[0] == '\0'))
62 return CSOUNDCFG_INVALID_NAME;
63 i = -1;
64 while (name[++i] != '\0') {
65 c = (int) ((unsigned char) name[i]);
66 if (UNLIKELY((c & 0x80) || !(c == '_' || isalpha(c) || isdigit(c))))
67 return CSOUNDCFG_INVALID_NAME;
68 }
69 return CSOUNDCFG_SUCCESS;
70 }
71
check_type(int type)72 static int check_type(int type)
73 {
74 switch (type) {
75 case CSOUNDCFG_INTEGER:
76 case CSOUNDCFG_BOOLEAN:
77 case CSOUNDCFG_FLOAT:
78 case CSOUNDCFG_DOUBLE:
79 case CSOUNDCFG_MYFLT:
80 case CSOUNDCFG_STRING:
81 break;
82 default:
83 return CSOUNDCFG_INVALID_TYPE;
84 }
85 return CSOUNDCFG_SUCCESS;
86 }
87
check_flags(int flags)88 static int check_flags(int flags)
89 {
90 if (UNLIKELY((flags & (~(CSOUNDCFG_POWOFTWO))) != 0))
91 return CSOUNDCFG_INVALID_FLAG;
92 return CSOUNDCFG_SUCCESS;
93 }
94
95 /**
96 * Allocate configuration variable structure with the specified parameters:
97 * name: name of the variable (may contain letters, digits, and _)
98 * p: pointer to variable
99 * type: type of variable, determines how 'p' is interpreted
100 * CSOUNDCFG_INTEGER: int*
101 * CSOUNDCFG_BOOLEAN: int* (value may be 0 or 1)
102 * CSOUNDCFG_FLOAT: float*
103 * CSOUNDCFG_DOUBLE: double*
104 * CSOUNDCFG_MYFLT: MYFLT*
105 * CSOUNDCFG_STRING: char* (should have enough space)
106 * flags: bitwise OR of flag values, currently only CSOUNDCFG_POWOFTWO
107 * is available, which requests CSOUNDCFG_INTEGER values to be
108 * power of two
109 * min: for CSOUNDCFG_INTEGER, CSOUNDCFG_FLOAT, CSOUNDCFG_DOUBLE, and
110 * CSOUNDCFG_MYFLT, a pointer to a variable of the type selected
111 * by 'type' that specifies the minimum allowed value.
112 * If 'min' is NULL, there is no minimum value.
113 * max: similar to 'min', except it sets the maximum allowed value.
114 * For CSOUNDCFG_STRING, it is a pointer to an int variable
115 * that defines the maximum length of the string (including the
116 * null character at the end) in bytes. This value is limited
117 * to the range 8 to 16384, and if max is NULL, it defaults to 256.
118 * shortDesc: a short description of the variable (may be NULL or an empty
119 * string if a description is not available)
120 * longDesc: a long description of the variable (may be NULL or an empty
121 * string if a description is not available)
122 * A pointer to the newly allocated structure is stored in (*ptr).
123 * Return value is CSOUNDCFG_SUCCESS, or one of the error codes.
124 */
125
cfg_alloc_structure(CSOUND * csound,csCfgVariable_t ** ptr,const char * name,void * p,int type,int flags,void * min,void * max,const char * shortDesc,const char * longDesc)126 static int cfg_alloc_structure(CSOUND* csound,
127 csCfgVariable_t **ptr,
128 const char *name,
129 void *p, int type, int flags,
130 void *min, void *max,
131 const char *shortDesc, const char *longDesc)
132 {
133 int structBytes = 0, nameBytes, sdBytes, ldBytes, totBytes;
134 void *pp;
135 unsigned char *sdp = NULL, *ldp = NULL;
136
137 (*ptr) = (csCfgVariable_t*) NULL;
138 /* check for valid parameters */
139 if (UNLIKELY(p == NULL))
140 return CSOUNDCFG_NULL_POINTER;
141 if (UNLIKELY(check_name(name) != CSOUNDCFG_SUCCESS))
142 return CSOUNDCFG_INVALID_NAME;
143 if (UNLIKELY(check_type(type) != CSOUNDCFG_SUCCESS))
144 return CSOUNDCFG_INVALID_TYPE;
145 if (UNLIKELY(check_flags(flags) != CSOUNDCFG_SUCCESS))
146 return CSOUNDCFG_INVALID_FLAG;
147 /* calculate the number of bytes to allocate */
148 structBytes = ((int) sizeof(csCfgVariable_t) + 15) & (~15);
149 nameBytes = (((int) strlen(name) + 1) + 15) & (~15);
150 if (shortDesc != NULL)
151 sdBytes = (shortDesc[0] == '\0' ? 0 : (int) strlen(shortDesc) + 1);
152 else
153 sdBytes = 0;
154 sdBytes = (sdBytes + 15) & (~15);
155 if (longDesc != NULL)
156 ldBytes = (longDesc[0] == '\0' ? 0 : (int) strlen(longDesc) + 1);
157 else
158 ldBytes = 0;
159 ldBytes = (ldBytes + 15) & (~15);
160 totBytes = structBytes + nameBytes + sdBytes + ldBytes;
161 /* allocate memory */
162 pp = (void*) csound->Calloc(csound, (size_t) totBytes);
163 if (UNLIKELY(pp == NULL))
164 return CSOUNDCFG_MEMORY;
165 //memset(pp, 0, (size_t) totBytes);
166 (*ptr) = (csCfgVariable_t*) pp;
167 /* copy name and descriptions */
168 strcpy(((char*) pp + (int) structBytes), name);
169 if (sdBytes > 0) {
170 sdp = (unsigned char*) pp + (int) (structBytes + nameBytes);
171 strcpy((char*) sdp, shortDesc);
172 }
173 else
174 sdp = NULL;
175 if (ldBytes > 0) {
176 ldp = (unsigned char*) pp + (int) (structBytes + nameBytes + sdBytes);
177 strcpy((char*) ldp, longDesc);
178 }
179 else
180 ldp = NULL;
181 /* set up structure */
182 (*ptr)->h.nxt = (csCfgVariable_t*) NULL;
183 (*ptr)->h.name = (unsigned char*) pp + (int) structBytes;
184 (*ptr)->h.p = p;
185 (*ptr)->h.type = type;
186 (*ptr)->h.flags = flags;
187 (*ptr)->h.shortDesc = sdp;
188 (*ptr)->h.longDesc = ldp;
189 /* depending on type */
190 switch (type) {
191 case CSOUNDCFG_INTEGER: /* integer */
192 (*ptr)->i.min = (min == NULL ? -0x7FFFFFFF : *((int*) min));
193 (*ptr)->i.max = (max == NULL ? 0x7FFFFFFF : *((int*) max));
194 break;
195 case CSOUNDCFG_BOOLEAN: /* boolean */
196 (*ptr)->b.flags &= (~(CSOUNDCFG_POWOFTWO));
197 break;
198 case CSOUNDCFG_FLOAT: /* float */
199 (*ptr)->f.flags &= (~(CSOUNDCFG_POWOFTWO));
200 (*ptr)->f.min = (min == NULL ? -1.0e30f : *((float*) min));
201 (*ptr)->f.max = (max == NULL ? 1.0e30f : *((float*) max));
202 break;
203 case CSOUNDCFG_DOUBLE: /* double */
204 (*ptr)->d.flags &= (~(CSOUNDCFG_POWOFTWO));
205 (*ptr)->d.min = (min == NULL ? -1.0e30 : *((double*) min));
206 (*ptr)->d.max = (max == NULL ? 1.0e30 : *((double*) max));
207 break;
208 case CSOUNDCFG_MYFLT: /* MYFLT */
209 (*ptr)->m.flags &= (~(CSOUNDCFG_POWOFTWO));
210 (*ptr)->m.min = (min == NULL ? (MYFLT) -1.0e30 : *((MYFLT*) min));
211 (*ptr)->m.max = (max == NULL ? (MYFLT) 1.0e30 : *((MYFLT*) max));
212 break;
213 case CSOUNDCFG_STRING: /* string */
214 (*ptr)->s.flags &= (~(CSOUNDCFG_POWOFTWO));
215 if (max != NULL) {
216 (*ptr)->s.maxlen = *((int*) max);
217 if ((*ptr)->s.maxlen < 8) (*ptr)->s.maxlen = 8;
218 if ((*ptr)->s.maxlen > 16384) (*ptr)->s.maxlen = 16384;
219 }
220 else
221 (*ptr)->s.maxlen = 256; /* default maximum string length */
222 break;
223 }
224 /* done */
225 return CSOUNDCFG_SUCCESS;
226 }
227
228 /**
229 * This function is similar to csoundCreateGlobalConfigurationVariable(),
230 * except it creates a configuration variable specific to Csound instance
231 * 'csound', and is suitable for calling from the Csound library
232 * (in csoundPreCompile()) or plugins (in csoundModuleCreate()).
233 * The other parameters and return value are the same as in the case of
234 * csoundCreateGlobalConfigurationVariable().
235 */
236
237 PUBLIC int
csoundCreateConfigurationVariable(CSOUND * csound,const char * name,void * p,int type,int flags,void * min,void * max,const char * shortDesc,const char * longDesc)238 csoundCreateConfigurationVariable(CSOUND *csound, const char *name,
239 void *p, int type, int flags,
240 void *min, void *max,
241 const char *shortDesc,
242 const char *longDesc)
243 {
244 csCfgVariable_t *pp;
245 int retval;
246
247 /* check if name is already in use */
248 if (UNLIKELY(csoundQueryConfigurationVariable(csound, name) != NULL))
249 return CSOUNDCFG_INVALID_NAME;
250 /* if database is not allocated yet, create an empty one */
251 if (csound->cfgVariableDB == NULL) {
252 csound->cfgVariableDB = cs_hash_table_create(csound);
253 if (UNLIKELY(csound->cfgVariableDB == NULL))
254 return CSOUNDCFG_MEMORY;
255 }
256 /* allocate structure */
257 retval = cfg_alloc_structure(csound, &pp, name, p, type, flags, min, max,
258 shortDesc, longDesc);
259 if (UNLIKELY(retval != CSOUNDCFG_SUCCESS))
260 return retval;
261 /* link into database */
262
263 cs_hash_table_put(csound, csound->cfgVariableDB, (char*)name, pp);
264
265 /* report success */
266 return CSOUNDCFG_SUCCESS;
267 }
268
269 /* set configuration variable to value (with error checking) */
270
set_cfgvariable_value(csCfgVariable_t * pp,void * value)271 static int set_cfgvariable_value(csCfgVariable_t *pp, void *value)
272 {
273 double dVal;
274 MYFLT mVal;
275 float fVal;
276 int iVal;
277 /* set value depending on type */
278 if (UNLIKELY(value == NULL))
279 return CSOUNDCFG_NULL_POINTER;
280 switch (pp->h.type) {
281 case CSOUNDCFG_INTEGER:
282 iVal = *((int*) value);
283 if (UNLIKELY(iVal < pp->i.min)) return CSOUNDCFG_TOO_LOW;
284 if (UNLIKELY(iVal > pp->i.max)) return CSOUNDCFG_TOO_HIGH;
285 if (pp->i.flags & CSOUNDCFG_POWOFTWO)
286 if (UNLIKELY(iVal < 1 || (iVal & (iVal - 1)) != 0))
287 return CSOUNDCFG_NOT_POWOFTWO;
288 *(pp->i.p) = iVal;
289 break;
290 case CSOUNDCFG_BOOLEAN:
291 iVal = *((int*) value);
292 if (UNLIKELY(iVal & (~1))) return CSOUNDCFG_INVALID_BOOLEAN;
293 *(pp->b.p) = iVal;
294 break;
295 case CSOUNDCFG_FLOAT:
296 fVal = *((float*) value);
297 if (UNLIKELY(fVal < pp->f.min)) return CSOUNDCFG_TOO_LOW;
298 if (UNLIKELY(fVal > pp->f.max)) return CSOUNDCFG_TOO_HIGH;
299 *(pp->f.p) = fVal;
300 break;
301 case CSOUNDCFG_DOUBLE:
302 dVal = *((double*) value);
303 if (UNLIKELY(dVal < pp->d.min)) return CSOUNDCFG_TOO_LOW;
304 if (UNLIKELY(dVal > pp->d.max)) return CSOUNDCFG_TOO_HIGH;
305 *(pp->d.p) = dVal;
306 break;
307 case CSOUNDCFG_MYFLT:
308 mVal = *((MYFLT*) value);
309 if (UNLIKELY(mVal < pp->m.min)) return CSOUNDCFG_TOO_LOW;
310 if (UNLIKELY(mVal > pp->m.max)) return CSOUNDCFG_TOO_HIGH;
311 *(pp->m.p) = mVal;
312 break;
313 case CSOUNDCFG_STRING:
314 if (UNLIKELY((int) strlen((char*) value) >= (pp->s.maxlen - 1)))
315 return CSOUNDCFG_STRING_LENGTH;
316 strcpy((char*) (pp->s.p), (char*) value);
317 break;
318 default:
319 return CSOUNDCFG_INVALID_TYPE;
320 }
321 return CSOUNDCFG_SUCCESS;
322 }
323
324 /**
325 * Set the value of a configuration variable of Csound instance 'csound'.
326 * The 'name' and 'value' parameters, and return value are the same as
327 * in the case of csoundSetGlobalConfigurationVariable().
328 */
329
330 PUBLIC int
csoundSetConfigurationVariable(CSOUND * csound,const char * name,void * value)331 csoundSetConfigurationVariable(CSOUND *csound, const char *name, void *value)
332 {
333 csCfgVariable_t *pp;
334
335 /* get pointer to variable */
336 pp = csoundQueryConfigurationVariable(csound, name);
337 if (UNLIKELY(pp == NULL))
338 return CSOUNDCFG_INVALID_NAME; /* not found */
339 return (set_cfgvariable_value(pp, value));
340 }
341
342 /* parse string value for configuration variable */
343
parse_cfg_variable(csCfgVariable_t * pp,const char * value)344 static int parse_cfg_variable(csCfgVariable_t *pp, const char *value)
345 {
346 double dVal;
347 MYFLT mVal;
348 float fVal;
349 int iVal;
350
351 if (UNLIKELY(value == NULL))
352 return CSOUNDCFG_NULL_POINTER;
353 switch (pp->h.type) {
354 case CSOUNDCFG_INTEGER:
355 iVal = (int) atoi(value);
356 return set_cfgvariable_value(pp, (void*) (&iVal));
357 case CSOUNDCFG_BOOLEAN:
358 if (strcmp(value, "0") == 0 ||
359 strcmp(value, "no") == 0 || strcmp(value, "No") == 0 ||
360 strcmp(value, "NO") == 0 || strcmp(value, "off") == 0 ||
361 strcmp(value, "Off") == 0 || strcmp(value, "OFF") == 0 ||
362 strcmp(value, "false") == 0 || strcmp(value, "False") == 0 ||
363 strcmp(value, "FALSE") == 0)
364 *(pp->b.p) = 0;
365 else if (strcmp(value, "1") == 0 ||
366 strcmp(value, "yes") == 0 || strcmp(value, "Yes") == 0 ||
367 strcmp(value, "YES") == 0 || strcmp(value, "on") == 0 ||
368 strcmp(value, "On") == 0 || strcmp(value, "ON") == 0 ||
369 strcmp(value, "true") == 0 || strcmp(value, "True") == 0 ||
370 strcmp(value, "TRUE") == 0)
371 *(pp->b.p) = 1;
372 else
373 return CSOUNDCFG_INVALID_BOOLEAN;
374 return CSOUNDCFG_SUCCESS;
375 case CSOUNDCFG_FLOAT:
376 fVal = (float) atof(value);
377 return set_cfgvariable_value(pp, (void*) (&fVal));
378 case CSOUNDCFG_DOUBLE:
379 dVal = (double) atof(value);
380 return set_cfgvariable_value(pp, (void*) (&dVal));
381 case CSOUNDCFG_MYFLT:
382 mVal = (MYFLT) atof(value);
383 return set_cfgvariable_value(pp, (void*) (&mVal));
384 case CSOUNDCFG_STRING:
385 return set_cfgvariable_value(pp, (void*) value);
386 }
387 return CSOUNDCFG_INVALID_TYPE;
388 }
389
390 /**
391 * Set the value of a configuration variable of Csound instance 'csound',
392 * by parsing a string.
393 * The 'name' and 'value' parameters, and return value are the same as
394 * in the case of csoundParseGlobalConfigurationVariable().
395 */
396
397 PUBLIC int
csoundParseConfigurationVariable(CSOUND * csound,const char * name,const char * value)398 csoundParseConfigurationVariable(CSOUND *csound, const char *name,
399 const char *value)
400 {
401 csCfgVariable_t *pp;
402
403 /* get pointer to variable */
404 pp = csoundQueryConfigurationVariable(csound, name);
405 if (UNLIKELY(pp == NULL))
406 return CSOUNDCFG_INVALID_NAME; /* not found */
407 return (parse_cfg_variable(pp, value));
408 }
409
410 /**
411 * Return pointer to the configuration variable of Csound instace 'csound'
412 * with the specified name.
413 * The return value may be NULL if the variable is not found in the database.
414 */
415
416 PUBLIC csCfgVariable_t
csoundQueryConfigurationVariable(CSOUND * csound,const char * name)417 *csoundQueryConfigurationVariable(CSOUND *csound, const char *name)
418 {
419 if (csound->cfgVariableDB == NULL) {
420 return NULL;
421 }
422 return (csCfgVariable_t*) cs_hash_table_get(csound,
423 csound->cfgVariableDB, (char*)name);
424 }
425
426 /* compare function for qsort() */
427
compare_func(const void * p1,const void * p2)428 static int compare_func(const void *p1, const void *p2)
429 {
430 return (int) strcmp((char*) ((*((csCfgVariable_t**) p1))->h.name),
431 (char*) ((*((csCfgVariable_t**) p2))->h.name));
432 }
433
434 /* create alphabetically sorted list of all entries in 'db' */
435
list_db_entries(CSOUND * csound,CS_HASH_TABLE * db)436 static csCfgVariable_t **list_db_entries(CSOUND* csound, CS_HASH_TABLE *db)
437 {
438 csCfgVariable_t **lst;
439 size_t cnt;
440 CONS_CELL* values;
441
442 values = cs_hash_table_values(csound, db);
443 cnt = cs_cons_length(values);
444
445
446 /* allocate memory for list */
447 lst = (csCfgVariable_t**) csound->Malloc(csound, sizeof(csCfgVariable_t*)
448 * (cnt + (size_t) 1));
449 if (UNLIKELY(lst == NULL))
450 return (csCfgVariable_t**) NULL; /* not enough memory */
451 /* create list */
452 if (cnt) {
453 cnt = 0;
454 while (values != NULL) {
455 lst[cnt++] = (csCfgVariable_t*)values->value;
456 values = values->next;
457 }
458 qsort((void*) lst, cnt, sizeof(csCfgVariable_t*), compare_func);
459 }
460
461 lst[cnt] = (csCfgVariable_t*) NULL;
462 /* return pointer to list */
463 return lst;
464 }
465
466 /**
467 * Create an alphabetically sorted list of all configuration variables
468 * of Csound instance 'csound'.
469 * Returns a pointer to a NULL terminated array of configuration variable
470 * pointers, or NULL on error.
471 * The caller is responsible for freeing the returned list with
472 * csoundDeleteCfgVarList(), however, the variable pointers in the list
473 * should not be freed.
474 */
475
csoundListConfigurationVariables(CSOUND * csound)476 PUBLIC csCfgVariable_t **csoundListConfigurationVariables(CSOUND *csound)
477 {
478 return (list_db_entries(csound, csound->cfgVariableDB));
479 }
480
481 /**
482 * Release a configuration variable list previously returned
483 * by csoundListGlobalConfigurationVariables() or
484 * csoundListConfigurationVariables().
485 */
486
csoundDeleteCfgVarList(CSOUND * csound,csCfgVariable_t ** lst)487 PUBLIC void csoundDeleteCfgVarList(CSOUND* csound, csCfgVariable_t **lst)
488 {
489 if (lst != NULL)
490 csound->Free(csound, lst);
491 }
492
493 /* remove a configuration variable from 'db' */
494
remove_entry_from_db(CSOUND * csound,CS_HASH_TABLE * db,const char * name)495 static int remove_entry_from_db(CSOUND* csound, CS_HASH_TABLE *db, const char *name)
496 {
497 csCfgVariable_t *pp = cs_hash_table_get(csound, db, (char*)name);
498
499 if (UNLIKELY(pp == NULL))
500 return CSOUNDCFG_INVALID_NAME;
501
502 csound->Free(csound, pp);
503 cs_hash_table_remove(csound, db, (char*)name);
504
505 return CSOUNDCFG_SUCCESS;
506 }
507
508 /**
509 * Remove the configuration variable of Csound instance 'csound' with the
510 * specified name from the database. Plugins need not call this, as all
511 * configuration variables are automatically deleted by csoundReset().
512 * Return value is CSOUNDCFG_SUCCESS in case of success, or
513 * CSOUNDCFG_INVALID_NAME if the variable was not found.
514 */
515
csoundDeleteConfigurationVariable(CSOUND * csound,const char * name)516 PUBLIC int csoundDeleteConfigurationVariable(CSOUND *csound, const char *name)
517 {
518 return remove_entry_from_db(csound, csound->cfgVariableDB, name);
519 }
520
destroy_entire_db(CSOUND * csound,CS_HASH_TABLE * db)521 static int destroy_entire_db(CSOUND *csound, CS_HASH_TABLE *db)
522 {
523 CONS_CELL *head, *current;
524 if (db == NULL)
525 return CSOUNDCFG_SUCCESS;
526
527 head = current = cs_hash_table_values(csound, db);
528
529 while (current != NULL) {
530 if (current->value != NULL) {
531 csound->Free(csound, current->value);
532 }
533 current = current->next;
534 }
535
536 cs_cons_free(csound, head);
537 cs_hash_table_free(csound, db);
538
539 return CSOUNDCFG_SUCCESS;
540 }
541
542 /**
543 * Remove all configuration variables of Csound instance 'csound'
544 * and free database. This function is called by csoundReset().
545 * Return value is CSOUNDCFG_SUCCESS in case of success.
546 */
547
csoundDeleteAllConfigurationVariables(CSOUND * csound)548 int csoundDeleteAllConfigurationVariables(CSOUND *csound)
549 {
550 int retval;
551 retval = destroy_entire_db(csound, csound->cfgVariableDB);
552 csound->cfgVariableDB = NULL;
553 return retval;
554 }
555
556 /**
557 * Returns pointer to an error string constant for the specified
558 * CSOUNDCFG error code. The string is not translated.
559 */
560
csoundCfgErrorCodeToString(int errcode)561 PUBLIC const char *csoundCfgErrorCodeToString(int errcode)
562 {
563 if (errcode > 0 || errcode < CSOUNDCFG_LASTERROR)
564 return errmsg_list[1 - CSOUNDCFG_LASTERROR]; /* unknown */
565 else
566 return errmsg_list[(-errcode)];
567 }
568