-
-
Save kode54/a7bb01a0db3f2e996145b77f0ca510d5 to your computer and use it in GitHub Desktop.
SoundFont List, as JSON
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"soundFonts": [ | |
{"fileName": "/Simple/SoundFont File.sf2"}, | |
{ | |
"fileName": "/Advanced/SoundFont With Mappings.sf2", | |
"gain": 6.0, | |
"channels": [1,3,5], | |
"patchMappings": [ | |
{"source": {"bank": 0, "program": 1}, "destination": {"bank": 0, "program": 20}}, | |
{"source": {"bank": 20, "program": 5}, "destination": {"bank": 0, "program": 5}} | |
] | |
}, | |
{ | |
"fileName": "/Another Simple/SoundFont File.sf2", | |
"patchMappings": [ | |
{"destination": {"bank": 1}} | |
] | |
}, | |
{"fileName": "/SoundFont List.sflist.json"}, | |
{ | |
"fileName": "/Patch.sfz", | |
"patchMappings": [ | |
{"destination": {"bank": 0, "program": 2}}, | |
{"destination": {"bank": 0, "program": 3}} | |
] | |
} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
If fileName specifies another sflist json file, no other arguments will be processed for that block, | |
and the contents of that file will be parsed and included. | |
SoundFont load priority is the same as the original SFList, in that files listed first will be overridden by files listed later. Nested .sflist.json loading has the same rules. Basically, first come, first loaded. Except that BASS treats first items in the list as higher priority, so I guess reverse the order when preparing the list for BASS? | |
Adding an integer priority value may be useful to some people, but I'm not sure if ipt's such a good idea. | |
patchMappings: | |
As many items as is necessary. SFZ ignores and therefore does not require the "source" argument. | |
Depending on how the loading operation is intended, "bank" or "program" may be optional. For source, | |
"bank" is assumed to be 'all banks' if not specified, and "program" is assumed to be 'all presets' | |
if not specified. For destination, "program" is assumed to be 'all presets' only if not present and | |
if source is not specified, while "bank" must be present, and is assumed to be the base bank if | |
loading all presets and banks from the source. | |
gain: | |
floating point, decibels, optional argument. | |
channels: | |
array of one or more numbers in range of 1-48, also optional. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* vim: set et ts=3 sw=3 sts=3 ft=c: | |
* | |
* Copyright (C) 2016 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 <ctype.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <math.h> | |
#include <json-builder.h> | |
#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_FONTEX * out, BASS_MIDI_FONTEX * fontex, const json_value * patchMappings, unsigned int channel) | |
{ | |
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->dbanklsb = channel; | |
*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_FONTEX 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; | |
} | |
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; | |
} | |
} | |
patches_needed = l; | |
} | |
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_FONTEX), 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; | |
/* 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) { | |
for (k = 0, l = channels->u.array.length; k < l; ++k) { | |
fontex.dbanklsb = (int)channels->u.array.values[k]->u.integer; | |
rval->presets[preset_number++] = fontex; | |
} | |
} | |
else if (channels->type == json_none) { | |
sflist_process_patchmappings(rval->presets + preset_number, &fontex, patchMappings, 0); | |
preset_number += patchMappings->u.array.length; | |
} | |
else { | |
for (k = 0, l = channels->u.array.length; k < l; ++k) { | |
sflist_process_patchmappings(rval->presets + preset_number, &fontex, patchMappings, (int)channels->u.array.values[k]->u.integer); | |
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 ); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* vim: set et ts=3 sw=3 sts=3 ft=c: | |
* | |
* Copyright (C) 2016 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. | |
*/ | |
#ifndef _SFLIST_H | |
#define _SFLIST_H | |
#ifdef HAVE_CONFIG_H | |
#include "config.h" | |
#endif | |
#include <bassmidi.h> | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
typedef struct sflist_presets | |
{ | |
unsigned int count; | |
BASS_MIDI_FONTEX * presets; | |
} sflist_presets; | |
#define sflist_max_error 1024 | |
sflist_presets * sflist_load(const char * sflist, size_t size, const char * base_path, char * error); | |
void sflist_free(sflist_presets *); | |
const char * sflist_upgrade(const char * sflist, size_t size, char * error); | |
void sflist_upgrade_free(const char *); | |
#ifdef __cplusplus | |
} | |
#endif | |
#endif | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* vim: set et ts=3 sw=3 sts=3 ft=c: | |
* | |
* Copyright (C) 2016 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 <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <limits.h> | |
#include "sflist.h" | |
int main(int argc, const char ** argv) | |
{ | |
char error[sflist_max_error]; | |
char path_temp[32768]; | |
int i; | |
FILE *f; | |
size_t length; | |
char * in; | |
const char * out; | |
for (i = 1; i < argc; ++i) { | |
const char * end = argv[i] + strlen(argv[i]); | |
if (((end - argv[i]) >= 5) && !strcmp(&end[-5], ".json")) continue; | |
strcpy(path_temp, argv[i]); | |
strcat(path_temp, ".json"); | |
f = fopen(argv[i], "r"); | |
fseek(f, 0, SEEK_END); | |
length = ftell(f); | |
fseek(f, 0, SEEK_SET); | |
in = malloc(length + 1); | |
if (!in) { | |
fclose(f); | |
fputs("Out of memory.\n", stderr); | |
return 1; | |
} | |
if (fread(in, length, 1, f) != 1) { | |
free(in); | |
fclose(f); | |
fprintf(stderr, "Cannot read all of file '%s'.\n", argv[i]); | |
return 1; | |
} | |
fclose(f); | |
in[length] = '\0'; | |
out = sflist_upgrade(in, length, error); | |
free(in); | |
if (!out) { | |
fprintf(stderr, "Error processing '%s': %s\n", argv[i], error); | |
return 1; | |
} | |
f = fopen(path_temp, "w"); | |
if (!f) { | |
sflist_upgrade_free(out); | |
fprintf(stderr, "Unable to open output file '%s'.\n", path_temp); | |
return 1; | |
} | |
if (fwrite(out, strlen(out), 1, f) != 1) { | |
fclose(f); | |
sflist_upgrade_free(out); | |
fprintf(stderr, "Unable to write to output file '%s'.\n", path_temp); | |
return 1; | |
} | |
fclose(f); | |
sflist_upgrade_free(out); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment