diff --git a/.github/workflows/cmake-lx.yml b/.github/workflows/cmake-lx.yml index a1d83127..78592d84 100644 --- a/.github/workflows/cmake-lx.yml +++ b/.github/workflows/cmake-lx.yml @@ -23,7 +23,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y libjansson-dev yasm libopus-dev + sudo apt-get install -y yasm libopus-dev - name: Create build environment # Some projects don't allow in-source building, so create a separate build directory diff --git a/.github/workflows/cmake-mac.yml b/.github/workflows/cmake-mac.yml index 54763d1b..89609338 100644 --- a/.github/workflows/cmake-mac.yml +++ b/.github/workflows/cmake-mac.yml @@ -19,7 +19,7 @@ jobs: fetch-depth: 0 - name: Install dependencies - run: brew install autoconf automake cmake pkgconfig ffmpeg jansson libao libvorbis mpg123 + run: brew install autoconf automake cmake pkgconfig ffmpeg libao libvorbis mpg123 - name: Cache celt uses: actions/cache@v4 diff --git a/.github/workflows/cmake-wasm.yml b/.github/workflows/cmake-wasm.yml index 338b3ad0..ece4ca44 100644 --- a/.github/workflows/cmake-wasm.yml +++ b/.github/workflows/cmake-wasm.yml @@ -49,13 +49,6 @@ jobs: ${{runner.workspace}}/embuild/dependencies/ffmpeg/bin/usr/local/lib key: wasm-ffmpeg-${{ hashFiles('cmake/dependencies/ffmpeg.cmake') }} - - name: Cache jansson - uses: actions/cache@v4 - with: - path: | - ${{runner.workspace}}/embuild/dependencies/jansson/src/.libs - key: wasm-jansson-${{ hashFiles('cmake/dependencies/jansson.cmake') }} - - name: Cache atrac9 uses: actions/cache@v4 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index f34fe127..4686d2ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,9 +63,6 @@ option(USE_G719 "Use libg719_decode for support ITU-T G.719" ON) option(USE_ATRAC9 "Use LibAtrac9 for support of ATRAC9" ON) option(USE_CELT "Use libcelt for support of FSB CELT versions 0.6.1 and 0.11.0" ON) option(USE_SPEEX "Use libspeex for support of SPEEX" ON) -if(NOT WIN32) - option(USE_JANSSON "Use jansson for JSON dumping" ON) -endif() if(NOT WIN32) set(MPEG_PATH CACHE PATH "Path to mpg123") @@ -78,7 +75,6 @@ if(NOT WIN32) set(CELT_0061_PATH CACHE PATH "Path to CELT version 0.6.1") set(CELT_0110_PATH CACHE PATH "Path to CELT version 0.11.0") set(LIBAO_PATH CACHE PATH "Path to libao") - set(JANSSON_PATH CACHE PATH "Path to jansson") endif() # Build choices @@ -158,7 +154,6 @@ include(dependencies/g719) include(dependencies/atrac9) include(dependencies/celt) include(dependencies/speex) -include(dependencies/jansson) include(dependencies/audacious) if(USE_G7221) @@ -273,7 +268,6 @@ message(STATUS " FSB CELT: ${USE_CELT} ${CELT_SOURCE}") message(STATUS " SPEEX: ${USE_SPEEX} ${SPEEX_SOURCE}") if(NOT WIN32) message(STATUS " LIBAO: ${BUILD_V123} ${LIBAO_SOURCE}") - message(STATUS " Jansson: ${USE_JANSSON} ${JANSSON_SOURCE}") endif() message(STATUS "") diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index 9dd34697..3f629bc0 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -10,12 +10,6 @@ set_target_properties(vgmstream_cli PROPERTIES # Link to the vgmstream library target_link_libraries(vgmstream_cli libvgmstream) -# Link to Jansson, if we have it -if (USE_JANSSON) - target_compile_definitions(vgmstream_cli PRIVATE HAVE_JSON) - target_include_directories(vgmstream_cli PRIVATE ${JANSSON_INCLUDE_DIRS}) - target_link_libraries(vgmstream_cli jansson) -endif() setup_target(vgmstream_cli TRUE) diff --git a/cli/Makefile b/cli/Makefile index fa6d773f..94957071 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -39,6 +39,8 @@ CFLAGS += $(LIBS_CFLAGS) LDFLAGS += $(LIBS_LDFLAGS) TARGET_EXT_LIBS += $(LIBS_TARGET_EXT_LIBS) +CLI_SRCS = vgmstream_cli.c vgmstream_cli_utils.c wav_utils.c +V123_SRCS = vgmstream123.c wav_utils.c export CFLAGS LDFLAGS @@ -47,11 +49,11 @@ export CFLAGS LDFLAGS ### targets vgmstream_cli: libvgmstream.a $(TARGET_EXT_LIBS) - $(CC) $(CFLAGS) vgmstream_cli.c vgmstream_cli_utils.c wav_utils.c $(LDFLAGS) -o $(OUTPUT_CLI) + $(CC) $(CFLAGS) $(CLI_SRCS) $(LDFLAGS) -o $(OUTPUT_CLI) $(STRIP) $(OUTPUT_CLI) vgmstream123: libvgmstream.a $(TARGET_EXT_LIBS) - $(CC) $(CFLAGS) $(LIBAO_INC) vgmstream123.c wav_utils.c $(LDFLAGS) $(LIBAO_LIB) -o $(OUTPUT_123) + $(CC) $(CFLAGS) $(LIBAO_INC) $(V123_SRCS) $(LDFLAGS) $(LIBAO_LIB) -o $(OUTPUT_123) $(STRIP) $(OUTPUT_123) api_example: libvgmstream.a $(TARGET_EXT_LIBS) diff --git a/cli/api_example.c b/cli/api_example.c index deae0ca4..d7687082 100644 --- a/cli/api_example.c +++ b/cli/api_example.c @@ -1,10 +1,11 @@ -#include "../src/api.h" +#include "../src/libvgmstream.h" #if LIBVGMSTREAM_ENABLE #include #include #include #include "../src/base/api_internal.h" +#include "../src/streamfile.h" static void usage(const char* progname) { @@ -29,14 +30,14 @@ static libvgmstream_streamfile_t* get_streamfile(const char* filename) { } static int api_example(const char* infile) { -VGM_STEP(); + VGM_STEP(); int err; FILE* outfile = NULL; - bool fill_test; - int pcm16_samples; - int pcm16_bytes; - short* pcm16 = NULL; + bool fill_test = true; + int fill_pcm16_samples; + int fill_pcm16_bytes; + short* fill_pcm16 = NULL; // main init @@ -49,6 +50,7 @@ VGM_STEP(); //.loop_count = 1.0, //.fade_time = 10.0, .ignore_loop = true, + .force_pcm16 = fill_test, }; libvgmstream_setup(lib, &cfg); @@ -57,7 +59,7 @@ VGM_STEP(); libvgmstream_options_t opt = { .libsf = get_streamfile(infile) }; - err = libvgmstream_open(lib, &opt); + err = libvgmstream_open_song(lib, &opt); // external SF is not needed after _open libvgmstream_streamfile_close(opt.libsf); @@ -94,33 +96,28 @@ VGM_STEP(); printf("channels: %i\n", lib->format->channels); printf("sample rate: %i\n", lib->format->sample_rate); printf("codec: %s\n", lib->format->codec_name); - printf("samples: %i\n", (int32_t)lib->format->sample_count); + printf("samples: %i\n", (int32_t)lib->format->stream_samples); printf("\n"); + printf("- decoding: %i\n" , (int32_t)lib->format->play_samples); - - - fill_test = true; - pcm16_samples = 512; - pcm16_bytes = pcm16_samples * sizeof(short) * lib->format->channels; - pcm16 = malloc(pcm16_bytes); - if (!pcm16) goto fail; + fill_pcm16_samples = 512; + fill_pcm16_bytes = fill_pcm16_samples * sizeof(short) * lib->format->channels; + fill_pcm16 = malloc(fill_pcm16_bytes); + if (!fill_pcm16) goto fail; // play file and do something with decoded samples - while (true) { + while (!lib->decoder->done) { //int pos; void* buf; int buf_bytes = 0; - if (lib->decoder->done) - break; - // get current samples if (fill_test) { - err = libvgmstream_fill(lib, pcm16, pcm16_samples); + err = libvgmstream_fill(lib, fill_pcm16, fill_pcm16_samples); if (err < 0) goto fail; - buf = pcm16; + buf = fill_pcm16; buf_bytes = err * sizeof(short) * lib->format->channels; } else { @@ -140,12 +137,12 @@ VGM_STEP(); printf("\n"); // close current streamfile before opening new ones, optional - //libvgmstream_close(lib); + //libvgmstream_close_song(lib); // process done libvgmstream_free(lib); fclose(outfile); - free(pcm16); + free(fill_pcm16); printf("done\n"); return EXIT_SUCCESS; @@ -153,7 +150,7 @@ fail: // process failed libvgmstream_free(lib); fclose(outfile); - free(pcm16); + free(fill_pcm16); printf("failed!\n"); return EXIT_FAILURE; diff --git a/cli/vgmstream123.c b/cli/vgmstream123.c index 4d58a8ef..e53fcb6c 100644 --- a/cli/vgmstream123.c +++ b/cli/vgmstream123.c @@ -868,7 +868,7 @@ again_opts: verbose = 1; break; default: - VGM_LOG("vgmstream123: unknown opt %x", opt); + printf("vgmstream123: unknown opt %x", opt); goto done; } } diff --git a/cli/vgmstream_cli.c b/cli/vgmstream_cli.c index 60ce7163..6615356d 100644 --- a/cli/vgmstream_cli.c +++ b/cli/vgmstream_cli.c @@ -3,6 +3,7 @@ */ #define POSIXLY_CORRECT +#include #include #ifdef WIN32 @@ -65,10 +66,8 @@ static void print_usage(const char* progname, bool is_help) { " -c: loop forever (continuously) to stdout\n" " -L: append a smpl chunk and create a looping wav\n" //" -w: allow .wav in original sample format rather than downmixing to PCM16\n" -#ifdef HAVE_JSON " -V: print version info and supported extensions as JSON\n" " -I: print requested file info as JSON\n" -#endif " -h: print all commands\n" , progname); @@ -109,11 +108,7 @@ static bool parse_config(cli_config_t* cfg, int argc, char** argv) { optind = 1; /* reset getopt's ugly globals (needed in wasm that may call same main() multiple times) */ /* read config */ - while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:tTk:K:hOvD:S:B:" -#ifdef HAVE_JSON - "VI" -#endif - )) != -1) { + while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:tTk:K:hOvD:S:B:VI")) != -1) { switch (opt) { case 'o': cfg->outfilename = optarg; @@ -228,14 +223,12 @@ static bool parse_config(cli_config_t* cfg, int argc, char** argv) { case 'h': print_usage(argv[0], true); goto fail; -#ifdef HAVE_JSON case 'V': print_json_version(VGMSTREAM_VERSION); goto fail; case 'I': cfg->print_metajson = true; break; -#endif case '?': fprintf(stderr, "missing argument or unknown option -%c\n", optopt); goto fail; @@ -458,10 +451,48 @@ static bool is_valid_extension(cli_config_t* cfg) { return vgmstream_ctx_is_valid(cfg->infilename, &vcfg); } +static VGMSTREAM* open_vgmstream(cli_config_t* cfg) { + STREAMFILE* sf = NULL; + VGMSTREAM* vgmstream = NULL; + + sf = open_stdio_streamfile(cfg->infilename); + if (!sf) { + fprintf(stderr, "file %s not found\n", cfg->infilename); + return NULL; + } + + sf->stream_index = cfg->subsong_index; + vgmstream = init_vgmstream_from_STREAMFILE(sf); + + if (!vgmstream) { + fprintf(stderr, "failed opening %s\n", cfg->infilename); + goto fail; + } + + /* modify the VGMSTREAM if needed (before printing file info) */ + apply_config(vgmstream, cfg); + + /* enable after config but before outbuf */ + if (cfg->downmix_channels) { + vgmstream_mixing_autodownmix(vgmstream, cfg->downmix_channels); + } + else if (cfg->stereo_track > 0) { + vgmstream_mixing_stereo_only(vgmstream, cfg->stereo_track - 1); + } + vgmstream_mixing_enable(vgmstream, cfg->sample_buffer_size, NULL, NULL); + + + close_streamfile(sf); + return vgmstream; +fail: + close_streamfile(sf); + close_vgmstream(vgmstream); + return NULL; +} static bool convert_file(cli_config_t* cfg) { VGMSTREAM* vgmstream = NULL; - char outfilename_temp[PATH_LIMIT]; + char outfilename_temp[CLI_PATH_LIMIT]; int32_t len_samples; @@ -470,40 +501,14 @@ static bool convert_file(cli_config_t* cfg) { return false; /* open streamfile and pass subsong */ - { - STREAMFILE* sf = open_stdio_streamfile(cfg->infilename); - if (!sf) { - fprintf(stderr, "file %s not found\n", cfg->infilename); - goto fail; - } + vgmstream = open_vgmstream(cfg); + if (!vgmstream) goto fail; - sf->stream_index = cfg->subsong_index; - vgmstream = init_vgmstream_from_STREAMFILE(sf); - close_streamfile(sf); - - if (!vgmstream) { - fprintf(stderr, "failed opening %s\n", cfg->infilename); - goto fail; - } - - /* force load total subsongs if signalled */ - if (cfg->subsong_end == -1) { - cfg->subsong_end = vgmstream->num_streams; - close_vgmstream(vgmstream); - return true; - } - - /* modify the VGMSTREAM if needed (before printing file info) */ - apply_config(vgmstream, cfg); - - /* enable after config but before outbuf */ - if (cfg->downmix_channels) { - vgmstream_mixing_autodownmix(vgmstream, cfg->downmix_channels); - } - else if (cfg->stereo_track > 0) { - vgmstream_mixing_stereo_only(vgmstream, cfg->stereo_track - 1); - } - vgmstream_mixing_enable(vgmstream, cfg->sample_buffer_size, NULL, NULL); + /* force load total subsongs if signalled */ + if (cfg->subsong_end == -1) { + cfg->subsong_end = vgmstream->num_streams; + close_vgmstream(vgmstream); + return true; } @@ -567,18 +572,14 @@ static bool convert_file(cli_config_t* cfg) { /* prints */ -#ifdef HAVE_JSON if (!cfg->print_metajson) { -#endif print_info(vgmstream, cfg); print_tags(cfg); print_title(vgmstream, cfg); -#ifdef HAVE_JSON } else { print_json_info(vgmstream, cfg, VGMSTREAM_VERSION); } -#endif /* prints done */ if (cfg->print_metaonly) { @@ -593,7 +594,7 @@ static bool convert_file(cli_config_t* cfg) { /* try again with reset (for testing, simulates a seek to 0 after changing internal state) * (could simulate by seeking to last sample then to 0, too) */ if (cfg->test_reset) { - char outfilename_reset[PATH_LIMIT]; + char outfilename_reset[CLI_PATH_LIMIT]; snprintf(outfilename_reset, sizeof(outfilename_reset), "%s.reset.wav", cfg->outfilename); cfg->outfilename = outfilename_reset; @@ -653,6 +654,9 @@ int main(int argc, char** argv) { bool res, ok; + vgmstream_set_log_stdout(VGM_LOG_LEVEL_ALL); + + /* read args */ res = parse_config(&cfg, argc, argv); if (!res) goto fail; @@ -660,8 +664,6 @@ int main(int argc, char** argv) { res = validate_config(&cfg); if (!res) goto fail; - vgmstream_set_log_stdout(VGM_LOG_LEVEL_ALL); - #ifdef WIN32 /* make stdout output work with windows */ if (cfg.play_sdtout) { diff --git a/cli/vgmstream_cli.h b/cli/vgmstream_cli.h index 661b6649..1b197306 100644 --- a/cli/vgmstream_cli.h +++ b/cli/vgmstream_cli.h @@ -1,9 +1,10 @@ -#ifndef _VGMSTREAM_CLI_H -#define _VGMSTREAM_CLI_H +#ifndef _VGMSTREAM_CLI_H_ +#define _VGMSTREAM_CLI_H_ #include "../src/api.h" #include "../src/vgmstream.h" +#define CLI_PATH_LIMIT 4096 typedef struct { char** infilenames; @@ -42,9 +43,7 @@ typedef struct { bool print_oggenc; bool print_batchvar; bool print_title; -#ifdef HAVE_JSON bool print_metajson; -#endif const char* tag_filename; // debug stuff @@ -68,10 +67,8 @@ void print_info(VGMSTREAM* vgmstream, cli_config_t* cfg); void print_tags(cli_config_t* cfg); void print_title(VGMSTREAM* vgmstream, cli_config_t* cfg); -#ifdef HAVE_JSON void print_json_version(const char* vgmstream_version); void print_json_info(VGMSTREAM* vgm, cli_config_t* cfg, const char* vgmstream_version); -#endif #endif diff --git a/cli/vgmstream_cli.vcxproj b/cli/vgmstream_cli.vcxproj index 791ff799..4c5af087 100644 --- a/cli/vgmstream_cli.vcxproj +++ b/cli/vgmstream_cli.vcxproj @@ -105,6 +105,7 @@ + diff --git a/cli/vgmstream_cli.vcxproj.filters b/cli/vgmstream_cli.vcxproj.filters index b70423af..be4cc317 100644 --- a/cli/vgmstream_cli.vcxproj.filters +++ b/cli/vgmstream_cli.vcxproj.filters @@ -22,6 +22,9 @@ Header Files + + Header Files + Header Files diff --git a/cli/vgmstream_cli_utils.c b/cli/vgmstream_cli_utils.c index 0ff13197..7f1954ab 100644 --- a/cli/vgmstream_cli_utils.c +++ b/cli/vgmstream_cli_utils.c @@ -1,12 +1,12 @@ -#include "vgmstream_cli.h" +#include +#include +#include +#include "vgmstream_cli.h" #include "../src/api.h" #include "../src/vgmstream.h" -//todo use <>? -#ifdef HAVE_JSON -#include "jansson/jansson.h" -#endif +#include "vjson.h" static void clean_filename(char* dst, int clean_paths) { @@ -23,9 +23,9 @@ static void clean_filename(char* dst, int clean_paths) { * ("?" was chosen since it's not a valid Windows filename char and hopefully nobody uses it on Linux) */ void replace_filename(char* dst, size_t dstsize, cli_config_t* cfg, VGMSTREAM* vgmstream) { int subsong; - char stream_name[PATH_LIMIT]; - char buf[PATH_LIMIT]; - char tmp[PATH_LIMIT]; + char stream_name[CLI_PATH_LIMIT]; + char buf[CLI_PATH_LIMIT]; + char tmp[CLI_PATH_LIMIT]; /* file has a "%" > temp replace for sprintf */ @@ -100,29 +100,34 @@ void replace_filename(char* dst, size_t dstsize, cli_config_t* cfg, VGMSTREAM* v void print_info(VGMSTREAM* vgmstream, cli_config_t* cfg) { int channels = vgmstream->channels; + int64_t num_samples = vgmstream->num_samples; + bool loop_flag = vgmstream->loop_flag; + int64_t loop_start = vgmstream->loop_start_sample; + int64_t loop_end = vgmstream->loop_start_sample; + if (!cfg->play_sdtout) { if (cfg->print_adxencd) { printf("adxencd"); if (!cfg->print_metaonly) - printf(" \"%s\"",cfg->outfilename); - if (vgmstream->loop_flag) - printf(" -lps%d -lpe%d", vgmstream->loop_start_sample, vgmstream->loop_end_sample); + printf(" \"%s\"", cfg->outfilename); + if (loop_flag) + printf(" -lps%"PRId64" -lpe%"PRId64, loop_start, loop_end); printf("\n"); } else if (cfg->print_oggenc) { printf("oggenc"); if (!cfg->print_metaonly) printf(" \"%s\"", cfg->outfilename); - if (vgmstream->loop_flag) - printf(" -c LOOPSTART=%d -c LOOPLENGTH=%d", vgmstream->loop_start_sample, vgmstream->loop_end_sample-vgmstream->loop_start_sample); + if (loop_flag) + printf(" -c LOOPSTART=%"PRId64" -c LOOPLENGTH=%"PRId64, loop_start, loop_end - loop_start); printf("\n"); } else if (cfg->print_batchvar) { if (!cfg->print_metaonly) printf("set fname=\"%s\"\n", cfg->outfilename); - printf("set tsamp=%d\nset chan=%d\n", vgmstream->num_samples, channels); - if (vgmstream->loop_flag) - printf("set lstart=%d\nset lend=%d\nset loop=1\n", vgmstream->loop_start_sample, vgmstream->loop_end_sample); + printf("set tsamp=%"PRId64"\nset chan=%d\n", num_samples, channels); + if (loop_flag) + printf("set lstart=%"PRId64"\nset lend=%"PRId64"\nset loop=1\n", loop_start, loop_end); else printf("set loop=0\n"); } @@ -183,114 +188,106 @@ void print_title(VGMSTREAM* vgmstream, cli_config_t* cfg) { printf("title: %s\n", title); } -#ifdef HAVE_JSON void print_json_version(const char* vgmstream_version) { - size_t extension_list_len; - size_t common_extension_list_len; + size_t extension_list_len = 0; const char** extension_list; - const char** common_extension_list; - extension_list = vgmstream_get_formats(&extension_list_len); - common_extension_list = vgmstream_get_common_formats(&common_extension_list_len); - json_t* ext_list = json_array(); - json_t* cext_list = json_array(); + vjson_t j = {0}; - for (size_t i = 0; i < extension_list_len; ++i) { - json_t* ext = json_string(extension_list[i]); - json_array_append(ext_list, ext); - } + char buf[0x4000]; // exts need ~0x1400 + vjson_init(&j, buf, sizeof(buf)); - for (size_t i = 0; i < common_extension_list_len; ++i) { - json_t* cext = json_string(common_extension_list[i]); - json_array_append(cext_list, cext); - } + vjson_obj_open(&j); + vjson_keystr(&j, "version", vgmstream_version); - json_t* version_string = json_string(vgmstream_version); + vjson_key(&j, "extensions"); + vjson_obj_open(&j); - json_t* final_object = json_object(); - json_object_set(final_object, "version", version_string); - json_decref(version_string); + vjson_key(&j, "vgm"); + vjson_arr_open(&j); + extension_list = vgmstream_get_formats(&extension_list_len); + for (int i = 0; i < extension_list_len; i++) { + vjson_str(&j, extension_list[i]); + } + vjson_arr_close(&j); - json_object_set(final_object, "extensions", - json_pack("{soso}", - "vgm", ext_list, - "common", cext_list)); + vjson_key(&j, "common"); + vjson_arr_open(&j); + extension_list = vgmstream_get_common_formats(&extension_list_len); + for (int i = 0; i < extension_list_len; i++) { + vjson_str(&j, extension_list[i]); + } + vjson_arr_close(&j); - json_dumpf(final_object, stdout, JSON_COMPACT); + vjson_obj_close(&j); + vjson_obj_close(&j); + + printf("%s\n", buf); } void print_json_info(VGMSTREAM* vgm, cli_config_t* cfg, const char* vgmstream_version) { - json_t* version_string = json_string(vgmstream_version); + char buf[0x1000]; // probably fine with ~0x400 + vjson_t j = {0}; + vjson_init(&j, buf, sizeof(buf)); + vgmstream_info info; describe_vgmstream_info(vgm, &info); + + vjson_obj_open(&j); + vjson_keystr(&j, "version", vgmstream_version); + vjson_keyint(&j, "sampleRate", info.sample_rate); + vjson_keyint(&j, "channels", info.channels); - json_t* mixing_info = NULL; + vjson_key(&j, "mixingInfo"); + if (info.mixing_info.input_channels > 0) { + vjson_obj_open(&j); + vjson_keyint(&j, "inputChannels", info.mixing_info.input_channels); + vjson_keyint(&j, "outputChannels", info.mixing_info.output_channels); + vjson_obj_close(&j); + } + else { + vjson_null(&j); + } - // The JSON pack format string is defined here: https://jansson.readthedocs.io/en/latest/apiref.html#building-values + vjson_keyintnull(&j, "channelLayout", info.channel_layout); - if (info.mixing_info.input_channels > 0) { - mixing_info = json_pack("{sisi}", - "inputChannels", info.mixing_info.input_channels, - "outputChannels", info.mixing_info.output_channels); - } + vjson_key(&j, "loopingInfo"); + if (info.loop_info.end > info.loop_info.start) { + vjson_obj_open(&j); + vjson_keyint(&j, "start", info.loop_info.start); + vjson_keyint(&j, "end", info.loop_info.start); + vjson_obj_close(&j); + } + else { + vjson_null(&j); + } - json_t* loop_info = NULL; + vjson_key(&j, "interleaveInfo"); + if (info.interleave_info.last_block > info.interleave_info.first_block) { + vjson_obj_open(&j); + vjson_keyint(&j, "firstBlock", info.interleave_info.last_block); + vjson_keyint(&j, "lastBlock", info.interleave_info.first_block); + vjson_obj_close(&j); + } + else { + vjson_null(&j); + } - if (info.loop_info.end > info.loop_info.start) { - loop_info = json_pack("{sisi}", - "start", info.loop_info.start, - "end", info.loop_info.end); - } + vjson_keyint(&j, "numberOfSamples", info.num_samples); + vjson_keystr(&j, "encoding", info.encoding); + vjson_keystr(&j, "layout", info.layout); + vjson_keyintnull(&j, "frameSize", info.frame_size); + vjson_keystr(&j, "metadataSource", info.metadata); + vjson_keyint(&j, "bitrate", info.bitrate); - json_t* interleave_info = NULL; + vjson_key(&j, "streamInfo"); + vjson_obj_open(&j); + vjson_keyint(&j, "index", info.stream_info.current); + vjson_keystr(&j, "name", info.stream_info.name); + vjson_keyint(&j, "total", info.stream_info.total); + vjson_obj_close(&j); - if (info.interleave_info.last_block > info.interleave_info.first_block) { - interleave_info = json_pack("{sisi}", - "firstBlock", info.interleave_info.first_block, - "lastBlock", info.interleave_info.last_block - ); - } + vjson_obj_close(&j); - json_t* stream_info = json_pack("{sisssi}", - "index", info.stream_info.current, - "name", info.stream_info.name, - "total", info.stream_info.total - ); - - if (info.stream_info.name[0] == '\0') { - json_object_set(stream_info, "name", json_null()); - } - - json_t* final_object = json_pack( - "{sssisiso?siso?so?sisssssisssiso?}", - "version", version_string, - "sampleRate", info.sample_rate, - "channels", info.channels, - "mixingInfo", mixing_info, - "channelLayout", info.channel_layout, - "loopingInfo", loop_info, - "interleaveInfo", interleave_info, - "numberOfSamples", info.num_samples, - "encoding", info.encoding, - "layout", info.layout, - "frameSize", info.frame_size, - "metadataSource", info.metadata, - "bitrate", info.bitrate, - "streamInfo", stream_info - ); - - if (info.frame_size == 0) { - json_object_set(final_object, "frameSize", json_null()); - } - - if (info.channel_layout == 0) { - json_object_set(final_object, "channelLayout", json_null()); - } - - json_dumpf(final_object, stdout, JSON_COMPACT); - - json_decref(final_object); - - printf("\n"); + printf("%s\n", buf); } -#endif diff --git a/cli/vjson.h b/cli/vjson.h new file mode 100644 index 00000000..98cf8843 --- /dev/null +++ b/cli/vjson.h @@ -0,0 +1,154 @@ +#ifndef _VJSON_H_ +#define _VJSON_H_ + +/* What is this crap, you may wonder? For probably non-existant use cases Jansson was added to write JSON info, + * but external libs are a pain to maintain. For now this glorified string joiner replaces it. + * + * On incorrect usage or small buf it'll create invalid JSON because who cares, try-parse-catch as usual. + */ + +#include +#include +#include +#include + +#define VJSON_STACK_MAX 16 +typedef struct { + char* buf; + int buf_len; + char* bufp; + int buf_left; + + bool stack[VJSON_STACK_MAX]; + int stack_pos; + bool is_last_key; +} vjson_t; + +static void vjson_init(vjson_t* j, char* buf, int buf_len) { + j->buf = buf; + j->buf_len = buf_len; + j->bufp = buf; + j->buf_left = buf_len; +} + +static void vjson_raw(vjson_t* j, const char* str) { + if (!str) + str = "null"; + int done = snprintf(j->bufp, j->buf_left, "%s", str); + + j->bufp += done; + j->buf_left -= done; +} + +static void vjson_comma_(vjson_t* j) { + if (j->stack[j->stack_pos] && !j->is_last_key) { + vjson_raw(j, ","); + } + j->stack[j->stack_pos] = true; + j->is_last_key = false; +} + +static void vjson_open_(vjson_t* j, const char* str) { + vjson_comma_(j); + vjson_raw(j, str); + + if (j->stack_pos + 1 >= VJSON_STACK_MAX) + return; + j->stack_pos++; + j->stack[j->stack_pos] = false; +} + +static void vjson_close_(vjson_t* j, const char* str) { + //vjson_comma_(j); + vjson_raw(j, str); + + if (j->stack_pos - 1 <= 0) + return; + j->stack[j->stack_pos] = false; + j->stack_pos--; +} + +static void vjson_arr_open(vjson_t* j) { + vjson_open_(j, "["); +} + +static void vjson_arr_close(vjson_t* j) { + vjson_close_(j, "]"); +} + +static void vjson_obj_open(vjson_t* j) { + vjson_open_(j, "{"); +} + +static void vjson_obj_close(vjson_t* j) { + vjson_close_(j, "}"); +} + +static void vjson_key(vjson_t* j, const char* key) { + vjson_comma_(j); + vjson_raw(j, "\""); + vjson_raw(j, key); + vjson_raw(j, "\":"); + j->is_last_key = true; +} + +static void vjson_str(vjson_t* j, const char* str) { + vjson_comma_(j); + if (!str || str[0] == '\0') { + vjson_raw(j, NULL); + } + else { + vjson_raw(j, "\""); + vjson_raw(j, str); + vjson_raw(j, "\""); + } +} + +static void vjson_int(vjson_t* j, int64_t num) { + vjson_comma_(j); + + char tmp[32] = {0}; + snprintf(tmp, sizeof(tmp), "%" PRId64, num); + vjson_raw(j, tmp); +} + +#if 0 +static void vjson_dbl(vjson_t* j, double num) { + vjson_comma_(j); + + char tmp[32] = {0}; + snprintf(tmp, sizeof(tmp), "%f", num); + vjson_raw(j, tmp); +} +#endif + +void vjson_null(vjson_t* j){ + vjson_comma_(j); + vjson_raw(j, "null"); +} + +static void vjson_intnull(vjson_t* j, int64_t num) { + if (num == 0) { + vjson_str(j, NULL); + } + else { + vjson_int(j, num); + } +} + +static void vjson_keystr(vjson_t* j, const char* key, const char* val) { + vjson_key(j, key); + vjson_str(j, val); +} + +static void vjson_keyint(vjson_t* j, const char* key, int64_t val) { + vjson_key(j, key); + vjson_int(j, val); +} + +static void vjson_keyintnull(vjson_t* j, const char* key, int64_t val) { + vjson_key(j, key); + vjson_intnull(j, val); +} + +#endif diff --git a/cmake/dependencies/jansson.cmake b/cmake/dependencies/jansson.cmake deleted file mode 100644 index a3c833d1..00000000 --- a/cmake/dependencies/jansson.cmake +++ /dev/null @@ -1,57 +0,0 @@ -if(NOT WIN32 AND USE_JANSSON) - if(NOT JANSSON_PATH AND NOT EMSCRIPTEN) - find_package(PkgConfig REQUIRED) - pkg_check_modules(JANSSON jansson>=2.3) - - if(JANSSON_FOUND) - set(JANSSON_SOURCE "(system)") - set(JANSSON_PKG libjansson) - link_directories(${JANSSON_LIBRARY_DIRS}) - endif() - endif() - if(JANSSON_PATH OR EMSCRIPTEN OR NOT JANSSON_FOUND) - FetchDependency(JANSSON - DIR jansson - GIT_REPOSITORY https://github.com/akheron/jansson - GIT_TAG v2.14 - ) - - if(JANSSON_PATH) - set(JANSSON_LINK_PATH ${JANSSON_BIN}/src/.libs/libjansson.a) - set(JANSSON_INCLUDE_DIRS ${JANSSON_PATH}/src) - - if(NOT EXISTS ${JANSSON_PATH}/configure) - add_custom_target(JANSSON_AUTORECONF - COMMAND autoreconf -iv - BYPRODUCTS ${JANSSON_PATH}/configure - WORKING_DIRECTORY ${JANSSON_PATH} - ) - endif() - - file(MAKE_DIRECTORY ${JANSSON_BIN}) - add_custom_target(JANSSON_CONFIGURE - COMMAND "${JANSSON_PATH}/configure" --enable-static --disable-shared CC="${CMAKE_C_COMPILER}" AR="${CMAKE_AR}" RANLIB="${CMAKE_RANLIB}" - DEPENDS ${JANSSON_PATH}/configure - BYPRODUCTS ${JANSSON_BIN}/Makefile - WORKING_DIRECTORY ${JANSSON_BIN} - ) - add_custom_target(JANSSON_MAKE - COMMAND make - DEPENDS ${JANSSON_BIN}/Makefile - BYPRODUCTS ${JANSSON_LINK_PATH} ${JANSSON_BIN} - WORKING_DIRECTORY ${JANSSON_BIN} - ) - - add_library(jansson STATIC IMPORTED) - if(NOT EXISTS ${JANSSON_LINK_PATH}) - add_dependencies(jansson JANSSON_MAKE) - endif() - set_target_properties(jansson PROPERTIES - IMPORTED_LOCATION ${JANSSON_LINK_PATH} - ) - endif() - endif() -endif() -if(NOT USE_JANSSON) - unset(JANSSON_SOURCE) -endif() diff --git a/configure.ac b/configure.ac index 94583272..9f20a8bc 100644 --- a/configure.ac +++ b/configure.ac @@ -62,11 +62,6 @@ PKG_CHECK_MODULES(AO, [ao >= 1.1.0], have_libao=yes, [AC_MSG_WARN([Cannot find libao - will not build vgmstream123])]) AM_CONDITIONAL(HAVE_LIBAO, test "$have_libao" = yes) -have_jansson=no -PKG_CHECK_MODULES(JANSSON, [jansson >= 2.3], have_jansson=yes, - [AC_MSG_WARN([Cannot find jansson - will disable JSON dumping in CLI])]) -AM_CONDITIONAL(HAVE_JANSSON, test "$have_jansson" = yes) - if test "_$GCC" = _yes then CFLAGS="$CFLAGS -Wall -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-but-set-variable" diff --git a/doc/CMAKE.md b/doc/CMAKE.md index 65dfb09b..270f840b 100644 --- a/doc/CMAKE.md +++ b/doc/CMAKE.md @@ -84,8 +84,6 @@ sudo apt-get install -y libmpg123-dev libvorbis-dev libspeex-dev sudo apt-get install -y libavformat-dev libavcodec-dev libavutil-dev libswresample-dev # for vgmstream123 and audacious sudo apt-get install -y libao-dev audacious-dev -# for JSON dumping -sudo apt-get install -y libjansson-dev # for static builds sudo apt-get install -y yasm libopus-dev # actual cmake @@ -138,8 +136,6 @@ The following option is currently only available for **Windows**: The following option is only available for **\*nix-based OSes**: -- **USE_JANSSON**: Chooses if you wish to use libjansson for support of JSON dumping capabilities. The default is `ON`. - #### Build Options All of these options are of type BOOL and can be set to either `ON` or `OFF`. Example usage: `cmake .. -DBUILD_CLI=ON` diff --git a/ext_includes/jansson/jansson.h b/ext_includes/jansson/jansson.h deleted file mode 100644 index fbc7381b..00000000 --- a/ext_includes/jansson/jansson.h +++ /dev/null @@ -1,386 +0,0 @@ -/* - * Copyright (c) 2009-2016 Petri Lehtinen - * - * Jansson is free software; you can redistribute it and/or modify - * it under the terms of the MIT license. See LICENSE for details. - */ - -#ifndef JANSSON_H -#define JANSSON_H - -#include -#include -#include /* for size_t */ - -#include "jansson_config.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* version */ - -#define JANSSON_MAJOR_VERSION 2 -#define JANSSON_MINOR_VERSION 13 -#define JANSSON_MICRO_VERSION 1 - -/* Micro version is omitted if it's 0 */ -#define JANSSON_VERSION "2.13.1" - -/* Version as a 3-byte hex number, e.g. 0x010201 == 1.2.1. Use this - for numeric comparisons, e.g. #if JANSSON_VERSION_HEX >= ... */ -#define JANSSON_VERSION_HEX \ - ((JANSSON_MAJOR_VERSION << 16) | (JANSSON_MINOR_VERSION << 8) | \ - (JANSSON_MICRO_VERSION << 0)) - -/* If __atomic or __sync builtins are available the library is thread - * safe for all read-only functions plus reference counting. */ -#if JSON_HAVE_ATOMIC_BUILTINS || JSON_HAVE_SYNC_BUILTINS -#define JANSSON_THREAD_SAFE_REFCOUNT 1 -#endif - -#if defined(__GNUC__) || defined(__clang__) -#define JANSSON_ATTRS(x) __attribute__(x) -#else -#define JANSSON_ATTRS(x) -#endif - -/* types */ - -typedef enum { - JSON_OBJECT, - JSON_ARRAY, - JSON_STRING, - JSON_INTEGER, - JSON_REAL, - JSON_TRUE, - JSON_FALSE, - JSON_NULL -} json_type; - -typedef struct json_t { - json_type type; - volatile size_t refcount; -} json_t; - -#ifndef JANSSON_USING_CMAKE /* disabled if using cmake */ -#if JSON_INTEGER_IS_LONG_LONG -#ifdef _WIN32 -#define JSON_INTEGER_FORMAT "I64d" -#else -#define JSON_INTEGER_FORMAT "lld" -#endif -typedef long long json_int_t; -#else -#define JSON_INTEGER_FORMAT "ld" -typedef long json_int_t; -#endif /* JSON_INTEGER_IS_LONG_LONG */ -#endif - -#define json_typeof(json) ((json)->type) -#define json_is_object(json) ((json) && json_typeof(json) == JSON_OBJECT) -#define json_is_array(json) ((json) && json_typeof(json) == JSON_ARRAY) -#define json_is_string(json) ((json) && json_typeof(json) == JSON_STRING) -#define json_is_integer(json) ((json) && json_typeof(json) == JSON_INTEGER) -#define json_is_real(json) ((json) && json_typeof(json) == JSON_REAL) -#define json_is_number(json) (json_is_integer(json) || json_is_real(json)) -#define json_is_true(json) ((json) && json_typeof(json) == JSON_TRUE) -#define json_is_false(json) ((json) && json_typeof(json) == JSON_FALSE) -#define json_boolean_value json_is_true -#define json_is_boolean(json) (json_is_true(json) || json_is_false(json)) -#define json_is_null(json) ((json) && json_typeof(json) == JSON_NULL) - -/* construction, destruction, reference counting */ - -json_t *json_object(void); -json_t *json_array(void); -json_t *json_string(const char *value); -json_t *json_stringn(const char *value, size_t len); -json_t *json_string_nocheck(const char *value); -json_t *json_stringn_nocheck(const char *value, size_t len); -json_t *json_integer(json_int_t value); -json_t *json_real(double value); -json_t *json_true(void); -json_t *json_false(void); -#define json_boolean(val) ((val) ? json_true() : json_false()) -json_t *json_null(void); - -/* do not call JSON_INTERNAL_INCREF or JSON_INTERNAL_DECREF directly */ -#if JSON_HAVE_ATOMIC_BUILTINS -#define JSON_INTERNAL_INCREF(json) \ - __atomic_add_fetch(&json->refcount, 1, __ATOMIC_ACQUIRE) -#define JSON_INTERNAL_DECREF(json) \ - __atomic_sub_fetch(&json->refcount, 1, __ATOMIC_RELEASE) -#elif JSON_HAVE_SYNC_BUILTINS -#define JSON_INTERNAL_INCREF(json) __sync_add_and_fetch(&json->refcount, 1) -#define JSON_INTERNAL_DECREF(json) __sync_sub_and_fetch(&json->refcount, 1) -#else -#define JSON_INTERNAL_INCREF(json) (++json->refcount) -#define JSON_INTERNAL_DECREF(json) (--json->refcount) -#endif - -static JSON_INLINE json_t *json_incref(json_t *json) { - if (json && json->refcount != (size_t)-1) - JSON_INTERNAL_INCREF(json); - return json; -} - -/* do not call json_delete directly */ -void json_delete(json_t *json); - -static JSON_INLINE void json_decref(json_t *json) { - if (json && json->refcount != (size_t)-1 && JSON_INTERNAL_DECREF(json) == 0) - json_delete(json); -} - -#if defined(__GNUC__) || defined(__clang__) -static JSON_INLINE void json_decrefp(json_t **json) { - if (json) { - json_decref(*json); - *json = NULL; - } -} - -#define json_auto_t json_t __attribute__((cleanup(json_decrefp))) -#endif - -/* error reporting */ - -#define JSON_ERROR_TEXT_LENGTH 160 -#define JSON_ERROR_SOURCE_LENGTH 80 - -typedef struct json_error_t { - int line; - int column; - int position; - char source[JSON_ERROR_SOURCE_LENGTH]; - char text[JSON_ERROR_TEXT_LENGTH]; -} json_error_t; - -enum json_error_code { - json_error_unknown, - json_error_out_of_memory, - json_error_stack_overflow, - json_error_cannot_open_file, - json_error_invalid_argument, - json_error_invalid_utf8, - json_error_premature_end_of_input, - json_error_end_of_input_expected, - json_error_invalid_syntax, - json_error_invalid_format, - json_error_wrong_type, - json_error_null_character, - json_error_null_value, - json_error_null_byte_in_key, - json_error_duplicate_key, - json_error_numeric_overflow, - json_error_item_not_found, - json_error_index_out_of_range -}; - -static JSON_INLINE enum json_error_code json_error_code(const json_error_t *e) { - return (enum json_error_code)e->text[JSON_ERROR_TEXT_LENGTH - 1]; -} - -/* getters, setters, manipulation */ - -void json_object_seed(size_t seed); -size_t json_object_size(const json_t *object); -json_t *json_object_get(const json_t *object, const char *key) - JANSSON_ATTRS((warn_unused_result)); -int json_object_set_new(json_t *object, const char *key, json_t *value); -int json_object_set_new_nocheck(json_t *object, const char *key, json_t *value); -int json_object_del(json_t *object, const char *key); -int json_object_clear(json_t *object); -int json_object_update(json_t *object, json_t *other); -int json_object_update_existing(json_t *object, json_t *other); -int json_object_update_missing(json_t *object, json_t *other); -int json_object_update_recursive(json_t *object, json_t *other); -void *json_object_iter(json_t *object); -void *json_object_iter_at(json_t *object, const char *key); -void *json_object_key_to_iter(const char *key); -void *json_object_iter_next(json_t *object, void *iter); -const char *json_object_iter_key(void *iter); -json_t *json_object_iter_value(void *iter); -int json_object_iter_set_new(json_t *object, void *iter, json_t *value); - -#define json_object_foreach(object, key, value) \ - for (key = json_object_iter_key(json_object_iter(object)); \ - key && (value = json_object_iter_value(json_object_key_to_iter(key))); \ - key = json_object_iter_key( \ - json_object_iter_next(object, json_object_key_to_iter(key)))) - -#define json_object_foreach_safe(object, n, key, value) \ - for (key = json_object_iter_key(json_object_iter(object)), \ - n = json_object_iter_next(object, json_object_key_to_iter(key)); \ - key && (value = json_object_iter_value(json_object_key_to_iter(key))); \ - key = json_object_iter_key(n), \ - n = json_object_iter_next(object, json_object_key_to_iter(key))) - -#define json_array_foreach(array, index, value) \ - for (index = 0; \ - index < json_array_size(array) && (value = json_array_get(array, index)); \ - index++) - -static JSON_INLINE int json_object_set(json_t *object, const char *key, json_t *value) { - return json_object_set_new(object, key, json_incref(value)); -} - -static JSON_INLINE int json_object_set_nocheck(json_t *object, const char *key, - json_t *value) { - return json_object_set_new_nocheck(object, key, json_incref(value)); -} - -static JSON_INLINE int json_object_iter_set(json_t *object, void *iter, json_t *value) { - return json_object_iter_set_new(object, iter, json_incref(value)); -} - -static JSON_INLINE int json_object_update_new(json_t *object, json_t *other) { - int ret = json_object_update(object, other); - json_decref(other); - return ret; -} - -static JSON_INLINE int json_object_update_existing_new(json_t *object, json_t *other) { - int ret = json_object_update_existing(object, other); - json_decref(other); - return ret; -} - -static JSON_INLINE int json_object_update_missing_new(json_t *object, json_t *other) { - int ret = json_object_update_missing(object, other); - json_decref(other); - return ret; -} - -size_t json_array_size(const json_t *array); -json_t *json_array_get(const json_t *array, size_t index) - JANSSON_ATTRS((warn_unused_result)); -int json_array_set_new(json_t *array, size_t index, json_t *value); -int json_array_append_new(json_t *array, json_t *value); -int json_array_insert_new(json_t *array, size_t index, json_t *value); -int json_array_remove(json_t *array, size_t index); -int json_array_clear(json_t *array); -int json_array_extend(json_t *array, json_t *other); - -static JSON_INLINE int json_array_set(json_t *array, size_t ind, json_t *value) { - return json_array_set_new(array, ind, json_incref(value)); -} - -static JSON_INLINE int json_array_append(json_t *array, json_t *value) { - return json_array_append_new(array, json_incref(value)); -} - -static JSON_INLINE int json_array_insert(json_t *array, size_t ind, json_t *value) { - return json_array_insert_new(array, ind, json_incref(value)); -} - -const char *json_string_value(const json_t *string); -size_t json_string_length(const json_t *string); -json_int_t json_integer_value(const json_t *integer); -double json_real_value(const json_t *real); -double json_number_value(const json_t *json); - -int json_string_set(json_t *string, const char *value); -int json_string_setn(json_t *string, const char *value, size_t len); -int json_string_set_nocheck(json_t *string, const char *value); -int json_string_setn_nocheck(json_t *string, const char *value, size_t len); -int json_integer_set(json_t *integer, json_int_t value); -int json_real_set(json_t *real, double value); - -/* pack, unpack */ - -json_t *json_pack(const char *fmt, ...) JANSSON_ATTRS((warn_unused_result)); -json_t *json_pack_ex(json_error_t *error, size_t flags, const char *fmt, ...) - JANSSON_ATTRS((warn_unused_result)); -json_t *json_vpack_ex(json_error_t *error, size_t flags, const char *fmt, va_list ap) - JANSSON_ATTRS((warn_unused_result)); - -#define JSON_VALIDATE_ONLY 0x1 -#define JSON_STRICT 0x2 - -int json_unpack(json_t *root, const char *fmt, ...); -int json_unpack_ex(json_t *root, json_error_t *error, size_t flags, const char *fmt, ...); -int json_vunpack_ex(json_t *root, json_error_t *error, size_t flags, const char *fmt, - va_list ap); - -/* sprintf */ - -json_t *json_sprintf(const char *fmt, ...) - JANSSON_ATTRS((warn_unused_result, format(printf, 1, 2))); -json_t *json_vsprintf(const char *fmt, va_list ap) - JANSSON_ATTRS((warn_unused_result, format(printf, 1, 0))); - -/* equality */ - -int json_equal(const json_t *value1, const json_t *value2); - -/* copying */ - -json_t *json_copy(json_t *value) JANSSON_ATTRS((warn_unused_result)); -json_t *json_deep_copy(const json_t *value) JANSSON_ATTRS((warn_unused_result)); - -/* decoding */ - -#define JSON_REJECT_DUPLICATES 0x1 -#define JSON_DISABLE_EOF_CHECK 0x2 -#define JSON_DECODE_ANY 0x4 -#define JSON_DECODE_INT_AS_REAL 0x8 -#define JSON_ALLOW_NUL 0x10 - -typedef size_t (*json_load_callback_t)(void *buffer, size_t buflen, void *data); - -json_t *json_loads(const char *input, size_t flags, json_error_t *error) - JANSSON_ATTRS((warn_unused_result)); -json_t *json_loadb(const char *buffer, size_t buflen, size_t flags, json_error_t *error) - JANSSON_ATTRS((warn_unused_result)); -json_t *json_loadf(FILE *input, size_t flags, json_error_t *error) - JANSSON_ATTRS((warn_unused_result)); -json_t *json_loadfd(int input, size_t flags, json_error_t *error) - JANSSON_ATTRS((warn_unused_result)); -json_t *json_load_file(const char *path, size_t flags, json_error_t *error) - JANSSON_ATTRS((warn_unused_result)); -json_t *json_load_callback(json_load_callback_t callback, void *data, size_t flags, - json_error_t *error) JANSSON_ATTRS((warn_unused_result)); - -/* encoding */ - -#define JSON_MAX_INDENT 0x1F -#define JSON_INDENT(n) ((n)&JSON_MAX_INDENT) -#define JSON_COMPACT 0x20 -#define JSON_ENSURE_ASCII 0x40 -#define JSON_SORT_KEYS 0x80 -#define JSON_PRESERVE_ORDER 0x100 -#define JSON_ENCODE_ANY 0x200 -#define JSON_ESCAPE_SLASH 0x400 -#define JSON_REAL_PRECISION(n) (((n)&0x1F) << 11) -#define JSON_EMBED 0x10000 - -typedef int (*json_dump_callback_t)(const char *buffer, size_t size, void *data); - -char *json_dumps(const json_t *json, size_t flags) JANSSON_ATTRS((warn_unused_result)); -size_t json_dumpb(const json_t *json, char *buffer, size_t size, size_t flags); -int json_dumpf(const json_t *json, FILE *output, size_t flags); -int json_dumpfd(const json_t *json, int output, size_t flags); -int json_dump_file(const json_t *json, const char *path, size_t flags); -int json_dump_callback(const json_t *json, json_dump_callback_t callback, void *data, - size_t flags); - -/* custom memory allocation */ - -typedef void *(*json_malloc_t)(size_t); -typedef void (*json_free_t)(void *); - -void json_set_alloc_funcs(json_malloc_t malloc_fn, json_free_t free_fn); -void json_get_alloc_funcs(json_malloc_t *malloc_fn, json_free_t *free_fn); - -/* runtime version checking */ - -const char *jansson_version_str(void); -int jansson_version_cmp(int major, int minor, int micro); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/ext_includes/jansson/jansson_config.h b/ext_includes/jansson/jansson_config.h deleted file mode 100644 index d3053808..00000000 --- a/ext_includes/jansson/jansson_config.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2010-2016 Petri Lehtinen - * - * Jansson is free software; you can redistribute it and/or modify - * it under the terms of the MIT license. See LICENSE for details. - * - * - * This file specifies a part of the site-specific configuration for - * Jansson, namely those things that affect the public API in - * jansson.h. - * - * The CMake system will generate the jansson_config.h file and - * copy it to the build and install directories. - */ - -#ifndef JANSSON_CONFIG_H -#define JANSSON_CONFIG_H - -/* Define this so that we can disable scattered automake configuration in source files */ -#ifndef JANSSON_USING_CMAKE -#define JANSSON_USING_CMAKE -#endif - - -/* If your compiler supports the inline keyword in C, JSON_INLINE is - defined to `inline', otherwise empty. In C++, the inline is always - supported. */ -#if defined(_MSC_VER) && (_MSC_VER < 1800) && !defined(__cplusplus) -#define JSON_INLINE __inline -#else -#define JSON_INLINE inline -#endif - - -#define json_int_t long -#define json_strtoint strtol -#define JSON_INTEGER_FORMAT "ld" - - -/* If locale.h and localeconv() are available, define to 1, otherwise to 0. */ -#define JSON_HAVE_LOCALECONV 1 - -/* If __atomic builtins are available they will be used to manage - reference counts of json_t. */ -#define JSON_HAVE_ATOMIC_BUILTINS 0 - -/* If __atomic builtins are not available we try using __sync builtins - to manage reference counts of json_t. */ -#define JSON_HAVE_SYNC_BUILTINS 0 - -/* Maximum recursion depth for parsing JSON input. - This limits the depth of e.g. array-within-array constructions. */ -#define JSON_PARSER_MAX_DEPTH 2048 - -#endif diff --git a/ext_libs/ext_libs.vcxproj b/ext_libs/ext_libs.vcxproj index 534702b5..f57e7389 100644 --- a/ext_libs/ext_libs.vcxproj +++ b/ext_libs/ext_libs.vcxproj @@ -94,11 +94,6 @@ lib /def:libspeex-1.def /machine:x86 /out:libspeex-1.lib libspeex-1.lib;libspeex-1.exp;%(Outputs) - - Building library stub - lib /def:jansson.def /machine:x86 /out:jansson.lib - jansson.lib;jansson.exp;%(Outputs) - diff --git a/ext_libs/jansson.def b/ext_libs/jansson.def deleted file mode 100644 index 55b39c83..00000000 --- a/ext_libs/jansson.def +++ /dev/null @@ -1,78 +0,0 @@ -EXPORTS - json_delete - json_true - json_false - json_null - json_sprintf - json_vsprintf - json_string - json_stringn - json_string_nocheck - json_stringn_nocheck - json_string_value - json_string_length - json_string_set - json_string_setn - json_string_set_nocheck - json_string_setn_nocheck - json_integer - json_integer_value - json_integer_set - json_real - json_real_value - json_real_set - json_number_value - json_array - json_array_size - json_array_get - json_array_set_new - json_array_append_new - json_array_insert_new - json_array_remove - json_array_clear - json_array_extend - json_object - json_object_size - json_object_get - json_object_set_new - json_object_set_new_nocheck - json_object_del - json_object_clear - json_object_update - json_object_update_existing - json_object_update_missing - json_object_update_recursive - json_object_iter - json_object_iter_at - json_object_iter_next - json_object_iter_key - json_object_iter_value - json_object_iter_set_new - json_object_key_to_iter - json_object_seed - json_dumps - json_dumpb - json_dumpf - json_dumpfd - json_dump_file - json_dump_callback - json_loads - json_loadb - json_loadf - json_loadfd - json_load_file - json_load_callback - json_equal - json_copy - json_deep_copy - json_pack - json_pack_ex - json_vpack_ex - json_unpack - json_unpack_ex - json_vunpack_ex - json_set_alloc_funcs - json_get_alloc_funcs - jansson_version_str - jansson_version_cmp - diff --git a/ext_libs/jansson.dll b/ext_libs/jansson.dll deleted file mode 100644 index dcca02b0..00000000 Binary files a/ext_libs/jansson.dll and /dev/null differ diff --git a/make-build-cmake.sh b/make-build-cmake.sh index f3859d3e..1269a91d 100755 --- a/make-build-cmake.sh +++ b/make-build-cmake.sh @@ -11,7 +11,6 @@ sudo apt-get install libavformat-dev libavcodec-dev libavutil-dev libswresample- sudo apt-get install yasm libopus-dev # optional: for vgmstream 123 and audacious sudo apt-get install -y libao-dev audacious-dev -#sudo apt-get install libjansson-dev mkdir -p build cd build diff --git a/msvc-build.ps1 b/msvc-build.ps1 index 8b60f25a..64a8a3ae 100644 --- a/msvc-build.ps1 +++ b/msvc-build.ps1 @@ -228,7 +228,7 @@ $fb2kFiles64 = @( ) $fb2kFiles_remove = @( - "bin/foobar2000/jansson.dll" + "bin/foobar2000/xxxx.dll" ) $cliPdbFiles32 = @( diff --git a/src/api.h b/src/api.h index 50b08394..85ee8144 100644 --- a/src/api.h +++ b/src/api.h @@ -1,59 +1,4 @@ #ifndef _API_H_ #define _API_H_ #include "base/plugins.h" //TODO: to be removed - -//#define LIBVGMSTREAM_ENABLE 1 -#if LIBVGMSTREAM_ENABLE - -/* vgmstream's public API - * - * By default vgmstream behaves like a simple decoder (extract samples until stream end), but you can configure it - * to loop N times or even downmix. In other words, it also behaves a bit like a player. - * - * It exposes multiple options and convenience functions beyond simple decoding mainly for various plugins, - * since it was faster moving shared behavior to core rather than reimplementing every time. - * - * All this may make the API a bit twisted and coupled (sorry, tried my best), probably will improve later. Probably. - * - * Notes: - * - vgmstream may dynamically allocate stuff as needed (not too much beyond some setup buffers, but varies per format) - * - previously the only way to use vgmstream was accesing its internals. Now there is an API internals may change in the future - * - some details described in the API may not happen at the moment (they are defined for future internal changes) - * - main reason it uses the slighly long-winded libvgmstream_* names is that internals use the vgmstream_* 'namespace' - * - c-strings should be in UTF-8 - * - the API is still WIP and may be slightly buggy overall due to lack of time, to be improved later - * - vgmstream's features are mostly stable, but this API may be tweaked from time to time (check API_VERSION) - * - * Basic usage (also see api_example.c): - * - libvgmstream_init(...) // base context - * - libvgmstream_setup(...) // config if needed - * - libvgmstream_open(...) // setup format - * - libvgmstream_play(...) // main decode - * - output samples + repeat libvgmstream_play until stream is done - * - libvgmstream_free(...) // cleanup - */ - -#include -#include - -/* standard C param call and name mangling (to avoid __stdcall / .defs) */ -//#define LIBVGMSTREAM_CALL __cdecl //needed? -//LIBVGMSTREAM_API (type) LIBVGMSTREAM_CALL libvgmstream_function(...); - -/* define external function behavior (during compilation) */ -#if defined(LIBVGMSTREAM_EXPORT) - #define LIBVGMSTREAM_API __declspec(dllexport) /* when exporting/creating vgmstream DLL */ -#elif defined(LIBVGMSTREAM_IMPORT) - #define LIBVGMSTREAM_API __declspec(dllimport) /* when importing/linking vgmstream DLL */ -#else - #define LIBVGMSTREAM_API /* nothing, internal/default */ -#endif - -#include "api_version.h" -#include "api_decode.h" -#include "api_helpers.h" -#include "api_streamfile.h" -#include "api_tags.h" - -#endif #endif diff --git a/src/api_decode.h b/src/api_decode.h deleted file mode 100644 index 3d0d6f11..00000000 --- a/src/api_decode.h +++ /dev/null @@ -1,202 +0,0 @@ -#ifndef _API_DECODE_H_ -#define _API_DECODE_H_ -#include "api.h" -#if LIBVGMSTREAM_ENABLE -#include "api_streamfile.h" - - -/* vgmstream's main (decode) API. - */ - - -/* interleaved samples: buf[0]=ch0, buf[1]=ch1, buf[2]=ch0, buf[3]=ch0, ... */ -typedef enum { - LIBVGMSTREAM_SAMPLE_PCM16 = 0x01, - LIBVGMSTREAM_SAMPLE_PCM24 = 0x02, - LIBVGMSTREAM_SAMPLE_PCM32 = 0x03, - LIBVGMSTREAM_SAMPLE_FLOAT = 0x04, -} libvgmstream_sample_t; - -/* current song info, may be copied around (values are info-only) */ -typedef struct { - /* main (always set) */ - libvgmstream_sample_t sample_type; // output buffer's sample type - int channels; // output channels - int sample_rate; // output sample rate - - int sample_size; // derived from sample_type (pcm16=2, float=4, etc) - - /* extra info (may be 0 if not known or not relevant) */ - uint32_t channel_layout; // standard WAVE bitflags - - int subsong_index; // 0 = none, N = loaded subsong N - int subsong_count; // 0 = format has no concept of subsongs, N = has N subsongs (1 = format has subsongs, and only 1) - - int input_channels; // original file's channels before downmixing (if any) - //int interleave; // when file is interleaved - //int interleave_first; // when file is interleaved - //int interleave_last; // when file is interleaved - //int frame_size; // when file has some configurable frame size - - /* sample info (may not be used depending on config) */ - int64_t sample_count; // file's max samples (not final play duration) - int64_t loop_start; // loop start sample - int64_t loop_end; // loop end sample - bool loop_flag; // if file loops; note that false + defined loops means looping was forcefully disabled - - bool play_forever; // if file loops forever based on current config (meaning _play never stops) - int64_t play_samples; // totals after all calculations (after applying loop/fade/etc config) - // ** may not be 100% accurate in some cases (must check decoder's 'done' flag rather than this count) - // ** if play_forever is set this is still provided for reference based on non-forever config - - int stream_bitrate; // average bitrate of the subsong (slightly bloated vs codec_bitrate; incorrect in rare cases) - //int codec_bitrate; // average bitrate of the codec data - // ** not possible / slow to calculate in most cases - - /* descriptions */ - char codec_name[128]; // - char layout_name[128]; // - char meta_name[128]; // (not internal "tag" metadata) - char stream_name[256]; // some internal name or representation, not always useful - // ** these are a bit big for a struct, but the typical use case of vgsmtream is opening a file > immediately - // query description and since libvgmstream returns its own copy it shouldn't be too much of a problem - // ** (may be separated later) - - /* misc */ - //bool rough_samples; // signal cases where loop points or sample count can't exactly reflect actual behavior (do not use to export) - - int format_internal_id; // may be used when reopening subfiles or similar formats without checking other possible formats first - // ** this value WILL change without warning between vgmstream versions/commits - -} libvgmstream_format_t; - - -typedef struct { - void* buf; // current decoded buf (valid after _decode until next call; may change between calls) - int buf_samples; // current buffer samples (0 is possible in some cases, meaning current _decode can't generate samples) - int buf_bytes; // current buffer bytes (channels * sample_size * samples) - - bool done; // flag when stream is done playing based on config; will still allow _play calls returning blank samples - // ** note that with play_forever this flag is never set -} libvgmstream_decoder_t; - - -/* vgmstream context/handle */ -typedef struct { - void* priv; // internal data - - /* pointers for easier ABI compatibility */ - const libvgmstream_format_t* format; // current song info, updated on _open - libvgmstream_decoder_t* decoder; // updated on each _decode call - -} libvgmstream_t; - - - -/* inits the vgmstream context - * - returns NULL on error - * - call libvgmstream_free when done. - */ -LIBVGMSTREAM_API libvgmstream_t* libvgmstream_init(void); - -/* frees vgmstream context and any other internal stuff that may not be closed - */ -LIBVGMSTREAM_API void libvgmstream_free(libvgmstream_t* lib); - - -/* configures how vgmstream behaves internally when playing a file */ -typedef struct { - bool disable_config_override; // ignore forced (TXTP) config - bool allow_play_forever; // must allow manually as some cases a TXTP may set loop forever but client may not handle it (ex. wave dumpers) - - bool play_forever; // keeps looping forever (file must have loop_flag set) - bool ignore_loop; // ignores loops points - bool force_loop; // enables full loops (0..samples) if file doesn't have loop points - bool really_force_loop; // forces full loops (0..samples) even if file has loop points - bool ignore_fade; // don't fade after N loops and play remaning stream (for files with outros) - - double loop_count; // target loops (values like 1.5 are ok) - double fade_time; // fade period after target loops - double fade_delay; // fade delay after target loops - - int auto_downmix_channels; // downmixing if vgmstream's channels are higher than value - // ** for players that can only handle N channels, but this type of downmixing is very simplistic and not recommended - - bool force_pcm16; // forces output buffer to be remixed into PCM16 - -} libvgmstream_config_t; - -/* pass default config, that will be applied to song on open - * - invalid config or complex cases (ex. some TXTP) may ignore these settings. - * - called without a song loaded (before _open or after _close), otherwise ignored. - * - without config vgmstream will decode the current stream once - */ -LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_config_t* cfg); - - -/* configures how vgmstream opens the format */ -typedef struct { - libvgmstream_streamfile_t* libsf; // custom IO streamfile that provides reader info for vgmstream - // ** not needed after _open and should be closed, as vgmstream re-opens its own SFs internally as needed - - int subsong; // target subsong (1..N) or 0 = default/first - // ** to check if a file has subsongs, _open first + check format->total_subsongs (then _open 2nd, 3rd, etc) - - int format_internal_id; // force a format (for example when loading new subsong of the same archive) - - int stereo_track; // forces vgmstream to decode one 2ch+2ch+2ch... 'track' and discard other channels, where 0 = disabled, 1..N = Nth track - -} libvgmstream_options_t; - -/* opens file based on config and prepares it to play if supported. - * - returns < 0 on error (file not recognised, invalid subsong index, etc) - * - will close currently loaded song if needed - */ -LIBVGMSTREAM_API int libvgmstream_open(libvgmstream_t* lib, libvgmstream_options_t* open_options); - -#if 0 -/* opens file based on config and returns its file info - * - returns < 0 on error (file not recognised, invalid subsong index, etc) - * - equivalent to _open, but doesn't update the current loaded song / format and copies to passed struct - * - may be used while current song is loaded/playing but you need to query next song with current config - * - to play a new song don't call _open_info to check the format first, just call _open + check format afterwards - * - in many cases the only way for vgmstream to get a file's format info is making it almost ready to play, - * so this isn't any faster than _open - */ -LIBVGMSTREAM_API int libvgmstream_open_info(libvgmstream_t* lib, libvgmstream_options_t* options, libvgmstream_format_t* format); -#endif - -/* closes current song; may still use libvgmstream to open other songs - */ -LIBVGMSTREAM_API void libvgmstream_close(libvgmstream_t* lib); - - -/* decodes next batch of samples - * - vgmstream supplies its own buffer, updated on lib->decoder->* values (may change between calls) - * - returns < 0 on error - */ -LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib); - -/* Same as _play, but fills some external buffer (also updates lib->decoder->* values) - * - returns < 0 on error, or N = number of filled samples. - * - buf must be at least as big as channels * sample_size * buf_samples - * - needs copying around from internal bufs so may be slightly slower; mainly for cases when you have buf constraints - */ -LIBVGMSTREAM_API int libvgmstream_fill(libvgmstream_t* lib, void* buf, int buf_samples); - -/* Gets current position within the song. - * - return < 0 on error (file not ready) - */ -LIBVGMSTREAM_API int64_t libvgmstream_get_play_position(libvgmstream_t* lib); - -/* Seeks to absolute position. Will clamp incorrect values such as seeking before/past playable length. - * - on play_forever may seek to any position - */ -LIBVGMSTREAM_API void libvgmstream_seek(libvgmstream_t* lib, int64_t sample); - -/* Reset current song - */ -LIBVGMSTREAM_API void libvgmstream_reset(libvgmstream_t* lib); - -#endif -#endif diff --git a/src/api_helpers.h b/src/api_helpers.h deleted file mode 100644 index 9d070ba1..00000000 --- a/src/api_helpers.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef _API_HELPERS_H_ -#define _API_HELPERS_H_ -#include "api.h" -#if LIBVGMSTREAM_ENABLE - - -/* vgmstream's helper stuff, for plugins. - */ - - -typedef enum { - LIBVGMSTREAM_LOG_LEVEL_ALL = 0, - LIBVGMSTREAM_LOG_LEVEL_DEBUG = 20, - LIBVGMSTREAM_LOG_LEVEL_INFO = 30, - LIBVGMSTREAM_LOG_LEVEL_NONE = 100, -} libvgmstream_loglevel_t; - -typedef struct { - libvgmstream_loglevel_t level; // log level - void (*callback)(int level, const char* str); // log callback - bool stdout_callback; // use default log callback rather than user supplied -} libvgmstream_log_t; - -/* defines a global log callback, as vgmstream sometimes communicates format issues to the user. - * - note that log is currently set globally rather than per libvgmstream_t -*/ -LIBVGMSTREAM_API void libvgmstream_set_log(libvgmstream_log_t* cfg); - - -/* Returns a list of supported extensions (WARNING: it's pretty big), such as "adx", "dsp", etc. - * Mainly for plugins that want to know which extensions are supported. - * - returns NULL if no size is provided - */ -LIBVGMSTREAM_API const char** libvgmstream_get_extensions(size_t* size); - - -/* Same as above, buf returns a list what vgmstream considers "common" formats (such as "wav", "ogg"), - * which usually one doesn't want to associate to vgmstream. - * - returns NULL if no size is provided - */ -LIBVGMSTREAM_API const char** libvgmstream_get_common_extensions(size_t* size); - - -typedef struct { - bool is_extension; /* set if filename is just an extension */ - bool skip_default; /* set if shouldn't check default formats */ - bool reject_extensionless; /* set if player can't play extensionless files */ - bool accept_unknown; /* set to allow any extension (for txth) */ - bool accept_common; /* set to allow known-but-common extension (when player has plugin priority) */ -} libvgmstream_valid_t; - -/* returns if vgmstream can parse a filename by extension, to reject some files earlier - * - doesn't check file contents (that's only done on _open) - * - config may be NULL - * - mainly for plugins that fail early; libvgmstream doesn't use this - */ -LIBVGMSTREAM_API bool libvgmstream_is_valid(const char* filename, libvgmstream_valid_t* cfg); - - -typedef struct { - bool force_title; // TODO: what was this for? - bool subsong_range; // print a range of possible subsongs after title 'filename#1~N' - bool remove_extension; // remove extension from passed filename - bool remove_archive; // remove '(archive)|(subfile)' format of some plugins - const char* filename; // base file's filename - // ** note that sometimes vgmstream doesn't have/know the original name, so it's needed again here -} libvgmstream_title_t; - -/* get a simple title for plugins, derived from internal stream name if available - * - valid after _open - */ -LIBVGMSTREAM_API int libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_title_t* cfg, char* buf, int buf_len); - - -/* Writes a description of the current song into dst. Will always be null-terminated. - * - returns < 0 if file was truncated, though will still succeed. - */ -LIBVGMSTREAM_API int libvgmstream_format_describe(libvgmstream_t* lib, char* dst, int dst_size); - -#endif -#endif diff --git a/src/api_tags.h b/src/api_tags.h deleted file mode 100644 index e0c00699..00000000 --- a/src/api_tags.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef _API_TAGS_H_ -#define _API_TAGS_H_ -#include "api.h" -#if LIBVGMSTREAM_ENABLE - -/* vgmstream's !tags.m3u API. - * Doesn't need a main libvgmstream as tags aren't tied to loaded songs. - * - * Meant to be a simple implementation; feel free to ignore and roll your own - * (or use another external tags plugin). - */ - - -/* tag state */ -typedef struct { - void* priv; // internal data - - const char* key; // current key - const char* val; // current value -} libvgmstream_tags_t; - -/* Initializes tags. - * - libsf should point to a !tags.m3u file - * - unlike libvgmstream_open, sf tagfile must be valid during the tag extraction process. - */ -LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libvgmstream_streamfile_t* libsf); - - -/* Finds tags for a new filename. Must be called first before extracting tags. - */ -LIBVGMSTREAM_API void libvgmstream_tags_find(libvgmstream_tags_t* tags, const char* target_filename); - - -/* Extracts next valid tag in tagfile to key/val. - * - returns false if no more tags are found (meant to be called repeatedly until false) - * - key/values are trimmed of beginning/end whitespaces and values are in UTF-8 - */ -LIBVGMSTREAM_API bool libvgmstream_tags_next_tag(libvgmstream_tags_t* tags); - - -/* Closes tags. */ -LIBVGMSTREAM_API void libvgmstream_tags_free(libvgmstream_tags_t* tags); - -#endif -#endif diff --git a/src/api_version.h b/src/api_version.h deleted file mode 100644 index 91771ca7..00000000 --- a/src/api_version.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef _API_VERSION_H_ -#define _API_VERSION_H_ -#include "api.h" -#if LIBVGMSTREAM_ENABLE - -/* Current API version. - * - only refers to the API itself, as changes related to formats/etc don't alter this (since they are usually additive) - * - vgmstream's features are mostly stable, but this API may be tweaked from time to time - */ -#define LIBVGMSTREAM_API_VERSION_MAJOR 1 // breaking API/ABI changes -#define LIBVGMSTREAM_API_VERSION_MINOR 0 // compatible API/ABI changes -#define LIBVGMSTREAM_API_VERSION_PATCH 0 // fixes - -/* returns API version in hex format: 0xMMmmpppp = MM-major, mm-minor, pppp-patch - * - use when loading vgmstream as a dynamic library to ensure API/ABI compatibility - */ -LIBVGMSTREAM_API uint32_t libvgmstream_get_version(void); - -/* CHANGELOG: - * - * - 1.0.0: initial version - */ - -#endif -#endif diff --git a/src/base/api_decode_open.c b/src/base/api_decode_open.c index 4a0d526f..6dc96367 100644 --- a/src/base/api_decode_open.c +++ b/src/base/api_decode_open.c @@ -9,9 +9,9 @@ static void load_vgmstream(libvgmstream_priv_t* priv, libvgmstream_options_t* op if (!sf_api) return; - //TODO: handle format_internal_id + //TODO: handle internal format_id - sf_api->stream_index = opt->subsong; + sf_api->stream_index = opt->subsong_index; priv->vgmstream = init_vgmstream_from_STREAMFILE(sf_api); close_streamfile(sf_api); } @@ -77,7 +77,7 @@ static void update_format_info(libvgmstream_priv_t* priv) { vgmstream_mixing_enable(v, 0, &fmt->input_channels, &fmt->channels); fmt->channel_layout = v->channel_layout; - fmt->sample_count = v->num_samples; + fmt->stream_samples = v->num_samples; fmt->loop_start = v->loop_start_sample; fmt->loop_end = v->loop_end_sample; fmt->loop_flag = v->loop_flag; @@ -85,6 +85,8 @@ static void update_format_info(libvgmstream_priv_t* priv) { fmt->play_forever = priv->pos.play_forever; fmt->play_samples = priv->pos.play_samples; + fmt->format_id = v->format_id; + fmt->stream_bitrate = get_vgmstream_average_bitrate(v); get_vgmstream_coding_description(v, fmt->codec_name, sizeof(fmt->codec_name)); @@ -94,18 +96,16 @@ static void update_format_info(libvgmstream_priv_t* priv) { if (v->stream_name[0] != '\0') { //snprintf UB for NULL args snprintf(fmt->stream_name, sizeof(fmt->stream_name), "%s", v->stream_name); } - - fmt->format_internal_id = 0; //TODO } -LIBVGMSTREAM_API int libvgmstream_open(libvgmstream_t* lib, libvgmstream_options_t* opt) { +LIBVGMSTREAM_API int libvgmstream_open_song(libvgmstream_t* lib, libvgmstream_options_t* opt) { if (!lib ||!lib->priv) return LIBVGMSTREAM_ERROR_GENERIC; - if (!opt || !opt->libsf || opt->subsong < 0) + if (!opt || !opt->libsf || opt->subsong_index < 0) return LIBVGMSTREAM_ERROR_GENERIC; // close loaded song if any + reset - libvgmstream_close(lib); + libvgmstream_close_song(lib); libvgmstream_priv_t* priv = lib->priv; @@ -123,7 +123,7 @@ LIBVGMSTREAM_API int libvgmstream_open(libvgmstream_t* lib, libvgmstream_options } -LIBVGMSTREAM_API void libvgmstream_close(libvgmstream_t* lib) { +LIBVGMSTREAM_API void libvgmstream_close_song(libvgmstream_t* lib) { if (!lib || !lib->priv) return; diff --git a/src/base/api_internal.h b/src/base/api_internal.h index 573b1a95..fec1035f 100644 --- a/src/base/api_internal.h +++ b/src/base/api_internal.h @@ -1,6 +1,7 @@ #ifndef _API_INTERNAL_H_ #define _API_INTERNAL_H_ -#include "../api.h" +#include "../libvgmstream.h" +#include "../util/log.h" #include "../vgmstream.h" #include "plugins.h" diff --git a/src/base/config.c b/src/base/config.c index cf3a0f4d..86ff2b76 100644 --- a/src/base/config.c +++ b/src/base/config.c @@ -5,7 +5,7 @@ -static void copy_time(int* dst_flag, int32_t* dst_time, double* dst_time_s, int* src_flag, int32_t* src_time, double* src_time_s) { +static void copy_time(bool* dst_flag, int32_t* dst_time, double* dst_time_s, bool* src_flag, int32_t* src_time, double* src_time_s) { if (!*src_flag) return; *dst_flag = 1; diff --git a/src/base/decode.c b/src/base/decode.c index b6ed6fab..eaa45b1b 100644 --- a/src/base/decode.c +++ b/src/base/decode.c @@ -188,7 +188,7 @@ void decode_seek(VGMSTREAM* vgmstream) { #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) if (vgmstream->coding_type == coding_MP4_AAC) { - seek_mp4_aac(vgmstream, vgmstream->loop_sample); + seek_mp4_aac(vgmstream, vgmstream->loop_current_sample); } #endif @@ -560,7 +560,7 @@ int decode_get_samples_per_frame(VGMSTREAM* vgmstream) { return 0; /* ~100 (range), ~16 (DCT) */ #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) case coding_MP4_AAC: - return ((mp4_aac_codec_data*)vgmstream->codec_data)->samples_per_frame; + return mp4_get_samples_per_frame(vgmstream->codec_data); #endif #ifdef VGM_USE_ATRAC9 case coding_ATRAC9: diff --git a/src/base/info.c b/src/base/info.c index 6a2be0a7..eb24828d 100644 --- a/src/base/info.c +++ b/src/base/info.c @@ -330,8 +330,7 @@ static STREAMFILE* get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM* v #endif #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) if (vgmstream->coding_type == coding_MP4_AAC) { - mp4_aac_codec_data *data = vgmstream->codec_data; - return data ? data->if_file.streamfile : NULL; + return mp4_aac_get_streamfile(vgmstream->codec_data); } #endif diff --git a/src/base/mixing.h b/src/base/mixing.h index 4d8705bb..53d2ab8f 100644 --- a/src/base/mixing.h +++ b/src/base/mixing.h @@ -2,6 +2,7 @@ #define _MIXING_H_ #include "../vgmstream.h" +#include "../util/log.h" /* Applies mixing commands to the sample buffer. Mixing must be externally enabled and * outbuf must big enough to hold output_channels*samples_to_do */ diff --git a/src/base/streamfile_stdio.c b/src/base/streamfile_stdio.c index 948f5ffc..5e43ef8f 100644 --- a/src/base/streamfile_stdio.c +++ b/src/base/streamfile_stdio.c @@ -10,6 +10,9 @@ #include #endif +// for testing purposes; generally slower since reads often aren't optimized for unbuffered IO +//#define DISABLE_BUFFER + /* Enables a minor optimization when reopening file descriptors. * Some systems/compilers have issues though, and dupe'd FILEs may fread garbage data in rare cases, * possibly due to underlying buffers that get shared/thrashed by dup(). Seen for example in some .HPS and Ubi @@ -101,6 +104,15 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size if (/*!sf->infile ||*/ !dst || length <= 0 || offset < 0) return 0; +#ifdef DISABLE_BUFFER + if (offset != sf->offset) { + fseek_v(sf->infile, offset, SEEK_SET); + } + read_total = fread(dst, sizeof(uint8_t), length, sf->infile); + + sf->offset = offset + read_total; + return read_total; +#else //;VGM_LOG("stdio: read %lx + %x (buf %lx + %x)\n", offset, length, sf->buf_offset, sf->valid_size, sf->buf_size); /* is the part of the requested length in the buffer? */ @@ -183,6 +195,7 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size sf->offset = offset; /* last fread offset */ return read_total; +#endif } static size_t stdio_get_size(STDIO_STREAMFILE* sf) { diff --git a/src/coding/coding.h b/src/coding/coding.h index c98a1a63..33d09bad 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -4,6 +4,7 @@ #include "../vgmstream.h" #include "../util/reader_sf.h" #include "../util/reader_get_nibbles.h" +#include "../util/log.h" //todo remove #include "libs/clhca.h" @@ -560,10 +561,19 @@ void free_g719(g719_codec_data* data, int channels); #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) /* mp4_aac_decoder */ +typedef struct mp4_aac_codec_data mp4_aac_codec_data; + +mp4_aac_codec_data* init_mp4_aac(STREAMFILE* sf); void decode_mp4_aac(mp4_aac_codec_data* data, sample_t* outbuf, int32_t samples_to_do, int channels); void reset_mp4_aac(VGMSTREAM* vgmstream); void seek_mp4_aac(VGMSTREAM* vgmstream, int32_t num_sample); void free_mp4_aac(mp4_aac_codec_data* data); + +STREAMFILE* mp4_aac_get_streamfile(mp4_aac_codec_data* data); +int32_t mp4_aac_get_samples(mp4_aac_codec_data* data); +int32_t mp4_aac_get_samples_per_frame(mp4_aac_codec_data* data); +int mp4_aac_get_sample_rate(mp4_aac_codec_data* data); +int mp4_aac_get_channels(mp4_aac_codec_data* data); #endif diff --git a/src/coding/mp4_aac_decoder.c b/src/coding/mp4_aac_decoder.c index 3712362f..01cbf8ca 100644 --- a/src/coding/mp4_aac_decoder.c +++ b/src/coding/mp4_aac_decoder.c @@ -1,6 +1,159 @@ #include "../vgmstream.h" #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) +#ifdef VGM_USE_MP4V2 +#define MP4V2_NO_STDINT_DEFS +#include +#endif + +#ifdef VGM_USE_FDKAAC +#include +#endif + +typedef struct { + STREAMFILE* streamfile; + uint64_t start; + uint64_t offset; + uint64_t size; +} mp4_streamfile; + +struct mp4_aac_codec_data { + mp4_streamfile if_file; + MP4FileHandle h_mp4file; + MP4TrackId track_id; + unsigned long sampleId; + unsigned long numSamples; + UINT codec_init_data_size; + HANDLE_AACDECODER h_aacdecoder; + unsigned int sample_ptr; + unsigned int samples_per_frame + unsigned int samples_discard; + INT_PCM sample_buffer[( (6) * (2048)*4 )]; +}; + + +// VGM_USE_MP4V2 +static void* mp4_file_open( const char* name, MP4FileMode mode ) { + char * endptr; +#ifdef _MSC_VER + unsigned __int64 ptr = _strtoui64( name, &endptr, 16 ); +#else + unsigned long ptr = strtoul( name, &endptr, 16 ); +#endif + return (void*) ptr; +} + +static int mp4_file_seek( void* handle, int64_t pos ) { + mp4_streamfile * file = ( mp4_streamfile * ) handle; + if ( pos > file->size ) pos = file->size; + pos += file->start; + file->offset = pos; + return 0; +} + +static int mp4_file_get_size( void* handle, int64_t* size ) { + mp4_streamfile * file = ( mp4_streamfile * ) handle; + *size = file->size; + return 0; +} + +static int mp4_file_read( void* handle, void* buffer, int64_t size, int64_t* nin, int64_t maxChunkSize ) { + mp4_streamfile * file = ( mp4_streamfile * ) handle; + int64_t max_size = file->size - file->offset - file->start; + if ( size > max_size ) size = max_size; + if ( size > 0 ) + { + *nin = read_streamfile( (uint8_t *) buffer, file->offset, size, file->streamfile ); + file->offset += *nin; + } + else + { + *nin = 0; + return 1; + } + return 0; +} + +static int mp4_file_write( void* handle, const void* buffer, int64_t size, int64_t* nout, int64_t maxChunkSize ) { + return 1; +} + +static int mp4_file_close( void* handle ) { + return 0; +} + +MP4FileProvider mp4_file_provider = { mp4_file_open, mp4_file_seek, mp4_file_read, mp4_file_write, mp4_file_close, mp4_file_get_size }; + + +mp4_aac_codec_data* init_mp4_aac(STREAMFILE* sf) { + char filename[PATH_LIMIT]; + uint32_t start = 0; + uint32_t size = get_streamfile_size(sf); + + CStreamInfo* stream_info = NULL; + + uint8_t* buffer = NULL; + uint32_t buffer_size; + UINT ubuffer_size, bytes_valid; + + mp4_aac_codec_data* data = calloc(1, sizeof(mp4_aac_codec_data)); + if (!data) goto fail; + + data->if_file.streamfile = sf; + data->if_file.start = start; + data->if_file.offset = start; + data->if_file.size = size; + + /* Big ol' kludge! */ + sprintf( filename, "%p", &data->if_file ); + data->h_mp4file = MP4ReadProvider( filename, &mp4_file_provider ); + if ( !data->h_mp4file ) goto fail; + + if ( MP4GetNumberOfTracks(data->h_mp4file, MP4_AUDIO_TRACK_TYPE, '\000') != 1 ) goto fail; + + data->track_id = MP4FindTrackId( data->h_mp4file, 0, MP4_AUDIO_TRACK_TYPE, '\000' ); + + data->h_aacdecoder = aacDecoder_Open( TT_MP4_RAW, 1 ); + if ( !data->h_aacdecoder ) goto fail; + + MP4GetTrackESConfiguration( data->h_mp4file, data->track_id, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size)); + + ubuffer_size = buffer_size; + if ( aacDecoder_ConfigRaw( data->h_aacdecoder, &buffer, &ubuffer_size ) ) goto fail; + + free( buffer ); buffer = NULL; + + data->sampleId = 1; + data->numSamples = MP4GetTrackNumberOfSamples( data->h_mp4file, data->track_id ); + + if (!MP4ReadSample(data->h_mp4file, data->track_id, data->sampleId, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size), 0, 0, 0, 0)) goto fail; + + ubuffer_size = buffer_size; + bytes_valid = buffer_size; + if ( aacDecoder_Fill( data->h_aacdecoder, &buffer, &ubuffer_size, &bytes_valid ) || bytes_valid ) goto fail; + if ( aacDecoder_DecodeFrame( data->h_aacdecoder, data->sample_buffer, ( (6) * (2048)*4 ), 0 ) ) goto fail; + + free( buffer ); buffer = NULL; + + data->sample_ptr = 0; + + stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder ); + + data->samples_per_frame = stream_info->frameSize; + data->samples_discard = 0; + + sf->get_name( sf, filename, sizeof(filename) ); + + data->if_file.streamfile = sf->open(sf, filename, 0); + if (!data->if_file.streamfile) goto fail; + + return data; +fail: + free( buffer ); buffer = NULL; + free_mp4_aac(data); +} + + static void convert_samples(INT_PCM * src, sample * dest, int32_t count) { int32_t i; for ( i = 0; i < count; i++ ) { @@ -111,4 +264,38 @@ void free_mp4_aac(mp4_aac_codec_data * data) { } } +void mp4_aac_get_streamfile(mp4_aac_codec_data* data) { + if (!data) + return NULL; + return data->if_file.streamfile; +} + +int32_t mp4_aac_get_samples(mp4_aac_codec_data* data) { + if (!data) + return 0; + return (int32_t)(data->samples_per_frame * data->numSamples); +} + +int32_t mp4_aac_get_samples_per_frame(mp4_aac_codec_data* data) { + if (!data) + return 0; + return (int32_t)(data->samples_per_frame); +} + +int mp4_aac_get_sample_rate(mp4_aac_codec_data* data) { + if (!data) + return 0; + + CStreamInfo* stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder ); + return stream_info->sample_rate; +} + +int mp4_aac_get_channels(mp4_aac_codec_data* data) { + if (!data) + return 0; + + CStreamInfo* stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder ); + return stream_info->numChannels; +} + #endif diff --git a/src/coding/nwa_decoder.c b/src/coding/nwa_decoder.c index 636f6a60..8af812ca 100644 --- a/src/coding/nwa_decoder.c +++ b/src/coding/nwa_decoder.c @@ -38,7 +38,7 @@ void decode_nwa(nwa_codec_data* data, sample_t* outbuf, int32_t samples_to_do) { nwa_codec_data* init_nwa(STREAMFILE* sf) { nwa_codec_data* data = NULL; - data = malloc(sizeof(nwa_codec_data)); + data = calloc(1, sizeof(nwa_codec_data)); if (!data) goto fail; data->nwa = nwalib_open(sf); diff --git a/src/layout/layout.h b/src/layout/layout.h index 4b3c8a4f..3423e2d8 100644 --- a/src/layout/layout.h +++ b/src/layout/layout.h @@ -4,6 +4,7 @@ #include "../streamtypes.h" #include "../vgmstream.h" #include "../util/reader_sf.h" +#include "../util/log.h" /* blocked layouts */ void render_vgmstream_blocked(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream); diff --git a/src/libvgmstream.h b/src/libvgmstream.h new file mode 100644 index 00000000..17768520 --- /dev/null +++ b/src/libvgmstream.h @@ -0,0 +1,361 @@ +/* libvgmstream: vgmstream's public API */ + +#ifndef _LIBVGMSTREAM_H_ +#define _LIBVGMSTREAM_H_ + +//#define LIBVGMSTREAM_ENABLE 1 +#if LIBVGMSTREAM_ENABLE + +/* By default vgmstream behaves like a decoder (decode samples until stream end), but you can configure + * it to loop N times or even downmix. In other words, it also behaves a bit like a player. + * It exposes multiple convenience stuff mainly for various plugins that mostly repeat the same features. + * All this may make the API still WIP and a bit twisted, probably will improve later. Probably. + * + * Notes: + * - now there is an API internals (vgmstream.h) may change in the future + * - may dynamically allocate stuff as needed (mainly some buffers, but varies per format) + * - some details described in the API may not happen at the moment (defined for future changes) + * - uses long-winded libvgmstream_* names since internals alredy use the vgmstream_* 'namespace', #define as needed + * - c-strings should be in UTF-8 + * + * Basic usage (also see api_example.c): + * - libvgmstream_init(...) // base context + * - libvgmstream_setup(...) // config if needed + * - libvgmstream_open_song(...) // setup format + * - libvgmstream_play(...) // main decode + * - output samples + repeat libvgmstream_play until stream is done + * - libvgmstream_free(...) // cleanup + */ + + +/*****************************************************************************/ +/* DEFINES */ + +///* standard C param call and name mangling (to avoid __stdcall / .defs) */ +//#define LIBVGMSTREAM_CALL __cdecl //needed? +//LIBVGMSTREAM_API (type) LIBVGMSTREAM_CALL libvgmstream_function(...); + +/* external function behavior (for compile time) */ +#if defined(LIBVGMSTREAM_EXPORT) + #define LIBVGMSTREAM_API __declspec(dllexport) /* when exporting/creating vgmstream DLL */ +#elif defined(LIBVGMSTREAM_IMPORT) + #define LIBVGMSTREAM_API __declspec(dllimport) /* when importing/linking vgmstream DLL */ +#else + #define LIBVGMSTREAM_API /* nothing, internal/default */ +#endif + +#include +#include +#include +#include "libvgmstream_streamfile.h" + + +/*****************************************************************************/ +/* VERSION */ + +/* Current API version, for static checks. + * - only refers to the API itself, changes related to formats/etc don't alter this + * - vgmstream's features are mostly stable, but this API may be tweaked from time to time + */ +#define LIBVGMSTREAM_API_VERSION_MAJOR 1 // breaking API/ABI changes +#define LIBVGMSTREAM_API_VERSION_MINOR 0 // compatible API/ABI changes +#define LIBVGMSTREAM_API_VERSION_PATCH 0 // fixes + +/* Current API version, for dynamic checks. returns hex value: 0xMMmmpppp = MM-major, mm-minor, pppp-patch + * - use when loading vgmstream as a dynamic library to ensure API/ABI compatibility + */ +LIBVGMSTREAM_API uint32_t libvgmstream_get_version(void); + +/* CHANGELOG: + * - 1.0.0: initial version + */ + + +/*****************************************************************************/ +/* DECODE */ + +/* interleaved samples: buf[0]=ch0, buf[1]=ch1, buf[2]=ch0, buf[3]=ch0, ... */ +typedef enum { + LIBVGMSTREAM_SAMPLE_PCM16 = 0x01, + LIBVGMSTREAM_SAMPLE_PCM24 = 0x02, + LIBVGMSTREAM_SAMPLE_PCM32 = 0x03, + LIBVGMSTREAM_SAMPLE_FLOAT = 0x04, +} libvgmstream_sample_t; + +/* current song info, may be copied around (values are info-only) */ +typedef struct { + /* main (always set) */ + int channels; // output channels + int sample_rate; // output sample rate + + libvgmstream_sample_t sample_type; // output buffer's sample type + int sample_size; // derived from sample_type (pcm16=0x02, float=0x04, etc) + + /* extra info (may be 0 if not known or not relevant) */ + uint32_t channel_layout; // standard WAVE bitflags + + int subsong_index; // 0 = none, N = loaded subsong N (1=first) + int subsong_count; // 0 = format has no concept of subsongs, N = has N subsongs + // ** 1 = format has subsongs, and only 1 for current file + + int input_channels; // original file's channels before downmixing (if any) + //int interleave; // when file is interleaved + //int interleave_first; // when file is interleaved + //int interleave_last; // when file is interleaved + //int frame_size; // when file has some configurable frame size + + /* sample info (may not be used depending on config) */ + int64_t stream_samples; // file's max samples (not final play duration) + int64_t loop_start; // loop start sample + int64_t loop_end; // loop end sample + bool loop_flag; // if file loops (false + defined loops means looping was forcefully disabled) + + bool play_forever; // if file loops forever based on current config (meaning _play never stops) + int64_t play_samples; // totals after all calculations (after applying loop/fade/etc config) + // ** may not be 100% accurate in some cases (check decoder's 'done' flag to stop) + // ** if play_forever is set this is still provided for reference based on non-forever config + + int stream_bitrate; // average bitrate of the subsong (slightly bloated vs codec_bitrate; incorrect in rare cases) + //int codec_bitrate; // average bitrate of the codec data + // ** not possible / slow to calculate in most cases + + /* descriptions */ + char codec_name[128]; // + char layout_name[128]; // + char meta_name[128]; // (not internal "tag" metadata) + char stream_name[256]; // some internal name or representation, not always useful + // ** these are a bit big for a struct, but the typical use case of vgsmtream is opening a file > immediately + // query description and since libvgmstream returns its own copy it shouldn't be too much of a problem + // ** (may be separated later) + + /* misc */ + //bool rough_samples; // signal cases where loop points or sample count can't exactly reflect actual behavior + + int format_id; // when reopening subfiles or similar formats without checking other all possible formats + // ** this value WILL change without warning between vgmstream versions/commits + +} libvgmstream_format_t; + +typedef struct { + void* buf; // current decoded buf (valid after _decode until next call; may change between calls) + int buf_samples; // current buffer samples (0 is possible in some cases) + int buf_bytes; // current buffer bytes (channels * sample_size * samples) + + bool done; // when stream is done based on config + // ** note that with play_forever this flag is never set +} libvgmstream_decoder_t; + +/* vgmstream context/handle */ +typedef struct { + void* priv; // internal data + + /* pointers for easier ABI compatibility */ + const libvgmstream_format_t* format; // current song info, updated on _open + libvgmstream_decoder_t* decoder; // updated on each _decode call + +} libvgmstream_t; + + + +/* Inits the vgmstream context + * - returns NULL on error + */ +LIBVGMSTREAM_API libvgmstream_t* libvgmstream_init(void); + +/* Frees the vgmstream context and any other internal stuff. + */ +LIBVGMSTREAM_API void libvgmstream_free(libvgmstream_t* lib); + + +/* configures how vgmstream behaves internally when playing a file */ +typedef struct { + bool disable_config_override; // ignore forced (TXTP) config + bool allow_play_forever; // must allow manually as some cases a TXTP may set loop forever but client may not handle it + + bool play_forever; // keeps looping forever (file must have loop_flag set) + bool ignore_loop; // ignores loops points + bool force_loop; // enables full loops (0..samples) if file doesn't have loop points + bool really_force_loop; // forces full loops (0..samples) even if file has loop points + bool ignore_fade; // don't fade after N loops and play remaning stream (for files with outros) + + double loop_count; // target loops (values like 1.5 are ok) + double fade_time; // fade period after target loops + double fade_delay; // fade delay after target loops + + int auto_downmix_channels; // downmixing if vgmstream's channels are higher than value + // ** for players that can only handle N channels + // ** this type of downmixing is very simplistic and not recommended + + bool force_pcm16; // forces output buffer to be remixed into PCM16 + +} libvgmstream_config_t; + +/* pass default config, that will be applied to song on open + * - invalid config or complex cases (ex. some TXTP) may ignore these settings. + * - called without a song loaded (before _open or after _close), otherwise ignored. + * - without config vgmstream will decode the current stream once + */ +LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_config_t* cfg); + + +/* configures how vgmstream opens the format */ +typedef struct { + libvgmstream_streamfile_t* libsf; // custom IO streamfile that provides reader info for vgmstream + // ** not needed after _open and should be closed, as vgmstream re-opens its own SFs internally as needed + + int subsong_index; // target subsong (1..N) or 0 = default/first + // ** to check if a file has subsongs, _open first + check format->total_subsongs (then _open 2nd, 3rd, etc) + + int format_id; // force a format (for example when loading new subsong of the same archive) + + int stereo_track; // forces vgmstream to decode one 2ch+2ch+2ch... 'track' and discard other channels, where 0 = disabled, 1..N = Nth track + +} libvgmstream_options_t; + +/* Opens file based on config and prepares it to play if supported. + * - returns < 0 on error (file not recognised, invalid subsong index, etc) + * - will close currently loaded song if needed + */ +LIBVGMSTREAM_API int libvgmstream_open_song(libvgmstream_t* lib, libvgmstream_options_t* open_options); + +/* Closes current song; may still use libvgmstream to open other songs + */ +LIBVGMSTREAM_API void libvgmstream_close_song(libvgmstream_t* lib); + + +/* Decodes next batch of samples + * - vgmstream supplies its own buffer, updated on lib->decoder->* values (may change between calls) + * - returns < 0 on error + */ +LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib); + +/* Same as _play, but fills some external buffer (also updates lib->decoder->* values) + * - returns < 0 on error, or N = number of filled samples. + * - buf must be at least as big as channels * sample_size * buf_samples + * - needs copying around from internal bufs so may be slightly slower; mainly for cases when you have buf constraints + */ +LIBVGMSTREAM_API int libvgmstream_fill(libvgmstream_t* lib, void* buf, int buf_samples); + +/* Gets current position within the song. + * - return < 0 on error + */ +LIBVGMSTREAM_API int64_t libvgmstream_get_play_position(libvgmstream_t* lib); + +/* Seeks to absolute position. Will clamp incorrect values such as seeking before/past playable length. + */ +LIBVGMSTREAM_API void libvgmstream_seek(libvgmstream_t* lib, int64_t sample); + +/* Reset current song + */ +LIBVGMSTREAM_API void libvgmstream_reset(libvgmstream_t* lib); + + + +/*****************************************************************************/ +/* HELPERS */ + +typedef enum { + LIBVGMSTREAM_LOG_LEVEL_ALL = 0, + LIBVGMSTREAM_LOG_LEVEL_DEBUG = 20, + LIBVGMSTREAM_LOG_LEVEL_INFO = 30, + LIBVGMSTREAM_LOG_LEVEL_NONE = 100, +} libvgmstream_loglevel_t; + +typedef struct { + libvgmstream_loglevel_t level; // log level + void (*callback)(int level, const char* str); // log callback + bool stdout_callback; // use default log callback rather than user supplied +} libvgmstream_log_t; + +/* Defines a global log callback, as vgmstream sometimes communicates format issues to the user. + * - note that log is currently set globally rather than per libvgmstream_t +*/ +LIBVGMSTREAM_API void libvgmstream_set_log(libvgmstream_log_t* cfg); + + +/* Returns a list of supported extensions (WARNING: it's pretty big), such as "adx", "dsp", etc. + * Mainly for plugins that want to know which extensions are supported. + * - returns NULL if no size is provided + */ +LIBVGMSTREAM_API const char** libvgmstream_get_extensions(size_t* size); + +/* Same as above, buf returns a list what vgmstream considers "common" formats (such as "wav", "ogg"), + * which usually one doesn't want to associate to vgmstream. + * - returns NULL if no size is provided + */ +LIBVGMSTREAM_API const char** libvgmstream_get_common_extensions(size_t* size); + + +typedef struct { + bool is_extension; /* set if filename is just an extension */ + bool skip_default; /* set if shouldn't check default formats */ + bool reject_extensionless; /* set if player can't play extensionless files */ + bool accept_unknown; /* set to allow any extension (for txth) */ + bool accept_common; /* set to allow known-but-common extension (when player has plugin priority) */ +} libvgmstream_valid_t; + +/* Returns if vgmstream can parse a filename by extension, to reject some files earlier + * - doesn't check file contents (that's only done on _open) + * - config may be NULL + * - mainly for plugins that fail early; libvgmstream doesn't use this + */ +LIBVGMSTREAM_API bool libvgmstream_is_valid(const char* filename, libvgmstream_valid_t* cfg); + + +typedef struct { + bool force_title; // TODO: what was this for? + bool subsong_range; // print a range of possible subsongs after title 'filename#1~N' + bool remove_extension; // remove extension from passed filename + bool remove_archive; // remove '(archive)|(subfile)' format of some plugins + const char* filename; // base file's filename + // ** note that sometimes vgmstream doesn't have/know the original name, so it's needed again here +} libvgmstream_title_t; + +/* Get a simple title for plugins, derived from internal stream name if available + * - valid after _open + */ +LIBVGMSTREAM_API int libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_title_t* cfg, char* buf, int buf_len); + +/* Writes a description of the current song into dst. Will always be null-terminated. + * - returns < 0 if file was truncated, though will still succeed. + */ +LIBVGMSTREAM_API int libvgmstream_format_describe(libvgmstream_t* lib, char* dst, int dst_size); + + + +/*****************************************************************************/ +/* TAGS */ + +/* Meant to be a simple implementation; feel free to ignore and roll your own (or use another tags plugin). + * Doesn't need a main libvgmstream as tags aren't tied to loaded songs. */ + +/* tag state */ +typedef struct { + void* priv; // internal data + + const char* key; // current key + const char* val; // current value +} libvgmstream_tags_t; + +/* Initializes tags. + * - libsf should point to a !tags.m3u file + * - unlike libvgmstream_open, sf tagfile must be valid during the tag extraction process. + */ +LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libvgmstream_streamfile_t* libsf); + +/* Finds tags for a new filename. Must be called first before extracting tags. + */ +LIBVGMSTREAM_API void libvgmstream_tags_find(libvgmstream_tags_t* tags, const char* target_filename); + +/* Extracts next valid tag in tagfile to key/val. + * - returns false if no more tags are found (meant to be called repeatedly until false) + * - key/values are trimmed of beginning/end whitespaces and values are in UTF-8 + */ +LIBVGMSTREAM_API bool libvgmstream_tags_next_tag(libvgmstream_tags_t* tags); + +/* Closes tags. */ +LIBVGMSTREAM_API void libvgmstream_tags_free(libvgmstream_tags_t* tags); + + +#endif +#endif diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index f739fb71..9252ad52 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -81,11 +81,8 @@ - - - - - + + @@ -170,6 +167,7 @@ + @@ -719,6 +717,8 @@ + + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 4ee205eb..919f4461 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -77,19 +77,10 @@ Header Files - + Header Files - - Header Files - - - Header Files - - - Header Files - - + Header Files @@ -344,6 +335,9 @@ meta\Header Files + + meta\Header Files + meta\Header Files @@ -1987,6 +1981,12 @@ meta\Source Files + + meta\Source Files + + + meta\Source Files + meta\Source Files diff --git a/src/api_streamfile.h b/src/libvgmstream_streamfile.h similarity index 51% rename from src/api_streamfile.h rename to src/libvgmstream_streamfile.h index ee830f60..19fdc497 100644 --- a/src/api_streamfile.h +++ b/src/libvgmstream_streamfile.h @@ -1,18 +1,13 @@ -#ifndef _API_STREAMFILE_H_ -#define _API_STREAMFILE_H_ -#include "api.h" +#ifndef _LIBVGMSTREAM_STREAMFILE_H_ +#define _LIBVGMSTREAM_STREAMFILE_H_ +#include "libvgmstream.h" #if LIBVGMSTREAM_ENABLE /* vgmstream's IO API, defined as a "streamfile" (SF). * - * Compared to typical IO, vgmstream has some extra needs that roughly assume there is an underlying filesystem (as usual in games): - * - seeking + reading from arbitrary offsets: header not in the beginning of a stream, rewinding back when looping, etc - * - opening other streamfiles: reopening a copy of the current SF, formats with split header + data, decryption files, etc - * - extracting the filename: opening similarly named companion files, basic extension sanity checks, heuristics for odd cases, etc - * - * If your IO can't fully satisfy those constraints, it may still be possible to create a streamfile that just simulates part of it. - * For example, returning a fake filename, and only handling "open" that reopens itself (same filename), while returning default/incorrect - * values for non-handled operations. Simpler formats will probably work just fine. + * vgmstream roughly assumes there is an underlying filesystem (as usual in games): seeking + reading from arbitrary offsets, + * opening companion files, filename tests, etc. If your case is too different you may still create a partial streamfile: returning + * a fake filename, only handling "open" that reopens itself (same filename), etc. Simpler formats will probably work just fine. */ @@ -28,26 +23,26 @@ typedef struct libvgmstream_streamfile_t { //uint32_t flags; // info flags for vgmstream void* user_data; // any internal structure - /* read 'length' data at internal offset to 'dst' (implicit seek if needed) - * - assumes 0 = failure/EOF + /* read 'length' data at internal offset to 'dst' + * - assumes 0 = failure/EOF */ int (*read)(void* user_data, uint8_t* dst, int dst_size); /* seek to offset - * - note that due to how vgmstream works this is a fairly common operation (to be optimized later) + * - note that vgmstream needs to seek + read fairly often (to be optimized later) */ int64_t (*seek)(void* user_data, int64_t offset, int whence); - /* get max offset + /* get max offset (typically for checks or calculations) */ int64_t (*get_size)(void* user_data); - /* get current filename + /* get current filename (used to open same or other streamfiles and heuristics; no need to be a real path) */ const char* (*get_name)(void* user_data); /* open another streamfile from filename (may be some path/protocol, or same as current get_name = reopen) - * - vgmstream mainly opens stuff based on current get_name (relative), so there shouldn't be need to transform this path + * - vgmstream opens stuff based on current get_name (relative), so there shouldn't be need to transform this path */ struct libvgmstream_streamfile_t* (*open)(void* user_data, const char* filename); diff --git a/src/meta/akb.c b/src/meta/akb.c index df763f07..8e36ffd9 100644 --- a/src/meta/akb.c +++ b/src/meta/akb.c @@ -147,7 +147,6 @@ VGMSTREAM* init_vgmstream_akb(STREAMFILE* sf) { #ifdef VGM_USE_FFMPEG case 0x06: { /* M4A with AAC [The World Ends with You (iPad)] */ - /* init_vgmstream_akb_mp4 above has priority, but this works fine too */ vgmstream->codec_data = init_ffmpeg_offset(sf, start_offset,stream_size-start_offset); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; diff --git a/src/meta/ea_eaac_tmx.c b/src/meta/ea_eaac_tmx.c index 50b47287..f2f20baf 100644 --- a/src/meta/ea_eaac_tmx.c +++ b/src/meta/ea_eaac_tmx.c @@ -16,7 +16,7 @@ VGMSTREAM* init_vgmstream_ea_tmx(STREAMFILE* sf) { if (is_id32be(0x0c, sf, "0001")) { read_u32 = read_u32be; } - else if (is_id32le(0x0c, sf, "1000")) { + else if (is_id32le(0x0c, sf, "0001")) { read_u32 = read_u32le; } else { diff --git a/src/meta/meta.h b/src/meta/meta.h index 702092ef..1721fef1 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -5,6 +5,7 @@ #include "../util/reader_sf.h" #include "../util/reader_text.h" #include "../util/sf_utils.h" +#include "../util/log.h" typedef VGMSTREAM* (*init_vgmstream_t)(STREAMFILE* sf); @@ -162,8 +163,7 @@ VGMSTREAM* init_vgmstream_mp4_aac_ffmpeg(STREAMFILE* sf); #endif #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) -VGMSTREAM * init_vgmstream_mp4_aac(STREAMFILE * streamFile); -VGMSTREAM * init_vgmstream_mp4_aac_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size); +VGMSTREAM* init_vgmstream_mp4_aac(STREAMFILE* sf); #endif VGMSTREAM* init_vgmstream_sli_loops(STREAMFILE* sf); diff --git a/src/meta/mp4_faac.c b/src/meta/mp4_faac.c index 379b4232..2ba769d8 100644 --- a/src/meta/mp4_faac.c +++ b/src/meta/mp4_faac.c @@ -2,159 +2,42 @@ #include "../coding/coding.h" #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) -// VGM_USE_MP4V2 -void* mp4_file_open( const char* name, MP4FileMode mode ) -{ - char * endptr; -#ifdef _MSC_VER - unsigned __int64 ptr = _strtoui64( name, &endptr, 16 ); -#else - unsigned long ptr = strtoul( name, &endptr, 16 ); -#endif - return (void*) ptr; -} - -int mp4_file_seek( void* handle, int64_t pos ) -{ - mp4_streamfile * file = ( mp4_streamfile * ) handle; - if ( pos > file->size ) pos = file->size; - pos += file->start; - file->offset = pos; - return 0; -} - -int mp4_file_get_size( void* handle, int64_t* size ) -{ - mp4_streamfile * file = ( mp4_streamfile * ) handle; - *size = file->size; - return 0; -} - -int mp4_file_read( void* handle, void* buffer, int64_t size, int64_t* nin, int64_t maxChunkSize ) -{ - mp4_streamfile * file = ( mp4_streamfile * ) handle; - int64_t max_size = file->size - file->offset - file->start; - if ( size > max_size ) size = max_size; - if ( size > 0 ) - { - *nin = read_streamfile( (uint8_t *) buffer, file->offset, size, file->streamfile ); - file->offset += *nin; - } - else - { - *nin = 0; - return 1; - } - return 0; -} - -int mp4_file_write( void* handle, const void* buffer, int64_t size, int64_t* nout, int64_t maxChunkSize ) -{ - return 1; -} - -int mp4_file_close( void* handle ) -{ - return 0; -} - -MP4FileProvider mp4_file_provider = { mp4_file_open, mp4_file_seek, mp4_file_read, mp4_file_write, mp4_file_close, mp4_file_get_size }; // VGM_USE_FDKAAC -VGMSTREAM * init_vgmstream_mp4_aac_offset(STREAMFILE *sf, uint64_t start, uint64_t size); +static VGMSTREAM* init_vgmstream_mp4_aac_offset(STREAMFILE* sf, uint64_t start, uint64_t size); -VGMSTREAM * init_vgmstream_mp4_aac(STREAMFILE *sf) { - return init_vgmstream_mp4_aac_offset( sf, 0, sf->get_size(sf) ); +VGMSTREAM* init_vgmstream_mp4_aac(STREAMFILE* sf) { + return init_vgmstream_mp4_aac_offset( sf, 0x00, get_streamfile_size(sf)); } -VGMSTREAM * init_vgmstream_mp4_aac_offset(STREAMFILE *sf, uint64_t start, uint64_t size) { - VGMSTREAM * vgmstream = NULL; +static VGMSTREAM* init_vgmstream_mp4_aac_offset(STREAMFILE* sf, uint64_t start, uint64_t size) { + VGMSTREAM* vgmstream = NULL; + mp4_aac_codec_data* data = NULL; - char filename[PATH_LIMIT]; + data = init_mp4_aac(sf); + if (!data) goto fail; - mp4_aac_codec_data * aac_file = ( mp4_aac_codec_data * ) calloc(1, sizeof(mp4_aac_codec_data)); + int channels = mp4_aac_get_channels(data); - CStreamInfo * stream_info; - - uint8_t * buffer = NULL; - uint32_t buffer_size; - UINT ubuffer_size, bytes_valid; - - if ( !aac_file ) goto fail; - - aac_file->if_file.streamfile = sf; - aac_file->if_file.start = start; - aac_file->if_file.offset = start; - aac_file->if_file.size = size; - - /* Big ol' kludge! */ - sprintf( filename, "%p", &aac_file->if_file ); - aac_file->h_mp4file = MP4ReadProvider( filename, &mp4_file_provider ); - if ( !aac_file->h_mp4file ) goto fail; - - if ( MP4GetNumberOfTracks(aac_file->h_mp4file, MP4_AUDIO_TRACK_TYPE, '\000') != 1 ) goto fail; - - aac_file->track_id = MP4FindTrackId( aac_file->h_mp4file, 0, MP4_AUDIO_TRACK_TYPE, '\000' ); - - aac_file->h_aacdecoder = aacDecoder_Open( TT_MP4_RAW, 1 ); - if ( !aac_file->h_aacdecoder ) goto fail; - - MP4GetTrackESConfiguration( aac_file->h_mp4file, aac_file->track_id, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size)); - - ubuffer_size = buffer_size; - if ( aacDecoder_ConfigRaw( aac_file->h_aacdecoder, &buffer, &ubuffer_size ) ) goto fail; - - free( buffer ); buffer = NULL; - - aac_file->sampleId = 1; - aac_file->numSamples = MP4GetTrackNumberOfSamples( aac_file->h_mp4file, aac_file->track_id ); - - if (!MP4ReadSample(aac_file->h_mp4file, aac_file->track_id, aac_file->sampleId, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size), 0, 0, 0, 0)) goto fail; - - ubuffer_size = buffer_size; - bytes_valid = buffer_size; - if ( aacDecoder_Fill( aac_file->h_aacdecoder, &buffer, &ubuffer_size, &bytes_valid ) || bytes_valid ) goto fail; - if ( aacDecoder_DecodeFrame( aac_file->h_aacdecoder, aac_file->sample_buffer, ( (6) * (2048)*4 ), 0 ) ) goto fail; - - free( buffer ); buffer = NULL; - - aac_file->sample_ptr = 0; - - stream_info = aacDecoder_GetStreamInfo( aac_file->h_aacdecoder ); - - aac_file->samples_per_frame = stream_info->frameSize; - aac_file->samples_discard = 0; - - sf->get_name( sf, filename, sizeof(filename) ); - - aac_file->if_file.streamfile = sf->open(sf, filename, STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!aac_file->if_file.streamfile) goto fail; - - vgmstream = allocate_vgmstream( stream_info->numChannels, 1 ); + vgmstream = allocate_vgmstream(channels, 0); if (!vgmstream) goto fail; - vgmstream->loop_flag = 0; - - vgmstream->codec_data = aac_file; - - vgmstream->channels = stream_info->numChannels; - vgmstream->sample_rate = stream_info->sampleRate; - - vgmstream->num_samples = stream_info->frameSize * aac_file->numSamples; + vgmstream->sample_rate = mp4_aac_get_sample_rate(data); + vgmstream->num_samples = mp4_aac_get_samples(data); + vgmstream->codec_data = data; vgmstream->coding_type = coding_MP4_AAC; + vgmstream->layout_type = layout_none; vgmstream->meta_type = meta_MP4; return vgmstream; - fail: - if ( buffer ) free( buffer ); - if ( aac_file ) { - if ( aac_file->h_aacdecoder ) aacDecoder_Close( aac_file->h_aacdecoder ); - if ( aac_file->h_mp4file ) MP4Close( aac_file->h_mp4file, 0 ); - free( aac_file ); - } + free_mp4_aac(data); + if (vgmstream) { + vgmstream->codec_data = NULL; + close_vgmstream(vgmstream); + } return NULL; } #endif diff --git a/src/meta/txtp.c b/src/meta/txtp.c index 64e4c4f8..cb736f7c 100644 --- a/src/meta/txtp.c +++ b/src/meta/txtp.c @@ -1,210 +1,48 @@ #include "meta.h" -#include "../coding/coding.h" -#include "../layout/layout.h" -#include "../base/mixing.h" -#include "../base/plugins.h" -#include "../util/text_reader.h" -#include "../util/paths.h" - -#include - - -#define TXT_LINE_MAX 2048 /* some wwise .txtp get wordy */ -#define TXT_LINE_KEY_MAX 128 -#define TXT_LINE_VAL_MAX (TXT_LINE_MAX - TXT_LINE_KEY_MAX) -#define TXTP_MIXING_MAX 512 -#define TXTP_GROUP_MODE_SEGMENTED 'S' -#define TXTP_GROUP_MODE_LAYERED 'L' -#define TXTP_GROUP_MODE_RANDOM 'R' -#define TXTP_GROUP_RANDOM_ALL '-' -#define TXTP_GROUP_REPEAT 'R' -#define TXTP_POSITION_LOOPS 'L' - -/* mixing info */ -typedef enum { - MIX_SWAP, - MIX_ADD, - MIX_ADD_VOLUME, - MIX_VOLUME, - MIX_LIMIT, - MIX_DOWNMIX, - MIX_KILLMIX, - MIX_UPMIX, - MIX_FADE, - - MACRO_VOLUME, - MACRO_TRACK, - MACRO_LAYER, - MACRO_CROSSTRACK, - MACRO_CROSSLAYER, - MACRO_DOWNMIX, - -} txtp_mix_t; - -typedef struct { - txtp_mix_t command; - /* common */ - int ch_dst; - int ch_src; - double vol; - - /* fade envelope */ - double vol_start; - double vol_end; - char shape; - int32_t sample_pre; - int32_t sample_start; - int32_t sample_end; - int32_t sample_post; - double time_pre; - double time_start; - double time_end; - double time_post; - double position; - char position_type; - - /* macros */ - int max; - uint32_t mask; - char mode; -} txtp_mix_data; - - -typedef struct { - /* main entry */ - char filename[TXT_LINE_MAX]; - int silent; - - /* TXTP settings (applied at the end) */ - int range_start; - int range_end; - int subsong; - - uint32_t channel_mask; - - int mixing_count; - txtp_mix_data mixing[TXTP_MIXING_MAX]; - - play_config_t config; - - int sample_rate; - - int loop_install_set; - int loop_end_max; - double loop_start_second; - int32_t loop_start_sample; - double loop_end_second; - int32_t loop_end_sample; - /* flags */ - int loop_anchor_start; - int loop_anchor_end; - - int trim_set; - double trim_second; - int32_t trim_sample; - -} txtp_entry; - - -typedef struct { - int position; - char type; - int count; - char repeat; - int selected; - - txtp_entry entry; - -} txtp_group; - -typedef struct { - txtp_entry* entry; - size_t entry_count; - size_t entry_max; - - txtp_group* group; - size_t group_count; - size_t group_max; - int group_pos; /* entry counter for groups */ - - VGMSTREAM** vgmstream; - size_t vgmstream_count; - - uint32_t loop_start_segment; - uint32_t loop_end_segment; - int is_loop_keep; - int is_loop_auto; - - txtp_entry default_entry; - int default_entry_set; - - int is_segmented; - int is_layered; - int is_single; -} txtp_header; - -static txtp_header* parse_txtp(STREAMFILE* sf); -static int parse_entries(txtp_header* txtp, STREAMFILE* sf); -static int parse_groups(txtp_header* txtp); -static void clean_txtp(txtp_header* txtp, int fail); -static void apply_settings(VGMSTREAM* vgmstream, txtp_entry* current); -void add_mixing(txtp_entry* cfg, txtp_mix_data* mix, txtp_mix_t command); +#include "txtp.h" /* TXTP - an artificial playlist-like format to play files with segments/layers/config */ VGMSTREAM* init_vgmstream_txtp(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; - txtp_header* txtp = NULL; - int ok; - + txtp_header_t* txtp = NULL; + bool ok; /* checks */ if (!check_extensions(sf, "txtp")) goto fail; /* read .txtp with all files and settings */ - txtp = parse_txtp(sf); + txtp = txtp_parse(sf); if (!txtp) goto fail; - /* process files in the .txtp */ - ok = parse_entries(txtp, sf); + /* apply settings to get final vgmstream */ + ok = txtp_process(txtp, sf); if (!ok) goto fail; - /* group files into layouts */ - ok = parse_groups(txtp); - if (!ok) goto fail; - - - /* may happen if using mixed mode but some files weren't grouped */ - if (txtp->vgmstream_count != 1) { - VGM_LOG("TXTP: wrong final vgmstream count %i\n", txtp->vgmstream_count); - goto fail; - } /* should result in a final, single vgmstream possibly containing multiple vgmstreams */ vgmstream = txtp->vgmstream[0]; + txtp->vgmstream[0] = NULL; /* flags for title config */ - vgmstream->config.is_txtp = 1; + vgmstream->config.is_txtp = true; vgmstream->config.is_mini_txtp = (get_streamfile_size(sf) == 0); - clean_txtp(txtp, 0); + txtp_clean(txtp); return vgmstream; fail: - clean_txtp(txtp, 1); + txtp_clean(txtp); return NULL; } -static void clean_txtp(txtp_header* txtp, int fail) { - int i, start; +void txtp_clean(txtp_header_t* txtp) { if (!txtp) return; - /* returns first vgmstream on success so it's not closed */ - start = fail ? 0 : 1; - - for (i = start; i < txtp->vgmstream_count; i++) { + /* first vgmstream may be NULL on success */ + for (int i = 0; i < txtp->vgmstream_count; i++) { close_vgmstream(txtp->vgmstream[i]); } @@ -214,507 +52,21 @@ static void clean_txtp(txtp_header* txtp, int fail) { free(txtp); } -//todo fragment parser later -/*******************************************************************************/ -/* ENTRIES */ -/*******************************************************************************/ - -static int parse_silents(txtp_header* txtp) { - int i; - VGMSTREAM* v_base = NULL; - - /* silents use same channels as close files */ - for (i = 0; i < txtp->vgmstream_count; i++) { - if (!txtp->entry[i].silent) { - v_base = txtp->vgmstream[i]; - break; - } - } - - /* actually open silents */ - for (i = 0; i < txtp->vgmstream_count; i++) { - if (!txtp->entry[i].silent) - continue; - - txtp->vgmstream[i] = init_vgmstream_silence_base(v_base); - if (!txtp->vgmstream[i]) goto fail; - - apply_settings(txtp->vgmstream[i], &txtp->entry[i]); - } - - return 1; -fail: - return 0; -} - -static int is_silent(const char* fn) { - /* should also contain "." in the filename for commands with seconds ("1.0") to work */ - return fn[0] == '?'; -} - -static int is_absolute(const char* fn) { - return fn[0] == '/' || fn[0] == '\\' || fn[1] == ':'; -} - -/* open all entries and apply settings to resulting VGMSTREAMs */ -static int parse_entries(txtp_header* txtp, STREAMFILE* sf) { - int i; - int has_silents = 0; - - - if (txtp->entry_count == 0) - goto fail; - - txtp->vgmstream = calloc(txtp->entry_count, sizeof(VGMSTREAM*)); - if (!txtp->vgmstream) goto fail; - - txtp->vgmstream_count = txtp->entry_count; - - - /* open all entry files first as they'll be modified by modes */ - for (i = 0; i < txtp->vgmstream_count; i++) { - STREAMFILE* temp_sf = NULL; - const char* filename = txtp->entry[i].filename; - - /* silent entry ignore */ - if (is_silent(filename)) { - txtp->entry[i].silent = 1; - has_silents = 1; - continue; - } - - /* absolute paths are detected for convenience, but since it's hard to unify all OSs - * and plugins, they aren't "officially" supported nor documented, thus may or may not work */ - if (is_absolute(filename)) - temp_sf = open_streamfile(sf, filename); /* from path as is */ - else - temp_sf = open_streamfile_by_filename(sf, filename); /* from current path */ - if (!temp_sf) { - vgm_logi("TXTP: cannot open %s\n", filename); - goto fail; - } - temp_sf->stream_index = txtp->entry[i].subsong; - - txtp->vgmstream[i] = init_vgmstream_from_STREAMFILE(temp_sf); - close_streamfile(temp_sf); - if (!txtp->vgmstream[i]) { - vgm_logi("TXTP: cannot parse %s#%i\n", filename, txtp->entry[i].subsong); - goto fail; - } - - apply_settings(txtp->vgmstream[i], &txtp->entry[i]); - } - - if (has_silents) { - if (!parse_silents(txtp)) - goto fail; - } - - return 1; -fail: - return 0; -} - - -/*******************************************************************************/ -/* GROUPS */ -/*******************************************************************************/ - -static void update_vgmstream_list(VGMSTREAM* vgmstream, txtp_header* txtp, int position, int count) { - int i; - - //;VGM_LOG("TXTP: compact position=%i count=%i, vgmstreams=%i\n", position, count, txtp->vgmstream_count); - - /* sets and compacts vgmstream list pulling back all following entries */ - txtp->vgmstream[position] = vgmstream; - for (i = position + count; i < txtp->vgmstream_count; i++) { - //;VGM_LOG("TXTP: copy %i to %i\n", i, i + 1 - count); - txtp->vgmstream[i + 1 - count] = txtp->vgmstream[i]; - txtp->entry[i + 1 - count] = txtp->entry[i]; /* memcpy old settings for other groups */ - } - - /* list can only become smaller, no need to alloc/free/etc */ - txtp->vgmstream_count = txtp->vgmstream_count + 1 - count; - //;VGM_LOG("TXTP: compact vgmstreams=%i\n", txtp->vgmstream_count); -} - -static int find_loop_anchors(txtp_header* txtp, int position, int count, int* p_loop_start, int* p_loop_end) { - int loop_start = 0, loop_end = 0; - int i, j; - - //;VGM_LOG("TXTP: find loop anchors from %i to %i\n", position, count); - - for (i = position, j = 0; i < position + count; i++, j++) { - /* catch first time anchors appear only, also logic elsewhere also uses +1 */ - if (txtp->entry[i].loop_anchor_start && !loop_start) { - loop_start = j + 1; - } - if (txtp->entry[i].loop_anchor_end && !loop_end) { - loop_end = j + 1; - } - } - - if (loop_start) { - if (!loop_end) - loop_end = count; - *p_loop_start = loop_start; - *p_loop_end = loop_end; - //;VGM_LOG("TXTP: loop anchors %i, %i\n", loop_start, loop_end); - return 1; - } - - return 0; -} - - -static int make_group_segment(txtp_header* txtp, txtp_group* grp, int position, int count) { - VGMSTREAM* vgmstream = NULL; - segmented_layout_data *data_s = NULL; - int i, loop_flag = 0; - int loop_start = 0, loop_end = 0; - - - /* allowed for actual groups (not final "mode"), otherwise skip to optimize */ - if (!grp && count == 1) { - //;VGM_LOG("TXTP: ignored single group\n"); - return 1; - } - - if (position + count > txtp->vgmstream_count || position < 0 || count < 0) { - VGM_LOG("TXTP: ignored segment position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count); - return 1; - } - - - /* set loops with "anchors" (this allows loop config inside groups, not just in the final group, - * which is sometimes useful when paired with random/selectable groups or loop times) */ - if (find_loop_anchors(txtp, position, count, &loop_start, &loop_end)) { - loop_flag = (loop_start > 0 && loop_start <= count); - } - /* loop segment settings only make sense if this group becomes final vgmstream */ - else if (position == 0 && txtp->vgmstream_count == count) { - loop_start = txtp->loop_start_segment; - loop_end = txtp->loop_end_segment; - - if (loop_start && !loop_end) { - loop_end = count; - } - else if (txtp->is_loop_auto) { /* auto set to last segment */ - loop_start = count; - loop_end = count; - } - loop_flag = (loop_start > 0 && loop_start <= count); - } - - - /* fix loop keep (do it before init'ing as loops/metadata may be disabled for segments) */ - int32_t loop_start_sample = 0, loop_end_sample = 0; - if (loop_flag && txtp->is_loop_keep) { - int32_t current_samples = 0; - for (i = 0; i < count; i++) { - if (loop_start == i+1 /*&& txtp->vgmstream[i + position]->loop_start_sample*/) { - loop_start_sample = current_samples + txtp->vgmstream[i + position]->loop_start_sample; - } - - current_samples += txtp->vgmstream[i + position]->num_samples; - - if (loop_end == i+1 && txtp->vgmstream[i + position]->loop_end_sample) { - loop_end_sample = current_samples - txtp->vgmstream[i + position]->num_samples + txtp->vgmstream[i + position]->loop_end_sample; - } - } - } - - /* init layout */ - data_s = init_layout_segmented(count); - if (!data_s) goto fail; - - /* copy each subfile */ - for (i = 0; i < count; i++) { - data_s->segments[i] = txtp->vgmstream[i + position]; - txtp->vgmstream[i + position] = NULL; /* will be freed by layout */ - } - - /* setup VGMSTREAMs */ - if (!setup_layout_segmented(data_s)) - goto fail; - - /* build the layout VGMSTREAM */ - vgmstream = allocate_segmented_vgmstream(data_s, loop_flag, loop_start - 1, loop_end - 1); - if (!vgmstream) goto fail; - - /* custom meta name if all parts don't match */ - for (i = 0; i < count; i++) { - if (vgmstream->meta_type != data_s->segments[i]->meta_type) { - vgmstream->meta_type = meta_TXTP; - break; - } - } - - /* fix loop keep */ - if (loop_flag && txtp->is_loop_keep) { - vgmstream->loop_start_sample = loop_start_sample; - vgmstream->loop_end_sample = loop_end_sample; - } - - - /* set new vgmstream and reorder positions */ - update_vgmstream_list(vgmstream, txtp, position, count); - - - /* special "whole loop" settings */ - if (grp && grp->entry.loop_anchor_start == 1) { - grp->entry.config.config_set = 1; - grp->entry.config.really_force_loop = 1; - } - - return 1; -fail: - close_vgmstream(vgmstream); - if (!vgmstream) - free_layout_segmented(data_s); - return 0; -} - -static int make_group_layer(txtp_header* txtp, txtp_group* grp, int position, int count) { - VGMSTREAM* vgmstream = NULL; - layered_layout_data* data_l = NULL; - int i; - - - /* allowed for actual groups (not final mode), otherwise skip to optimize */ - if (!grp && count == 1) { - //;VGM_LOG("TXTP: ignored single group\n"); - return 1; - } - - if (position + count > txtp->vgmstream_count || position < 0 || count < 0) { - VGM_LOG("TXTP: ignored layer position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count); - return 1; - } - - - /* init layout */ - data_l = init_layout_layered(count); - if (!data_l) goto fail; - - /* copy each subfile */ - for (i = 0; i < count; i++) { - data_l->layers[i] = txtp->vgmstream[i + position]; - txtp->vgmstream[i + position] = NULL; /* will be freed by layout */ - } - - /* setup VGMSTREAMs */ - if (!setup_layout_layered(data_l)) - goto fail; - - /* build the layout VGMSTREAM */ - vgmstream = allocate_layered_vgmstream(data_l); - if (!vgmstream) goto fail; - - /* custom meta name if all parts don't match */ - for (i = 0; i < count; i++) { - if (vgmstream->meta_type != data_l->layers[i]->meta_type) { - vgmstream->meta_type = meta_TXTP; - break; - } - } - - /* set new vgmstream and reorder positions */ - update_vgmstream_list(vgmstream, txtp, position, count); - - - /* special "whole loop" settings (also loop if this group becomes final vgmstream) */ - if (grp && (grp->entry.loop_anchor_start == 1 - || (position == 0 && txtp->vgmstream_count == count && txtp->is_loop_auto))) { - grp->entry.config.config_set = 1; - grp->entry.config.really_force_loop = 1; - } - - return 1; -fail: - close_vgmstream(vgmstream); - if (!vgmstream) - free_layout_layered(data_l); - return 0; -} - -static int make_group_random(txtp_header* txtp, txtp_group* grp, int position, int count, int selected) { - VGMSTREAM* vgmstream = NULL; - int i; - - /* allowed for actual groups (not final mode), otherwise skip to optimize */ - if (!grp && count == 1) { - //;VGM_LOG("TXTP: ignored single group\n"); - return 1; - } - - if (position + count > txtp->vgmstream_count || position < 0 || count < 0) { - VGM_LOG("TXTP: ignored random position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count); - return 1; - } - - /* 0=actually random for fun and testing, but undocumented since random music is kinda weird, may change anytime - * (plus foobar caches song duration unless .txtp is modifies, so it can get strange if randoms are too different) */ - if (selected < 0) { - static int random_seed = 0; - srand((unsigned)txtp + random_seed++); /* whatevs */ - selected = (rand() % count); /* 0..count-1 */ - //;VGM_LOG("TXTP: autoselected random %i\n", selected); - } - - if (selected < 0 || selected > count) { - goto fail; - } - - if (selected == count) { - /* special case meaning "select all", basically for quick testing and clearer Wwise */ - if (!make_group_segment(txtp, grp, position, count)) - goto fail; - vgmstream = txtp->vgmstream[position]; - } - else { - /* get selected and remove non-selected */ - vgmstream = txtp->vgmstream[position + selected]; - txtp->vgmstream[position + selected] = NULL; - for (i = 0; i < count; i++) { - close_vgmstream(txtp->vgmstream[i + position]); - } - - /* set new vgmstream and reorder positions */ - update_vgmstream_list(vgmstream, txtp, position, count); - } - - - /* special "whole loop" settings */ - if (grp && grp->entry.loop_anchor_start == 1) { - grp->entry.config.config_set = 1; - grp->entry.config.really_force_loop = 1; - } - - /* force selected vgmstream to be a segment when not a group already, and - * group + vgmstream has config (AKA must loop/modify over the result) */ - //todo could optimize to not generate segment in some cases? - if (grp && - !(vgmstream->layout_type == layout_layered || vgmstream->layout_type == layout_segmented) && - (grp->entry.config.config_set && vgmstream->config.config_set) ) { - if (!make_group_segment(txtp, grp, position, 1)) - goto fail; - } - - return 1; -fail: - close_vgmstream(vgmstream); - return 0; -} - -static int parse_groups(txtp_header* txtp) { - int i; - - /* detect single files before grouping */ - if (txtp->group_count == 0 && txtp->vgmstream_count == 1) { - txtp->is_single = 1; - txtp->is_segmented = 0; - txtp->is_layered = 0; - } - - /* group files as needed */ - for (i = 0; i < txtp->group_count; i++) { - txtp_group *grp = &txtp->group[i]; - int pos, groups; - - //;VGM_LOG("TXTP: apply group %i%c%i%c\n",txtp->group[i].position,txtp->group[i].type,txtp->group[i].count,txtp->group[i].repeat); - - /* special meaning of "all files" */ - if (grp->position < 0 || grp->position >= txtp->vgmstream_count) - grp->position = 0; - if (grp->count <= 0) - grp->count = txtp->vgmstream_count - grp->position; - - /* repeats N groups (trailing files are not grouped) */ - if (grp->repeat == TXTP_GROUP_REPEAT) { - groups = ((txtp->vgmstream_count - grp->position) / grp->count); - } - else { - groups = 1; - } - - /* as groups are compacted position goes 1 by 1 */ - for (pos = grp->position; pos < grp->position + groups; pos++) { - //;VGM_LOG("TXTP: group=%i, count=%i, groups=%i\n", pos, grp->count, groups); - switch(grp->type) { - case TXTP_GROUP_MODE_LAYERED: - if (!make_group_layer(txtp, grp, pos, grp->count)) - goto fail; - break; - case TXTP_GROUP_MODE_SEGMENTED: - if (!make_group_segment(txtp, grp, pos, grp->count)) - goto fail; - break; - case TXTP_GROUP_MODE_RANDOM: - if (!make_group_random(txtp, grp, pos, grp->count, grp->selected)) - goto fail; - break; - default: - goto fail; - } - } - - - /* group may also have settings (like downmixing) */ - apply_settings(txtp->vgmstream[grp->position], &grp->entry); - txtp->entry[grp->position] = grp->entry; /* memcpy old settings for subgroups */ - } - - /* final tweaks (should be integrated with the above?) */ - if (txtp->is_layered) { - if (!make_group_layer(txtp, NULL, 0, txtp->vgmstream_count)) - goto fail; - } - if (txtp->is_segmented) { - if (!make_group_segment(txtp, NULL, 0, txtp->vgmstream_count)) - goto fail; - } - if (txtp->is_single) { - /* special case of setting start_segment to force/overwrite looping - * (better to use #E but left for compatibility with older TXTPs) */ - if (txtp->loop_start_segment == 1 && !txtp->loop_end_segment) { - //todo try look settings - //txtp->default_entry.config.config_set = 1; - //txtp->default_entry.config.really_force_loop = 1; - vgmstream_force_loop(txtp->vgmstream[0], 1, txtp->vgmstream[0]->loop_start_sample, txtp->vgmstream[0]->num_samples); - } - } - - /* apply default settings to the resulting file */ - if (txtp->default_entry_set) { - apply_settings(txtp->vgmstream[0], &txtp->default_entry); - } - - return 1; -fail: - return 0; -} - - -/*******************************************************************************/ -/* CONFIG */ -/*******************************************************************************/ - -static void copy_flag(int* dst_flag, int* src_flag) { +static void copy_flag(bool* dst_flag, bool* src_flag) { if (!*src_flag) return; *dst_flag = 1; } -static void copy_secs(int* dst_flag, double* dst_secs, int* src_flag, double* src_secs) { +static void copy_secs(bool* dst_flag, double* dst_secs, bool* src_flag, double* src_secs) { if (!*src_flag) return; *dst_flag = 1; *dst_secs = *src_secs; } -static void copy_time(int* dst_flag, int32_t* dst_time, double* dst_time_s, int* src_flag, int32_t* src_time, double* src_time_s) { +static void copy_time(bool* dst_flag, int32_t* dst_time, double* dst_time_s, bool* src_flag, int32_t* src_time, double* src_time_s) { if (!*src_flag) return; *dst_flag = 1; @@ -722,7 +74,7 @@ static void copy_time(int* dst_flag, int32_t* dst_time, double* dst_time_s, int* *dst_time_s = *src_time_s; } -static void copy_config(play_config_t* dst, play_config_t* src) { +void txtp_copy_config(play_config_t* dst, play_config_t* src) { if (!src->config_set) return; @@ -757,459 +109,8 @@ static void init_config(VGMSTREAM* vgmstream) { } #endif -static void apply_settings(VGMSTREAM* vgmstream, txtp_entry* current) { - /* base settings */ - if (current->sample_rate > 0) { - vgmstream->sample_rate = current->sample_rate; - } - - if (current->loop_install_set) { - if (current->loop_start_second > 0 || current->loop_end_second > 0) { - current->loop_start_sample = current->loop_start_second * vgmstream->sample_rate; - current->loop_end_sample = current->loop_end_second * vgmstream->sample_rate; - if (current->loop_end_sample > vgmstream->num_samples && - current->loop_end_sample - vgmstream->num_samples <= 0.1 * vgmstream->sample_rate) - current->loop_end_sample = vgmstream->num_samples; /* allow some rounding leeway */ - } - - if (current->loop_end_max) { - current->loop_end_sample = vgmstream->num_samples; - } - - vgmstream_force_loop(vgmstream, current->loop_install_set, current->loop_start_sample, current->loop_end_sample); - } - - if (current->trim_set) { - if (current->trim_second != 0.0) { - /* trim sample can become 0 here when second is too small (rounded) */ - current->trim_sample = (double)current->trim_second * (double)vgmstream->sample_rate; - } - - if (current->trim_sample < 0) { - vgmstream->num_samples += current->trim_sample; /* trim from end (add negative) */ - } - else if (current->trim_sample > 0 && vgmstream->num_samples > current->trim_sample) { - vgmstream->num_samples = current->trim_sample; /* trim to value >0 */ - } - - /* readjust after triming if it went over (could check for more edge cases but eh) */ - if (vgmstream->loop_end_sample > vgmstream->num_samples) - vgmstream->loop_end_sample = vgmstream->num_samples; - } - - - /* add macro to mixing list */ - if (current->channel_mask) { - int ch; - for (ch = 0; ch < vgmstream->channels; ch++) { - if (!((current->channel_mask >> ch) & 1)) { - txtp_mix_data mix = {0}; - mix.ch_dst = ch + 1; - mix.vol = 0.0f; - add_mixing(current, &mix, MIX_VOLUME); - } - } - } - - /* copy mixing list (should be done last as some mixes depend on config) */ - if (current->mixing_count > 0) { - int m, position_samples; - - for (m = 0; m < current->mixing_count; m++) { - txtp_mix_data *mix = ¤t->mixing[m]; - - switch(mix->command) { - /* base mixes */ - case MIX_SWAP: mixing_push_swap(vgmstream, mix->ch_dst, mix->ch_src); break; - case MIX_ADD: mixing_push_add(vgmstream, mix->ch_dst, mix->ch_src, 1.0); break; - case MIX_ADD_VOLUME: mixing_push_add(vgmstream, mix->ch_dst, mix->ch_src, mix->vol); break; - case MIX_VOLUME: mixing_push_volume(vgmstream, mix->ch_dst, mix->vol); break; - case MIX_LIMIT: mixing_push_limit(vgmstream, mix->ch_dst, mix->vol); break; - case MIX_UPMIX: mixing_push_upmix(vgmstream, mix->ch_dst); break; - case MIX_DOWNMIX: mixing_push_downmix(vgmstream, mix->ch_dst); break; - case MIX_KILLMIX: mixing_push_killmix(vgmstream, mix->ch_dst); break; - case MIX_FADE: - /* Convert from time to samples now that sample rate is final. - * Samples and time values may be mixed though, so it's done for every - * value (if one is 0 the other will be too, though) */ - if (mix->time_pre > 0.0) mix->sample_pre = mix->time_pre * vgmstream->sample_rate; - if (mix->time_start > 0.0) mix->sample_start = mix->time_start * vgmstream->sample_rate; - if (mix->time_end > 0.0) mix->sample_end = mix->time_end * vgmstream->sample_rate; - if (mix->time_post > 0.0) mix->sample_post = mix->time_post * vgmstream->sample_rate; - /* convert special meaning too */ - if (mix->time_pre < 0.0) mix->sample_pre = -1; - if (mix->time_post < 0.0) mix->sample_post = -1; - - if (mix->position_type == TXTP_POSITION_LOOPS && vgmstream->loop_flag) { - int loop_pre = vgmstream->loop_start_sample; - int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); - - position_samples = loop_pre + loop_samples * mix->position; - - if (mix->sample_pre >= 0) mix->sample_pre += position_samples; - mix->sample_start += position_samples; - mix->sample_end += position_samples; - if (mix->sample_post >= 0) mix->sample_post += position_samples; - } - - - mixing_push_fade(vgmstream, mix->ch_dst, mix->vol_start, mix->vol_end, mix->shape, - mix->sample_pre, mix->sample_start, mix->sample_end, mix->sample_post); - break; - - /* macro mixes */ - case MACRO_VOLUME: mixing_macro_volume(vgmstream, mix->vol, mix->mask); break; - case MACRO_TRACK: mixing_macro_track(vgmstream, mix->mask); break; - case MACRO_LAYER: mixing_macro_layer(vgmstream, mix->max, mix->mask, mix->mode); break; - case MACRO_CROSSTRACK: mixing_macro_crosstrack(vgmstream, mix->max); break; - case MACRO_CROSSLAYER: mixing_macro_crosslayer(vgmstream, mix->max, mix->mode); break; - case MACRO_DOWNMIX: mixing_macro_downmix(vgmstream, mix->max); break; - - default: - break; - } - } - } - - - /* default play config (last after sample rate mods/mixing/etc) */ - copy_config(&vgmstream->config, ¤t->config); - setup_state_vgmstream(vgmstream); - /* config is enabled in layouts or externally (for compatibility, since we don't know yet if this - * VGMSTREAM will part of a layout, or is enabled externally to not mess up plugins's calcs) */ -} - - -/*******************************************************************************/ -/* PARSER - HELPERS */ -/*******************************************************************************/ - -/* sscanf 101: "matches = sscanf(string-from, string-commands, parameters...)" - * - reads linearly and matches "%" commands to input parameters as found - * - reads until string end (NULL) or not being able to match current parameter - * - returns number of matched % parameters until stop, or -1 if no matches and reached string end - * - must supply pointer param for every "%" in the string - * - %d/f: match number until end or *non-number* (so "%d" reads "5t" as "5") - * - %s: reads string (dangerous due to overflows and surprising as %s%d can't match numbers since string eats all chars) - * - %[^(chars)] match string with chars not in the list (stop reading at those chars) - * - %*(command) read but don't match (no need to supply parameterr) - * - " ": ignore all spaces until next non-space - * - other chars in string must exist: ("%dt t%dt" reads "5t t5t" as "5" and "5", while "t5t 5t" matches only first "5") - * - %n: special match (not counted in return value), chars consumed until that point (can appear and be set multiple times) - */ - -static int get_double(const char* params, double *value, int *is_set) { - int n, m; - double temp; - - if (is_set) *is_set = 0; - - m = sscanf(params, " %lf%n", &temp,&n); - if (m != 1 || temp < 0) - return 0; - - if (is_set) *is_set = 1; - *value = temp; - return n; -} - -static int get_int(const char* params, int *value) { - int n,m; - int temp; - - m = sscanf(params, " %d%n", &temp,&n); - if (m != 1 || temp < 0) - return 0; - - *value = temp; - return n; -} - -static int get_position(const char* params, double* value_f, char* value_type) { - int n,m; - double temp_f; - char temp_c; - - /* test if format is position: N.n(type) */ - m = sscanf(params, " %lf%c%n", &temp_f,&temp_c,&n); - if (m != 2 || temp_f < 0.0) - return 0; - /* test accepted chars as it will capture anything */ - if (temp_c != TXTP_POSITION_LOOPS) - return 0; - - *value_f = temp_f; - *value_type = temp_c; - return n; -} - -static int get_volume(const char* params, double *value, int *is_set) { - int n, m; - double temp_f; - char temp_c1, temp_c2; - - if (is_set) *is_set = 0; - - /* test if format is NdB (decibels) */ - m = sscanf(params, " %lf%c%c%n", &temp_f, &temp_c1, &temp_c2, &n); - if (m == 3 && temp_c1 == 'd' && (temp_c2 == 'B' || temp_c2 == 'b')) { - /* dB 101: - * - logaritmic scale - * - dB = 20 * log(percent / 100) - * - percent = pow(10, dB / 20)) * 100 - * - for audio: 100% = 0dB (base max volume of current file = reference dB) - * - negative dB decreases volume, positive dB increases - * ex. - * 200% = 20 * log(200 / 100) = +6.02059991328 dB - * 50% = 20 * log( 50 / 100) = -6.02059991328 dB - * 6dB = pow(10, 6 / 20) * 100 = +195.26231497 % - * -6dB = pow(10, -6 / 20) * 100 = +50.50118723362 % - */ - - if (is_set) *is_set = 1; - *value = pow(10, temp_f / 20.0); /* dB to % where 1.0 = max */ - return n; - } - - /* test if format is N.N (percent) */ - m = sscanf(params, " %lf%n", &temp_f, &n); - if (m == 1) { - if (is_set) *is_set = 1; - *value = temp_f; - return n; - } - - return 0; -} - -static int get_time(const char* params, double* value_f, int32_t* value_i) { - int n,m; - int temp_i1, temp_i2; - double temp_f1, temp_f2; - char temp_c; - - /* test if format is hour: N:N(.n) or N_N(.n) */ - m = sscanf(params, " %d%c%d%n", &temp_i1,&temp_c,&temp_i2,&n); - if (m == 3 && (temp_c == ':' || temp_c == '_')) { - m = sscanf(params, " %lf%c%lf%n", &temp_f1,&temp_c,&temp_f2,&n); - if (m != 3 || /*temp_f1 < 0.0 ||*/ temp_f1 >= 60.0 || temp_f2 < 0.0 || temp_f2 >= 60.0) - return 0; - - *value_f = temp_f1 * 60.0 + temp_f2; - return n; - } - - /* test if format is seconds: N.n */ - m = sscanf(params, " %d.%d%n", &temp_i1,&temp_i2,&n); - if (m == 2) { - m = sscanf(params, " %lf%n", &temp_f1,&n); - if (m != 1 /*|| temp_f1 < 0.0*/) - return 0; - *value_f = temp_f1; - return n; - } - - /* test is format is hex samples: 0xN */ - m = sscanf(params, " 0x%x%n", &temp_i1,&n); - if (m == 1) { - /* allow negative samples for special meanings */ - //if (temp_i1 < 0) - // return 0; - - *value_i = temp_i1; - return n; - } - - /* assume format is samples: N */ - m = sscanf(params, " %d%n", &temp_i1,&n); - if (m == 1) { - /* allow negative samples for special meanings */ - //if (temp_i1 < 0) - // return 0; - - *value_i = temp_i1; - return n; - } - - return 0; -} - -static int get_time_f(const char* params, double* value_f, int32_t* value_i, int* flag) { - int n = get_time(params, value_f, value_i); - if (n > 0) - *flag = 1; - return n; -} - -static int get_bool(const char* params, int* value) { - int n,m; - char temp; - - n = 0; /* init as it's not matched if c isn't */ - m = sscanf(params, " %c%n", &temp, &n); - if (m >= 1 && !(temp == '#' || temp == '\r' || temp == '\n')) - return 0; /* ignore if anything non-space/comment matched */ - - if (m >= 1 && temp == '#') - n--; /* don't consume separator when returning totals */ - *value = 1; - return n; -} - -static int get_mask(const char* params, uint32_t* value) { - int n, m, total_n = 0; - int temp1,temp2, r1, r2; - int i; - char cmd; - uint32_t mask = *value; - - while (params[0] != '\0') { - m = sscanf(params, " %c%n", &cmd,&n); /* consume comma */ - if (m == 1 && (cmd == ',' || cmd == '-')) { /* '-' is alt separator (space is ok too, implicitly) */ - params += n; - continue; - } - - m = sscanf(params, " %d%n ~ %d%n", &temp1,&n, &temp2,&n); - if (m == 1) { /* single values */ - r1 = temp1 - 1; - r2 = temp1 - 1; - } - else if (m == 2) { /* range */ - r1 = temp1 - 1; - r2 = temp2 - 1; - } - else { /* no more matches */ - break; - } - - if (n == 0 || r1 < 0 || r1 > 31 || r2 < 0 || r2 > 31) - break; - - for (i = r1; i < r2 + 1; i++) { - mask |= (1 << i); - } - - params += n; - total_n += n; - - if (params[0]== ',' || params[0]== '-') - params++; - } - - *value = mask; - return total_n; -} - - -static int get_fade(const char* params, txtp_mix_data* mix, int* p_n) { - int n, m, tn = 0; - char type, separator; - - m = sscanf(params, " %d %c%n", &mix->ch_dst, &type, &n); - if (m != 2 || n == 0) goto fail; - params += n; - tn += n; - - if (type == '^') { - /* full definition */ - m = sscanf(params, " %lf ~ %lf = %c @%n", &mix->vol_start, &mix->vol_end, &mix->shape, &n); - if (m != 3 || n == 0) goto fail; - params += n; - tn += n; - - n = get_time(params, &mix->time_pre, &mix->sample_pre); - if (n == 0) goto fail; - params += n; - tn += n; - - m = sscanf(params, " %c%n", &separator, &n); - if ( m != 1 || n == 0 || separator != '~') goto fail; - params += n; - tn += n; - - n = get_time(params, &mix->time_start, &mix->sample_start); - if (n == 0) goto fail; - params += n; - tn += n; - - m = sscanf(params, " %c%n", &separator, &n); - if (m != 1 || n == 0 || separator != '+') goto fail; - params += n; - tn += n; - - n = get_time(params, &mix->time_end, &mix->sample_end); - if (n == 0) goto fail; - params += n; - tn += n; - - m = sscanf(params, " %c%n", &separator, &n); - if (m != 1 || n == 0 || separator != '~') goto fail; - params += n; - tn += n; - - n = get_time(params, &mix->time_post, &mix->sample_post); - if (n == 0) goto fail; - params += n; - tn += n; - } - else { - /* simplified definition */ - if (type == '{' || type == '(') { - mix->vol_start = 0.0; - mix->vol_end = 1.0; - } - else if (type == '}' || type == ')') { - mix->vol_start = 1.0; - mix->vol_end = 0.0; - } - else { - goto fail; - } - - mix->shape = type; /* internally converted */ - - mix->time_pre = -1.0; - mix->sample_pre = -1; - - n = get_position(params, &mix->position, &mix->position_type); - //if (n == 0) goto fail; /* optional */ - params += n; - tn += n; - - n = get_time(params, &mix->time_start, &mix->sample_start); - if (n == 0) goto fail; - params += n; - tn += n; - - m = sscanf(params, " %c%n", &separator, &n); - if (m != 1 || n == 0 || separator != '+') goto fail; - params += n; - tn += n; - - n = get_time(params, &mix->time_end, &mix->sample_end); - if (n == 0) goto fail; - params += n; - tn += n; - - mix->time_post = -1.0; - mix->sample_post = -1; - } - - mix->time_end = mix->time_start + mix->time_end; /* defined as length */ - - *p_n = tn; - return 1; -fail: - return 0; -} - -/*******************************************************************************/ -/* PARSER - MAIN */ -/*******************************************************************************/ - -void add_mixing(txtp_entry* entry, txtp_mix_data* mix, txtp_mix_t command) { +void txtp_add_mixing(txtp_entry_t* entry, txtp_mix_data_t* mix, txtp_mix_t command) { if (entry->mixing_count + 1 > TXTP_MIXING_MAX) { VGM_LOG("TXTP: too many mixes\n"); return; @@ -1224,746 +125,3 @@ void add_mixing(txtp_entry* entry, txtp_mix_data* mix, txtp_mix_t command) { entry->mixing[entry->mixing_count] = *mix; /* memcpy'ed */ entry->mixing_count++; } - -static void add_settings(txtp_entry* current, txtp_entry* entry, const char* filename) { - - /* don't memcopy to allow list additions and ignore values not set, as current can be "default" settings */ - //*current = *cfg; - - if (filename) - strcpy(current->filename, filename); - - - /* play config */ - copy_config(¤t->config, &entry->config); - - /* file settings */ - if (entry->subsong) - current->subsong = entry->subsong; - - if (entry->sample_rate > 0) - current->sample_rate = entry->sample_rate; - - if (entry->channel_mask) - current->channel_mask = entry->channel_mask; - - if (entry->loop_install_set) { - current->loop_install_set = entry->loop_install_set; - current->loop_end_max = entry->loop_end_max; - current->loop_start_sample = entry->loop_start_sample; - current->loop_start_second = entry->loop_start_second; - current->loop_end_sample = entry->loop_end_sample; - current->loop_end_second = entry->loop_end_second; - } - - if (entry->trim_set) { - current->trim_set = entry->trim_set; - current->trim_second = entry->trim_second; - current->trim_sample = entry->trim_sample; - } - - if (entry->mixing_count > 0) { - int i; - for (i = 0; i < entry->mixing_count; i++) { - current->mixing[current->mixing_count] = entry->mixing[i]; - current->mixing_count++; - } - } - - current->loop_anchor_start = entry->loop_anchor_start; - current->loop_anchor_end = entry->loop_anchor_end; -} - -//TODO use -static inline int is_match(const char* str1, const char* str2) { - return strcmp(str1, str2) == 0; -} - -static void parse_params(txtp_entry* entry, char* params) { - /* parse params: #(commands) */ - int n, nc, nm, mc; - char command[TXT_LINE_MAX]; - play_config_t* tcfg = &entry->config; - - entry->range_start = 0; - entry->range_end = 1; - - while (params != NULL) { - /* position in next #(command) */ - params = strchr(params, '#'); - if (!params) break; - //;VGM_LOG("TXTP: params='%s'\n", params); - - /* get command until next space/number/comment/end */ - command[0] = '\0'; - mc = sscanf(params, "#%n%[^ #0-9\r\n]%n", &nc, command, &nc); - //;VGM_LOG("TXTP: command='%s', nc=%i, mc=%i\n", command, nc, mc); - if (mc <= 0 && nc == 0) break; - - params[0] = '\0'; //todo don't modify input string and properly calculate filename end - - params += nc; /* skip '#' and command */ - - /* check command string (though at the moment we only use single letters) */ - if (strcmp(command,"c") == 0) { - /* channel mask: file.ext#c1,2 = play channels 1,2 and mutes rest */ - - params += get_mask(params, &entry->channel_mask); - //;VGM_LOG("TXTP: channel_mask ");{int i; for (i=0;i<16;i++)VGM_LOG("%i ",(entry->channel_mask>>i)&1);}VGM_LOG("\n"); - } - else if (strcmp(command,"m") == 0) { - /* channel mixing: file.ext#m(sub-command),(sub-command),etc */ - char cmd; - - while (params[0] != '\0') { - txtp_mix_data mix = {0}; - - //;VGM_LOG("TXTP: subcommand='%s'\n", params); - - //todo use strchr instead? - if (sscanf(params, " %c%n", &cmd, &n) == 1 && n != 0 && cmd == ',') { - params += n; - continue; - } - - if (sscanf(params, " %d - %d%n", &mix.ch_dst, &mix.ch_src, &n) == 2 && n != 0) { - //;VGM_LOG("TXTP: mix %i-%i\n", mix.ch_dst, mix.ch_src); - add_mixing(entry, &mix, MIX_SWAP); /* N-M: swaps M with N */ - params += n; - continue; - } - - if ((sscanf(params, " %d + %d * %lf%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0) || - (sscanf(params, " %d + %d x %lf%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0)) { - //;VGM_LOG("TXTP: mix %i+%i*%f\n", mix.ch_dst, mix.ch_src, mix.vol); - add_mixing(entry, &mix, MIX_ADD_VOLUME); /* N+M*V: mixes M*volume to N */ - params += n; - continue; - } - - if (sscanf(params, " %d + %d%n", &mix.ch_dst, &mix.ch_src, &n) == 2 && n != 0) { - //;VGM_LOG("TXTP: mix %i+%i\n", mix.ch_dst, mix.ch_src); - add_mixing(entry, &mix, MIX_ADD); /* N+M: mixes M to N */ - params += n; - continue; - } - - if ((sscanf(params, " %d * %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0) || - (sscanf(params, " %d x %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) { - //;VGM_LOG("TXTP: mix %i*%f\n", mix.ch_dst, mix.vol); - add_mixing(entry, &mix, MIX_VOLUME); /* N*V: changes volume of N */ - params += n; - continue; - } - - if ((sscanf(params, " %d = %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) { - //;VGM_LOG("TXTP: mix %i=%f\n", mix.ch_dst, mix.vol); - add_mixing(entry, &mix, MIX_LIMIT); /* N=V: limits volume of N */ - params += n; - continue; - } - - if (sscanf(params, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'D') { - //;VGM_LOG("TXTP: mix %iD\n", mix.ch_dst); - add_mixing(entry, &mix, MIX_KILLMIX); /* ND: downmix N and all following channels */ - params += n; - continue; - } - - if (sscanf(params, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'd') { - //;VGM_LOG("TXTP: mix %id\n", mix.ch_dst); - add_mixing(entry, &mix, MIX_DOWNMIX);/* Nd: downmix N only */ - params += n; - continue; - } - - if (sscanf(params, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'u') { - //;VGM_LOG("TXTP: mix %iu\n", mix.ch_dst); - add_mixing(entry, &mix, MIX_UPMIX); /* Nu: upmix N */ - params += n; - continue; - } - - if (get_fade(params, &mix, &n) != 0) { - //;VGM_LOG("TXTP: fade %d^%f~%f=%c@%f~%f+%f~%f\n", - // mix.ch_dst, mix.vol_start, mix.vol_end, mix.shape, - // mix.time_pre, mix.time_start, mix.time_end, mix.time_post); - add_mixing(entry, &mix, MIX_FADE); /* N^V1~V2@T1~T2+T3~T4: fades volumes between positions */ - params += n; - continue; - } - - break; /* unknown mix/new command/end */ - } - } - else if (strcmp(command,"s") == 0 || (nc == 1 && params[0] >= '0' && params[0] <= '9')) { - /* subsongs: file.ext#s2 = play subsong 2, file.ext#2~10 = play subsong range */ - int subsong_start = 0, subsong_end = 0; - - //todo also advance params? - if (sscanf(params, " %d ~ %d", &subsong_start, &subsong_end) == 2) { - if (subsong_start > 0 && subsong_end > 0) { - entry->range_start = subsong_start-1; - entry->range_end = subsong_end; - } - //;VGM_LOG("TXTP: subsong range %i~%i\n", range_start, range_end); - } - else if (sscanf(params, " %d", &subsong_start) == 1) { - if (subsong_start > 0) { - entry->range_start = subsong_start-1; - entry->range_end = subsong_start; - } - //;VGM_LOG("TXTP: subsong single %i-%i\n", range_start, range_end); - } - else { /* wrong setting, ignore */ - //;VGM_LOG("TXTP: subsong none\n"); - } - } - - /* play config */ - else if (strcmp(command,"i") == 0) { - params += get_bool(params, &tcfg->ignore_loop); - tcfg->config_set = 1; - } - else if (strcmp(command,"e") == 0) { - params += get_bool(params, &tcfg->force_loop); - tcfg->config_set = 1; - } - else if (strcmp(command,"E") == 0) { - params += get_bool(params, &tcfg->really_force_loop); - tcfg->config_set = 1; - } - else if (strcmp(command,"F") == 0) { - params += get_bool(params, &tcfg->ignore_fade); - tcfg->config_set = 1; - } - else if (strcmp(command,"L") == 0) { - params += get_bool(params, &tcfg->play_forever); - tcfg->config_set = 1; - } - else if (strcmp(command,"l") == 0) { - params += get_double(params, &tcfg->loop_count, &tcfg->loop_count_set); - if (tcfg->loop_count < 0) - tcfg->loop_count_set = 0; - tcfg->config_set = 1; - } - else if (strcmp(command,"f") == 0) { - params += get_double(params, &tcfg->fade_time, &tcfg->fade_time_set); - if (tcfg->fade_time < 0) - tcfg->fade_time_set = 0; - tcfg->config_set = 1; - } - else if (strcmp(command,"d") == 0) { - params += get_double(params, &tcfg->fade_delay, &tcfg->fade_delay_set); - if (tcfg->fade_delay < 0) - tcfg->fade_delay_set = 0; - tcfg->config_set = 1; - } - else if (strcmp(command,"p") == 0) { - params += get_time_f(params, &tcfg->pad_begin_s, &tcfg->pad_begin, &tcfg->pad_begin_set); - tcfg->config_set = 1; - } - else if (strcmp(command,"P") == 0) { - params += get_time_f(params, &tcfg->pad_end_s, &tcfg->pad_end, &tcfg->pad_end_set); - tcfg->config_set = 1; - } - else if (strcmp(command,"r") == 0) { - params += get_time_f(params, &tcfg->trim_begin_s, &tcfg->trim_begin, &tcfg->trim_begin_set); - tcfg->config_set = 1; - } - else if (strcmp(command,"R") == 0) { - params += get_time_f(params, &tcfg->trim_end_s, &tcfg->trim_end, &tcfg->trim_end_set); - tcfg->config_set = 1; - } - else if (strcmp(command,"b") == 0) { - params += get_time_f(params, &tcfg->body_time_s, &tcfg->body_time, &tcfg->body_time_set); - tcfg->config_set = 1; - } - else if (strcmp(command,"B") == 0) { - params += get_time_f(params, &tcfg->body_time_s, &tcfg->body_time, &tcfg->body_time_set); - tcfg->config_set = 1; - /* similar to 'b' but implies no fades */ - tcfg->fade_time_set = 1; - tcfg->fade_time = 0; - tcfg->fade_delay_set = 1; - tcfg->fade_delay = 0; - } - - /* other settings */ - else if (strcmp(command,"h") == 0) { - params += get_int(params, &entry->sample_rate); - //;VGM_LOG("TXTP: sample_rate %i\n", cfg->sample_rate); - } - else if (strcmp(command,"I") == 0) { - n = get_time(params, &entry->loop_start_second, &entry->loop_start_sample); - if (n > 0) { /* first value must exist */ - params += n; - - n = get_time(params, &entry->loop_end_second, &entry->loop_end_sample); - if (n == 0) { /* second value is optional */ - entry->loop_end_max = 1; - } - - params += n; - entry->loop_install_set = 1; - } - - //;VGM_LOG("TXTP: loop_install %i (max=%i): %i %i / %f %f\n", entry->loop_install, entry->loop_end_max, - // entry->loop_start_sample, entry->loop_end_sample, entry->loop_start_second, entry->loop_end_second); - } - else if (strcmp(command,"t") == 0) { - entry->trim_set = get_time(params, &entry->trim_second, &entry->trim_sample); - //;VGM_LOG("TXTP: trim %i - %f / %i\n", entry->trim_set, entry->trim_second, entry->trim_sample); - } - - else if (is_match(command,"a") || is_match(command,"@loop")) { - entry->loop_anchor_start = 1; - //;VGM_LOG("TXTP: anchor start set\n"); - } - else if (is_match(command,"A") || is_match(command,"@loop-end")) { - entry->loop_anchor_end = 1; - //;VGM_LOG("TXTP: anchor end set\n"); - } - - //todo cleanup - /* macros */ - else if (is_match(command,"v") || is_match(command,"@volume")) { - txtp_mix_data mix = {0}; - - nm = get_volume(params, &mix.vol, NULL); - params += nm; - - if (nm == 0) continue; - - nm = get_mask(params, &mix.mask); - params += nm; - - add_mixing(entry, &mix, MACRO_VOLUME); - } - else if (strcmp(command,"@track") == 0 || - strcmp(command,"C") == 0 ) { - txtp_mix_data mix = {0}; - - nm = get_mask(params, &mix.mask); - params += nm; - if (nm == 0) continue; - - add_mixing(entry, &mix, MACRO_TRACK); - } - else if (strcmp(command,"@layer-v") == 0 || - strcmp(command,"@layer-b") == 0 || - strcmp(command,"@layer-e") == 0) { - txtp_mix_data mix = {0}; - - nm = get_int(params, &mix.max); - params += nm; - - if (nm > 0) { /* max is optional (auto-detects and uses max channels) */ - nm = get_mask(params, &mix.mask); - params += nm; - } - - mix.mode = command[7]; /* pass letter */ - add_mixing(entry, &mix, MACRO_LAYER); - } - else if (strcmp(command,"@crosslayer-v") == 0 || - strcmp(command,"@crosslayer-b") == 0 || - strcmp(command,"@crosslayer-e") == 0 || - strcmp(command,"@crosstrack") == 0) { - txtp_mix_data mix = {0}; - txtp_mix_t type; - if (strcmp(command,"@crosstrack") == 0) { - type = MACRO_CROSSTRACK; - } - else { - type = MACRO_CROSSLAYER; - mix.mode = command[12]; /* pass letter */ - } - - nm = get_int(params, &mix.max); - params += nm; - if (nm == 0) continue; - - add_mixing(entry, &mix, type); - } - else if (strcmp(command,"@downmix") == 0) { - txtp_mix_data mix = {0}; - - mix.max = 2; /* stereo only for now */ - //nm = get_int(params, &mix.max); - //params += nm; - //if (nm == 0) continue; - - add_mixing(entry, &mix, MACRO_DOWNMIX); - } - else if (params[nc] == ' ') { - //;VGM_LOG("TXTP: comment\n"); - break; /* comment, ignore rest */ - } - else { - //;VGM_LOG("TXTP: unknown command\n"); - /* end, incorrect command, or possibly a comment or double ## comment too - * (shouldn't fail for forward compatibility) */ - break; - } - } -} - - -static int add_group(txtp_header* txtp, char* line) { - int n, m; - txtp_group cfg = {0}; - int auto_pos = 0; - char c; - - /* parse group: (position)(type)(count)(repeat) #(commands) */ - //;VGM_LOG("TXTP: parse group '%s'\n", line); - - m = sscanf(line, " %c%n", &c, &n); - if (m == 1 && c == '-') { - auto_pos = 1; - line += n; - } - - m = sscanf(line, " %d%n", &cfg.position, &n); - if (m == 1) { - cfg.position--; /* externally 1=first but internally 0=first */ - line += n; - } - - m = sscanf(line, " %c%n", &cfg.type, &n); - if (m == 1) { - line += n; - } - - m = sscanf(line, " %d%n", &cfg.count, &n); - if (m == 1) { - line += n; - } - - m = sscanf(line, " %c%n", &cfg.repeat, &n); - if (m == 1 && cfg.repeat == TXTP_GROUP_REPEAT) { - auto_pos = 0; - line += n; - } - - m = sscanf(line, " >%c%n", &c, &n); - if (m == 1 && c == TXTP_GROUP_RANDOM_ALL) { - cfg.type = TXTP_GROUP_MODE_RANDOM; /* usually R>- but allows L/S>- */ - cfg.selected = cfg.count; /* special meaning */ - line += n; - } - else { - m = sscanf(line, " >%d%n", &cfg.selected, &n); - if (m == 1) { - cfg.type = TXTP_GROUP_MODE_RANDOM; /* usually R>1 but allows L/S>1 */ - cfg.selected--; /* externally 1=first but internally 0=first */ - line += n; - } - else if (cfg.type == TXTP_GROUP_MODE_RANDOM) { - /* was a random but didn't select anything, just select all */ - cfg.selected = cfg.count; - } - } - - parse_params(&cfg.entry, line); - - /* Groups can use "auto" position of last N files, so we need a counter that changes like this: - * #layer of 2 (pos = 0) - * #sequence of 2 - * bgm pos +1 > 1 - * bgm pos +1 > 2 - * group = -S2 pos -2 +1 > 1 (group is at 1 now since it "collapses" wems but becomes a position) - * #sequence of 3 - * bgm pos +1 > 2 - * bgm pos +1 > 3 - * #sequence of 2 - * bgm pos +1 > 4 - * bgm pos +1 > 5 - * group = -S2 pos -2 +1 > 4 (groups is at 4 now since are uncollapsed wems at 2/3) - * group = -S3 pos -3 +1 > 2 - * group = -L2 pos -2 +1 > 1 - */ - txtp->group_pos++; - txtp->group_pos -= cfg.count; - if (auto_pos) { - cfg.position = txtp->group_pos - 1; /* internally 1 = first */ - } - - //;VGM_LOG("TXTP: parsed group %i%c%i%c, auto=%i\n",cfg.position+1,cfg.type,cfg.count,cfg.repeat, auto_pos); - - /* add final group */ - { - /* resize in steps if not enough */ - if (txtp->group_count+1 > txtp->group_max) { - txtp_group *temp_group; - - txtp->group_max += 5; - temp_group = realloc(txtp->group, sizeof(txtp_group) * txtp->group_max); - if (!temp_group) goto fail; - txtp->group = temp_group; - } - - /* new group */ - txtp->group[txtp->group_count] = cfg; /* memcpy */ - - txtp->group_count++; - } - - return 1; -fail: - return 0; -} - - -static void clean_filename(char* filename) { - int i; - size_t len; - - if (filename[0] == '\0') - return; - - /* normalize paths */ - fix_dir_separators(filename); - - /* remove trailing spaces */ - len = strlen(filename); - for (i = len-1; i > 0; i--) { - if (filename[i] != ' ') - break; - filename[i] = '\0'; - } - -} - -//TODO see if entry can be set to &default/&entry[entry_count] to avoid add_settings -static int add_entry(txtp_header* txtp, char* filename, int is_default) { - int i; - txtp_entry entry = {0}; - - - //;VGM_LOG("TXTP: filename=%s\n", filename); - - /* parse filename: file.ext#(commands) */ - { - char* params; - - if (is_default) { - params = filename; /* multiple commands without filename */ - } - else { - /* find settings start after filenames (filenames can also contain dots and #, - * so this may be fooled by certain patterns) */ - params = strchr(filename, '.'); /* first dot (may be a false positive) */ - if (!params) /* extensionless */ - params = filename; - params = strchr(params, '#'); /* next should be actual settings */ - if (!params) - params = NULL; - } - - parse_params(&entry, params); - } - - - clean_filename(filename); - //;VGM_LOG("TXTP: clean filename='%s'\n", filename); - - /* settings that applies to final vgmstream */ - if (is_default) { - txtp->default_entry_set = 1; - add_settings(&txtp->default_entry, &entry, NULL); - return 1; - } - - /* add final entry */ - for (i = entry.range_start; i < entry.range_end; i++){ - txtp_entry* current; - - /* resize in steps if not enough */ - if (txtp->entry_count+1 > txtp->entry_max) { - txtp_entry* temp_entry; - - txtp->entry_max += 5; - temp_entry = realloc(txtp->entry, sizeof(txtp_entry) * txtp->entry_max); - if (!temp_entry) goto fail; - txtp->entry = temp_entry; - } - - /* new entry */ - current = &txtp->entry[txtp->entry_count]; - memset(current,0, sizeof(txtp_entry)); - entry.subsong = (i+1); - - add_settings(current, &entry, filename); - - txtp->entry_count++; - txtp->group_pos++; - } - - return 1; -fail: - return 0; -} - - -/*******************************************************************************/ -/* PARSER - BASE */ -/*******************************************************************************/ - -static int is_substring(const char* val, const char* cmp) { - int n; - char subval[TXT_LINE_MAX]; - - /* read string without trailing spaces or comments/commands */ - if (sscanf(val, " %s%n[^ #\t\r\n]%n", subval, &n, &n) != 1) - return 0; - - if (0 != strcmp(subval,cmp)) - return 0; - return n; -} - -static int parse_num(const char* val, uint32_t* out_value) { - int hex = (val[0]=='0' && val[1]=='x'); - if (sscanf(val, hex ? "%x" : "%u", out_value) != 1) - goto fail; - - return 1; -fail: - return 0; -} - -static int parse_keyval(txtp_header* txtp, const char* key, const char* val) { - //;VGM_LOG("TXTP: key=val '%s'='%s'\n", key,val); - - - if (0==strcmp(key,"loop_start_segment")) { - if (!parse_num(val, &txtp->loop_start_segment)) goto fail; - } - else if (0==strcmp(key,"loop_end_segment")) { - if (!parse_num(val, &txtp->loop_end_segment)) goto fail; - } - else if (0==strcmp(key,"mode")) { - if (is_substring(val,"layers")) { - txtp->is_segmented = 0; - txtp->is_layered = 1; - } - else if (is_substring(val,"segments")) { - txtp->is_segmented = 1; - txtp->is_layered = 0; - } - else if (is_substring(val,"mixed")) { - txtp->is_segmented = 0; - txtp->is_layered = 0; - } - else { - goto fail; - } - } - else if (0==strcmp(key,"loop_mode")) { - if (is_substring(val,"keep")) { - txtp->is_loop_keep = 1; - } - else if (is_substring(val,"auto")) { - txtp->is_loop_auto = 1; - } - else { - goto fail; - } - } - else if (0==strcmp(key,"commands")) { - char val2[TXT_LINE_MAX]; - strcpy(val2, val); /* copy since val is modified here but probably not important */ - if (!add_entry(txtp, val2, 1)) goto fail; - } - else if (0==strcmp(key,"group")) { - char val2[TXT_LINE_MAX]; - strcpy(val2, val); /* copy since val is modified here but probably not important */ - if (!add_group(txtp, val2)) goto fail; - - } - else { - goto fail; - } - - return 1; -fail: - VGM_LOG("TXTP: error while parsing key=val '%s'='%s'\n", key,val); - return 0; -} - -static txtp_header* parse_txtp(STREAMFILE* sf) { - txtp_header* txtp = NULL; - uint32_t txt_offset; - - - txtp = calloc(1,sizeof(txtp_header)); - if (!txtp) goto fail; - - /* defaults */ - txtp->is_segmented = 1; - - txt_offset = read_bom(sf); - - /* read and parse lines */ - { - text_reader_t tr; - uint8_t buf[TXT_LINE_MAX + 1]; - char key[TXT_LINE_KEY_MAX]; - char val[TXT_LINE_VAL_MAX]; - int ok, line_len; - char* line; - - if (!text_reader_init(&tr, buf, sizeof(buf), sf, txt_offset, 0)) - goto fail; - - do { - line_len = text_reader_get_line(&tr, &line); - if (line_len < 0) goto fail; /* too big for buf (maybe not text)) */ - - if (line == NULL) /* EOF */ - break; - - if (line_len == 0) /* empty */ - continue; - - /* try key/val (ignores lead/trail spaces, # may be commands or comments) */ - ok = sscanf(line, " %[^ \t#=] = %[^\t\r\n] ", key,val); - if (ok == 2) { /* key=val */ - if (!parse_keyval(txtp, key, val)) /* read key/val */ - goto fail; - continue; - } - - /* must be a filename (only remove spaces from start/end, as filenames con contain mid spaces/#/etc) */ - ok = sscanf(line, " %[^\t\r\n] ", val); - if (ok != 1) /* not a filename either */ - continue; - if (val[0] == '#') - continue; /* simple comment */ - - /* filename with settings */ - if (!add_entry(txtp, val, 0)) - goto fail; - - } while (line_len >= 0); - } - - /* mini-txth: if no entries are set try with filename, ex. from "song.ext#3.txtp" use "song.ext#3" - * (it's possible to have default "commands" inside the .txtp plus filename+settings) */ - if (txtp->entry_count == 0) { - char filename[PATH_LIMIT]; - - filename[0] = '\0'; - get_streamfile_basename(sf, filename, sizeof(filename)); - - add_entry(txtp, filename, 0); - } - - - return txtp; -fail: - clean_txtp(txtp, 1); - return NULL; -} diff --git a/src/meta/txtp.h b/src/meta/txtp.h new file mode 100644 index 00000000..cdec14df --- /dev/null +++ b/src/meta/txtp.h @@ -0,0 +1,153 @@ +#ifndef _TXTP_H_ +#define _TXTP_H_ + +#include "meta.h" +#include "../coding/coding.h" +#include "../layout/layout.h" +#include "../base/mixing.h" +#include "../base/plugins.h" +#include "../util/text_reader.h" +#include "../util/paths.h" + +#include + + +#define TXTP_FILENAME_MAX 1024 +#define TXTP_MIXING_MAX 512 +#define TXTP_GROUP_MODE_SEGMENTED 'S' +#define TXTP_GROUP_MODE_LAYERED 'L' +#define TXTP_GROUP_MODE_RANDOM 'R' +#define TXTP_GROUP_RANDOM_ALL '-' +#define TXTP_GROUP_REPEAT 'R' +#define TXTP_POSITION_LOOPS 'L' + +/* mixing info */ +typedef enum { + MIX_SWAP, + MIX_ADD, + MIX_ADD_VOLUME, + MIX_VOLUME, + MIX_LIMIT, + MIX_DOWNMIX, + MIX_KILLMIX, + MIX_UPMIX, + MIX_FADE, + + MACRO_VOLUME, + MACRO_TRACK, + MACRO_LAYER, + MACRO_CROSSTRACK, + MACRO_CROSSLAYER, + MACRO_DOWNMIX, + +} txtp_mix_t; + +typedef struct { + txtp_mix_t command; + /* common */ + int ch_dst; + int ch_src; + double vol; + + /* fade envelope */ + double vol_start; + double vol_end; + char shape; + int32_t sample_pre; + int32_t sample_start; + int32_t sample_end; + int32_t sample_post; + double time_pre; + double time_start; + double time_end; + double time_post; + double position; + char position_type; + + /* macros */ + int max; + uint32_t mask; + char mode; +} txtp_mix_data_t; + + +typedef struct { + /* main entry */ + char filename[TXTP_FILENAME_MAX]; + bool silent; + + /* TXTP settings (applied at the end) */ + int range_start; + int range_end; + int subsong; + + uint32_t channel_mask; + + int mixing_count; + txtp_mix_data_t mixing[TXTP_MIXING_MAX]; + + play_config_t config; + + int sample_rate; + + int loop_install_set; + int loop_end_max; + double loop_start_second; + int32_t loop_start_sample; + double loop_end_second; + int32_t loop_end_sample; + /* flags */ + int loop_anchor_start; + int loop_anchor_end; + + int trim_set; + double trim_second; + int32_t trim_sample; + +} txtp_entry_t; + + +typedef struct { + int position; + char type; + int count; + char repeat; + int selected; + + txtp_entry_t entry; + +} txtp_group_t; + +typedef struct { + txtp_entry_t* entry; + size_t entry_count; + size_t entry_max; + + txtp_group_t* group; + size_t group_count; + size_t group_max; + int group_pos; /* entry counter for groups */ + + VGMSTREAM** vgmstream; + size_t vgmstream_count; + + uint32_t loop_start_segment; + uint32_t loop_end_segment; + bool is_loop_keep; + bool is_loop_auto; + + txtp_entry_t default_entry; + int default_entry_set; + + bool is_segmented; + bool is_layered; + bool is_single; +} txtp_header_t; + +txtp_header_t* txtp_parse(STREAMFILE* sf); +bool txtp_process(txtp_header_t* txtp, STREAMFILE* sf); + +void txtp_clean(txtp_header_t* txtp); +void txtp_add_mixing(txtp_entry_t* entry, txtp_mix_data_t* mix, txtp_mix_t command); +void txtp_copy_config(play_config_t* dst, play_config_t* src); +#endif diff --git a/src/meta/txtp_parser.c b/src/meta/txtp_parser.c new file mode 100644 index 00000000..d362d981 --- /dev/null +++ b/src/meta/txtp_parser.c @@ -0,0 +1,1080 @@ +#include + +#include "txtp.h" +#include "../util/text_reader.h" +#include "../util/paths.h" + +#define TXT_LINE_MAX 2048 /* some wwise .txtp get wordy */ +#define TXT_LINE_KEY_MAX 128 +#define TXT_LINE_VAL_MAX (TXT_LINE_MAX - TXT_LINE_KEY_MAX) + + +/*******************************************************************************/ +/* PARSER - HELPERS */ +/*******************************************************************************/ + +/* sscanf 101: "matches = sscanf(string-from, string-commands, parameters...)" + * - reads linearly and matches "%" commands to input parameters as found + * - reads until string end (NULL) or not being able to match current parameter + * - returns number of matched % parameters until stop, or -1 if no matches and reached string end + * - must supply pointer param for every "%" in the string + * - %d/f: match number until end or *non-number* (so "%d" reads "5t" as "5") + * - %s: reads string (dangerous due to overflows and surprising as %s%d can't match numbers since string eats all chars) + * - %[^(chars)] match string with chars not in the list (stop reading at those chars) + * - %*(command) read but don't match (no need to supply parameterr) + * - " ": ignore all spaces until next non-space + * - other chars in string must exist: ("%dt t%dt" reads "5t t5t" as "5" and "5", while "t5t 5t" matches only first "5") + * - %n: special match (not counted in return value), chars consumed until that point (can appear and be set multiple times) + */ + +static int get_double(const char* params, double* value, bool* is_set) { + int n, m; + double temp; + + if (is_set) *is_set = 0; + + m = sscanf(params, " %lf%n", &temp,&n); + if (m != 1 || temp < 0) + return 0; + + if (is_set) *is_set = 1; + *value = temp; + return n; +} + +static int get_int(const char* params, int *value) { + int n,m; + int temp; + + m = sscanf(params, " %d%n", &temp,&n); + if (m != 1 || temp < 0) + return 0; + + *value = temp; + return n; +} + +static int get_position(const char* params, double* value_f, char* value_type) { + int n,m; + double temp_f; + char temp_c; + + /* test if format is position: N.n(type) */ + m = sscanf(params, " %lf%c%n", &temp_f,&temp_c,&n); + if (m != 2 || temp_f < 0.0) + return 0; + /* test accepted chars as it will capture anything */ + if (temp_c != TXTP_POSITION_LOOPS) + return 0; + + *value_f = temp_f; + *value_type = temp_c; + return n; +} + +static int get_volume(const char* params, double *value, int *is_set) { + int n, m; + double temp_f; + char temp_c1, temp_c2; + + if (is_set) *is_set = 0; + + /* test if format is NdB (decibels) */ + m = sscanf(params, " %lf%c%c%n", &temp_f, &temp_c1, &temp_c2, &n); + if (m == 3 && temp_c1 == 'd' && (temp_c2 == 'B' || temp_c2 == 'b')) { + /* dB 101: + * - logaritmic scale + * - dB = 20 * log(percent / 100) + * - percent = pow(10, dB / 20)) * 100 + * - for audio: 100% = 0dB (base max volume of current file = reference dB) + * - negative dB decreases volume, positive dB increases + * ex. + * 200% = 20 * log(200 / 100) = +6.02059991328 dB + * 50% = 20 * log( 50 / 100) = -6.02059991328 dB + * 6dB = pow(10, 6 / 20) * 100 = +195.26231497 % + * -6dB = pow(10, -6 / 20) * 100 = +50.50118723362 % + */ + + if (is_set) *is_set = 1; + *value = pow(10, temp_f / 20.0); /* dB to % where 1.0 = max */ + return n; + } + + /* test if format is N.N (percent) */ + m = sscanf(params, " %lf%n", &temp_f, &n); + if (m == 1) { + if (is_set) *is_set = 1; + *value = temp_f; + return n; + } + + return 0; +} + +static int get_time(const char* params, double* value_f, int32_t* value_i) { + int n,m; + int temp_i1, temp_i2; + double temp_f1, temp_f2; + char temp_c; + + /* test if format is hour: N:N(.n) or N_N(.n) */ + m = sscanf(params, " %d%c%d%n", &temp_i1,&temp_c,&temp_i2,&n); + if (m == 3 && (temp_c == ':' || temp_c == '_')) { + m = sscanf(params, " %lf%c%lf%n", &temp_f1,&temp_c,&temp_f2,&n); + if (m != 3 || /*temp_f1 < 0.0 ||*/ temp_f1 >= 60.0 || temp_f2 < 0.0 || temp_f2 >= 60.0) + return 0; + + *value_f = temp_f1 * 60.0 + temp_f2; + return n; + } + + /* test if format is seconds: N.n */ + m = sscanf(params, " %d.%d%n", &temp_i1,&temp_i2,&n); + if (m == 2) { + m = sscanf(params, " %lf%n", &temp_f1,&n); + if (m != 1 /*|| temp_f1 < 0.0*/) + return 0; + *value_f = temp_f1; + return n; + } + + /* test is format is hex samples: 0xN */ + m = sscanf(params, " 0x%x%n", &temp_i1,&n); + if (m == 1) { + /* allow negative samples for special meanings */ + //if (temp_i1 < 0) + // return 0; + + *value_i = temp_i1; + return n; + } + + /* assume format is samples: N */ + m = sscanf(params, " %d%n", &temp_i1,&n); + if (m == 1) { + /* allow negative samples for special meanings */ + //if (temp_i1 < 0) + // return 0; + + *value_i = temp_i1; + return n; + } + + return 0; +} + +static int get_time_f(const char* params, double* value_f, int32_t* value_i, bool* flag) { + int n = get_time(params, value_f, value_i); + if (n > 0) + *flag = 1; + return n; +} + +static int get_bool(const char* params, bool* value) { + int n,m; + char temp; + + n = 0; /* init as it's not matched if c isn't */ + m = sscanf(params, " %c%n", &temp, &n); + if (m >= 1 && !(temp == '#' || temp == '\r' || temp == '\n')) + return 0; /* ignore if anything non-space/comment matched */ + + if (m >= 1 && temp == '#') + n--; /* don't consume separator when returning totals */ + *value = 1; + return n; +} + +static int get_mask(const char* params, uint32_t* value) { + int n, m, total_n = 0; + int temp1,temp2, r1, r2; + int i; + char cmd; + uint32_t mask = *value; + + while (params[0] != '\0') { + m = sscanf(params, " %c%n", &cmd,&n); /* consume comma */ + if (m == 1 && (cmd == ',' || cmd == '-')) { /* '-' is alt separator (space is ok too, implicitly) */ + params += n; + continue; + } + + m = sscanf(params, " %d%n ~ %d%n", &temp1,&n, &temp2,&n); + if (m == 1) { /* single values */ + r1 = temp1 - 1; + r2 = temp1 - 1; + } + else if (m == 2) { /* range */ + r1 = temp1 - 1; + r2 = temp2 - 1; + } + else { /* no more matches */ + break; + } + + if (n == 0 || r1 < 0 || r1 > 31 || r2 < 0 || r2 > 31) + break; + + for (i = r1; i < r2 + 1; i++) { + mask |= (1 << i); + } + + params += n; + total_n += n; + + if (params[0]== ',' || params[0]== '-') + params++; + } + + *value = mask; + return total_n; +} + + +static int get_fade(const char* params, txtp_mix_data_t* mix, int* p_n) { + int n, m, tn = 0; + char type, separator; + + m = sscanf(params, " %d %c%n", &mix->ch_dst, &type, &n); + if (m != 2 || n == 0) goto fail; + params += n; + tn += n; + + if (type == '^') { + /* full definition */ + m = sscanf(params, " %lf ~ %lf = %c @%n", &mix->vol_start, &mix->vol_end, &mix->shape, &n); + if (m != 3 || n == 0) goto fail; + params += n; + tn += n; + + n = get_time(params, &mix->time_pre, &mix->sample_pre); + if (n == 0) goto fail; + params += n; + tn += n; + + m = sscanf(params, " %c%n", &separator, &n); + if ( m != 1 || n == 0 || separator != '~') goto fail; + params += n; + tn += n; + + n = get_time(params, &mix->time_start, &mix->sample_start); + if (n == 0) goto fail; + params += n; + tn += n; + + m = sscanf(params, " %c%n", &separator, &n); + if (m != 1 || n == 0 || separator != '+') goto fail; + params += n; + tn += n; + + n = get_time(params, &mix->time_end, &mix->sample_end); + if (n == 0) goto fail; + params += n; + tn += n; + + m = sscanf(params, " %c%n", &separator, &n); + if (m != 1 || n == 0 || separator != '~') goto fail; + params += n; + tn += n; + + n = get_time(params, &mix->time_post, &mix->sample_post); + if (n == 0) goto fail; + params += n; + tn += n; + } + else { + /* simplified definition */ + if (type == '{' || type == '(') { + mix->vol_start = 0.0; + mix->vol_end = 1.0; + } + else if (type == '}' || type == ')') { + mix->vol_start = 1.0; + mix->vol_end = 0.0; + } + else { + goto fail; + } + + mix->shape = type; /* internally converted */ + + mix->time_pre = -1.0; + mix->sample_pre = -1; + + n = get_position(params, &mix->position, &mix->position_type); + //if (n == 0) goto fail; /* optional */ + params += n; + tn += n; + + n = get_time(params, &mix->time_start, &mix->sample_start); + if (n == 0) goto fail; + params += n; + tn += n; + + m = sscanf(params, " %c%n", &separator, &n); + if (m != 1 || n == 0 || separator != '+') goto fail; + params += n; + tn += n; + + n = get_time(params, &mix->time_end, &mix->sample_end); + if (n == 0) goto fail; + params += n; + tn += n; + + mix->time_post = -1.0; + mix->sample_post = -1; + } + + mix->time_end = mix->time_start + mix->time_end; /* defined as length */ + + *p_n = tn; + return 1; +fail: + return 0; +} + +/*******************************************************************************/ +/* PARSER - MAIN */ +/*******************************************************************************/ + +static void add_settings(txtp_entry_t* current, txtp_entry_t* entry, const char* filename) { + + /* don't memcopy to allow list additions and ignore values not set, as current can be "default" settings */ + //*current = *cfg; + + if (filename) + strcpy(current->filename, filename); + + + /* play config */ + txtp_copy_config(¤t->config, &entry->config); + + /* file settings */ + if (entry->subsong) + current->subsong = entry->subsong; + + if (entry->sample_rate > 0) + current->sample_rate = entry->sample_rate; + + if (entry->channel_mask) + current->channel_mask = entry->channel_mask; + + if (entry->loop_install_set) { + current->loop_install_set = entry->loop_install_set; + current->loop_end_max = entry->loop_end_max; + current->loop_start_sample = entry->loop_start_sample; + current->loop_start_second = entry->loop_start_second; + current->loop_end_sample = entry->loop_end_sample; + current->loop_end_second = entry->loop_end_second; + } + + if (entry->trim_set) { + current->trim_set = entry->trim_set; + current->trim_second = entry->trim_second; + current->trim_sample = entry->trim_sample; + } + + if (entry->mixing_count > 0) { + int i; + for (i = 0; i < entry->mixing_count; i++) { + current->mixing[current->mixing_count] = entry->mixing[i]; + current->mixing_count++; + } + } + + current->loop_anchor_start = entry->loop_anchor_start; + current->loop_anchor_end = entry->loop_anchor_end; +} + +//TODO use +static inline int is_match(const char* str1, const char* str2) { + return strcmp(str1, str2) == 0; +} + +static void parse_params(txtp_entry_t* entry, char* params) { + /* parse params: #(commands) */ + int n, nc, nm, mc; + char command[TXT_LINE_MAX]; + play_config_t* tcfg = &entry->config; + + entry->range_start = 0; + entry->range_end = 1; + + while (params != NULL) { + /* position in next #(command) */ + params = strchr(params, '#'); + if (!params) break; + //;VGM_LOG("TXTP: params='%s'\n", params); + + /* get command until next space/number/comment/end */ + command[0] = '\0'; + mc = sscanf(params, "#%n%[^ #0-9\r\n]%n", &nc, command, &nc); + //;VGM_LOG("TXTP: command='%s', nc=%i, mc=%i\n", command, nc, mc); + if (mc <= 0 && nc == 0) break; + + params[0] = '\0'; //todo don't modify input string and properly calculate filename end + + params += nc; /* skip '#' and command */ + + /* check command string (though at the moment we only use single letters) */ + if (strcmp(command,"c") == 0) { + /* channel mask: file.ext#c1,2 = play channels 1,2 and mutes rest */ + + params += get_mask(params, &entry->channel_mask); + //;VGM_LOG("TXTP: channel_mask ");{int i; for (i=0;i<16;i++)VGM_LOG("%i ",(entry->channel_mask>>i)&1);}VGM_LOG("\n"); + } + else if (strcmp(command,"m") == 0) { + /* channel mixing: file.ext#m(sub-command),(sub-command),etc */ + char cmd; + + while (params[0] != '\0') { + txtp_mix_data_t mix = {0}; + + //;VGM_LOG("TXTP: subcommand='%s'\n", params); + + //todo use strchr instead? + if (sscanf(params, " %c%n", &cmd, &n) == 1 && n != 0 && cmd == ',') { + params += n; + continue; + } + + if (sscanf(params, " %d - %d%n", &mix.ch_dst, &mix.ch_src, &n) == 2 && n != 0) { + //;VGM_LOG("TXTP: mix %i-%i\n", mix.ch_dst, mix.ch_src); + txtp_add_mixing(entry, &mix, MIX_SWAP); /* N-M: swaps M with N */ + params += n; + continue; + } + + if ((sscanf(params, " %d + %d * %lf%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0) || + (sscanf(params, " %d + %d x %lf%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0)) { + //;VGM_LOG("TXTP: mix %i+%i*%f\n", mix.ch_dst, mix.ch_src, mix.vol); + txtp_add_mixing(entry, &mix, MIX_ADD_VOLUME); /* N+M*V: mixes M*volume to N */ + params += n; + continue; + } + + if (sscanf(params, " %d + %d%n", &mix.ch_dst, &mix.ch_src, &n) == 2 && n != 0) { + //;VGM_LOG("TXTP: mix %i+%i\n", mix.ch_dst, mix.ch_src); + txtp_add_mixing(entry, &mix, MIX_ADD); /* N+M: mixes M to N */ + params += n; + continue; + } + + if ((sscanf(params, " %d * %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0) || + (sscanf(params, " %d x %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) { + //;VGM_LOG("TXTP: mix %i*%f\n", mix.ch_dst, mix.vol); + txtp_add_mixing(entry, &mix, MIX_VOLUME); /* N*V: changes volume of N */ + params += n; + continue; + } + + if ((sscanf(params, " %d = %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) { + //;VGM_LOG("TXTP: mix %i=%f\n", mix.ch_dst, mix.vol); + txtp_add_mixing(entry, &mix, MIX_LIMIT); /* N=V: limits volume of N */ + params += n; + continue; + } + + if (sscanf(params, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'D') { + //;VGM_LOG("TXTP: mix %iD\n", mix.ch_dst); + txtp_add_mixing(entry, &mix, MIX_KILLMIX); /* ND: downmix N and all following channels */ + params += n; + continue; + } + + if (sscanf(params, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'd') { + //;VGM_LOG("TXTP: mix %id\n", mix.ch_dst); + txtp_add_mixing(entry, &mix, MIX_DOWNMIX);/* Nd: downmix N only */ + params += n; + continue; + } + + if (sscanf(params, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'u') { + //;VGM_LOG("TXTP: mix %iu\n", mix.ch_dst); + txtp_add_mixing(entry, &mix, MIX_UPMIX); /* Nu: upmix N */ + params += n; + continue; + } + + if (get_fade(params, &mix, &n) != 0) { + //;VGM_LOG("TXTP: fade %d^%f~%f=%c@%f~%f+%f~%f\n", + // mix.ch_dst, mix.vol_start, mix.vol_end, mix.shape, + // mix.time_pre, mix.time_start, mix.time_end, mix.time_post); + txtp_add_mixing(entry, &mix, MIX_FADE); /* N^V1~V2@T1~T2+T3~T4: fades volumes between positions */ + params += n; + continue; + } + + break; /* unknown mix/new command/end */ + } + } + else if (strcmp(command,"s") == 0 || (nc == 1 && params[0] >= '0' && params[0] <= '9')) { + /* subsongs: file.ext#s2 = play subsong 2, file.ext#2~10 = play subsong range */ + int subsong_start = 0, subsong_end = 0; + + //todo also advance params? + if (sscanf(params, " %d ~ %d", &subsong_start, &subsong_end) == 2) { + if (subsong_start > 0 && subsong_end > 0) { + entry->range_start = subsong_start-1; + entry->range_end = subsong_end; + } + //;VGM_LOG("TXTP: subsong range %i~%i\n", range_start, range_end); + } + else if (sscanf(params, " %d", &subsong_start) == 1) { + if (subsong_start > 0) { + entry->range_start = subsong_start-1; + entry->range_end = subsong_start; + } + //;VGM_LOG("TXTP: subsong single %i-%i\n", range_start, range_end); + } + else { /* wrong setting, ignore */ + //;VGM_LOG("TXTP: subsong none\n"); + } + } + + /* play config */ + else if (strcmp(command,"i") == 0) { + params += get_bool(params, &tcfg->ignore_loop); + tcfg->config_set = 1; + } + else if (strcmp(command,"e") == 0) { + params += get_bool(params, &tcfg->force_loop); + tcfg->config_set = 1; + } + else if (strcmp(command,"E") == 0) { + params += get_bool(params, &tcfg->really_force_loop); + tcfg->config_set = 1; + } + else if (strcmp(command,"F") == 0) { + params += get_bool(params, &tcfg->ignore_fade); + tcfg->config_set = 1; + } + else if (strcmp(command,"L") == 0) { + params += get_bool(params, &tcfg->play_forever); + tcfg->config_set = 1; + } + else if (strcmp(command,"l") == 0) { + params += get_double(params, &tcfg->loop_count, &tcfg->loop_count_set); + if (tcfg->loop_count < 0) + tcfg->loop_count_set = 0; + tcfg->config_set = 1; + } + else if (strcmp(command,"f") == 0) { + params += get_double(params, &tcfg->fade_time, &tcfg->fade_time_set); + if (tcfg->fade_time < 0) + tcfg->fade_time_set = 0; + tcfg->config_set = 1; + } + else if (strcmp(command,"d") == 0) { + params += get_double(params, &tcfg->fade_delay, &tcfg->fade_delay_set); + if (tcfg->fade_delay < 0) + tcfg->fade_delay_set = 0; + tcfg->config_set = 1; + } + else if (strcmp(command,"p") == 0) { + params += get_time_f(params, &tcfg->pad_begin_s, &tcfg->pad_begin, &tcfg->pad_begin_set); + tcfg->config_set = 1; + } + else if (strcmp(command,"P") == 0) { + params += get_time_f(params, &tcfg->pad_end_s, &tcfg->pad_end, &tcfg->pad_end_set); + tcfg->config_set = 1; + } + else if (strcmp(command,"r") == 0) { + params += get_time_f(params, &tcfg->trim_begin_s, &tcfg->trim_begin, &tcfg->trim_begin_set); + tcfg->config_set = 1; + } + else if (strcmp(command,"R") == 0) { + params += get_time_f(params, &tcfg->trim_end_s, &tcfg->trim_end, &tcfg->trim_end_set); + tcfg->config_set = 1; + } + else if (strcmp(command,"b") == 0) { + params += get_time_f(params, &tcfg->body_time_s, &tcfg->body_time, &tcfg->body_time_set); + tcfg->config_set = 1; + } + else if (strcmp(command,"B") == 0) { + params += get_time_f(params, &tcfg->body_time_s, &tcfg->body_time, &tcfg->body_time_set); + tcfg->config_set = 1; + /* similar to 'b' but implies no fades */ + tcfg->fade_time_set = 1; + tcfg->fade_time = 0; + tcfg->fade_delay_set = 1; + tcfg->fade_delay = 0; + } + + /* other settings */ + else if (strcmp(command,"h") == 0) { + params += get_int(params, &entry->sample_rate); + //;VGM_LOG("TXTP: sample_rate %i\n", cfg->sample_rate); + } + else if (strcmp(command,"I") == 0) { + n = get_time(params, &entry->loop_start_second, &entry->loop_start_sample); + if (n > 0) { /* first value must exist */ + params += n; + + n = get_time(params, &entry->loop_end_second, &entry->loop_end_sample); + if (n == 0) { /* second value is optional */ + entry->loop_end_max = 1; + } + + params += n; + entry->loop_install_set = 1; + } + + //;VGM_LOG("TXTP: loop_install %i (max=%i): %i %i / %f %f\n", entry->loop_install, entry->loop_end_max, + // entry->loop_start_sample, entry->loop_end_sample, entry->loop_start_second, entry->loop_end_second); + } + else if (strcmp(command,"t") == 0) { + entry->trim_set = get_time(params, &entry->trim_second, &entry->trim_sample); + //;VGM_LOG("TXTP: trim %i - %f / %i\n", entry->trim_set, entry->trim_second, entry->trim_sample); + } + + else if (is_match(command,"a") || is_match(command,"@loop")) { + entry->loop_anchor_start = 1; + //;VGM_LOG("TXTP: anchor start set\n"); + } + else if (is_match(command,"A") || is_match(command,"@loop-end")) { + entry->loop_anchor_end = 1; + //;VGM_LOG("TXTP: anchor end set\n"); + } + + //todo cleanup + /* macros */ + else if (is_match(command,"v") || is_match(command,"@volume")) { + txtp_mix_data_t mix = {0}; + + nm = get_volume(params, &mix.vol, NULL); + params += nm; + + if (nm == 0) continue; + + nm = get_mask(params, &mix.mask); + params += nm; + + txtp_add_mixing(entry, &mix, MACRO_VOLUME); + } + else if (strcmp(command,"@track") == 0 || + strcmp(command,"C") == 0 ) { + txtp_mix_data_t mix = {0}; + + nm = get_mask(params, &mix.mask); + params += nm; + if (nm == 0) continue; + + txtp_add_mixing(entry, &mix, MACRO_TRACK); + } + else if (strcmp(command,"@layer-v") == 0 || + strcmp(command,"@layer-b") == 0 || + strcmp(command,"@layer-e") == 0) { + txtp_mix_data_t mix = {0}; + + nm = get_int(params, &mix.max); + params += nm; + + if (nm > 0) { /* max is optional (auto-detects and uses max channels) */ + nm = get_mask(params, &mix.mask); + params += nm; + } + + mix.mode = command[7]; /* pass letter */ + txtp_add_mixing(entry, &mix, MACRO_LAYER); + } + else if (strcmp(command,"@crosslayer-v") == 0 || + strcmp(command,"@crosslayer-b") == 0 || + strcmp(command,"@crosslayer-e") == 0 || + strcmp(command,"@crosstrack") == 0) { + txtp_mix_data_t mix = {0}; + txtp_mix_t type; + if (strcmp(command,"@crosstrack") == 0) { + type = MACRO_CROSSTRACK; + } + else { + type = MACRO_CROSSLAYER; + mix.mode = command[12]; /* pass letter */ + } + + nm = get_int(params, &mix.max); + params += nm; + if (nm == 0) continue; + + txtp_add_mixing(entry, &mix, type); + } + else if (strcmp(command,"@downmix") == 0) { + txtp_mix_data_t mix = {0}; + + mix.max = 2; /* stereo only for now */ + //nm = get_int(params, &mix.max); + //params += nm; + //if (nm == 0) continue; + + txtp_add_mixing(entry, &mix, MACRO_DOWNMIX); + } + else if (params[nc] == ' ') { + //;VGM_LOG("TXTP: comment\n"); + break; /* comment, ignore rest */ + } + else { + //;VGM_LOG("TXTP: unknown command\n"); + /* end, incorrect command, or possibly a comment or double ## comment too + * (shouldn't fail for forward compatibility) */ + break; + } + } +} + + +static int add_group(txtp_header_t* txtp, char* line) { + int n, m; + txtp_group_t cfg = {0}; + int auto_pos = 0; + char c; + + /* parse group: (position)(type)(count)(repeat) #(commands) */ + //;VGM_LOG("TXTP: parse group '%s'\n", line); + + m = sscanf(line, " %c%n", &c, &n); + if (m == 1 && c == '-') { + auto_pos = 1; + line += n; + } + + m = sscanf(line, " %d%n", &cfg.position, &n); + if (m == 1) { + cfg.position--; /* externally 1=first but internally 0=first */ + line += n; + } + + m = sscanf(line, " %c%n", &cfg.type, &n); + if (m == 1) { + line += n; + } + + m = sscanf(line, " %d%n", &cfg.count, &n); + if (m == 1) { + line += n; + } + + m = sscanf(line, " %c%n", &cfg.repeat, &n); + if (m == 1 && cfg.repeat == TXTP_GROUP_REPEAT) { + auto_pos = 0; + line += n; + } + + m = sscanf(line, " >%c%n", &c, &n); + if (m == 1 && c == TXTP_GROUP_RANDOM_ALL) { + cfg.type = TXTP_GROUP_MODE_RANDOM; /* usually R>- but allows L/S>- */ + cfg.selected = cfg.count; /* special meaning */ + line += n; + } + else { + m = sscanf(line, " >%d%n", &cfg.selected, &n); + if (m == 1) { + cfg.type = TXTP_GROUP_MODE_RANDOM; /* usually R>1 but allows L/S>1 */ + cfg.selected--; /* externally 1=first but internally 0=first */ + line += n; + } + else if (cfg.type == TXTP_GROUP_MODE_RANDOM) { + /* was a random but didn't select anything, just select all */ + cfg.selected = cfg.count; + } + } + + parse_params(&cfg.entry, line); + + /* Groups can use "auto" position of last N files, so we need a counter that changes like this: + * #layer of 2 (pos = 0) + * #sequence of 2 + * bgm pos +1 > 1 + * bgm pos +1 > 2 + * group = -S2 pos -2 +1 > 1 (group is at 1 now since it "collapses" wems but becomes a position) + * #sequence of 3 + * bgm pos +1 > 2 + * bgm pos +1 > 3 + * #sequence of 2 + * bgm pos +1 > 4 + * bgm pos +1 > 5 + * group = -S2 pos -2 +1 > 4 (groups is at 4 now since are uncollapsed wems at 2/3) + * group = -S3 pos -3 +1 > 2 + * group = -L2 pos -2 +1 > 1 + */ + txtp->group_pos++; + txtp->group_pos -= cfg.count; + if (auto_pos) { + cfg.position = txtp->group_pos - 1; /* internally 1 = first */ + } + + //;VGM_LOG("TXTP: parsed group %i%c%i%c, auto=%i\n",cfg.position+1,cfg.type,cfg.count,cfg.repeat, auto_pos); + + /* add final group */ + { + /* resize in steps if not enough */ + if (txtp->group_count+1 > txtp->group_max) { + txtp_group_t *temp_group; + + txtp->group_max += 5; + temp_group = realloc(txtp->group, sizeof(txtp_group_t) * txtp->group_max); + if (!temp_group) goto fail; + txtp->group = temp_group; + } + + /* new group */ + txtp->group[txtp->group_count] = cfg; /* memcpy */ + + txtp->group_count++; + } + + return 1; +fail: + return 0; +} + + +static void clean_filename(char* filename) { + int i; + size_t len; + + if (filename[0] == '\0') + return; + + /* normalize paths */ + fix_dir_separators(filename); + + /* remove trailing spaces */ + len = strlen(filename); + for (i = len-1; i > 0; i--) { + if (filename[i] != ' ') + break; + filename[i] = '\0'; + } + +} + +//TODO see if entry can be set to &default/&entry[entry_count] to avoid add_settings +static int add_entry(txtp_header_t* txtp, char* filename, int is_default) { + int i; + txtp_entry_t entry = {0}; + + + //;VGM_LOG("TXTP: filename=%s\n", filename); + + /* parse filename: file.ext#(commands) */ + { + char* params; + + if (is_default) { + params = filename; /* multiple commands without filename */ + } + else { + /* find settings start after filenames (filenames can also contain dots and #, + * so this may be fooled by certain patterns) */ + params = strchr(filename, '.'); /* first dot (may be a false positive) */ + if (!params) /* extensionless */ + params = filename; + params = strchr(params, '#'); /* next should be actual settings */ + if (!params) + params = NULL; + } + + parse_params(&entry, params); + } + + + clean_filename(filename); + //;VGM_LOG("TXTP: clean filename='%s'\n", filename); + + /* settings that applies to final vgmstream */ + if (is_default) { + txtp->default_entry_set = 1; + add_settings(&txtp->default_entry, &entry, NULL); + return 1; + } + + /* add final entry */ + for (i = entry.range_start; i < entry.range_end; i++){ + txtp_entry_t* current; + + /* resize in steps if not enough */ + if (txtp->entry_count+1 > txtp->entry_max) { + txtp_entry_t* temp_entry; + + txtp->entry_max += 5; + temp_entry = realloc(txtp->entry, sizeof(txtp_entry_t) * txtp->entry_max); + if (!temp_entry) goto fail; + txtp->entry = temp_entry; + } + + /* new entry */ + current = &txtp->entry[txtp->entry_count]; + memset(current,0, sizeof(txtp_entry_t)); + entry.subsong = (i+1); + + add_settings(current, &entry, filename); + + txtp->entry_count++; + txtp->group_pos++; + } + + return 1; +fail: + return 0; +} + +/*******************************************************************************/ +/* PARSER - BASE */ +/*******************************************************************************/ + +static int is_substring(const char* val, const char* cmp) { + int n; + char subval[TXT_LINE_MAX]; + + /* read string without trailing spaces or comments/commands */ + if (sscanf(val, " %s%n[^ #\t\r\n]%n", subval, &n, &n) != 1) + return 0; + + if (0 != strcmp(subval,cmp)) + return 0; + return n; +} + +static int parse_num(const char* val, uint32_t* out_value) { + int hex = (val[0]=='0' && val[1]=='x'); + if (sscanf(val, hex ? "%x" : "%u", out_value) != 1) + goto fail; + + return 1; +fail: + return 0; +} + +static int parse_keyval(txtp_header_t* txtp, const char* key, const char* val) { + //;VGM_LOG("TXTP: key=val '%s'='%s'\n", key,val); + + + if (0==strcmp(key,"loop_start_segment")) { + if (!parse_num(val, &txtp->loop_start_segment)) goto fail; + } + else if (0==strcmp(key,"loop_end_segment")) { + if (!parse_num(val, &txtp->loop_end_segment)) goto fail; + } + else if (0==strcmp(key,"mode")) { + if (is_substring(val,"layers")) { + txtp->is_segmented = false; + txtp->is_layered = true; + } + else if (is_substring(val,"segments")) { + txtp->is_segmented = true; + txtp->is_layered = false; + } + else if (is_substring(val,"mixed")) { + txtp->is_segmented = false; + txtp->is_layered = false; + } + else { + goto fail; + } + } + else if (0==strcmp(key,"loop_mode")) { + if (is_substring(val,"keep")) { + txtp->is_loop_keep = true; + } + else if (is_substring(val,"auto")) { + txtp->is_loop_auto = true; + } + else { + goto fail; + } + } + else if (0==strcmp(key,"commands")) { + char val2[TXT_LINE_MAX]; + strcpy(val2, val); /* copy since val is modified here but probably not important */ + if (!add_entry(txtp, val2, 1)) goto fail; + } + else if (0==strcmp(key,"group")) { + char val2[TXT_LINE_MAX]; + strcpy(val2, val); /* copy since val is modified here but probably not important */ + if (!add_group(txtp, val2)) goto fail; + + } + else { + goto fail; + } + + return 1; +fail: + VGM_LOG("TXTP: error while parsing key=val '%s'='%s'\n", key,val); + return 0; +} + +txtp_header_t* txtp_parse(STREAMFILE* sf) { + txtp_header_t* txtp = NULL; + uint32_t txt_offset; + + + txtp = calloc(1,sizeof(txtp_header_t)); + if (!txtp) goto fail; + + /* defaults */ + txtp->is_segmented = 1; + + txt_offset = read_bom(sf); + + /* read and parse lines */ + { + text_reader_t tr; + uint8_t buf[TXT_LINE_MAX + 1]; + char key[TXT_LINE_KEY_MAX]; + char val[TXT_LINE_VAL_MAX]; + int ok, line_len; + char* line; + + if (!text_reader_init(&tr, buf, sizeof(buf), sf, txt_offset, 0)) + goto fail; + + do { + line_len = text_reader_get_line(&tr, &line); + if (line_len < 0) goto fail; /* too big for buf (maybe not text)) */ + + if (line == NULL) /* EOF */ + break; + + if (line_len == 0) /* empty */ + continue; + + /* try key/val (ignores lead/trail spaces, # may be commands or comments) */ + ok = sscanf(line, " %[^ \t#=] = %[^\t\r\n] ", key,val); + if (ok == 2) { /* key=val */ + if (!parse_keyval(txtp, key, val)) /* read key/val */ + goto fail; + continue; + } + + /* must be a filename (only remove spaces from start/end, as filenames con contain mid spaces/#/etc) */ + ok = sscanf(line, " %[^\t\r\n] ", val); + if (ok != 1) /* not a filename either */ + continue; + if (val[0] == '#') + continue; /* simple comment */ + + /* filename with settings */ + if (!add_entry(txtp, val, 0)) + goto fail; + + } while (line_len >= 0); + } + + /* mini-txth: if no entries are set try with filename, ex. from "song.ext#3.txtp" use "song.ext#3" + * (it's possible to have default "commands" inside the .txtp plus filename+settings) */ + if (txtp->entry_count == 0) { + char filename[PATH_LIMIT]; + + filename[0] = '\0'; + get_streamfile_basename(sf, filename, sizeof(filename)); + + add_entry(txtp, filename, 0); + } + + + return txtp; +fail: + txtp_clean(txtp); + return NULL; +} diff --git a/src/meta/txtp_process.c b/src/meta/txtp_process.c new file mode 100644 index 00000000..63af2949 --- /dev/null +++ b/src/meta/txtp_process.c @@ -0,0 +1,634 @@ +#include + +#include "txtp.h" +#include "../coding/coding.h" +#include "../layout/layout.h" +#include "../base/mixing.h" +#include "../base/plugins.h" + + +/*******************************************************************************/ +/* CONFIG */ +/*******************************************************************************/ + + +static void apply_settings(VGMSTREAM* vgmstream, txtp_entry_t* current) { + + /* base settings */ + if (current->sample_rate > 0) { + vgmstream->sample_rate = current->sample_rate; + } + + if (current->loop_install_set) { + if (current->loop_start_second > 0 || current->loop_end_second > 0) { + current->loop_start_sample = current->loop_start_second * vgmstream->sample_rate; + current->loop_end_sample = current->loop_end_second * vgmstream->sample_rate; + if (current->loop_end_sample > vgmstream->num_samples && + current->loop_end_sample - vgmstream->num_samples <= 0.1 * vgmstream->sample_rate) + current->loop_end_sample = vgmstream->num_samples; /* allow some rounding leeway */ + } + + if (current->loop_end_max) { + current->loop_end_sample = vgmstream->num_samples; + } + + vgmstream_force_loop(vgmstream, current->loop_install_set, current->loop_start_sample, current->loop_end_sample); + } + + if (current->trim_set) { + if (current->trim_second != 0.0) { + /* trim sample can become 0 here when second is too small (rounded) */ + current->trim_sample = (double)current->trim_second * (double)vgmstream->sample_rate; + } + + if (current->trim_sample < 0) { + vgmstream->num_samples += current->trim_sample; /* trim from end (add negative) */ + } + else if (current->trim_sample > 0 && vgmstream->num_samples > current->trim_sample) { + vgmstream->num_samples = current->trim_sample; /* trim to value >0 */ + } + + /* readjust after triming if it went over (could check for more edge cases but eh) */ + if (vgmstream->loop_end_sample > vgmstream->num_samples) + vgmstream->loop_end_sample = vgmstream->num_samples; + } + + + /* add macro to mixing list */ + if (current->channel_mask) { + int ch; + for (ch = 0; ch < vgmstream->channels; ch++) { + if (!((current->channel_mask >> ch) & 1)) { + txtp_mix_data_t mix = {0}; + mix.ch_dst = ch + 1; + mix.vol = 0.0f; + txtp_add_mixing(current, &mix, MIX_VOLUME); + } + } + } + + /* copy mixing list (should be done last as some mixes depend on config) */ + if (current->mixing_count > 0) { + int m, position_samples; + + for (m = 0; m < current->mixing_count; m++) { + txtp_mix_data_t *mix = ¤t->mixing[m]; + + switch(mix->command) { + /* base mixes */ + case MIX_SWAP: mixing_push_swap(vgmstream, mix->ch_dst, mix->ch_src); break; + case MIX_ADD: mixing_push_add(vgmstream, mix->ch_dst, mix->ch_src, 1.0); break; + case MIX_ADD_VOLUME: mixing_push_add(vgmstream, mix->ch_dst, mix->ch_src, mix->vol); break; + case MIX_VOLUME: mixing_push_volume(vgmstream, mix->ch_dst, mix->vol); break; + case MIX_LIMIT: mixing_push_limit(vgmstream, mix->ch_dst, mix->vol); break; + case MIX_UPMIX: mixing_push_upmix(vgmstream, mix->ch_dst); break; + case MIX_DOWNMIX: mixing_push_downmix(vgmstream, mix->ch_dst); break; + case MIX_KILLMIX: mixing_push_killmix(vgmstream, mix->ch_dst); break; + case MIX_FADE: + /* Convert from time to samples now that sample rate is final. + * Samples and time values may be mixed though, so it's done for every + * value (if one is 0 the other will be too, though) */ + if (mix->time_pre > 0.0) mix->sample_pre = mix->time_pre * vgmstream->sample_rate; + if (mix->time_start > 0.0) mix->sample_start = mix->time_start * vgmstream->sample_rate; + if (mix->time_end > 0.0) mix->sample_end = mix->time_end * vgmstream->sample_rate; + if (mix->time_post > 0.0) mix->sample_post = mix->time_post * vgmstream->sample_rate; + /* convert special meaning too */ + if (mix->time_pre < 0.0) mix->sample_pre = -1; + if (mix->time_post < 0.0) mix->sample_post = -1; + + if (mix->position_type == TXTP_POSITION_LOOPS && vgmstream->loop_flag) { + int loop_pre = vgmstream->loop_start_sample; + int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); + + position_samples = loop_pre + loop_samples * mix->position; + + if (mix->sample_pre >= 0) mix->sample_pre += position_samples; + mix->sample_start += position_samples; + mix->sample_end += position_samples; + if (mix->sample_post >= 0) mix->sample_post += position_samples; + } + + + mixing_push_fade(vgmstream, mix->ch_dst, mix->vol_start, mix->vol_end, mix->shape, + mix->sample_pre, mix->sample_start, mix->sample_end, mix->sample_post); + break; + + /* macro mixes */ + case MACRO_VOLUME: mixing_macro_volume(vgmstream, mix->vol, mix->mask); break; + case MACRO_TRACK: mixing_macro_track(vgmstream, mix->mask); break; + case MACRO_LAYER: mixing_macro_layer(vgmstream, mix->max, mix->mask, mix->mode); break; + case MACRO_CROSSTRACK: mixing_macro_crosstrack(vgmstream, mix->max); break; + case MACRO_CROSSLAYER: mixing_macro_crosslayer(vgmstream, mix->max, mix->mode); break; + case MACRO_DOWNMIX: mixing_macro_downmix(vgmstream, mix->max); break; + + default: + break; + } + } + } + + + /* default play config (last after sample rate mods/mixing/etc) */ + txtp_copy_config(&vgmstream->config, ¤t->config); + setup_state_vgmstream(vgmstream); + /* config is enabled in layouts or externally (for compatibility, since we don't know yet if this + * VGMSTREAM will part of a layout, or is enabled externally to not mess up plugins's calcs) */ +} + + +/*******************************************************************************/ +/* ENTRIES */ +/*******************************************************************************/ + +static bool parse_silents(txtp_header_t* txtp) { + VGMSTREAM* v_base = NULL; + + /* silents use same channels as close files */ + for (int i = 0; i < txtp->vgmstream_count; i++) { + if (!txtp->entry[i].silent) { + v_base = txtp->vgmstream[i]; + break; + } + } + + /* actually open silents */ + for (int i = 0; i < txtp->vgmstream_count; i++) { + if (!txtp->entry[i].silent) + continue; + + txtp->vgmstream[i] = init_vgmstream_silence_base(v_base); + if (!txtp->vgmstream[i]) goto fail; + + apply_settings(txtp->vgmstream[i], &txtp->entry[i]); + } + + return true; +fail: + return false; +} + +static bool is_silent(const char* fn) { + /* should also contain "." in the filename for commands with seconds ("1.0") to work */ + return fn[0] == '?'; +} + +static bool is_absolute(const char* fn) { + return fn[0] == '/' || fn[0] == '\\' || fn[1] == ':'; +} + +/* open all entries and apply settings to resulting VGMSTREAMs */ +static bool parse_entries(txtp_header_t* txtp, STREAMFILE* sf) { + bool has_silents = false; + + + if (txtp->entry_count == 0) + goto fail; + + txtp->vgmstream = calloc(txtp->entry_count, sizeof(VGMSTREAM*)); + if (!txtp->vgmstream) goto fail; + + txtp->vgmstream_count = txtp->entry_count; + + + /* open all entry files first as they'll be modified by modes */ + for (int i = 0; i < txtp->vgmstream_count; i++) { + STREAMFILE* temp_sf = NULL; + const char* filename = txtp->entry[i].filename; + + /* silent entry ignore */ + if (is_silent(filename)) { + txtp->entry[i].silent = true; + has_silents = true; + continue; + } + + /* absolute paths are detected for convenience, but since it's hard to unify all OSs + * and plugins, they aren't "officially" supported nor documented, thus may or may not work */ + if (is_absolute(filename)) + temp_sf = open_streamfile(sf, filename); /* from path as is */ + else + temp_sf = open_streamfile_by_filename(sf, filename); /* from current path */ + if (!temp_sf) { + vgm_logi("TXTP: cannot open %s\n", filename); + goto fail; + } + temp_sf->stream_index = txtp->entry[i].subsong; + + txtp->vgmstream[i] = init_vgmstream_from_STREAMFILE(temp_sf); + close_streamfile(temp_sf); + if (!txtp->vgmstream[i]) { + vgm_logi("TXTP: cannot parse %s#%i\n", filename, txtp->entry[i].subsong); + goto fail; + } + + apply_settings(txtp->vgmstream[i], &txtp->entry[i]); + } + + if (has_silents) { + if (!parse_silents(txtp)) + goto fail; + } + + return true; +fail: + return false; +} + + +/*******************************************************************************/ +/* GROUPS */ +/*******************************************************************************/ + +static void update_vgmstream_list(VGMSTREAM* vgmstream, txtp_header_t* txtp, int position, int count) { + //;VGM_LOG("TXTP: compact position=%i count=%i, vgmstreams=%i\n", position, count, txtp->vgmstream_count); + + /* sets and compacts vgmstream list pulling back all following entries */ + txtp->vgmstream[position] = vgmstream; + for (int i = position + count; i < txtp->vgmstream_count; i++) { + //;VGM_LOG("TXTP: copy %i to %i\n", i, i + 1 - count); + txtp->vgmstream[i + 1 - count] = txtp->vgmstream[i]; + txtp->entry[i + 1 - count] = txtp->entry[i]; /* memcpy old settings for other groups */ + } + + /* list can only become smaller, no need to alloc/free/etc */ + txtp->vgmstream_count = txtp->vgmstream_count + 1 - count; + //;VGM_LOG("TXTP: compact vgmstreams=%i\n", txtp->vgmstream_count); +} + +static bool find_loop_anchors(txtp_header_t* txtp, int position, int count, int* p_loop_start, int* p_loop_end) { + int loop_start = 0, loop_end = 0; + int i, j; + + //;VGM_LOG("TXTP: find loop anchors from %i to %i\n", position, count); + + for (i = position, j = 0; i < position + count; i++, j++) { + /* catch first time anchors appear only, also logic elsewhere also uses +1 */ + if (txtp->entry[i].loop_anchor_start && !loop_start) { + loop_start = j + 1; + } + if (txtp->entry[i].loop_anchor_end && !loop_end) { + loop_end = j + 1; + } + } + + if (loop_start) { + if (!loop_end) + loop_end = count; + *p_loop_start = loop_start; + *p_loop_end = loop_end; + //;VGM_LOG("TXTP: loop anchors %i, %i\n", loop_start, loop_end); + return true; + } + + return false; +} + + +static bool make_group_segment(txtp_header_t* txtp, txtp_group_t* grp, int position, int count) { + VGMSTREAM* vgmstream = NULL; + segmented_layout_data* data_s = NULL; + int loop_flag = 0; + int loop_start = 0, loop_end = 0; + + + /* allowed for actual groups (not final "mode"), otherwise skip to optimize */ + if (!grp && count == 1) { + //;VGM_LOG("TXTP: ignored single group\n"); + return true; + } + + if (position + count > txtp->vgmstream_count || position < 0 || count < 0) { + VGM_LOG("TXTP: ignored segment position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count); + return true; + } + + + /* set loops with "anchors" (this allows loop config inside groups, not just in the final group, + * which is sometimes useful when paired with random/selectable groups or loop times) */ + if (find_loop_anchors(txtp, position, count, &loop_start, &loop_end)) { + loop_flag = (loop_start > 0 && loop_start <= count); + } + /* loop segment settings only make sense if this group becomes final vgmstream */ + else if (position == 0 && txtp->vgmstream_count == count) { + loop_start = txtp->loop_start_segment; + loop_end = txtp->loop_end_segment; + + if (loop_start && !loop_end) { + loop_end = count; + } + else if (txtp->is_loop_auto) { /* auto set to last segment */ + loop_start = count; + loop_end = count; + } + loop_flag = (loop_start > 0 && loop_start <= count); + } + + + /* fix loop keep (do it before init'ing as loops/metadata may be disabled for segments) */ + int32_t loop_start_sample = 0, loop_end_sample = 0; + if (loop_flag && txtp->is_loop_keep) { + int32_t current_samples = 0; + for (int i = 0; i < count; i++) { + if (loop_start == i+1 /*&& txtp->vgmstream[i + position]->loop_start_sample*/) { + loop_start_sample = current_samples + txtp->vgmstream[i + position]->loop_start_sample; + } + + current_samples += txtp->vgmstream[i + position]->num_samples; + + if (loop_end == i+1 && txtp->vgmstream[i + position]->loop_end_sample) { + loop_end_sample = current_samples - txtp->vgmstream[i + position]->num_samples + txtp->vgmstream[i + position]->loop_end_sample; + } + } + } + + /* init layout */ + data_s = init_layout_segmented(count); + if (!data_s) goto fail; + + /* copy each subfile */ + for (int i = 0; i < count; i++) { + data_s->segments[i] = txtp->vgmstream[i + position]; + txtp->vgmstream[i + position] = NULL; /* will be freed by layout */ + } + + /* setup VGMSTREAMs */ + if (!setup_layout_segmented(data_s)) + goto fail; + + /* build the layout VGMSTREAM */ + vgmstream = allocate_segmented_vgmstream(data_s, loop_flag, loop_start - 1, loop_end - 1); + if (!vgmstream) goto fail; + + /* custom meta name if all parts don't match */ + for (int i = 0; i < count; i++) { + if (vgmstream->meta_type != data_s->segments[i]->meta_type) { + vgmstream->meta_type = meta_TXTP; + break; + } + } + + /* fix loop keep */ + if (loop_flag && txtp->is_loop_keep) { + vgmstream->loop_start_sample = loop_start_sample; + vgmstream->loop_end_sample = loop_end_sample; + } + + + /* set new vgmstream and reorder positions */ + update_vgmstream_list(vgmstream, txtp, position, count); + + + /* special "whole loop" settings */ + if (grp && grp->entry.loop_anchor_start == 1) { + grp->entry.config.config_set = 1; + grp->entry.config.really_force_loop = 1; + } + + return true; +fail: + close_vgmstream(vgmstream); + if (!vgmstream) + free_layout_segmented(data_s); + return false; +} + +static bool make_group_layer(txtp_header_t* txtp, txtp_group_t* grp, int position, int count) { + VGMSTREAM* vgmstream = NULL; + layered_layout_data* data_l = NULL; + + + /* allowed for actual groups (not final mode), otherwise skip to optimize */ + if (!grp && count == 1) { + //;VGM_LOG("TXTP: ignored single group\n"); + return true; + } + + if (position + count > txtp->vgmstream_count || position < 0 || count < 0) { + VGM_LOG("TXTP: ignored layer position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count); + return 1; + } + + + /* init layout */ + data_l = init_layout_layered(count); + if (!data_l) goto fail; + + /* copy each subfile */ + for (int i = 0; i < count; i++) { + data_l->layers[i] = txtp->vgmstream[i + position]; + txtp->vgmstream[i + position] = NULL; /* will be freed by layout */ + } + + /* setup VGMSTREAMs */ + if (!setup_layout_layered(data_l)) + goto fail; + + /* build the layout VGMSTREAM */ + vgmstream = allocate_layered_vgmstream(data_l); + if (!vgmstream) goto fail; + + /* custom meta name if all parts don't match */ + for (int i = 0; i < count; i++) { + if (vgmstream->meta_type != data_l->layers[i]->meta_type) { + vgmstream->meta_type = meta_TXTP; + break; + } + } + + /* set new vgmstream and reorder positions */ + update_vgmstream_list(vgmstream, txtp, position, count); + + + /* special "whole loop" settings (also loop if this group becomes final vgmstream) */ + if (grp && (grp->entry.loop_anchor_start == 1 + || (position == 0 && txtp->vgmstream_count == count && txtp->is_loop_auto))) { + grp->entry.config.config_set = 1; + grp->entry.config.really_force_loop = 1; + } + + return true; +fail: + close_vgmstream(vgmstream); + if (!vgmstream) + free_layout_layered(data_l); + return false; +} + +static int make_group_random(txtp_header_t* txtp, txtp_group_t* grp, int position, int count, int selected) { + VGMSTREAM* vgmstream = NULL; + + /* allowed for actual groups (not final mode), otherwise skip to optimize */ + if (!grp && count == 1) { + //;VGM_LOG("TXTP: ignored single group\n"); + return true; + } + + if (position + count > txtp->vgmstream_count || position < 0 || count < 0) { + VGM_LOG("TXTP: ignored random position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count); + return true; + } + + /* 0=actually random for fun and testing, but undocumented since random music is kinda weird, may change anytime + * (plus foobar caches song duration unless .txtp is modifies, so it can get strange if randoms are too different) */ + if (selected < 0) { + static int random_seed = 0; + srand((unsigned)txtp + random_seed++); /* whatevs */ + selected = (rand() % count); /* 0..count-1 */ + //;VGM_LOG("TXTP: autoselected random %i\n", selected); + } + + if (selected < 0 || selected > count) { + goto fail; + } + + if (selected == count) { + /* special case meaning "select all", basically for quick testing and clearer Wwise */ + if (!make_group_segment(txtp, grp, position, count)) + goto fail; + vgmstream = txtp->vgmstream[position]; + } + else { + /* get selected and remove non-selected */ + vgmstream = txtp->vgmstream[position + selected]; + txtp->vgmstream[position + selected] = NULL; + for (int i = 0; i < count; i++) { + close_vgmstream(txtp->vgmstream[i + position]); + } + + /* set new vgmstream and reorder positions */ + update_vgmstream_list(vgmstream, txtp, position, count); + } + + + /* special "whole loop" settings */ + if (grp && grp->entry.loop_anchor_start == 1) { + grp->entry.config.config_set = 1; + grp->entry.config.really_force_loop = 1; + } + + /* force selected vgmstream to be a segment when not a group already, and + * group + vgmstream has config (AKA must loop/modify over the result) */ + //todo could optimize to not generate segment in some cases? + if (grp && + !(vgmstream->layout_type == layout_layered || vgmstream->layout_type == layout_segmented) && + (grp->entry.config.config_set && vgmstream->config.config_set) ) { + if (!make_group_segment(txtp, grp, position, 1)) + goto fail; + } + + return true; +fail: + close_vgmstream(vgmstream); + return false; +} + +static bool parse_groups(txtp_header_t* txtp) { + + /* detect single files before grouping */ + if (txtp->group_count == 0 && txtp->vgmstream_count == 1) { + txtp->is_single = true; + txtp->is_segmented = false; + txtp->is_layered = false; + } + + /* group files as needed */ + for (int i = 0; i < txtp->group_count; i++) { + txtp_group_t *grp = &txtp->group[i]; + int pos, groups; + + //;VGM_LOG("TXTP: apply group %i%c%i%c\n",txtp->group[i].position,txtp->group[i].type,txtp->group[i].count,txtp->group[i].repeat); + + /* special meaning of "all files" */ + if (grp->position < 0 || grp->position >= txtp->vgmstream_count) + grp->position = 0; + if (grp->count <= 0) + grp->count = txtp->vgmstream_count - grp->position; + + /* repeats N groups (trailing files are not grouped) */ + if (grp->repeat == TXTP_GROUP_REPEAT) { + groups = ((txtp->vgmstream_count - grp->position) / grp->count); + } + else { + groups = 1; + } + + /* as groups are compacted position goes 1 by 1 */ + for (pos = grp->position; pos < grp->position + groups; pos++) { + //;VGM_LOG("TXTP: group=%i, count=%i, groups=%i\n", pos, grp->count, groups); + switch(grp->type) { + case TXTP_GROUP_MODE_LAYERED: + if (!make_group_layer(txtp, grp, pos, grp->count)) + goto fail; + break; + case TXTP_GROUP_MODE_SEGMENTED: + if (!make_group_segment(txtp, grp, pos, grp->count)) + goto fail; + break; + case TXTP_GROUP_MODE_RANDOM: + if (!make_group_random(txtp, grp, pos, grp->count, grp->selected)) + goto fail; + break; + default: + goto fail; + } + } + + + /* group may also have settings (like downmixing) */ + apply_settings(txtp->vgmstream[grp->position], &grp->entry); + txtp->entry[grp->position] = grp->entry; /* memcpy old settings for subgroups */ + } + + /* final tweaks (should be integrated with the above?) */ + if (txtp->is_layered) { + if (!make_group_layer(txtp, NULL, 0, txtp->vgmstream_count)) + goto fail; + } + if (txtp->is_segmented) { + if (!make_group_segment(txtp, NULL, 0, txtp->vgmstream_count)) + goto fail; + } + if (txtp->is_single) { + /* special case of setting start_segment to force/overwrite looping + * (better to use #E but left for compatibility with older TXTPs) */ + if (txtp->loop_start_segment == 1 && !txtp->loop_end_segment) { + //todo try look settings + //txtp->default_entry.config.config_set = 1; + //txtp->default_entry.config.really_force_loop = 1; + vgmstream_force_loop(txtp->vgmstream[0], 1, txtp->vgmstream[0]->loop_start_sample, txtp->vgmstream[0]->num_samples); + } + } + + /* apply default settings to the resulting file */ + if (txtp->default_entry_set) { + apply_settings(txtp->vgmstream[0], &txtp->default_entry); + } + + return true; +fail: + return false; +} + + +bool txtp_process(txtp_header_t* txtp, STREAMFILE* sf) { + bool ok; + + /* process files in the .txtp */ + ok = parse_entries(txtp, sf); + if (!ok) goto fail; + + /* group files into layouts */ + ok = parse_groups(txtp); + if (!ok) goto fail; + + + /* may happen if using mixed mode but some files weren't grouped */ + if (txtp->vgmstream_count != 1) { + VGM_LOG("TXTP: wrong final vgmstream count %i\n", txtp->vgmstream_count); + goto fail; + } + + return true; +fail: + return false; +} diff --git a/src/vgmstream.c b/src/vgmstream.c index 31cbd938..3d37461b 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -16,7 +16,7 @@ #include "util/sf_utils.h" -static void try_dual_file_stereo(VGMSTREAM* opened_vgmstream, STREAMFILE* sf, int format_id); +static void try_dual_file_stereo(VGMSTREAM* opened_vgmstream, STREAMFILE* sf); /*****************************************************************************/ @@ -39,7 +39,7 @@ VGMSTREAM* init_vgmstream_from_STREAMFILE(STREAMFILE* sf) { } -bool prepare_vgmstream(VGMSTREAM* vgmstream, STREAMFILE* sf, int format_id) { +bool prepare_vgmstream(VGMSTREAM* vgmstream, STREAMFILE* sf) { /* fail if there is nothing/too much to play (<=0 generates empty files, >N writes GBs of garbage) */ if (vgmstream->num_samples <= 0 || vgmstream->num_samples > VGMSTREAM_MAX_NUM_SAMPLES) { @@ -68,7 +68,7 @@ bool prepare_vgmstream(VGMSTREAM* vgmstream, STREAMFILE* sf, int format_id) { /* test if candidate for dual stereo */ if (vgmstream->channels == 1 && vgmstream->allow_dual_stereo == 1) { - try_dual_file_stereo(vgmstream, sf, format_id); + try_dual_file_stereo(vgmstream, sf); } @@ -428,7 +428,7 @@ fail: /* See if there is a second file which may be the second channel, given an already opened mono vgmstream. * If a suitable file is found, open it and change opened_vgmstream to a stereo vgmstream. */ -static void try_dual_file_stereo(VGMSTREAM* opened_vgmstream, STREAMFILE* sf, int format_id) { +static void try_dual_file_stereo(VGMSTREAM* opened_vgmstream, STREAMFILE* sf) { /* filename search pairs for dual file stereo */ static const char* const dfs_pairs[][2] = { {"L","R"}, /* most common in .dsp and .vag */ @@ -516,7 +516,7 @@ static void try_dual_file_stereo(VGMSTREAM* opened_vgmstream, STREAMFILE* sf, in return; //;VGM_LOG("DFS: match %i filename=%s\n", dfs_pair, new_filename); - init_vgmstream_t init_vgmstream_function = get_vgmstream_format_init(format_id); + init_vgmstream_t init_vgmstream_function = get_vgmstream_format_init(opened_vgmstream->format_id); if (init_vgmstream_function == NULL) goto fail; diff --git a/src/vgmstream.h b/src/vgmstream.h index 4e86fc2c..69f7575e 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -1,8 +1,8 @@ /* - * vgmstream.h - definitions for VGMSTREAM, encapsulating a multi-channel, looped audio stream + * vgmstream.h - internal definitions for VGMSTREAM, encapsulating a multi-channel, looped audio stream */ -#ifndef _VGMSTREAM_H -#define _VGMSTREAM_H +#ifndef _VGMSTREAM_H_ +#define _VGMSTREAM_H_ /* Due mostly to licensing issues, Vorbis, MPEG, G.722.1, etc decoding is done by external libraries. * Libs are disabled by default, defined on compile-time for builds that support it */ @@ -23,29 +23,19 @@ #include "streamfile.h" #include "vgmstream_types.h" -#include "util/log.h" - -#ifdef VGM_USE_MP4V2 -#define MP4V2_NO_STDINT_DEFS -#include -#endif - -#ifdef VGM_USE_FDKAAC -#include -#endif #include "coding/g72x_state.h" typedef struct { - int config_set; /* some of the mods below are set */ + bool config_set; /* some of the mods below are set */ /* modifiers */ - int play_forever; - int ignore_loop; - int force_loop; - int really_force_loop; - int ignore_fade; + bool play_forever; + bool ignore_loop; + bool force_loop; + bool really_force_loop; + bool ignore_fade; /* processing */ double loop_count; @@ -66,18 +56,18 @@ typedef struct { double pad_end_s; /* internal flags */ - int pad_begin_set; - int trim_begin_set; - int body_time_set; - int loop_count_set; - int trim_end_set; - int fade_delay_set; - int fade_time_set; - int pad_end_set; + bool pad_begin_set; + bool trim_begin_set; + bool body_time_set; + bool loop_count_set; + bool trim_end_set; + bool fade_delay_set; + bool fade_time_set; + bool pad_end_set; /* for lack of a better place... */ - int is_txtp; - int is_mini_txtp; + bool is_txtp; + bool is_mini_txtp; } play_config_t; @@ -165,7 +155,7 @@ typedef struct { meta_t meta_type; /* type of metadata */ /* loop config */ - int loop_flag; /* is this stream looped? */ + bool loop_flag; /* is this stream looped? */ int32_t loop_start_sample; /* first sample of the loop (included in the loop) */ int32_t loop_end_sample; /* last sample of the loop (not included in the loop) */ @@ -187,7 +177,7 @@ typedef struct { /* other config */ bool allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */ - + int format_id; /* internal format ID */ /* layout/block state */ size_t full_block_size; /* actual data size of an entire block (ie. may be fixed, include padding/headers, etc) */ @@ -267,26 +257,6 @@ typedef struct { } layered_layout_data; -#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) -typedef struct { - STREAMFILE* streamfile; - uint64_t start; - uint64_t offset; - uint64_t size; -} mp4_streamfile; - -typedef struct { - mp4_streamfile if_file; - MP4FileHandle h_mp4file; - MP4TrackId track_id; - unsigned long sampleId, numSamples; - UINT codec_init_data_size; - HANDLE_AACDECODER h_aacdecoder; - unsigned int sample_ptr, samples_per_frame, samples_discard; - INT_PCM sample_buffer[( (6) * (2048)*4 )]; -} mp4_aac_codec_data; -#endif - // VGMStream description in structure format typedef struct { int sample_rate; diff --git a/src/vgmstream_init.c b/src/vgmstream_init.c index 0fe06334..625357ed 100644 --- a/src/vgmstream_init.c +++ b/src/vgmstream_init.c @@ -237,11 +237,8 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_kt_wiibgm, init_vgmstream_bfstm, init_vgmstream_mca, -#if 0 - init_vgmstream_mp4_aac, -#endif #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) - init_vgmstream_akb_mp4, + init_vgmstream_mp4_aac, #endif init_vgmstream_ktss, init_vgmstream_hca, @@ -575,10 +572,10 @@ VGMSTREAM* detect_vgmstream_format(STREAMFILE* sf) { if (!vgmstream) continue; - int format_id = i + 1; + vgmstream->format_id = i + 1; /* validate + setup vgmstream */ - if (!prepare_vgmstream(vgmstream, sf, format_id)) { + if (!prepare_vgmstream(vgmstream, sf)) { /* keep trying if wasn't valid, as simpler formats may return a vgmstream by mistake */ close_vgmstream(vgmstream); continue; diff --git a/src/vgmstream_init.h b/src/vgmstream_init.h index 30e8c812..0fa1aa6a 100644 --- a/src/vgmstream_init.h +++ b/src/vgmstream_init.h @@ -4,7 +4,7 @@ #include "meta/meta.h" #include "vgmstream.h" -bool prepare_vgmstream(VGMSTREAM* vgmstream, STREAMFILE* sf, int format_id); +bool prepare_vgmstream(VGMSTREAM* vgmstream, STREAMFILE* sf); VGMSTREAM* detect_vgmstream_format(STREAMFILE* sf); init_vgmstream_t get_vgmstream_format_init(int format_id); diff --git a/vgmstream_msvc.props b/vgmstream_msvc.props index 6734d642..08eb060b 100644 --- a/vgmstream_msvc.props +++ b/vgmstream_msvc.props @@ -47,7 +47,7 @@ $(VCmnPreprocessorDefinitions);IN_VGMSTREAM_EXPORTS;VGM_WINAMP_UNICODE ../ext_libs/libvorbis.lib;../ext_libs/libmpg123-0.lib;../ext_libs/libg719_decode.lib;../ext_libs/avcodec.lib;../ext_libs/avformat.lib;../ext_libs/avutil.lib;../ext_libs/swresample.lib;../ext_libs/libatrac9.lib;../ext_libs/libcelt-0061.lib;../ext_libs/libcelt-0110.lib;../ext_libs/libspeex-1.lib - $(VCmnAdditionalDependencies32);../ext_libs/jansson.lib + $(VCmnAdditionalDependencies32); $(VCmnAdditionalDependencies32);$(VCmnDependenciesDir)/foobar/foobar2000/shared/shared-Win32.lib ../ext_libs/dll-x64/libvorbis.lib;../ext_libs/dll-x64/libmpg123-0.lib;../ext_libs/dll-x64/libg719_decode.lib;../ext_libs/dll-x64/avcodec.lib;../ext_libs/dll-x64/avformat.lib;../ext_libs/dll-x64/avutil.lib;../ext_libs/dll-x64/swresample.lib;../ext_libs/dll-x64/libatrac9.lib;../ext_libs/dll-x64/libcelt-0061.lib;../ext_libs/dll-x64/libcelt-0110.lib;../ext_libs/dll-x64/libspeex-1.lib $(VCmnAdditionalDependencies64)