/* vim: set et ts=3 sw=3 sts=3 ft=c: * * Copyright (C) 2021 Christopher Snowhill. All rights reserved. * https://github.com/kode54/sflist * https://gist.github.com/kode54/a7bb01a0db3f2e996145b77f0ca510d5 * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include "sflist.h" #ifndef PRId64 #ifdef _MSC_VER #define PRId64 "I64d" #else #define PRId64 "lld" #endif #endif /* Extras needed */ static int json_equal(const json_value *a, const json_value *b); static int json_equal_array(const json_value *a, const json_value *b) { unsigned int i, j; if(a->u.array.length != b->u.array.length) return 0; for(i = 0, j = a->u.array.length; i < j; ++i) { if(!json_equal(a->u.array.values[i], b->u.array.values[i])) return 0; } return 1; } static int json_equal_object(const json_value *a, const json_value *b) { unsigned int i, j; if(a->u.object.length != b->u.object.length) return 0; for(i = 0, j = a->u.object.length; i < j; ++i) { if(strcmp(a->u.object.values[i].name, b->u.object.values[i].name)) return 0; if(!json_equal(a->u.object.values[i].value, b->u.object.values[i].value)) return 0; } return 1; } static int json_equal(const json_value *a, const json_value *b) { if(a->type != b->type) return 0; switch(a->type) { case json_none: case json_null: return 1; case json_integer: return a->u.integer == b->u.integer; case json_double: return a->u.dbl == b->u.dbl; case json_boolean: return !a->u.boolean == !b->u.boolean; case json_string: return !strcmp(a->u.string.ptr, b->u.string.ptr); case json_array: return json_equal_array(a, b); case json_object: return json_equal_object(a, b); } return 0; } static int json_signum(double val) { return (val > 0.0) - (val < 0.0); } #define json_compare_invalid -1000 static int json_compare(const json_value *a, const json_value *b) { if(a->type != b->type) return json_compare_invalid; switch(a->type) { case json_none: case json_null: return 0; case json_integer: return (int)(a->u.integer - b->u.integer); case json_double: return json_signum(a->u.dbl - b->u.dbl); case json_boolean: return !!a->u.boolean - !!b->u.boolean; case json_string: return strcmp(a->u.string.ptr, b->u.string.ptr); case json_array: case json_object: return json_compare_invalid; } return json_compare_invalid; } static int json_array_contains_value(const json_value *array, const json_value *value) { unsigned int i, j; for(i = 0, j = array->u.array.length; i < j; ++i) { if(json_equal(array->u.array.values[i], value)) return 1; } return 0; } json_value *json_array_merge(json_value *arrayA, json_value *arrayB) { unsigned int i, j; if(arrayA->type != json_array || arrayB->type != json_array) return 0; for(i = 0, j = arrayB->u.array.length; i < j; ++i) { if(!json_array_contains_value(arrayA, arrayB->u.array.values[i])) { json_array_push(arrayA, arrayB->u.array.values[i]); } } json_builder_free(arrayB); return arrayA; } static int json_compare_callback(const void *a, const void *b) { const json_value *aa = (const json_value *)a; const json_value *bb = (const json_value *)b; return json_compare(aa, bb); } json_value *json_array_sort(json_value *array) { unsigned int i, j; json_type type; if(array->type != json_array) return 0; if(array->u.array.length < 2) return array; type = array->u.array.values[0]->type; for(i = 1, j = array->u.array.length; i < j; ++i) { if(array->u.array.values[i]->type != type) return 0; } qsort(array->u.array.values, j, sizeof(json_value *), json_compare_callback); return array; } /* Processing begins */ static size_t sflist_parse_int(const char *in, const char **end) { size_t rval = 0; while(in < *end) { if(isdigit(*in)) { rval = (rval * 10) + (*in - '0'); } else break; ++in; } *end = in; return rval; } static double sflist_parse_float(const char *in, const char **end) { size_t whole = 0; size_t decimal = 0; size_t decimal_places = 0; double sign = 1.0; const char *end_orig = *end; const char *ptr = in; if(*ptr == '-') { ++ptr; sign = -1.0; } whole = sflist_parse_int(ptr, end); if(*end == ptr || (**end != '.' && *end < end_orig)) { *end = in; return 0.0; } if(*end < end_orig) { ptr = *end + 1; *end = end_orig; decimal = sflist_parse_int(ptr, end); if(*end == ptr || *end < end_orig) { *end = in; return 0.0; } decimal_places = *end - ptr; } return (((double)whole) + (((double)decimal) / pow(10.0, (double)decimal_places))) * sign; } static json_value *sflist_load_v1(const char *sflist, size_t size, char *error_buf) { json_value *rval = 0; json_value *arr = json_array_new(0); json_value *channels = 0; json_value *patchMappings = 0; double gain = 0.0; const char *ptr = sflist; const char *end = sflist + size; unsigned int cur_line = 0; while(ptr < end) { const char *line_start = ptr; json_value *obj = 0; const char *path = 0; const char *pipe = 0; const char *lend = ptr; ++cur_line; while(lend < end && *lend && *lend != '\r' && *lend != '\n') { if(*lend == '|') pipe = lend; ++lend; } if(pipe) path = pipe + 1; else path = ptr; if(pipe) { while(ptr < pipe) { char c; const char *fend = ptr; const char *vend; while(fend < pipe && *fend != '&') ++fend; vend = fend; switch(c = *ptr++) { case '&': continue; case 'c': { json_value *this_channels; size_t channel_low = sflist_parse_int(ptr, &vend); size_t channel_high = 0; size_t i; if(vend == ptr || (*vend != '-' && *vend != '&' && *vend != '|')) { sprintf(error_buf, "Invalid channel number (%u:%u)", cur_line, (int)(vend - line_start + 1)); goto error; } if(*vend != '-') channel_high = channel_low; else { ptr = vend + 1; vend = fend; channel_high = sflist_parse_int(ptr, &vend); if(vend == ptr || (*vend != '&' && *vend != '|')) { sprintf(error_buf, "Invalid channel range end value (%u:%u)", cur_line, (int)(vend - line_start + 1)); goto error; } } if(!channels) channels = json_array_new(0); this_channels = json_array_new(0); for(i = channel_low; i <= channel_high; ++i) json_array_push(this_channels, json_integer_new(i)); channels = json_array_merge(channels, this_channels); ptr = fend; } break; case 'p': { json_value *mapping = 0; json_value *mapping_destination = 0; json_value *mapping_source = 0; long source_bank = -1; long source_program = -1; long dest_bank = -1; long dest_program = -1; size_t val = sflist_parse_int(ptr, &vend); if(vend == ptr || (*vend != '=' && *vend != ',' && *vend != '|')) { sprintf(error_buf, "Invalid preset number (%u:%u)", cur_line, (int)(vend - line_start + 1)); goto error; } dest_program = val; if(*vend == ',') { dest_bank = val; ptr = vend + 1; vend = fend; val = sflist_parse_int(ptr, &vend); if(vend == ptr || (*vend != '=' && *vend != '|')) { sprintf(error_buf, "Invalid preset number (%u:%u)", cur_line, (int)(vend - line_start + 1)); goto error; } dest_program = val; } if(*vend == '=') { ptr = vend + 1; vend = fend; val = sflist_parse_int(ptr, &vend); if(vend == ptr || (*vend != ',' && *vend != '|')) { sprintf(error_buf, "Invalid preset number (%u:%u)", cur_line, (int)(vend - line_start + 1)); goto error; } source_program = val; if(*vend == ',') { source_bank = val; ptr = vend + 1; vend = fend; val = sflist_parse_int(ptr, &vend); if(vend == ptr || (*vend != '&' && *vend != '|')) { sprintf(error_buf, "Invalid preset number (%u:%u)", cur_line, (int)(vend - line_start + 1)); goto error; } source_program = val; } } if(!patchMappings) patchMappings = json_array_new(0); mapping = json_object_new(0); mapping_destination = json_object_new(0); if(dest_bank != -1) { json_object_push(mapping_destination, "bank", json_integer_new(dest_bank)); } json_object_push(mapping_destination, "program", json_integer_new(dest_program)); json_object_push(mapping, "destination", mapping_destination); if(source_program != -1) { mapping_source = json_object_new(0); if(source_bank != -1) { json_object_push(mapping_source, "bank", json_integer_new(source_bank)); } json_object_push(mapping_source, "program", json_integer_new(source_program)); json_object_push(mapping, "source", mapping_source); } json_array_push(patchMappings, mapping); ptr = fend; } break; case 'g': { double val = sflist_parse_float(ptr, &vend); if(vend == ptr || vend < fend) { sprintf(error_buf, "Invalid gain value (%u:%u)", cur_line, (int)(vend - line_start + 1)); goto error; } gain = val; ptr = fend; } break; default: sprintf(error_buf, "Invalid character in preset '%c' (%u:%u)", c, cur_line, (unsigned int)(ptr - line_start)); goto error; } } } obj = json_object_new(0); json_object_push(obj, "fileName", json_string_new_length((unsigned int)(lend - path), path)); if(gain != 0.0) { json_object_push(obj, "gain", json_double_new(gain)); gain = 0.0; } if(channels) { channels = json_array_sort(channels); json_object_push(obj, "channels", channels); channels = 0; } if(patchMappings) { json_object_push(obj, "patchMappings", patchMappings); patchMappings = 0; } json_array_push(arr, obj); ptr = lend; while(ptr < end && (*ptr == '\n' || *ptr == '\r')) ++ptr; } rval = json_object_new(1); json_object_push(rval, "soundFonts", arr); return rval; error: if(channels) json_builder_free(channels); if(patchMappings) json_builder_free(patchMappings); if(arr) json_builder_free(arr); return 0; } static json_value *sflist_load_v2(const char *sflist, size_t size, char *error) { json_value *rval = 0; json_settings settings = { 0 }; settings.value_extra = json_builder_extra; rval = json_parse_ex(&settings, sflist, size, error); return rval; } static const json_value *json_object_item(const json_value *object, const char *name) { unsigned int i, j; if(object->type != json_object) return &json_value_none; for(i = 0, j = object->u.object.length; i < j; ++i) { if(!strcmp(object->u.object.values[i].name, name)) return object->u.object.values[i].value; } return &json_value_none; } static void sflist_process_patchmappings(BASS_MIDI_FONTEX2 *out, BASS_MIDI_FONTEX2 *fontex, const json_value *patchMappings, unsigned int channel, unsigned int channelCount) { unsigned int i, j; for(i = 0, j = patchMappings->u.array.length; i < j; ++i) { json_value *preset = patchMappings->u.array.values[i]; const json_value *destination = json_object_item(preset, "destination"); const json_value *source = json_object_item(preset, "source"); const json_value *destination_bank = json_object_item(destination, "bank"); const json_value *destination_program = json_object_item(destination, "program"); const json_value *source_bank = json_object_item(source, "bank"); const json_value *source_program = json_object_item(source, "program"); fontex->spreset = (source_program->type == json_none) ? -1 : (int)source_program->u.integer; fontex->sbank = (source_bank->type == json_none) ? -1 : (int)source_bank->u.integer; fontex->dpreset = (destination_program->type == json_none) ? -1 : (int)destination_program->u.integer; fontex->dbank = (destination_bank->type == json_none) ? 0 : (int)destination_bank->u.integer; fontex->minchan = channel; fontex->numchan = channelCount; *out++ = *fontex; } } static sflist_presets *sflist_process(const json_value *sflist, const char *base_path, char *error_buf) { #ifdef _WIN32 wchar_t path16[32768]; #endif char path_temp[32768]; const char *base_path_end = base_path + strlen(base_path) - 1; unsigned int presets_to_allocate = 0; sflist_presets *rval = calloc(1, sizeof(sflist_presets)); json_value *arr; unsigned int i, j, k, l, preset_number; HSOUNDFONT hfont = 0; BASS_MIDI_FONTEX2 fontex; if(!rval) { strcpy(error_buf, "Out of memory"); goto error; } if(sflist->type != json_object || sflist->u.object.length != 1 || strcmp(sflist->u.object.values[0].name, "soundFonts")) { if(sflist->type != json_object) strcpy(error_buf, "Base JSON item is not an object"); else if(sflist->u.object.length != 1) sprintf(error_buf, "Base JSON object contains unexpected number of items (wanted 1, got %u)", sflist->u.object.length); else sprintf(error_buf, "Base JSON object contains '%s' object instead of 'soundFonts'", sflist->u.object.values[0].name); goto error; } arr = sflist->u.object.values[0].value; if(arr->type != json_array) { strcpy(error_buf, "JSON 'soundFonts' object is not an array"); goto error; } for(i = 0, j = arr->u.array.length; i < j; ++i) { const json_value *obj = arr->u.array.values[i]; const json_value *path = 0; const json_value *gain = 0; const json_value *channels = 0; const json_value *patchMappings = 0; unsigned int patches_needed = 1; if(obj->type != json_object) { sprintf(error_buf, "soundFont item #%u is not an object", i + 1); goto error; } path = json_object_item(obj, "fileName"); gain = json_object_item(obj, "gain"); channels = json_object_item(obj, "channels"); patchMappings = json_object_item(obj, "patchMappings"); if(path->type == json_none) { sprintf(error_buf, "soundFont item #%u has no 'fileName'", i + 1); goto error; } if(path->type != json_string) { sprintf(error_buf, "soundFont item #%u 'fileName' is not a string", i + 1); goto error; } if(gain->type != json_none && gain->type != json_integer && gain->type != json_double) { sprintf(error_buf, "soundFont item #%u has an invalid gain value", i + 1); goto error; } if(channels->type != json_none) { if(channels->type != json_array) { sprintf(error_buf, "soundFont item #%u 'channels' is not an array", i + 1); goto error; } int prevchannel = -1; int contiguouschannelsets = 0; for(k = 0, l = channels->u.array.length; k < l; ++k) { json_value *channel = channels->u.array.values[k]; if(channel->type != json_integer) { sprintf(error_buf, "soundFont item #%u 'channels' #%u is not an integer", i + 1, k + 1); goto error; } if(channel->u.integer < 1 || channel->u.integer > 48) { sprintf(error_buf, "soundFont item #%u 'channels' #%u is out of range (wanted 1-48, got %" PRId64 ")", i + 1, k + 1, channel->u.integer); goto error; } if(prevchannel < 0 || channel->u.integer > (prevchannel + 1)) { ++contiguouschannelsets; } prevchannel = (int)channel->u.integer; } patches_needed = contiguouschannelsets; } if(patchMappings->type != json_none) { if(patchMappings->type != json_array) { sprintf(error_buf, "soundFont item #%u 'patchMappings' is not an array", i + 1); goto error; } for(k = 0, l = patchMappings->u.array.length; k < l; ++k) { unsigned int m, n; unsigned int source_found = 0; unsigned int destination_found = 0; json_value *mapping = patchMappings->u.array.values[k]; if(mapping->type != json_object) { sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u is not an object", i + 1, k + 1); goto error; } for(m = 0, n = mapping->u.object.length; m < n; ++m) { unsigned int o, p; json_value *item = mapping->u.object.values[m].value; const char *name = mapping->u.object.values[m].name; unsigned int bank_found = 0; unsigned int program_found = 0; if(strcmp(name, "source") && strcmp(name, "destination")) { sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u contains an invalid '%s' field", i + 1, k + 1, name); goto error; } if(item->type != json_object) { sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u '%s' is not an object", i + 1, k + 1, name); goto error; } if(!strcmp(name, "source")) ++source_found; else ++destination_found; for(o = 0, p = item->u.object.length; o < p; ++o) { int range_min = 0; int range_max = 128; json_value *item2 = item->u.object.values[o].value; const char *name2 = item->u.object.values[o].name; if(strcmp(name2, "bank") && strcmp(name2, "program")) { sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u '%s' contains an invalid '%s' field", i + 1, k + 1, name, name2); goto error; } if(item2->type != json_integer) { sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u '%s' '%s' is not an integer", i + 1, k + 1, name, name2); } if(!strcmp(name2, "program")) { if(!strcmp(name, "destination")) range_max = 65535; else range_max = 127; } if(item2->u.integer < range_min || item2->u.integer > range_max) { sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u '%s' '%s' is out of range (expected %d-%d, got %" PRId64 ")", i + 1, k + 1, name, name2, range_min, range_max, item->u.integer); goto error; } if(!strcmp(name2, "bank")) ++bank_found; else ++program_found; } if(!bank_found && !program_found) { sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u '%s' contains no 'bank' or 'program'", i + 1, k + 1, name); goto error; } if(bank_found > 1) { sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u '%s' contains more than one 'bank'", i + 1, k + 1, name); goto error; } if(program_found > 1) { sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u '%s' contains more than one 'program'", i + 1, k + 1, name); goto error; } } if(!destination_found) { sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u is missing 'destination'", i + 1, k + 1); goto error; } if(destination_found > 1) { sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u contains more than one 'destination'", i + 1, k + 1); goto error; } if(source_found > 1) { sprintf(error_buf, "soundFont item #%u 'patchMappings' #%u contains more than one 'source'", i + 1, k + 1); } } patches_needed *= l; } presets_to_allocate += patches_needed; } rval->count = presets_to_allocate; rval->presets = calloc(sizeof(BASS_MIDI_FONTEX2), rval->count); if(!rval->presets) { strcpy(error_buf, "Out of memory"); goto error; } preset_number = 0; for(i = arr->u.array.length, j = 0; i--; ++j) { const json_value *obj = arr->u.array.values[i]; const json_value *path = json_object_item(obj, "fileName"); const json_value *gain = json_object_item(obj, "gain"); const json_value *channels = json_object_item(obj, "channels"); const json_value *patchMappings = json_object_item(obj, "patchMappings"); const void *bass_path; const char *path_ptr = path->u.string.ptr; unsigned int bass_flags = 0; #ifdef _WIN32 if(!(isalpha(*path_ptr) && path_ptr[1] == ':')) { if(strlen(path_ptr) + (base_path_end - base_path + 2) > 32767) { strcpy(error_buf, "Base path plus SoundFont relative path is longer than 32767 characters"); goto error; } strcpy(path_temp, base_path); if(*base_path_end != '\\' && *base_path_end != '/') strcat(path_temp, "\\"); strcat(path_temp, path_ptr); path_ptr = path_temp; } MultiByteToWideChar(CP_UTF8, 0, path_ptr, -1, path16, 32767); path16[32767] = '\0'; bass_path = (void *)path16; bass_flags = BASS_UNICODE; #else if(*path_ptr != '/') { if(strlen(path_ptr) + (base_path_end - base_path + 2) > 32767) { strcpy(error_buf, "Base path plus SoundFont relative path is longer than 32767 characters"); goto error; } strcpy(path_temp, base_path); if(*base_path_end != '/') strcat(path_temp, "/"); strcat(path_temp, path_ptr); path_ptr = path_temp; } bass_path = (void *)path_ptr; #endif hfont = BASS_MIDI_FontInit(bass_path, bass_flags); if(!hfont) { int error_code = BASS_ErrorGetCode(); if(error_code == BASS_ERROR_FILEOPEN) { sprintf(error_buf, "Could not open SoundFont bank '%s'", path->u.string.ptr); goto error; } else if(error_code == BASS_ERROR_FILEFORM) { sprintf(error_buf, "SoundFont bank '%s' is not a supported format", path->u.string.ptr); goto error; } else { sprintf(error_buf, "SoundFont bank '%s' failed to load with error #%u", path->u.string.ptr, error_code); goto error; } } if(gain->type != json_none) { double gain_value = 0.0; if(gain->type == json_integer) { gain_value = (double)gain->u.integer; } else if(gain->type == json_double) { gain_value = gain->u.dbl; } gain_value = pow(10.0, gain_value / 20.0); BASS_MIDI_FontSetVolume(hfont, gain_value); } fontex.font = hfont; fontex.spreset = -1; fontex.sbank = -1; fontex.dpreset = -1; fontex.dbank = 0; fontex.dbanklsb = 0; fontex.minchan = 0; fontex.numchan = 48; /* Simplest case, whole bank loading */ if(channels->type == json_none && patchMappings->type == json_none) { rval->presets[preset_number++] = fontex; } else if(patchMappings->type == json_none) { int prevchannel = -1; int firstchannel = -1; for(k = 0, l = channels->u.array.length; k < l; ++k) { int channel = (int)channels->u.array.values[k]->u.integer; if(firstchannel < 0) { firstchannel = channel; prevchannel = channel; } if(channel > (prevchannel + 1)) { fontex.minchan = firstchannel; fontex.numchan = prevchannel - firstchannel + 1; rval->presets[preset_number++] = fontex; firstchannel = channel; } prevchannel = channel; } fontex.minchan = firstchannel; fontex.numchan = prevchannel - firstchannel + 1; rval->presets[preset_number++] = fontex; } else if(channels->type == json_none) { sflist_process_patchmappings(rval->presets + preset_number, &fontex, patchMappings, 0, 48); preset_number += patchMappings->u.array.length; } else { int prevchannel = -1; int firstchannel = -1; for(k = 0, l = channels->u.array.length; k < l; ++k) { int channel = (int)channels->u.array.values[k]->u.integer; if(firstchannel < 0) { firstchannel = channel; prevchannel = channel; } if(channel > (prevchannel + 1)) { sflist_process_patchmappings(rval->presets + preset_number, &fontex, patchMappings, firstchannel, prevchannel - firstchannel + 1); preset_number += patchMappings->u.array.length; } prevchannel = channel; } sflist_process_patchmappings(rval->presets + preset_number, &fontex, patchMappings, firstchannel, prevchannel - firstchannel + 1); preset_number += patchMappings->u.array.length; } } return rval; error: if(hfont) { BASS_MIDI_FontFree(hfont); } if(rval) { sflist_free(rval); } return 0; } static int strpbrkn_all(const char *str, size_t size, const char *chrs) { const char *end = str + size; while(str < end && *chrs) { while(str < end && *str != *chrs) ++str; ++str, ++chrs; } return str < end; } sflist_presets *sflist_load(const char *sflist, size_t size, const char *base_path, char *error) { sflist_presets *rval; json_value *list = 0; /* Handle Unicode byte order markers */ if(size >= 2) { if((sflist[0] == 0xFF && sflist[1] == 0xFE) || (sflist[0] == 0xFE && sflist[1] == 0xFF)) { strcpy(error, "UTF-16 encoding is not supported at this time"); return 0; } if(size >= 3 && sflist[0] == 0xEF && sflist[1] == 0xBB && sflist[2] == 0xBF) { sflist += 3; size -= 3; } } list = sflist_load_v2(sflist, size, error); if(!list) { if(!strpbrkn_all(sflist, size, "{[]}")) list = sflist_load_v1(sflist, size, error); } if(!list) { return 0; } rval = sflist_process(list, base_path, error); json_builder_free(list); return rval; } void sflist_free(sflist_presets *presetlist) { if(presetlist) { if(presetlist->presets) { unsigned int i, j; for(i = 0, j = presetlist->count; i < j; ++i) { HSOUNDFONT hfont = presetlist->presets[i].font; if(hfont) { BASS_MIDI_FontFree(hfont); } } free(presetlist->presets); } free(presetlist); } } const char *sflist_upgrade(const char *sflist, size_t size, char *error_buf) { char *rval = 0; json_value *list = 0; size_t length = 0; const json_serialize_opts opts = { json_serialize_mode_multiline, 0, 3 /* indent_size */ }; list = sflist_load_v2(sflist, size, error_buf); if(!list) { if(!strpbrkn_all(sflist, size, "{[]}")) list = sflist_load_v1(sflist, size, error_buf); } if(!list) { return 0; } length = json_measure_ex(list, opts); rval = (char *)malloc(length + 1); if(!rval) { strcpy(error_buf, "Out of memory"); goto error; } json_serialize_ex(rval, list, opts); json_builder_free(list); rval[length] = '\0'; return (const char *)rval; error: if(list) { json_builder_free(list); } return 0; } void sflist_upgrade_free(const char *ptr) { free((void *)ptr); }