diff --git a/cli/vgmstream_cli.c b/cli/vgmstream_cli.c index b72be68d..993c581f 100644 --- a/cli/vgmstream_cli.c +++ b/cli/vgmstream_cli.c @@ -554,6 +554,7 @@ void replace_filename(char* dst, size_t dstsize, const char* outfilename, const /* ************************************************************ */ static int convert_file(cli_config* cfg); +static int write_file(VGMSTREAM* vgmstream, cli_config* cfg); int main(int argc, char** argv) { cli_config cfg = {0}; @@ -596,13 +597,8 @@ fail: static int convert_file(cli_config* cfg) { VGMSTREAM* vgmstream = NULL; - FILE* outfile = NULL; char outfilename_temp[PATH_LIMIT]; - - sample_t* buf = NULL; - int channels, input_channels; int32_t len_samples; - int i, j; /* for plugin testing */ @@ -641,19 +637,27 @@ static int convert_file(cli_config* cfg) { /* modify the VGMSTREAM if needed (before printing file info) */ apply_config(vgmstream, cfg); - channels = vgmstream->channels; - input_channels = vgmstream->channels; - /* enable after config but before outbuf */ - if (cfg->downmix_channels) + if (cfg->downmix_channels) { vgmstream_mixing_autodownmix(vgmstream, cfg->downmix_channels); - vgmstream_mixing_enable(vgmstream, SAMPLE_BUFFER_SIZE, &input_channels, &channels); + } + else if (cfg->only_stereo >= 0) { + vgmstream_mixing_stereo_only(vgmstream, cfg->only_stereo); + } + vgmstream_mixing_enable(vgmstream, SAMPLE_BUFFER_SIZE, NULL, NULL); /* get final play config */ len_samples = vgmstream_get_samples(vgmstream); if (len_samples <= 0) goto fail; + if (cfg->seek_samples1 < -1) /* ex value for loop testing */ + cfg->seek_samples1 = vgmstream->loop_start_sample; + if (cfg->seek_samples1 >= len_samples) + cfg->seek_samples1 = -1; + if (cfg->seek_samples2 >= len_samples) + cfg->seek_samples2 = -1; + if (cfg->play_forever && !vgmstream_get_play_forever(vgmstream)) { fprintf(stderr, "file can't be played forever"); goto fail; @@ -661,10 +665,7 @@ static int convert_file(cli_config* cfg) { /* prepare output */ - if (cfg->play_sdtout) { - outfile = stdout; - } - else if (!cfg->print_metaonly && !cfg->decode_only) { + { if (cfg->outfilename_config) { /* special substitution */ replace_filename(outfilename_temp, sizeof(outfilename_temp), cfg->outfilename_config, cfg->infilename, vgmstream); @@ -684,16 +685,6 @@ static int convert_file(cli_config* cfg) { fprintf(stderr, "same infile and outfile name: %s\n", cfg->outfilename); goto fail; } - - outfile = fopen(cfg->outfilename,"wb"); - if (!outfile) { - fprintf(stderr, "failed to open %s for output\n", cfg->outfilename); - goto fail; - } - - /* no improvement */ - //setvbuf(outfile, NULL, _IOFBF, SAMPLE_BUFFER_SIZE * sizeof(sample_t) * input_channels); - //setvbuf(outfile, NULL, _IONBF, 0); } @@ -713,26 +704,49 @@ static int convert_file(cli_config* cfg) { /* prints done */ if (cfg->print_metaonly) { - if (!cfg->play_sdtout) { - if (outfile != NULL) - fclose(outfile); - } close_vgmstream(vgmstream); return 1; } - if (cfg->seek_samples1 < -1) /* ex value for loop testing */ - cfg->seek_samples1 = vgmstream->loop_start_sample; - if (cfg->seek_samples1 >= len_samples) - cfg->seek_samples1 = -1; - if (cfg->seek_samples2 >= len_samples) - cfg->seek_samples2 = -1; - if (cfg->seek_samples2 >= 0) - len_samples -= cfg->seek_samples2; - else if (cfg->seek_samples1 >= 0) - len_samples -= cfg->seek_samples1; + /* main decode */ + write_file(vgmstream, cfg); + /* try again with (for testing reset_vgmstream, 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]; + strcpy(outfilename_reset, cfg->outfilename); + strcat(outfilename_reset, ".reset.wav"); + + cfg->outfilename = outfilename_reset; + + reset_vgmstream(vgmstream); + + write_file(vgmstream, cfg); + } + + close_vgmstream(vgmstream); + return 1; + +fail: + close_vgmstream(vgmstream); + return 0; +} + + +static int write_file(VGMSTREAM* vgmstream, cli_config* cfg) { + FILE* outfile = NULL; + int32_t len_samples; + sample_t* buf = NULL; + int i; + int channels, input_channels; + + + channels = vgmstream->channels; + input_channels = vgmstream->channels; + + vgmstream_mixing_enable(vgmstream, 0, &input_channels, &channels); /* last init */ buf = malloc(SAMPLE_BUFFER_SIZE * sizeof(sample_t) * input_channels); @@ -741,6 +755,36 @@ static int convert_file(cli_config* cfg) { goto fail; } + /* simulate seek */ + len_samples = vgmstream_get_samples(vgmstream); + if (cfg->seek_samples2 >= 0) + len_samples -= cfg->seek_samples2; + else if (cfg->seek_samples1 >= 0) + len_samples -= cfg->seek_samples1; + + if (cfg->seek_samples1 >= 0) + seek_vgmstream(vgmstream, cfg->seek_samples1); + if (cfg->seek_samples2 >= 0) + seek_vgmstream(vgmstream, cfg->seek_samples2); + + + /* output file */ + if (cfg->play_sdtout) { + outfile = stdout; + } + else if (!cfg->decode_only) { + outfile = fopen(cfg->outfilename, "wb"); + if (!outfile) { + fprintf(stderr, "failed to open %s for output\n", cfg->outfilename); + goto fail; + } + + /* no improvement */ + //setvbuf(outfile, NULL, _IOFBF, SAMPLE_BUFFER_SIZE * sizeof(sample_t) * input_channels); + //setvbuf(outfile, NULL, _IONBF, 0); + } + + /* decode forever */ while (cfg->play_forever) { int to_get = SAMPLE_BUFFER_SIZE; @@ -748,35 +792,24 @@ static int convert_file(cli_config* cfg) { render_vgmstream(buf, to_get, vgmstream); swap_samples_le(buf, channels * to_get); /* write PC endian */ - if (cfg->only_stereo != -1) { - for (j = 0; j < to_get; j++) { - fwrite(buf + j*channels + (cfg->only_stereo*2), sizeof(sample_t), 2, outfile); - } - } else { - fwrite(buf, sizeof(sample_t) * channels, to_get, outfile); - } + fwrite(buf, sizeof(sample_t) * channels, to_get, outfile); + /* should write infinitely until program kill */ } /* slap on a .wav header */ if (!cfg->decode_only) { uint8_t wav_buf[0x100]; - int channels_write = (cfg->only_stereo != -1) ? 2 : channels; size_t bytes_done; bytes_done = make_wav_header(wav_buf,0x100, - len_samples, vgmstream->sample_rate, channels_write, + len_samples, vgmstream->sample_rate, channels, cfg->write_lwav, cfg->lwav_loop_start, cfg->lwav_loop_end); - fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile); + fwrite(wav_buf, sizeof(uint8_t), bytes_done, outfile); } - if (cfg->seek_samples1 >= 0) - seek_vgmstream(vgmstream, cfg->seek_samples1); - if (cfg->seek_samples2 >= 0) - seek_vgmstream(vgmstream, cfg->seek_samples2); - /* decode */ for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) { int to_get = SAMPLE_BUFFER_SIZE; @@ -787,96 +820,22 @@ static int convert_file(cli_config* cfg) { if (!cfg->decode_only) { swap_samples_le(buf, channels * to_get); /* write PC endian */ - if (cfg->only_stereo != -1) { - for (j = 0; j < to_get; j++) { - fwrite(buf + j*channels + (cfg->only_stereo*2), sizeof(sample_t), 2, outfile); - } - } else { - fwrite(buf, sizeof(sample_t), to_get * channels, outfile); - } + fwrite(buf, sizeof(sample_t) * channels, to_get, outfile); } } - if (outfile != NULL) { + if (outfile && !cfg->play_sdtout) fclose(outfile); - outfile = NULL; - } - - - /* try again with (for testing reset_vgmstream, simulates a seek to 0 after changing internal state) */ - if (cfg->test_reset) { - char outfilename_reset[PATH_LIMIT]; - strcpy(outfilename_reset, cfg->outfilename); - strcat(outfilename_reset, ".reset.wav"); - - outfile = fopen(outfilename_reset,"wb"); - if (!outfile) { - fprintf(stderr, "failed to open %s for output\n", outfilename_reset); - goto fail; - } - - /* slap on a .wav header */ - if (!cfg->decode_only) { - uint8_t wav_buf[0x100]; - int channels_write = (cfg->only_stereo != -1) ? 2 : channels; - size_t bytes_done; - - bytes_done = make_wav_header(wav_buf,0x100, - len_samples, vgmstream->sample_rate, channels_write, - cfg->write_lwav, cfg->lwav_loop_start, cfg->lwav_loop_end); - - fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile); - } - - - reset_vgmstream(vgmstream); - - if (cfg->seek_samples1 >= 0) - seek_vgmstream(vgmstream, cfg->seek_samples1); - if (cfg->seek_samples2 >= 0) - seek_vgmstream(vgmstream, cfg->seek_samples2); - - /* decode */ - for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) { - int to_get = SAMPLE_BUFFER_SIZE; - if (i + SAMPLE_BUFFER_SIZE > len_samples) - to_get = len_samples - i; - - render_vgmstream(buf, to_get, vgmstream); - - if (!cfg->decode_only) { - swap_samples_le(buf, channels * to_get); /* write PC endian */ - if (cfg->only_stereo != -1) { - for (j = 0; j < to_get; j++) { - fwrite(buf + j*channels + (cfg->only_stereo*2), sizeof(sample_t), 2, outfile); - } - } else { - fwrite(buf, sizeof(sample_t) * channels, to_get, outfile); - } - } - } - - if (outfile != NULL) { - fclose(outfile); - outfile = NULL; - } - } - - close_vgmstream(vgmstream); free(buf); - return 1; - fail: - if (!cfg->play_sdtout) { - if (outfile != NULL) - fclose(outfile); - } - close_vgmstream(vgmstream); + if (outfile && !cfg->play_sdtout) + fclose(outfile); free(buf); return 0; } + #ifdef HAVE_JSON static void print_json_info(VGMSTREAM* vgm, cli_config* cfg) { json_t* version_string = json_string(VERSION); diff --git a/src/coding/psx_decoder.c b/src/coding/psx_decoder.c index 00bb2810..f4bb7e43 100644 --- a/src/coding/psx_decoder.c +++ b/src/coding/psx_decoder.c @@ -358,7 +358,7 @@ size_t ps_find_padding(STREAMFILE *streamFile, off_t start_offset, size_t data_s size_t interleave_consumed = 0; - if (data_size == 0 || channels == 0 || (channels > 0 && interleave == 0)) + if (data_size == 0 || channels == 0 || (channels > 1 && interleave == 0)) return 0; offset = start_offset + data_size; @@ -405,7 +405,7 @@ size_t ps_find_padding(STREAMFILE *streamFile, off_t start_offset, size_t data_s interleave_consumed += 0x10; if (interleave_consumed == interleave) { interleave_consumed = 0; - offset -= interleave*(channels - 1); + offset -= interleave * (channels - 1); } } diff --git a/src/formats.c b/src/formats.c index 7000bb45..f34a8453 100644 --- a/src/formats.c +++ b/src/formats.c @@ -311,7 +311,7 @@ static const char* extension_list[] = { "mdsp", "med", "mjb", - "mi4", + "mi4", //fake extension for .mib (renamed, to be removed) "mib", "mic", "mihb", @@ -581,7 +581,6 @@ static const char* extension_list[] = { "wii", "wip", //txth/reserved [Colin McRae DiRT (PC)] "wlv", //txth/reserved [ToeJam & Earl III: Mission to Earth (DC)] - "wma", //common "wmus", "wp2", "wpd", @@ -658,6 +657,7 @@ static const char* common_extension_list[] = { "ogg", //common "opus", //common "wav", //common + "wma", //common }; diff --git a/src/meta/msb_msh.c b/src/meta/msb_msh.c index 14ade6c7..4887fc69 100644 --- a/src/meta/msb_msh.c +++ b/src/meta/msb_msh.c @@ -8,7 +8,9 @@ VGMSTREAM* init_vgmstream_msb_msh(STREAMFILE* sf) { off_t start_offset, header_offset = 0; size_t stream_size; int loop_flag, channels, sample_rate; + int32_t loop_start, loop_end; int total_subsongs, target_subsong = sf->stream_index; + uint32_t config; /* checks */ @@ -20,12 +22,12 @@ VGMSTREAM* init_vgmstream_msb_msh(STREAMFILE* sf) { if (read_u32le(0x00,sh) != get_streamfile_size(sh)) goto fail; - /* 0x04: unknown */ + /* 0x04: flags? (0x04/34*/ /* parse entries */ { int i; - int entries = read_s32le(0x08,sh); + int entries = read_s32le(0x08,sh); /* may be less than file size, or include dummies (all dummies is possible too) */ total_subsongs = 0; if (target_subsong == 0) target_subsong = 1; @@ -45,15 +47,20 @@ VGMSTREAM* init_vgmstream_msb_msh(STREAMFILE* sf) { } - loop_flag = 0; - channels = 1; - stream_size = read_u32le(header_offset+0x00, sh); - if (read_u32le(header_offset+0x04, sh) != 0) /* stereo flag? */ - goto fail; + config = read_u32le(header_offset+0x04, sh); /* volume (0~100), null, null, loop (0/1) */ start_offset = read_u32le(header_offset+0x08, sh); sample_rate = read_u32le(header_offset+0x0c, sh); /* Ace Combat 2 seems to set wrong values but probably their bug */ + loop_flag = (config & 1); + channels = 1; + + /* rare [Dr. Seuss Cat in the Hat (PS2)] */ + if (loop_flag) { + /* when loop is set ADPCM has loop flags, but rarely appear too without loop set */ + loop_flag = ps_find_loop_offsets(sf, start_offset, stream_size, channels, 0, &loop_start, &loop_end); + } + /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channels, loop_flag); @@ -62,6 +69,8 @@ VGMSTREAM* init_vgmstream_msb_msh(STREAMFILE* sf) { vgmstream->meta_type = meta_MSB_MSH; vgmstream->sample_rate = sample_rate; vgmstream->num_samples = ps_bytes_to_samples(stream_size, channels); + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; vgmstream->num_streams = total_subsongs; vgmstream->stream_size = stream_size; diff --git a/src/plugins.c b/src/plugins.c index af03eed3..2f466948 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -532,3 +532,17 @@ void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels) { return; } + +void vgmstream_mixing_stereo_only(VGMSTREAM *vgmstream, int start) { + if (start < 0) + return; + /* could check to avoid making mono files in edge cases but meh */ + + /* remove channels before start */ + while (start) { + mixing_push_downmix(vgmstream, 0); + start--; + } + /* remove channels after stereo */ + mixing_push_killmix(vgmstream, start + 2); +} diff --git a/src/plugins.h b/src/plugins.h index b31b4862..917c8c9b 100644 --- a/src/plugins.h +++ b/src/plugins.h @@ -215,7 +215,10 @@ void vgmstream_tags_close(VGMSTREAM_TAGS* tags); void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels); /* sets automatic downmixing if vgmstream's channels are higher than max_channels */ -void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels); +void vgmstream_mixing_autodownmix(VGMSTREAM* vgmstream, int max_channels); + +/* downmixes to get stereo from start channel */ +void vgmstream_mixing_stereo_only(VGMSTREAM* vgmstream, int start); /* sets a fadeout */ //void vgmstream_mixing_fadeout(VGMSTREAM *vgmstream, float start_second, float duration_seconds); diff --git a/src/vgmstream.c b/src/vgmstream.c index 8b9bedf1..02cde58e 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -1409,7 +1409,7 @@ static int get_vgmstream_file_bitrate_from_streamfile(STREAMFILE* sf, int sample return get_vgmstream_file_bitrate_from_size(get_streamfile_size(sf), sample_rate, length_samples); } -static int get_vgmstream_file_bitrate_main(VGMSTREAM* vgmstream, bitrate_info_t* br) { +static int get_vgmstream_file_bitrate_main(VGMSTREAM* vgmstream, bitrate_info_t* br, int* p_uniques) { int i, ch; int bitrate = 0; @@ -1423,15 +1423,18 @@ static int get_vgmstream_file_bitrate_main(VGMSTREAM* vgmstream, bitrate_info_t* * become a bit high since its hard to detect only part of the file is needed. */ if (vgmstream->layout_type == layout_segmented) { + int uniques = 0; segmented_layout_data *data = (segmented_layout_data *) vgmstream->layout_data; for (i = 0; i < data->segment_count; i++) { - bitrate += get_vgmstream_file_bitrate_main(data->segments[i], br); + bitrate += get_vgmstream_file_bitrate_main(data->segments[i], br, &uniques); } + if (uniques) + bitrate /= uniques; /* average */ } else if (vgmstream->layout_type == layout_layered) { layered_layout_data *data = vgmstream->layout_data; for (i = 0; i < data->layer_count; i++) { - bitrate += get_vgmstream_file_bitrate_main(data->layers[i], br); + bitrate += get_vgmstream_file_bitrate_main(data->layers[i], br, NULL); } } else { @@ -1467,6 +1470,8 @@ static int get_vgmstream_file_bitrate_main(VGMSTREAM* vgmstream, bitrate_info_t* br->subsong[br->count] = subsong_cur; br->count++; + if (p_uniques) + (*p_uniques)++; if (vgmstream->stream_size) { /* stream_size applies to both channels but should add once and detect repeats (for current subsong) */ @@ -1494,7 +1499,7 @@ int get_vgmstream_average_bitrate(VGMSTREAM* vgmstream) { bitrate_info_t br = {0}; br.count_max = BITRATE_FILES_MAX; - return get_vgmstream_file_bitrate_main(vgmstream, &br); + return get_vgmstream_file_bitrate_main(vgmstream, &br, NULL); }