Rewrote the MIDI SFList loader, and added support for the new JSON based format.
parent
03e7b666ae
commit
78ccb5d2bf
|
@ -7,6 +7,9 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
830C94D11D171B65000E404F /* sflist.c in Sources */ = {isa = PBXBuildFile; fileRef = 830C94CB1D171B65000E404F /* sflist.c */; };
|
||||
830C94D21D171B65000E404F /* json-builder.c in Sources */ = {isa = PBXBuildFile; fileRef = 830C94CD1D171B65000E404F /* json-builder.c */; };
|
||||
830C94D31D171B65000E404F /* json.c in Sources */ = {isa = PBXBuildFile; fileRef = 830C94CF1D171B65000E404F /* json.c */; };
|
||||
83686AAC1C5C69D400671C7A /* AUPlayerView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83686AAB1C5C69D400671C7A /* AUPlayerView.mm */; };
|
||||
83686AB11C5C783000671C7A /* CoreAudioKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83686AB01C5C783000671C7A /* CoreAudioKit.framework */; };
|
||||
8398F2E01C438C7D00EB9639 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8398F2DF1C438C7D00EB9639 /* AudioUnit.framework */; };
|
||||
|
@ -83,6 +86,12 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
830C94CB1D171B65000E404F /* sflist.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sflist.c; path = ../../../ThirdParty/BASS/sflist.c; sourceTree = "<group>"; };
|
||||
830C94CC1D171B65000E404F /* sflist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sflist.h; path = ../../../ThirdParty/BASS/sflist.h; sourceTree = "<group>"; };
|
||||
830C94CD1D171B65000E404F /* json-builder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "json-builder.c"; path = "../../../ThirdParty/json/json-builder.c"; sourceTree = "<group>"; };
|
||||
830C94CE1D171B65000E404F /* json-builder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "json-builder.h"; path = "../../../ThirdParty/json/json-builder.h"; sourceTree = "<group>"; };
|
||||
830C94CF1D171B65000E404F /* json.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = json.c; path = ../../../ThirdParty/json/json.c; sourceTree = "<group>"; };
|
||||
830C94D01D171B65000E404F /* json.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = json.h; path = ../../../ThirdParty/json/json.h; sourceTree = "<group>"; };
|
||||
833F68431CDBCABE00AFB9F0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
83686AAB1C5C69D400671C7A /* AUPlayerView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AUPlayerView.mm; sourceTree = "<group>"; };
|
||||
83686AAD1C5C6A2700671C7A /* AUPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AUPlayerView.h; sourceTree = "<group>"; };
|
||||
|
@ -160,6 +169,19 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
830C94CA1D171B30000E404F /* sflist */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
830C94CB1D171B65000E404F /* sflist.c */,
|
||||
830C94CC1D171B65000E404F /* sflist.h */,
|
||||
830C94CD1D171B65000E404F /* json-builder.c */,
|
||||
830C94CE1D171B65000E404F /* json-builder.h */,
|
||||
830C94CF1D171B65000E404F /* json.c */,
|
||||
830C94D01D171B65000E404F /* json.h */,
|
||||
);
|
||||
name = sflist;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
83A09F551CFA83F2001E7D2D /* synthlib_doom */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -253,6 +275,7 @@
|
|||
83B06690180D5668008E3612 /* MIDI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
830C94CA1D171B30000E404F /* sflist */,
|
||||
83A09F6E1CFA8D6B001E7D2D /* MSPlayer.cpp */,
|
||||
83A09F6D1CFA8D6B001E7D2D /* MSPlayer.h */,
|
||||
83A09F661CFA883D001E7D2D /* interface.h */,
|
||||
|
@ -387,6 +410,7 @@
|
|||
files = (
|
||||
83E973471C4378880007F413 /* AUPlayer.mm in Sources */,
|
||||
83686AAC1C5C69D400671C7A /* AUPlayerView.mm in Sources */,
|
||||
830C94D31D171B65000E404F /* json.c in Sources */,
|
||||
83DFEA071CBC87BB00BCC565 /* SCCore.cpp in Sources */,
|
||||
83A09F621CFA83F2001E7D2D /* i_oplmusic.cpp in Sources */,
|
||||
83B06709180D64DA008E3612 /* MIDIPlayer.cpp in Sources */,
|
||||
|
@ -398,6 +422,8 @@
|
|||
83B0670C180D6665008E3612 /* BMPlayer.cpp in Sources */,
|
||||
83A09F651CFA83F2001E7D2D /* opl3class.cpp in Sources */,
|
||||
83C35705180EDD1C007E9DF0 /* MIDIMetadataReader.mm in Sources */,
|
||||
830C94D21D171B65000E404F /* json-builder.c in Sources */,
|
||||
830C94D11D171B65000E404F /* sflist.c in Sources */,
|
||||
83A09F641CFA83F2001E7D2D /* opl3.cpp in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -501,6 +527,10 @@
|
|||
COMBINE_HIDPI_IMAGES = YES;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "MIDI/MIDI-Prefix.pch";
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(SRCROOT)/../../ThirdParty/BASS",
|
||||
"$(SRCROOT)/../../ThirdParty/json",
|
||||
);
|
||||
INFOPLIST_FILE = "MIDI/MIDI-Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
|
@ -521,6 +551,10 @@
|
|||
COMBINE_HIDPI_IMAGES = YES;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "MIDI/MIDI-Prefix.pch";
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(SRCROOT)/../../ThirdParty/BASS",
|
||||
"$(SRCROOT)/../../ThirdParty/json",
|
||||
);
|
||||
INFOPLIST_FILE = "MIDI/MIDI-Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "BMPlayer.h"
|
||||
|
||||
#include <sflist.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <string>
|
||||
|
@ -20,7 +22,8 @@ struct Cached_SoundFont
|
|||
unsigned long ref_count;
|
||||
std::chrono::steady_clock::time_point time_released;
|
||||
HSOUNDFONT handle;
|
||||
Cached_SoundFont() : handle( 0 ) { }
|
||||
sflist_presets * presetlist;
|
||||
Cached_SoundFont() : handle( 0 ), presetlist( 0 ) { }
|
||||
};
|
||||
|
||||
static std::mutex Cache_Lock;
|
||||
|
@ -46,11 +49,14 @@ static void cache_deinit()
|
|||
|
||||
for ( auto it = Cache_List.begin(); it != Cache_List.end(); ++it )
|
||||
{
|
||||
BASS_MIDI_FontFree( it->second.handle );
|
||||
if ( it->second.handle )
|
||||
BASS_MIDI_FontFree( it->second.handle );
|
||||
if ( it->second.presetlist )
|
||||
sflist_free( it->second.presetlist );
|
||||
}
|
||||
}
|
||||
|
||||
static HSOUNDFONT cache_open( const char * path )
|
||||
static HSOUNDFONT cache_open_font( const char * path )
|
||||
{
|
||||
HSOUNDFONT font = NULL;
|
||||
|
||||
|
@ -80,7 +86,80 @@ static HSOUNDFONT cache_open( const char * path )
|
|||
return font;
|
||||
}
|
||||
|
||||
static void cache_close( HSOUNDFONT handle )
|
||||
static sflist_presets * sflist_open_file( const char * path )
|
||||
{
|
||||
sflist_presets * presetlist;
|
||||
FILE * f;
|
||||
char * sflist_file;
|
||||
char * separator;
|
||||
size_t length;
|
||||
char error[sflist_max_error];
|
||||
char base_path[32768];
|
||||
strcpy(base_path, path);
|
||||
separator = strrchr(base_path, '/');
|
||||
if (separator)
|
||||
*separator = '\0';
|
||||
else
|
||||
base_path[0] = '\0';
|
||||
|
||||
f = fopen( path, "r" );
|
||||
if (!f) return 0;
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
length = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
sflist_file = (char *) malloc(length + 1);
|
||||
if (!sflist_file)
|
||||
{
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
length = fread(sflist_file, 1, length, f);
|
||||
fclose(f);
|
||||
|
||||
sflist_file[length] = '\0';
|
||||
|
||||
presetlist = sflist_load(sflist_file, strlen(sflist_file), base_path, error);
|
||||
|
||||
free(sflist_file);
|
||||
|
||||
return presetlist;
|
||||
}
|
||||
|
||||
static sflist_presets * cache_open_list( const char * path )
|
||||
{
|
||||
sflist_presets * presetlist = NULL;
|
||||
|
||||
std::lock_guard<std::mutex> lock( Cache_Lock );
|
||||
|
||||
Cached_SoundFont & entry = Cache_List[ path ];
|
||||
|
||||
if ( !entry.presetlist )
|
||||
{
|
||||
|
||||
presetlist = sflist_open_file( path );
|
||||
if ( presetlist )
|
||||
{
|
||||
entry.presetlist = presetlist;
|
||||
entry.ref_count = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Cache_List.erase( path );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
presetlist = entry.presetlist;
|
||||
++entry.ref_count;
|
||||
}
|
||||
|
||||
return presetlist;
|
||||
}
|
||||
|
||||
static void cache_close_font( HSOUNDFONT handle )
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( Cache_Lock );
|
||||
|
||||
|
@ -95,6 +174,21 @@ static void cache_close( HSOUNDFONT handle )
|
|||
}
|
||||
}
|
||||
|
||||
static void cache_close_list( sflist_presets * presetlist )
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( Cache_Lock );
|
||||
|
||||
for ( auto it = Cache_List.begin(); it != Cache_List.end(); ++it )
|
||||
{
|
||||
if ( it->second.presetlist == presetlist )
|
||||
{
|
||||
if ( --it->second.ref_count == 0 )
|
||||
it->second.time_released = std::chrono::steady_clock::now();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void cache_run()
|
||||
{
|
||||
std::chrono::milliseconds dura( 250 );
|
||||
|
@ -114,7 +208,10 @@ static void cache_run()
|
|||
auto elapsed = std::chrono::duration_cast<std::chrono::seconds> ( now - it->second.time_released );
|
||||
if ( elapsed.count() >= 10 )
|
||||
{
|
||||
BASS_MIDI_FontFree( it->second.handle );
|
||||
if ( it->second.handle )
|
||||
BASS_MIDI_FontFree( it->second.handle );
|
||||
if ( it->second.presetlist )
|
||||
sflist_free( it->second.presetlist );
|
||||
it = Cache_List.erase( it );
|
||||
continue;
|
||||
}
|
||||
|
@ -200,6 +297,7 @@ BMPlayer::BMPlayer() : MIDIPlayer()
|
|||
{
|
||||
memset(_stream, 0, sizeof(_stream));
|
||||
bSincInterpolation = false;
|
||||
_presetList = 0;
|
||||
|
||||
if ( !g_initializer.initialize() ) throw std::runtime_error( "Unable to initialize BASS" );
|
||||
}
|
||||
|
@ -290,9 +388,14 @@ void BMPlayer::shutdown()
|
|||
memset(_stream, 0, sizeof(_stream));
|
||||
for ( unsigned long i = 0; i < _soundFonts.size(); ++i )
|
||||
{
|
||||
cache_close( _soundFonts[i] );
|
||||
cache_close_font( _soundFonts[i] );
|
||||
}
|
||||
_soundFonts.resize( 0 );
|
||||
if ( _presetList )
|
||||
{
|
||||
cache_close_list( _presetList );
|
||||
_presetList = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void BMPlayer::compound_presets( std::vector<BASS_MIDI_FONTEX> & out, std::vector<BASS_MIDI_FONTEX> & in, std::vector<long> & channels )
|
||||
|
@ -335,7 +438,19 @@ bool BMPlayer::startup()
|
|||
}
|
||||
memset( bank_lsb_override, 0, sizeof( bank_lsb_override ) );
|
||||
std::vector<BASS_MIDI_FONTEX> presetList;
|
||||
if (sSoundFontName.length())
|
||||
if ( sFileSoundFontName.length() )
|
||||
{
|
||||
HSOUNDFONT font = cache_open_font( sFileSoundFontName.c_str() );
|
||||
if ( !font )
|
||||
{
|
||||
shutdown();
|
||||
return false;
|
||||
}
|
||||
_soundFonts.push_back( font );
|
||||
presetList.push_back( { font, -1, -1, -1, 0, 0 } );
|
||||
}
|
||||
|
||||
if (sSoundFontName.length())
|
||||
{
|
||||
std::string ext;
|
||||
size_t dot = sSoundFontName.find_last_of('.');
|
||||
|
@ -346,7 +461,7 @@ bool BMPlayer::startup()
|
|||
#endif
|
||||
)
|
||||
{
|
||||
HSOUNDFONT font = cache_open( sSoundFontName.c_str() );
|
||||
HSOUNDFONT font = cache_open_font( sSoundFontName.c_str() );
|
||||
if ( !font )
|
||||
{
|
||||
shutdown();
|
||||
|
@ -355,233 +470,24 @@ bool BMPlayer::startup()
|
|||
_soundFonts.push_back( font );
|
||||
presetList.push_back( {font, -1, -1, -1, 0, 0} );
|
||||
}
|
||||
else if ( !strcasecmp( ext.c_str(), "sflist" ) )
|
||||
else if ( !strcasecmp( ext.c_str(), "sflist" ) || !strcasecmp( ext.c_str(), "json" ) )
|
||||
{
|
||||
FILE * fl = fopen( sSoundFontName.c_str(), "r" );
|
||||
if ( fl )
|
||||
{
|
||||
std::string path, temp;
|
||||
char name[32768];
|
||||
char *nameptr;
|
||||
size_t slash = sSoundFontName.find_last_of('/');
|
||||
if ( slash != std::string::npos ) path.assign( sSoundFontName.begin(), sSoundFontName.begin() + slash + 1 );
|
||||
while ( !feof( fl ) )
|
||||
{
|
||||
std::vector<BASS_MIDI_FONTEX> presets;
|
||||
|
||||
if ( !fgets( name, 32767, fl ) ) break;
|
||||
name[32767] = 0;
|
||||
char * cr = strchr( name, '\n' );
|
||||
if ( cr ) *cr = 0;
|
||||
cr = strchr( name, '\r' );
|
||||
if ( cr ) *cr = 0;
|
||||
cr = strchr( name, '|' );
|
||||
if ( cr )
|
||||
{
|
||||
std::vector<BASS_MIDI_FONTEX> nested_presets;
|
||||
std::vector<long> channels;
|
||||
bool valid = true;
|
||||
bool pushed_back = true;
|
||||
char *endchr;
|
||||
nameptr = cr + 1;
|
||||
*cr = 0;
|
||||
cr = name;
|
||||
while ( *cr && valid )
|
||||
{
|
||||
switch ( *cr++ )
|
||||
{
|
||||
case 'p':
|
||||
{
|
||||
// patch override - "p[db#,]dp#=[sb#,]sp#" ex. "p0,5=0,1"
|
||||
// may be used once per preset group
|
||||
pushed_back = false;
|
||||
long dbank = 0;
|
||||
long dpreset = strtol( cr, &endchr, 10 );
|
||||
if ( endchr == cr )
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
if ( *endchr == ',' )
|
||||
{
|
||||
dbank = dpreset;
|
||||
cr = endchr + 1;
|
||||
dpreset = strtol( cr, &endchr, 10 );
|
||||
if ( endchr == cr )
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( *endchr != '=' )
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
cr = endchr + 1;
|
||||
long sbank = -1;
|
||||
long spreset = strtol( cr, &endchr, 10 );
|
||||
if ( endchr == cr )
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
if ( *endchr == ',' )
|
||||
{
|
||||
sbank = spreset;
|
||||
cr = endchr + 1;
|
||||
spreset = strtol( cr, &endchr, 10 );
|
||||
if ( endchr == cr )
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( *endchr && *endchr != ';' && *endchr != '&' )
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
cr = endchr;
|
||||
nested_presets.push_back( { 0, (int) spreset, (int) sbank, (int) dpreset, (int) dbank, 0 } );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
{
|
||||
// channel override - implemented using bank LSB, which is disabled from
|
||||
// actual use. - format "c#[-#]" ex. "c16" (range is 1-48)
|
||||
// may be used multiple times per preset group
|
||||
pushed_back = false;
|
||||
long channel_start = strtol(cr, &endchr, 10);
|
||||
long channel_end;
|
||||
if ( endchr == cr )
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
if ( channel_start < 1 || channel_start > 48 )
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
channel_end = channel_start;
|
||||
if ( *endchr == '-' )
|
||||
{
|
||||
channel_end = strtol(cr, &endchr, 10);
|
||||
if ( channel_end <= channel_start || channel_end > 48 )
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for ( auto it = channels.begin(); it != channels.end(); ++it )
|
||||
{
|
||||
if ( *it >= channel_start || *it <= channel_end )
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( *endchr && *endchr != ';' )
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
cr = endchr;
|
||||
for ( long channel = channel_start; channel <= channel_end; ++channel )
|
||||
channels.push_back( channel );
|
||||
}
|
||||
break;
|
||||
|
||||
case '&':
|
||||
{
|
||||
// separates preset groups per SoundFont bank
|
||||
if ( !pushed_back )
|
||||
{
|
||||
compound_presets( presets, nested_presets, channels );
|
||||
nested_presets.clear(); channels.clear();
|
||||
pushed_back = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ';':
|
||||
// separates preset items
|
||||
break;
|
||||
|
||||
default:
|
||||
// invalid command character
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !pushed_back && valid )
|
||||
compound_presets( presets, nested_presets, channels );
|
||||
if ( !valid )
|
||||
{
|
||||
presets.clear();
|
||||
presets.push_back( { 0, -1, -1, -1, 0, 0 } );
|
||||
memset( bank_lsb_override, 0, sizeof(bank_lsb_override) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
presets.push_back( { 0, -1, -1, -1, 0, 0 } );
|
||||
nameptr = name;
|
||||
}
|
||||
if ( nameptr[0] == '/' )
|
||||
{
|
||||
temp = nameptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
temp = path;
|
||||
temp += nameptr;
|
||||
}
|
||||
HSOUNDFONT font = cache_open( temp.c_str() );
|
||||
if ( !font )
|
||||
{
|
||||
fclose( fl );
|
||||
shutdown();
|
||||
return false;
|
||||
}
|
||||
for ( auto it = presets.begin(); it != presets.end(); ++it )
|
||||
{
|
||||
it->font = font;
|
||||
presetList.push_back( *it );
|
||||
}
|
||||
_soundFonts.push_back( font );
|
||||
}
|
||||
fclose( fl );
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_presetList = cache_open_list( sSoundFontName.c_str() );
|
||||
if ( !_presetList )
|
||||
{
|
||||
shutdown();
|
||||
return false;
|
||||
}
|
||||
for (unsigned int i = 0, j = _presetList->count; i < j; ++i)
|
||||
{
|
||||
presetList.push_back( _presetList->presets[i] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( sFileSoundFontName.length() )
|
||||
{
|
||||
HSOUNDFONT font = cache_open( sFileSoundFontName.c_str() );
|
||||
if ( !font )
|
||||
{
|
||||
shutdown();
|
||||
return false;
|
||||
}
|
||||
_soundFonts.push_back( font );
|
||||
presetList.push_back( { font, -1, -1, -1, 0, 0 } );
|
||||
}
|
||||
|
||||
std::vector< BASS_MIDI_FONTEX > fonts;
|
||||
for ( unsigned long i = 0, j = presetList.size(); i < j; ++i )
|
||||
{
|
||||
fonts.push_back( presetList[j - i - 1] );
|
||||
}
|
||||
BASS_MIDI_StreamSetFonts( _stream[0], &fonts[0], (unsigned int) fonts.size() | BASS_MIDI_FONT_EX );
|
||||
BASS_MIDI_StreamSetFonts( _stream[1], &fonts[0], (unsigned int) fonts.size() | BASS_MIDI_FONT_EX );
|
||||
BASS_MIDI_StreamSetFonts( _stream[2], &fonts[0], (unsigned int) fonts.size() | BASS_MIDI_FONT_EX );
|
||||
BASS_MIDI_StreamSetFonts( _stream[0], &presetList[0], (unsigned int) presetList.size() | BASS_MIDI_FONT_EX );
|
||||
BASS_MIDI_StreamSetFonts( _stream[1], &presetList[0], (unsigned int) presetList.size() | BASS_MIDI_FONT_EX );
|
||||
BASS_MIDI_StreamSetFonts( _stream[2], &presetList[0], (unsigned int) presetList.size() | BASS_MIDI_FONT_EX );
|
||||
|
||||
reset_parameters();
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
#include "MIDIPlayer.h"
|
||||
|
||||
#include "../../../ThirdParty/BASS/bassmidi.h"
|
||||
#include <bassmidi.h>
|
||||
|
||||
typedef struct sflist_presets sflist_presets;
|
||||
|
||||
class BMPlayer : public MIDIPlayer
|
||||
{
|
||||
|
@ -31,6 +33,7 @@ private:
|
|||
void reset_parameters();
|
||||
|
||||
std::vector<HSOUNDFONT> _soundFonts;
|
||||
sflist_presets * _presetList;
|
||||
std::string sSoundFontName;
|
||||
std::string sFileSoundFontName;
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
- (IBAction)setSoundFont:(id)sender
|
||||
{
|
||||
NSArray *fileTypes = [NSArray arrayWithObjects:@"sf2",@"sf2pack",@"sflist",nil];
|
||||
NSArray *fileTypes = [NSArray arrayWithObjects:@"sf2",@"sf2pack",@"sflist",@"json",nil];
|
||||
NSOpenPanel * panel = [NSOpenPanel openPanel];
|
||||
[panel setAllowsMultipleSelection:NO];
|
||||
[panel setCanChooseDirectories:NO];
|
||||
|
|
|
@ -0,0 +1,894 @@
|
|||
/* vim: set et ts=3 sw=3 sts=3 ft=c:
|
||||
*
|
||||
* Copyright (C) 2016 Christopher Snowhill. All rights reserved.
|
||||
* https://github.com/kode54/sflist
|
||||
*
|
||||
* 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"
|
||||
|
||||
/* 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 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(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 : source_program->u.integer;
|
||||
fontex->sbank = (source_bank->type == json_none) ? -1 : source_bank->u.integer;
|
||||
fontex->dpreset = (destination_program->type == json_none) ? -1 : destination_program->u.integer;
|
||||
fontex->dbank = (destination_bank->type == json_none) ? 0 : 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 (item->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 (item->u.integer < range_min || item->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 (!isletter(*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 = 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, 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;
|
||||
|
||||
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 );
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/* vim: set et ts=3 sw=3 sts=3 ft=c:
|
||||
*
|
||||
* Copyright (C) 2016 Christopher Snowhill. All rights reserved.
|
||||
* https://github.com/kode54/sflist
|
||||
*
|
||||
* 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
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/* vim: set et ts=3 sw=3 sts=3 ft=c:
|
||||
*
|
||||
* Copyright (C) 2016 Christopher Snowhill. All rights reserved.
|
||||
* https://github.com/kode54/sflist
|
||||
*
|
||||
* 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;
|
||||
}
|
|
@ -0,0 +1,992 @@
|
|||
|
||||
/* vim: set et ts=3 sw=3 sts=3 ft=c:
|
||||
*
|
||||
* Copyright (C) 2014 James McLaughlin. All rights reserved.
|
||||
* https://github.com/udp/json-builder
|
||||
*
|
||||
* 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 "json-builder.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
const static json_serialize_opts default_opts =
|
||||
{
|
||||
json_serialize_mode_single_line,
|
||||
0,
|
||||
3 /* indent_size */
|
||||
};
|
||||
|
||||
typedef struct json_builder_value
|
||||
{
|
||||
json_value value;
|
||||
|
||||
int is_builder_value;
|
||||
|
||||
size_t additional_length_allocated;
|
||||
size_t length_iterated;
|
||||
|
||||
} json_builder_value;
|
||||
|
||||
static int builderize (json_value * value)
|
||||
{
|
||||
if (((json_builder_value *) value)->is_builder_value)
|
||||
return 1;
|
||||
|
||||
if (value->type == json_object)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
/* Values straight out of the parser have the names of object entries
|
||||
* allocated in the same allocation as the values array itself. This is
|
||||
* not desirable when manipulating values because the names would be easy
|
||||
* to clobber.
|
||||
*/
|
||||
for (i = 0; i < value->u.object.length; ++ i)
|
||||
{
|
||||
json_char * name_copy;
|
||||
json_object_entry * entry = &value->u.object.values [i];
|
||||
|
||||
if (! (name_copy = (json_char *) malloc ((entry->name_length + 1) * sizeof (json_char))))
|
||||
return 0;
|
||||
|
||||
memcpy (name_copy, entry->name, entry->name_length + 1);
|
||||
entry->name = name_copy;
|
||||
}
|
||||
}
|
||||
|
||||
((json_builder_value *) value)->is_builder_value = 1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
const size_t json_builder_extra = sizeof(json_builder_value) - sizeof(json_value);
|
||||
|
||||
/* These flags are set up from the opts before serializing to make the
|
||||
* serializer conditions simpler.
|
||||
*/
|
||||
const int f_spaces_around_brackets = (1 << 0);
|
||||
const int f_spaces_after_commas = (1 << 1);
|
||||
const int f_spaces_after_colons = (1 << 2);
|
||||
const int f_tabs = (1 << 3);
|
||||
|
||||
int get_serialize_flags (json_serialize_opts opts)
|
||||
{
|
||||
int flags = 0;
|
||||
|
||||
if (opts.mode == json_serialize_mode_packed)
|
||||
return 0;
|
||||
|
||||
if (opts.mode == json_serialize_mode_multiline)
|
||||
{
|
||||
if (opts.opts & json_serialize_opt_use_tabs)
|
||||
flags |= f_tabs;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (! (opts.opts & json_serialize_opt_pack_brackets))
|
||||
flags |= f_spaces_around_brackets;
|
||||
|
||||
if (! (opts.opts & json_serialize_opt_no_space_after_comma))
|
||||
flags |= f_spaces_after_commas;
|
||||
}
|
||||
|
||||
if (! (opts.opts & json_serialize_opt_no_space_after_colon))
|
||||
flags |= f_spaces_after_colons;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
json_value * json_array_new (size_t length)
|
||||
{
|
||||
json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
|
||||
|
||||
if (!value)
|
||||
return NULL;
|
||||
|
||||
((json_builder_value *) value)->is_builder_value = 1;
|
||||
|
||||
value->type = json_array;
|
||||
|
||||
if (! (value->u.array.values = (json_value **) malloc (length * sizeof (json_value *))))
|
||||
{
|
||||
free (value);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
((json_builder_value *) value)->additional_length_allocated = length;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
json_value * json_array_push (json_value * array, json_value * value)
|
||||
{
|
||||
assert (array->type == json_array);
|
||||
|
||||
if (!builderize (array) || !builderize (value))
|
||||
return NULL;
|
||||
|
||||
if (((json_builder_value *) array)->additional_length_allocated > 0)
|
||||
{
|
||||
-- ((json_builder_value *) array)->additional_length_allocated;
|
||||
}
|
||||
else
|
||||
{
|
||||
json_value ** values_new = (json_value **) realloc
|
||||
(array->u.array.values, sizeof (json_value *) * (array->u.array.length + 1));
|
||||
|
||||
if (!values_new)
|
||||
return NULL;
|
||||
|
||||
array->u.array.values = values_new;
|
||||
}
|
||||
|
||||
array->u.array.values [array->u.array.length] = value;
|
||||
++ array->u.array.length;
|
||||
|
||||
value->parent = array;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
json_value * json_object_new (size_t length)
|
||||
{
|
||||
json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
|
||||
|
||||
if (!value)
|
||||
return NULL;
|
||||
|
||||
((json_builder_value *) value)->is_builder_value = 1;
|
||||
|
||||
value->type = json_object;
|
||||
|
||||
if (! (value->u.object.values = (json_object_entry *) calloc
|
||||
(length, sizeof (*value->u.object.values))))
|
||||
{
|
||||
free (value);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
((json_builder_value *) value)->additional_length_allocated = length;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
json_value * json_object_push (json_value * object,
|
||||
const json_char * name,
|
||||
json_value * value)
|
||||
{
|
||||
return json_object_push_length (object, strlen (name), name, value);
|
||||
}
|
||||
|
||||
json_value * json_object_push_length (json_value * object,
|
||||
unsigned int name_length, const json_char * name,
|
||||
json_value * value)
|
||||
{
|
||||
json_char * name_copy;
|
||||
|
||||
assert (object->type == json_object);
|
||||
|
||||
if (! (name_copy = (json_char *) malloc ((name_length + 1) * sizeof (json_char))))
|
||||
return NULL;
|
||||
|
||||
memcpy (name_copy, name, name_length * sizeof (json_char));
|
||||
name_copy [name_length] = 0;
|
||||
|
||||
if (!json_object_push_nocopy (object, name_length, name_copy, value))
|
||||
{
|
||||
free (name_copy);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
json_value * json_object_push_nocopy (json_value * object,
|
||||
unsigned int name_length, json_char * name,
|
||||
json_value * value)
|
||||
{
|
||||
json_object_entry * entry;
|
||||
|
||||
assert (object->type == json_object);
|
||||
|
||||
if (!builderize (object) || !builderize (value))
|
||||
return NULL;
|
||||
|
||||
if (((json_builder_value *) object)->additional_length_allocated > 0)
|
||||
{
|
||||
-- ((json_builder_value *) object)->additional_length_allocated;
|
||||
}
|
||||
else
|
||||
{
|
||||
json_object_entry * values_new = (json_object_entry *)
|
||||
realloc (object->u.object.values, sizeof (*object->u.object.values)
|
||||
* (object->u.object.length + 1));
|
||||
|
||||
if (!values_new)
|
||||
return NULL;
|
||||
|
||||
object->u.object.values = values_new;
|
||||
}
|
||||
|
||||
entry = object->u.object.values + object->u.object.length;
|
||||
|
||||
entry->name_length = name_length;
|
||||
entry->name = name;
|
||||
entry->value = value;
|
||||
|
||||
++ object->u.object.length;
|
||||
|
||||
value->parent = object;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
json_value * json_string_new (const json_char * buf)
|
||||
{
|
||||
return json_string_new_length (strlen (buf), buf);
|
||||
}
|
||||
|
||||
json_value * json_string_new_length (unsigned int length, const json_char * buf)
|
||||
{
|
||||
json_value * value;
|
||||
json_char * copy = (json_char *) malloc ((length + 1) * sizeof (json_char));
|
||||
|
||||
if (!copy)
|
||||
return NULL;
|
||||
|
||||
memcpy (copy, buf, length * sizeof (json_char));
|
||||
copy [length] = 0;
|
||||
|
||||
if (! (value = json_string_new_nocopy (length, copy)))
|
||||
{
|
||||
free (copy);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
json_value * json_string_new_nocopy (unsigned int length, json_char * buf)
|
||||
{
|
||||
json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
|
||||
|
||||
if (!value)
|
||||
return NULL;
|
||||
|
||||
((json_builder_value *) value)->is_builder_value = 1;
|
||||
|
||||
value->type = json_string;
|
||||
value->u.string.length = length;
|
||||
value->u.string.ptr = buf;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
json_value * json_integer_new (json_int_t integer)
|
||||
{
|
||||
json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
|
||||
|
||||
if (!value)
|
||||
return NULL;
|
||||
|
||||
((json_builder_value *) value)->is_builder_value = 1;
|
||||
|
||||
value->type = json_integer;
|
||||
value->u.integer = integer;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
json_value * json_double_new (double dbl)
|
||||
{
|
||||
json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
|
||||
|
||||
if (!value)
|
||||
return NULL;
|
||||
|
||||
((json_builder_value *) value)->is_builder_value = 1;
|
||||
|
||||
value->type = json_double;
|
||||
value->u.dbl = dbl;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
json_value * json_boolean_new (int b)
|
||||
{
|
||||
json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
|
||||
|
||||
if (!value)
|
||||
return NULL;
|
||||
|
||||
((json_builder_value *) value)->is_builder_value = 1;
|
||||
|
||||
value->type = json_boolean;
|
||||
value->u.boolean = b;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
json_value * json_null_new ()
|
||||
{
|
||||
json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
|
||||
|
||||
if (!value)
|
||||
return NULL;
|
||||
|
||||
((json_builder_value *) value)->is_builder_value = 1;
|
||||
|
||||
value->type = json_null;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void json_object_sort (json_value * object, json_value * proto)
|
||||
{
|
||||
unsigned int i, out_index = 0;
|
||||
|
||||
if (!builderize (object))
|
||||
return; /* TODO error */
|
||||
|
||||
assert (object->type == json_object);
|
||||
assert (proto->type == json_object);
|
||||
|
||||
for (i = 0; i < proto->u.object.length; ++ i)
|
||||
{
|
||||
unsigned int j;
|
||||
json_object_entry proto_entry = proto->u.object.values [i];
|
||||
|
||||
for (j = 0; j < object->u.object.length; ++ j)
|
||||
{
|
||||
json_object_entry entry = object->u.object.values [j];
|
||||
|
||||
if (entry.name_length != proto_entry.name_length)
|
||||
continue;
|
||||
|
||||
if (memcmp (entry.name, proto_entry.name, entry.name_length) != 0)
|
||||
continue;
|
||||
|
||||
object->u.object.values [j] = object->u.object.values [out_index];
|
||||
object->u.object.values [out_index] = entry;
|
||||
|
||||
++ out_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json_value * json_object_merge (json_value * objectA, json_value * objectB)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
assert (objectA->type == json_object);
|
||||
assert (objectB->type == json_object);
|
||||
assert (objectA != objectB);
|
||||
|
||||
if (!builderize (objectA) || !builderize (objectB))
|
||||
return NULL;
|
||||
|
||||
if (objectB->u.object.length <=
|
||||
((json_builder_value *) objectA)->additional_length_allocated)
|
||||
{
|
||||
((json_builder_value *) objectA)->additional_length_allocated
|
||||
-= objectB->u.object.length;
|
||||
}
|
||||
else
|
||||
{
|
||||
json_object_entry * values_new;
|
||||
|
||||
unsigned int alloc =
|
||||
objectA->u.object.length
|
||||
+ ((json_builder_value *) objectA)->additional_length_allocated
|
||||
+ objectB->u.object.length;
|
||||
|
||||
if (! (values_new = (json_object_entry *)
|
||||
realloc (objectA->u.object.values, sizeof (json_object_entry) * alloc)))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
objectA->u.object.values = values_new;
|
||||
}
|
||||
|
||||
for (i = 0; i < objectB->u.object.length; ++ i)
|
||||
{
|
||||
json_object_entry * entry = &objectA->u.object.values[objectA->u.object.length + i];
|
||||
|
||||
*entry = objectB->u.object.values[i];
|
||||
entry->value->parent = objectA;
|
||||
}
|
||||
|
||||
objectA->u.object.length += objectB->u.object.length;
|
||||
|
||||
free (objectB->u.object.values);
|
||||
free (objectB);
|
||||
|
||||
return objectA;
|
||||
}
|
||||
|
||||
static size_t measure_string (unsigned int length,
|
||||
const json_char * str)
|
||||
{
|
||||
unsigned int i;
|
||||
size_t measured_length = 0;
|
||||
|
||||
for(i = 0; i < length; ++ i)
|
||||
{
|
||||
json_char c = str [i];
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
case '\\':
|
||||
case '\b':
|
||||
case '\f':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
|
||||
measured_length += 2;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
++ measured_length;
|
||||
break;
|
||||
};
|
||||
};
|
||||
|
||||
return measured_length;
|
||||
}
|
||||
|
||||
#define PRINT_ESCAPED(c) do { \
|
||||
*buf ++ = '\\'; \
|
||||
*buf ++ = (c); \
|
||||
} while(0); \
|
||||
|
||||
static size_t serialize_string (json_char * buf,
|
||||
unsigned int length,
|
||||
const json_char * str)
|
||||
{
|
||||
json_char * orig_buf = buf;
|
||||
unsigned int i;
|
||||
|
||||
for(i = 0; i < length; ++ i)
|
||||
{
|
||||
json_char c = str [i];
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '"': PRINT_ESCAPED ('\"'); continue;
|
||||
case '\\': PRINT_ESCAPED ('\\'); continue;
|
||||
case '\b': PRINT_ESCAPED ('b'); continue;
|
||||
case '\f': PRINT_ESCAPED ('f'); continue;
|
||||
case '\n': PRINT_ESCAPED ('n'); continue;
|
||||
case '\r': PRINT_ESCAPED ('r'); continue;
|
||||
case '\t': PRINT_ESCAPED ('t'); continue;
|
||||
|
||||
default:
|
||||
|
||||
*buf ++ = c;
|
||||
break;
|
||||
};
|
||||
};
|
||||
|
||||
return buf - orig_buf;
|
||||
}
|
||||
|
||||
size_t json_measure (json_value * value)
|
||||
{
|
||||
return json_measure_ex (value, default_opts);
|
||||
}
|
||||
|
||||
#define MEASURE_NEWLINE() do { \
|
||||
++ newlines; \
|
||||
indents += depth; \
|
||||
} while(0); \
|
||||
|
||||
size_t json_measure_ex (json_value * value, json_serialize_opts opts)
|
||||
{
|
||||
size_t total = 1; /* null terminator */
|
||||
size_t newlines = 0;
|
||||
size_t depth = 0;
|
||||
size_t indents = 0;
|
||||
int flags;
|
||||
int bracket_size, comma_size, colon_size;
|
||||
|
||||
flags = get_serialize_flags (opts);
|
||||
|
||||
/* to reduce branching
|
||||
*/
|
||||
bracket_size = flags & f_spaces_around_brackets ? 2 : 1;
|
||||
comma_size = flags & f_spaces_after_commas ? 2 : 1;
|
||||
colon_size = flags & f_spaces_after_colons ? 2 : 1;
|
||||
|
||||
while (value)
|
||||
{
|
||||
json_int_t integer;
|
||||
json_object_entry * entry;
|
||||
|
||||
switch (value->type)
|
||||
{
|
||||
case json_array:
|
||||
|
||||
if (((json_builder_value *) value)->length_iterated == 0)
|
||||
{
|
||||
if (value->u.array.length == 0)
|
||||
{
|
||||
total += 2; /* `[]` */
|
||||
break;
|
||||
}
|
||||
|
||||
total += bracket_size; /* `[` */
|
||||
|
||||
++ depth;
|
||||
MEASURE_NEWLINE(); /* \n after [ */
|
||||
}
|
||||
|
||||
if (((json_builder_value *) value)->length_iterated == value->u.array.length)
|
||||
{
|
||||
-- depth;
|
||||
MEASURE_NEWLINE();
|
||||
total += bracket_size; /* `]` */
|
||||
|
||||
((json_builder_value *) value)->length_iterated = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (((json_builder_value *) value)->length_iterated > 0)
|
||||
{
|
||||
total += comma_size; /* `, ` */
|
||||
|
||||
MEASURE_NEWLINE();
|
||||
}
|
||||
|
||||
((json_builder_value *) value)->length_iterated++;
|
||||
value = value->u.array.values [((json_builder_value *) value)->length_iterated - 1];
|
||||
continue;
|
||||
|
||||
case json_object:
|
||||
|
||||
if (((json_builder_value *) value)->length_iterated == 0)
|
||||
{
|
||||
if (value->u.object.length == 0)
|
||||
{
|
||||
total += 2; /* `{}` */
|
||||
break;
|
||||
}
|
||||
|
||||
total += bracket_size; /* `{` */
|
||||
|
||||
++ depth;
|
||||
MEASURE_NEWLINE(); /* \n after { */
|
||||
}
|
||||
|
||||
if (((json_builder_value *) value)->length_iterated == value->u.object.length)
|
||||
{
|
||||
-- depth;
|
||||
MEASURE_NEWLINE();
|
||||
total += bracket_size; /* `}` */
|
||||
|
||||
((json_builder_value *) value)->length_iterated = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (((json_builder_value *) value)->length_iterated > 0)
|
||||
{
|
||||
total += comma_size; /* `, ` */
|
||||
MEASURE_NEWLINE();
|
||||
}
|
||||
|
||||
entry = value->u.object.values + (((json_builder_value *) value)->length_iterated ++);
|
||||
|
||||
total += 2 + colon_size; /* `"": ` */
|
||||
total += measure_string (entry->name_length, entry->name);
|
||||
|
||||
value = entry->value;
|
||||
continue;
|
||||
|
||||
case json_string:
|
||||
|
||||
total += 2; /* `""` */
|
||||
total += measure_string (value->u.string.length, value->u.string.ptr);
|
||||
break;
|
||||
|
||||
case json_integer:
|
||||
|
||||
integer = value->u.integer;
|
||||
|
||||
if (integer < 0)
|
||||
{
|
||||
total += 1; /* `-` */
|
||||
integer = - integer;
|
||||
}
|
||||
|
||||
++ total; /* first digit */
|
||||
|
||||
while (integer >= 10)
|
||||
{
|
||||
++ total; /* another digit */
|
||||
integer /= 10;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case json_double:
|
||||
|
||||
total += snprintf (NULL, 0, "%g", value->u.dbl);
|
||||
|
||||
if (value->u.dbl - floor (value->u.dbl) < 0.001)
|
||||
total += 2;
|
||||
|
||||
break;
|
||||
|
||||
case json_boolean:
|
||||
|
||||
total += value->u.boolean ?
|
||||
4: /* `true` */
|
||||
5; /* `false` */
|
||||
|
||||
break;
|
||||
|
||||
case json_null:
|
||||
|
||||
total += 4; /* `null` */
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
value = value->parent;
|
||||
}
|
||||
|
||||
if (opts.mode == json_serialize_mode_multiline)
|
||||
{
|
||||
total += newlines * (((opts.opts & json_serialize_opt_CRLF) ? 2 : 1) + opts.indent_size);
|
||||
total += indents * opts.indent_size;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
void json_serialize (json_char * buf, json_value * value)
|
||||
{
|
||||
json_serialize_ex (buf, value, default_opts);
|
||||
}
|
||||
|
||||
#define PRINT_NEWLINE() do { \
|
||||
if (opts.mode == json_serialize_mode_multiline) { \
|
||||
if (opts.opts & json_serialize_opt_CRLF) \
|
||||
*buf ++ = '\r'; \
|
||||
*buf ++ = '\n'; \
|
||||
for(i = 0; i < indent; ++ i) \
|
||||
*buf ++ = indent_char; \
|
||||
} \
|
||||
} while(0); \
|
||||
|
||||
#define PRINT_OPENING_BRACKET(c) do { \
|
||||
*buf ++ = (c); \
|
||||
if (flags & f_spaces_around_brackets) \
|
||||
*buf ++ = ' '; \
|
||||
} while(0); \
|
||||
|
||||
#define PRINT_CLOSING_BRACKET(c) do { \
|
||||
if (flags & f_spaces_around_brackets) \
|
||||
*buf ++ = ' '; \
|
||||
*buf ++ = (c); \
|
||||
} while(0); \
|
||||
|
||||
void json_serialize_ex (json_char * buf, json_value * value, json_serialize_opts opts)
|
||||
{
|
||||
json_int_t integer, orig_integer;
|
||||
json_object_entry * entry;
|
||||
json_char * ptr, * dot;
|
||||
int indent = 0;
|
||||
char indent_char;
|
||||
int i;
|
||||
int flags;
|
||||
|
||||
flags = get_serialize_flags (opts);
|
||||
|
||||
indent_char = flags & f_tabs ? '\t' : ' ';
|
||||
|
||||
while (value)
|
||||
{
|
||||
switch (value->type)
|
||||
{
|
||||
case json_array:
|
||||
|
||||
if (((json_builder_value *) value)->length_iterated == 0)
|
||||
{
|
||||
if (value->u.array.length == 0)
|
||||
{
|
||||
*buf ++ = '[';
|
||||
*buf ++ = ']';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
PRINT_OPENING_BRACKET ('[');
|
||||
|
||||
indent += opts.indent_size;
|
||||
PRINT_NEWLINE();
|
||||
}
|
||||
|
||||
if (((json_builder_value *) value)->length_iterated == value->u.array.length)
|
||||
{
|
||||
indent -= opts.indent_size;
|
||||
PRINT_NEWLINE();
|
||||
PRINT_CLOSING_BRACKET (']');
|
||||
|
||||
((json_builder_value *) value)->length_iterated = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (((json_builder_value *) value)->length_iterated > 0)
|
||||
{
|
||||
*buf ++ = ',';
|
||||
|
||||
if (flags & f_spaces_after_commas)
|
||||
*buf ++ = ' ';
|
||||
|
||||
PRINT_NEWLINE();
|
||||
}
|
||||
|
||||
((json_builder_value *) value)->length_iterated++;
|
||||
value = value->u.array.values [((json_builder_value *) value)->length_iterated - 1];
|
||||
continue;
|
||||
|
||||
case json_object:
|
||||
|
||||
if (((json_builder_value *) value)->length_iterated == 0)
|
||||
{
|
||||
if (value->u.object.length == 0)
|
||||
{
|
||||
*buf ++ = '{';
|
||||
*buf ++ = '}';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
PRINT_OPENING_BRACKET ('{');
|
||||
|
||||
indent += opts.indent_size;
|
||||
PRINT_NEWLINE();
|
||||
}
|
||||
|
||||
if (((json_builder_value *) value)->length_iterated == value->u.object.length)
|
||||
{
|
||||
indent -= opts.indent_size;
|
||||
PRINT_NEWLINE();
|
||||
PRINT_CLOSING_BRACKET ('}');
|
||||
|
||||
((json_builder_value *) value)->length_iterated = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (((json_builder_value *) value)->length_iterated > 0)
|
||||
{
|
||||
*buf ++ = ',';
|
||||
|
||||
if (flags & f_spaces_after_commas)
|
||||
*buf ++ = ' ';
|
||||
|
||||
PRINT_NEWLINE();
|
||||
}
|
||||
|
||||
entry = value->u.object.values + (((json_builder_value *) value)->length_iterated ++);
|
||||
|
||||
*buf ++ = '\"';
|
||||
buf += serialize_string (buf, entry->name_length, entry->name);
|
||||
*buf ++ = '\"';
|
||||
*buf ++ = ':';
|
||||
|
||||
if (flags & f_spaces_after_colons)
|
||||
*buf ++ = ' ';
|
||||
|
||||
value = entry->value;
|
||||
continue;
|
||||
|
||||
case json_string:
|
||||
|
||||
*buf ++ = '\"';
|
||||
buf += serialize_string (buf, value->u.string.length, value->u.string.ptr);
|
||||
*buf ++ = '\"';
|
||||
break;
|
||||
|
||||
case json_integer:
|
||||
|
||||
integer = value->u.integer;
|
||||
|
||||
if (integer < 0)
|
||||
{
|
||||
*buf ++ = '-';
|
||||
integer = - integer;
|
||||
}
|
||||
|
||||
orig_integer = integer;
|
||||
|
||||
++ buf;
|
||||
|
||||
while (integer >= 10)
|
||||
{
|
||||
++ buf;
|
||||
integer /= 10;
|
||||
}
|
||||
|
||||
integer = orig_integer;
|
||||
ptr = buf;
|
||||
|
||||
do
|
||||
{
|
||||
*-- ptr = "0123456789"[integer % 10];
|
||||
|
||||
} while ((integer /= 10) > 0);
|
||||
|
||||
break;
|
||||
|
||||
case json_double:
|
||||
|
||||
ptr = buf;
|
||||
|
||||
buf += sprintf (buf, "%g", value->u.dbl);
|
||||
|
||||
if ((dot = strchr (ptr, ',')))
|
||||
{
|
||||
*dot = '.';
|
||||
}
|
||||
else if (!strchr (ptr, '.'))
|
||||
{
|
||||
*buf ++ = '.';
|
||||
*buf ++ = '0';
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case json_boolean:
|
||||
|
||||
if (value->u.boolean)
|
||||
{
|
||||
memcpy (buf, "true", 4);
|
||||
buf += 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy (buf, "false", 5);
|
||||
buf += 5;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case json_null:
|
||||
|
||||
memcpy (buf, "null", 4);
|
||||
buf += 4;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
value = value->parent;
|
||||
}
|
||||
|
||||
*buf = 0;
|
||||
}
|
||||
|
||||
void json_builder_free (json_value * value)
|
||||
{
|
||||
json_value * cur_value;
|
||||
|
||||
if (!value)
|
||||
return;
|
||||
|
||||
value->parent = 0;
|
||||
|
||||
while (value)
|
||||
{
|
||||
switch (value->type)
|
||||
{
|
||||
case json_array:
|
||||
|
||||
if (!value->u.array.length)
|
||||
{
|
||||
free (value->u.array.values);
|
||||
break;
|
||||
}
|
||||
|
||||
value = value->u.array.values [-- value->u.array.length];
|
||||
continue;
|
||||
|
||||
case json_object:
|
||||
|
||||
if (!value->u.object.length)
|
||||
{
|
||||
free (value->u.object.values);
|
||||
break;
|
||||
}
|
||||
|
||||
-- value->u.object.length;
|
||||
|
||||
if (((json_builder_value *) value)->is_builder_value)
|
||||
{
|
||||
/* Names are allocated separately for builder values. In parser
|
||||
* values, they are part of the same allocation as the values array
|
||||
* itself.
|
||||
*/
|
||||
free (value->u.object.values [value->u.object.length].name);
|
||||
}
|
||||
|
||||
value = value->u.object.values [value->u.object.length].value;
|
||||
continue;
|
||||
|
||||
case json_string:
|
||||
|
||||
free (value->u.string.ptr);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
cur_value = value;
|
||||
value = value->parent;
|
||||
free (cur_value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
|
||||
/* vim: set et ts=3 sw=3 sts=3 ft=c:
|
||||
*
|
||||
* Copyright (C) 2014 James McLaughlin. All rights reserved.
|
||||
* https://github.com/udp/json-builder
|
||||
*
|
||||
* 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 _JSON_BUILDER_H
|
||||
#define _JSON_BUILDER_H
|
||||
|
||||
/* Requires json.h from json-parser
|
||||
* https://github.com/udp/json-parser
|
||||
*/
|
||||
#include <json.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/* IMPORTANT NOTE: If you want to use json-builder functions with values
|
||||
* allocated by json-parser as part of the parsing process, you must pass
|
||||
* json_builder_extra as the value_extra setting in json_settings when
|
||||
* parsing. Otherwise there will not be room for the extra state and
|
||||
* json-builder WILL invoke undefined behaviour.
|
||||
*
|
||||
* Also note that unlike json-parser, json-builder does not currently support
|
||||
* custom allocators (for no particular reason other than that it doesn't have
|
||||
* any settings or global state.)
|
||||
*/
|
||||
extern const size_t json_builder_extra;
|
||||
|
||||
|
||||
/*** Arrays
|
||||
***
|
||||
* Note that all of these length arguments are just a hint to allow for
|
||||
* pre-allocation - passing 0 is fine.
|
||||
*/
|
||||
json_value * json_array_new (size_t length);
|
||||
json_value * json_array_push (json_value * array, json_value *);
|
||||
|
||||
|
||||
/*** Objects
|
||||
***/
|
||||
json_value * json_object_new (size_t length);
|
||||
|
||||
json_value * json_object_push (json_value * object,
|
||||
const json_char * name,
|
||||
json_value *);
|
||||
|
||||
/* Same as json_object_push, but doesn't call strlen() for you.
|
||||
*/
|
||||
json_value * json_object_push_length (json_value * object,
|
||||
unsigned int name_length, const json_char * name,
|
||||
json_value *);
|
||||
|
||||
/* Same as json_object_push_length, but doesn't copy the name buffer before
|
||||
* storing it in the value. Use this micro-optimisation at your own risk.
|
||||
*/
|
||||
json_value * json_object_push_nocopy (json_value * object,
|
||||
unsigned int name_length, json_char * name,
|
||||
json_value *);
|
||||
|
||||
/* Merges all entries from objectB into objectA and destroys objectB.
|
||||
*/
|
||||
json_value * json_object_merge (json_value * objectA, json_value * objectB);
|
||||
|
||||
/* Sort the entries of an object based on the order in a prototype object.
|
||||
* Helpful when reading JSON and writing it again to preserve user order.
|
||||
*/
|
||||
void json_object_sort (json_value * object, json_value * proto);
|
||||
|
||||
|
||||
|
||||
/*** Strings
|
||||
***/
|
||||
json_value * json_string_new (const json_char *);
|
||||
json_value * json_string_new_length (unsigned int length, const json_char *);
|
||||
json_value * json_string_new_nocopy (unsigned int length, json_char *);
|
||||
|
||||
|
||||
/*** Everything else
|
||||
***/
|
||||
json_value * json_integer_new (json_int_t);
|
||||
json_value * json_double_new (double);
|
||||
json_value * json_boolean_new (int);
|
||||
json_value * json_null_new ();
|
||||
|
||||
|
||||
/*** Serializing
|
||||
***/
|
||||
#define json_serialize_mode_multiline 0
|
||||
#define json_serialize_mode_single_line 1
|
||||
#define json_serialize_mode_packed 2
|
||||
|
||||
#define json_serialize_opt_CRLF (1 << 1)
|
||||
#define json_serialize_opt_pack_brackets (1 << 2)
|
||||
#define json_serialize_opt_no_space_after_comma (1 << 3)
|
||||
#define json_serialize_opt_no_space_after_colon (1 << 4)
|
||||
#define json_serialize_opt_use_tabs (1 << 5)
|
||||
|
||||
typedef struct json_serialize_opts
|
||||
{
|
||||
int mode;
|
||||
int opts;
|
||||
int indent_size;
|
||||
|
||||
} json_serialize_opts;
|
||||
|
||||
|
||||
/* Returns a length in characters that is at least large enough to hold the
|
||||
* value in its serialized form, including a null terminator.
|
||||
*/
|
||||
size_t json_measure (json_value *);
|
||||
size_t json_measure_ex (json_value *, json_serialize_opts);
|
||||
|
||||
|
||||
/* Serializes a JSON value into the buffer given (which must already be
|
||||
* allocated with a length of at least json_measure(value, opts))
|
||||
*/
|
||||
void json_serialize (json_char * buf, json_value *);
|
||||
void json_serialize_ex (json_char * buf, json_value *, json_serialize_opts);
|
||||
|
||||
|
||||
/*** Cleaning up
|
||||
***/
|
||||
void json_builder_free (json_value *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,283 @@
|
|||
|
||||
/* vim: set et ts=3 sw=3 sts=3 ft=c:
|
||||
*
|
||||
* Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved.
|
||||
* https://github.com/udp/json-parser
|
||||
*
|
||||
* 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 _JSON_H
|
||||
#define _JSON_H
|
||||
|
||||
#ifndef json_char
|
||||
#define json_char char
|
||||
#endif
|
||||
|
||||
#ifndef json_int_t
|
||||
#ifndef _MSC_VER
|
||||
#include <inttypes.h>
|
||||
#define json_int_t int64_t
|
||||
#else
|
||||
#define json_int_t __int64
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <string.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned long max_memory;
|
||||
int settings;
|
||||
|
||||
/* Custom allocator support (leave null to use malloc/free)
|
||||
*/
|
||||
|
||||
void * (* mem_alloc) (size_t, int zero, void * user_data);
|
||||
void (* mem_free) (void *, void * user_data);
|
||||
|
||||
void * user_data; /* will be passed to mem_alloc and mem_free */
|
||||
|
||||
size_t value_extra; /* how much extra space to allocate for values? */
|
||||
|
||||
} json_settings;
|
||||
|
||||
#define json_enable_comments 0x01
|
||||
|
||||
typedef enum
|
||||
{
|
||||
json_none,
|
||||
json_object,
|
||||
json_array,
|
||||
json_integer,
|
||||
json_double,
|
||||
json_string,
|
||||
json_boolean,
|
||||
json_null
|
||||
|
||||
} json_type;
|
||||
|
||||
extern const struct _json_value json_value_none;
|
||||
|
||||
typedef struct _json_object_entry
|
||||
{
|
||||
json_char * name;
|
||||
unsigned int name_length;
|
||||
|
||||
struct _json_value * value;
|
||||
|
||||
} json_object_entry;
|
||||
|
||||
typedef struct _json_value
|
||||
{
|
||||
struct _json_value * parent;
|
||||
|
||||
json_type type;
|
||||
|
||||
union
|
||||
{
|
||||
int boolean;
|
||||
json_int_t integer;
|
||||
double dbl;
|
||||
|
||||
struct
|
||||
{
|
||||
unsigned int length;
|
||||
json_char * ptr; /* null terminated */
|
||||
|
||||
} string;
|
||||
|
||||
struct
|
||||
{
|
||||
unsigned int length;
|
||||
|
||||
json_object_entry * values;
|
||||
|
||||
#if defined(__cplusplus) && __cplusplus >= 201103L
|
||||
decltype(values) begin () const
|
||||
{ return values;
|
||||
}
|
||||
decltype(values) end () const
|
||||
{ return values + length;
|
||||
}
|
||||
#endif
|
||||
|
||||
} object;
|
||||
|
||||
struct
|
||||
{
|
||||
unsigned int length;
|
||||
struct _json_value ** values;
|
||||
|
||||
#if defined(__cplusplus) && __cplusplus >= 201103L
|
||||
decltype(values) begin () const
|
||||
{ return values;
|
||||
}
|
||||
decltype(values) end () const
|
||||
{ return values + length;
|
||||
}
|
||||
#endif
|
||||
|
||||
} array;
|
||||
|
||||
} u;
|
||||
|
||||
union
|
||||
{
|
||||
struct _json_value * next_alloc;
|
||||
void * object_mem;
|
||||
|
||||
} _reserved;
|
||||
|
||||
#ifdef JSON_TRACK_SOURCE
|
||||
|
||||
/* Location of the value in the source JSON
|
||||
*/
|
||||
unsigned int line, col;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/* Some C++ operator sugar */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
public:
|
||||
|
||||
inline _json_value ()
|
||||
{ memset (this, 0, sizeof (_json_value));
|
||||
}
|
||||
|
||||
inline const struct _json_value &operator [] (int index) const
|
||||
{
|
||||
if (type != json_array || index < 0
|
||||
|| ((unsigned int) index) >= u.array.length)
|
||||
{
|
||||
return json_value_none;
|
||||
}
|
||||
|
||||
return *u.array.values [index];
|
||||
}
|
||||
|
||||
inline const struct _json_value &operator [] (const char * index) const
|
||||
{
|
||||
if (type != json_object)
|
||||
return json_value_none;
|
||||
|
||||
for (unsigned int i = 0; i < u.object.length; ++ i)
|
||||
if (!strcmp (u.object.values [i].name, index))
|
||||
return *u.object.values [i].value;
|
||||
|
||||
return json_value_none;
|
||||
}
|
||||
|
||||
inline operator const char * () const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case json_string:
|
||||
return u.string.ptr;
|
||||
|
||||
default:
|
||||
return "";
|
||||
};
|
||||
}
|
||||
|
||||
inline operator json_int_t () const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case json_integer:
|
||||
return u.integer;
|
||||
|
||||
case json_double:
|
||||
return (json_int_t) u.dbl;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
inline operator bool () const
|
||||
{
|
||||
if (type != json_boolean)
|
||||
return false;
|
||||
|
||||
return u.boolean != 0;
|
||||
}
|
||||
|
||||
inline operator double () const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case json_integer:
|
||||
return (double) u.integer;
|
||||
|
||||
case json_double:
|
||||
return u.dbl;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} json_value;
|
||||
|
||||
json_value * json_parse (const json_char * json,
|
||||
size_t length);
|
||||
|
||||
#define json_error_max 128
|
||||
json_value * json_parse_ex (json_settings * settings,
|
||||
const json_char * json,
|
||||
size_t length,
|
||||
char * error);
|
||||
|
||||
void json_value_free (json_value *);
|
||||
|
||||
|
||||
/* Not usually necessary, unless you used a custom mem_alloc and now want to
|
||||
* use a custom mem_free.
|
||||
*/
|
||||
void json_value_free_ex (json_settings * settings,
|
||||
json_value *);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
Loading…
Reference in New Issue