From a49d9851a11a0d12feef6a2a96338adaf784b214 Mon Sep 17 00:00:00 2001 From: bnnm Date: Fri, 15 Feb 2019 18:35:24 +0100 Subject: [PATCH 01/18] Fix some .baf [James Bond 007: Blood Stone (X360)] --- src/meta/baf.c | 62 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/src/meta/baf.c b/src/meta/baf.c index 80308a4f..02c70714 100644 --- a/src/meta/baf.c +++ b/src/meta/baf.c @@ -27,7 +27,7 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *streamFile) { /* 0x04: bank size */ version = read_32bit(0x08,streamFile); - if (version != 0x03 && version != 0x04) + if (version != 0x03 && version != 0x04 && version != 0x05) goto fail; total_subsongs = read_32bit(0x0c,streamFile); if (target_subsong == 0) target_subsong = 1; @@ -35,7 +35,7 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *streamFile) { /* - in v3 */ /* 0x10: 0? */ /* 0x11: bank name */ - /* - in v4 */ + /* - in v4/5 */ /* 0x10: 1? */ /* 0x11: padding flag? */ /* 0x12: bank name */ @@ -49,6 +49,11 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *streamFile) { if (i+1 == target_subsong) break; offset += read_32bit(offset+0x04, streamFile); /* WAVE size, variable per codec */ + + /* skip companion "CUE " (found in 007: Blood Stone, contains segment cues) */ + if (read_32bitBE(offset+0x00, streamFile) == 0x43554520) { + offset += read_32bit(offset+0x04, streamFile); /* CUE size */ + } } header_offset = offset; } @@ -60,38 +65,63 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *streamFile) { name_offset = header_offset + 0x0c; start_offset = read_32bit(header_offset+0x2c, streamFile); stream_size = read_32bit(header_offset+0x30, streamFile); + switch(codec) { case 0x03: switch(version) { case 0x03: /* Geometry Wars (PC) */ - sample_rate = read_32bit(header_offset + 0x38, streamFile); - channel_count = read_32bit(header_offset + 0x40, streamFile); + sample_rate = read_32bit(header_offset+0x38, streamFile); + channel_count = read_32bit(header_offset+0x40, streamFile); /* no actual flag, just loop +15sec songs */ loop_flag = (pcm_bytes_to_samples(stream_size, channel_count, 16) > 15*sample_rate); break; case 0x04: /* Project Gotham Racing 4 (X360) */ - sample_rate = read_32bit(header_offset + 0x3c, streamFile); - channel_count = read_32bit(header_offset + 0x44, streamFile); - loop_flag = read_8bit(header_offset+0x4b, streamFile); + sample_rate = read_32bit(header_offset+0x3c, streamFile); + channel_count = read_32bit(header_offset+0x44, streamFile); + loop_flag = read_8bit(header_offset+0x4b, streamFile); break; + + default: + goto fail; } break; - case 0x07: /* Blur (PS3) */ - sample_rate = read_32bit(header_offset+0x40, streamFile); - loop_flag = read_8bit(header_offset+0x48, streamFile); - channel_count = read_8bit(header_offset+0x4b, streamFile); + case 0x07: + switch(version) { + case 0x04: /* Blur (PS3) */ + case 0x05: /* James Bond 007: Blood Stone (X360) */ + sample_rate = read_32bit(header_offset+0x40, streamFile); + loop_flag = read_8bit(header_offset+0x48, streamFile); + channel_count = read_8bit(header_offset+0x4b, streamFile); + break; + + default: + goto fail; + } break; - case 0x08: /* Project Gotham Racing (X360) */ - sample_rate = read_32bit(header_offset+0x3c, streamFile); - channel_count = read_32bit(header_offset+0x44, streamFile); - loop_flag = read_8bit(header_offset+0x54, streamFile) != 0; + + case 0x08: + switch(version) { + case 0x04: /* Project Gotham Racing (X360) */ + sample_rate = read_32bit(header_offset+0x3c, streamFile); + channel_count = read_32bit(header_offset+0x44, streamFile); + loop_flag = read_8bit(header_offset+0x54, streamFile) != 0; + break; + + case 0x05: /* James Bond 007: Blood Stone (X360) */ + sample_rate = read_32bit(header_offset+0x40, streamFile); + channel_count = read_32bit(header_offset+0x48, streamFile); + loop_flag = read_8bit(header_offset+0x58, streamFile) != 0; + break; + + default: + goto fail; + } break; default: - VGM_LOG("BAF: unknown version %x\n", version); goto fail; } /* others: pan/vol? fixed values? (0x19, 0x10) */ From 9e9495168cef4fd74b7bf0c6f0e373feaacdafa2 Mon Sep 17 00:00:00 2001 From: bnnm Date: Fri, 15 Feb 2019 18:36:12 +0100 Subject: [PATCH 02/18] Add TXTP "loop_mode=keep" for intro+main songs that also loop normally --- src/layout/segmented.c | 48 +++++++++++++++++++++++++++++------------- src/meta/txtp.c | 20 ++++++++++++++++-- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/src/layout/segmented.c b/src/layout/segmented.c index ff87c68e..4350e717 100644 --- a/src/layout/segmented.c +++ b/src/layout/segmented.c @@ -6,7 +6,7 @@ * Chains together sequential vgmstreams, for data divided into separate sections or files * (like one part for intro and other for loop segments, which may even use different codecs). */ void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) { - int samples_written = 0; + int samples_written = 0, loop_samples_skip = 0; segmented_layout_data *data = vgmstream->layout_data; @@ -16,28 +16,34 @@ void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) { - /* handle looping, finding loop segment */ - int loop_segment = 0, samples = 0, loop_samples_skip = 0; - while (samples < vgmstream->num_samples) { + int segment, loop_segment, total_samples; + + /* handle looping by finding loop segment and loop_start inside that segment */ + loop_segment = 0; + total_samples = 0; + while (total_samples < vgmstream->num_samples) { int32_t segment_samples = data->segments[loop_segment]->num_samples; - if (vgmstream->loop_start_sample >= samples && vgmstream->loop_start_sample < samples + segment_samples) { - loop_samples_skip = vgmstream->loop_start_sample - samples; + + if (vgmstream->loop_start_sample >= total_samples && vgmstream->loop_start_sample < total_samples + segment_samples) { + loop_samples_skip = vgmstream->loop_start_sample - total_samples; break; /* loop_start falls within loop_segment's samples */ } - samples += segment_samples; + total_samples += segment_samples; loop_segment++; } + if (loop_segment == data->segment_count) { VGM_LOG("segmented_layout: can't find loop segment\n"); loop_segment = 0; } - if (loop_samples_skip > 0) { - VGM_LOG("segmented_layout: loop starts after %i samples\n", loop_samples_skip); - //todo skip/fix, but probably won't happen - } data->current_segment = loop_segment; - reset_vgmstream(data->segments[data->current_segment]); + + /* loops can span multiple segments */ + for (segment = loop_segment; segment < data->segment_count; segment++) { + reset_vgmstream(data->segments[segment]); + } + vgmstream->samples_into_block = 0; continue; } @@ -46,6 +52,12 @@ void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM if (samples_to_do > sample_count - samples_written) samples_to_do = sample_count - samples_written; + /* segment looping: discard until actual start */ + if (loop_samples_skip > 0) { + if (samples_to_do > loop_samples_skip) + samples_to_do = loop_samples_skip; + } + /* detect segment change and restart */ if (samples_to_do == 0) { data->current_segment++; @@ -57,9 +69,15 @@ void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM render_vgmstream(&buffer[samples_written*data->segments[data->current_segment]->channels], samples_to_do,data->segments[data->current_segment]); - samples_written += samples_to_do; - vgmstream->current_sample += samples_to_do; - vgmstream->samples_into_block += samples_to_do; + if (loop_samples_skip > 0) { + loop_samples_skip -= samples_to_do; + vgmstream->samples_into_block += samples_to_do; + } + else { + samples_written += samples_to_do; + vgmstream->current_sample += samples_to_do; + vgmstream->samples_into_block += samples_to_do; + } } } diff --git a/src/meta/txtp.c b/src/meta/txtp.c index faa6c496..a54fa6d5 100644 --- a/src/meta/txtp.c +++ b/src/meta/txtp.c @@ -32,6 +32,7 @@ typedef struct { int default_entry_set; size_t is_layered; + int is_loop_keep; } txtp_header; static txtp_header* parse_txtp(STREAMFILE* streamFile); @@ -148,17 +149,24 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) { if (txtp->loop_start_segment && !txtp->loop_end_segment) txtp->loop_end_segment = txtp->entry_count; loop_flag = (txtp->loop_start_segment > 0 && txtp->loop_start_segment <= txtp->entry_count); + num_samples = 0; for (i = 0; i < data_s->segment_count; i++) { if (loop_flag && txtp->loop_start_segment == i+1) { - loop_start_sample = num_samples; + if (txtp->is_loop_keep /*&& data_s->segments[i]->loop_start_sample*/) + loop_start_sample = num_samples + data_s->segments[i]->loop_start_sample; + else + loop_start_sample = num_samples; } num_samples += data_s->segments[i]->num_samples; if (loop_flag && txtp->loop_end_segment == i+1) { - loop_end_sample = num_samples; + if (txtp->is_loop_keep && data_s->segments[i]->loop_end_sample) + loop_end_sample = num_samples - data_s->segments[i]->num_samples + data_s->segments[i]->loop_end_sample; + else + loop_end_sample = num_samples; } } @@ -475,6 +483,14 @@ static int parse_keyval(txtp_header * txtp, const char * key, const char * val) goto fail; } } + else if (0==strcmp(key,"loop_mode")) { + if (0==strcmp(val,"keep")) { + txtp->is_loop_keep = 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 */ From 0e379a4e28bd5a1288b4e53eafff827eca81f4ad Mon Sep 17 00:00:00 2001 From: bnnm Date: Fri, 15 Feb 2019 18:36:45 +0100 Subject: [PATCH 03/18] Fix some more BAOs --- src/meta/ubi_bao.c | 177 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 152 insertions(+), 25 deletions(-) diff --git a/src/meta/ubi_bao.c b/src/meta/ubi_bao.c index a2a32ec9..5b3393fa 100644 --- a/src/meta/ubi_bao.c +++ b/src/meta/ubi_bao.c @@ -8,7 +8,7 @@ #define BAO_MAX_CHAIN_COUNT 128 /* POP:TFS goes up to ~100 */ typedef enum { CODEC_NONE = 0, UBI_IMA, RAW_PCM, RAW_PSX, RAW_XMA1, RAW_XMA2_OLD, RAW_XMA2_NEW, RAW_AT3, RAW_AT3_105, FMT_AT3, RAW_DSP, FMT_OGG } ubi_bao_codec; -typedef enum { TYPE_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE } ubi_bao_type; +typedef enum { TYPE_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE, UBI_SILENCE } ubi_bao_type; typedef enum { FILE_NONE = 0, UBI_FORGE, UBI_FORGE_b } ubi_bao_file; typedef struct { @@ -16,6 +16,8 @@ typedef struct { size_t header_base_size; size_t header_skip; + int header_less_le_flag; /* horrid but not sure what to do */ + off_t header_id; off_t header_type; @@ -56,7 +58,7 @@ typedef struct { int layer_external_and; int layer_ignore_error; - //off_t silence_duration_float; + off_t silence_duration_float; ubi_bao_codec codec_map[16]; ubi_bao_file file_type; @@ -111,7 +113,7 @@ typedef struct { int sequence_loop; int sequence_single; - //float duration; + float duration; char resource_name[255]; @@ -423,7 +425,7 @@ static VGMSTREAM * init_vgmstream_ubi_bao_audio(ubi_bao_header * bao, STREAMFILE streamData = setup_bao_streamfile(bao, streamFile); if (!streamData) goto fail; - +//dump_streamfile(streamData, "test.out"); vgmstream = init_vgmstream_ubi_bao_base(bao, streamFile, streamData); if (!vgmstream) goto fail; @@ -576,6 +578,7 @@ static VGMSTREAM * init_vgmstream_ubi_bao_sequence(ubi_bao_header *bao, STREAMFI if (!setup_layout_segmented(data)) goto fail; + /* build the base VGMSTREAM */ vgmstream = allocate_vgmstream(data->segments[0]->channels, !bao->sequence_single); if (!vgmstream) goto fail; @@ -605,9 +608,78 @@ fail: return NULL; } -//static VGMSTREAM * init_vgmstream_ubi_bao_silence(ubi_bao_header *bao, STREAMFILE *streamFile) { -// return NULL; -//} + +static size_t silence_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, void* data) { + int i; + for (i = 0; i < length; i++) { + dest[i] = 0; + } + return length; /* pretend we read zeroes */ +} +static size_t silence_io_size(STREAMFILE *streamfile, void* data) { + return 0x7FFFFFF; /* whatevs */ +} +static STREAMFILE* setup_silence_streamfile(STREAMFILE *streamFile) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + + /* setup custom streamfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_io_streamfile(temp_streamFile, NULL,0, silence_io_read,silence_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +static VGMSTREAM * init_vgmstream_ubi_bao_silence(ubi_bao_header *bao, STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + int channel_count, sample_rate; + + channel_count = bao->channels; + sample_rate = bao->sample_rate; + + /* by default silences don't have settings so let's pretend */ + if (channel_count == 0) + channel_count = 2; + if (sample_rate == 0) + sample_rate = 48000; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, 0); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_UBI_BAO; + vgmstream->sample_rate = sample_rate; + + vgmstream->num_samples = bao->duration * sample_rate; + vgmstream->num_streams = bao->total_subsongs; + vgmstream->stream_size = vgmstream->num_samples * channel_count * 0x02; /* PCM size */ + + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + + temp_streamFile = setup_silence_streamfile(streamFile); + if ( !vgmstream_open_stream(vgmstream, temp_streamFile, 0x00) ) + goto fail; + + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_vgmstream(vgmstream); + close_streamfile(temp_streamFile); + return vgmstream; +} static VGMSTREAM * init_vgmstream_ubi_bao_header(ubi_bao_header * bao, STREAMFILE * streamFile) { @@ -640,16 +712,15 @@ static VGMSTREAM * init_vgmstream_ubi_bao_header(ubi_bao_header * bao, STREAMFIL vgmstream = init_vgmstream_ubi_bao_sequence(bao, streamFile); break; - //case UBI_SILENCE: - // vgmstream = init_vgmstream_ubi_bao_silence(bao, streamFile); - // break; + case UBI_SILENCE: + vgmstream = init_vgmstream_ubi_bao_silence(bao, streamFile); + break; default: VGM_LOG("UBI BAO: subsong not found/parsed\n"); goto fail; } - if (!vgmstream) goto fail; strcpy(vgmstream->stream_name, bao->readable_name); @@ -925,7 +996,7 @@ static int parse_type_layer(ubi_bao_header * bao, off_t offset, STREAMFILE* stre bao->num_samples = read_32bit(table_offset + bao->cfg.layer_num_samples, streamFile); for (i = 0; i < bao->layer_count; i++) { - int channels = read_32bit(table_offset + bao->cfg.layer_channels, streamFile); + //int channels = read_32bit(table_offset + bao->cfg.layer_channels, streamFile); int sample_rate = read_32bit(table_offset + bao->cfg.layer_sample_rate, streamFile); int stream_type = read_32bit(table_offset + bao->cfg.layer_stream_type, streamFile); int num_samples = read_32bit(table_offset + bao->cfg.layer_num_samples, streamFile); @@ -941,7 +1012,7 @@ static int parse_type_layer(ubi_bao_header * bao, off_t offset, STREAMFILE* stre } /* unusual but happens, layers handle it fine [Rayman Raving Rabbids: TV Party (Wii) ex. 0x22000cbc.pk] */ - VGM_ASSERT_ONCE(bao->channels != channels, "UBI BAO: layer channels don't match at %x\n", (uint32_t)table_offset); + //;VGM_ASSERT_ONCE(bao->channels != channels, "UBI BAO: layer channels don't match at %x\n", (uint32_t)table_offset); /* can be +-1 */ if (bao->num_samples != num_samples && bao->num_samples + 1 == num_samples) { @@ -956,10 +1027,39 @@ fail: return 0; } +static int parse_type_silence(ubi_bao_header * bao, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = bao->big_endian ? read_32bitBE : read_32bitLE; + off_t h_offset = offset + bao->header_skip; + uint32_t duration_int; + float* duration_float; + + /* silence header */ + bao->type = UBI_SILENCE; + if (bao->cfg.silence_duration_float == 0) { + VGM_LOG("UBI BAO: silence duration not configured at %x\n", (uint32_t)offset); + goto fail; + } + + { + duration_int = (uint32_t)read_32bit(h_offset + bao->cfg.silence_duration_float, streamFile); + duration_float = (float*)&duration_int; + bao->duration = *duration_float; + } + + if (bao->duration <= 0) { + VGM_LOG("UBI BAO: bad duration %f at %x\n", bao->duration, (uint32_t)offset); + goto fail; + } + + return 1; +fail: + return 0; +} + /* adjust some common values */ static int parse_values(ubi_bao_header * bao, STREAMFILE *streamFile) { - if (bao->type == UBI_SEQUENCE) + if (bao->type == UBI_SEQUENCE || bao->type == UBI_SILENCE) return 1; /* common validations */ @@ -1015,7 +1115,7 @@ static int parse_offsets(ubi_bao_header * bao, STREAMFILE *streamFile) { off_t bao_offset; size_t bao_size; - if (bao->type == UBI_SEQUENCE) + if (bao->type == UBI_SEQUENCE || bao->type == UBI_SILENCE) return 1; if (!bao->is_external && bao->is_prefetched) { @@ -1036,7 +1136,8 @@ static int parse_offsets(ubi_bao_header * bao, STREAMFILE *streamFile) { if (bao->is_prefetched) { bao->prefetch_offset = bao->memory_skip; } - else if (bao->is_external) { + + if (bao->is_external) { bao->stream_offset = bao->stream_skip; } else { @@ -1153,9 +1254,14 @@ static int parse_header(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offs bao->header_size = bao->cfg.header_base_size; + /* hack for games with smaller than standard + * (can't use lowest size as other games also have extra unused field) */ + if (bao->cfg.header_less_le_flag && !bao->big_endian) { + bao->header_size -= 0x04; + } /* detect extra unused field in PC/Wii * (could be improved but no apparent flags or anything useful) */ - if (get_streamfile_size(streamFile) > offset + bao->header_size) { + else if (get_streamfile_size(streamFile) > offset + bao->header_size) { /* may read next BAO version, layer header, cues, resource table size, etc, always > 1 */ int32_t end_field = read_32bit(offset + bao->header_size, streamFile); @@ -1176,6 +1282,10 @@ static int parse_header(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offs if (!parse_type_layer(bao, offset, streamFile)) goto fail; break; + case 0x08: + if (!parse_type_silence(bao, offset, streamFile)) + goto fail; + break; default: VGM_LOG("UBI BAO: unknown header type at %x\n", (uint32_t)offset); goto fail; @@ -1501,7 +1611,7 @@ static void config_bao_layer_m(ubi_bao_header * bao, off_t stream_id, off_t laye bao->cfg.layer_external_flag = external_flag; bao->cfg.layer_stream_size = stream_size; bao->cfg.layer_extra_size = extra_size; - bao->cfg.layer_prefetch_size = prefetch_size; /* possible flag: 0x3c */ + bao->cfg.layer_prefetch_size = prefetch_size; bao->cfg.layer_cue_count = cue_count; bao->cfg.layer_cue_labels = cue_labels; bao->cfg.layer_external_and = external_and; @@ -1515,6 +1625,12 @@ static void config_bao_layer_e(ubi_bao_header * bao, off_t entry_size, off_t sam bao->cfg.layer_num_samples = num_samples; } +static void config_bao_silence_f(ubi_bao_header * bao, off_t duration) { + /* silence headers in float value */ + bao->cfg.silence_duration_float = duration; +} + + static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { /* Ubi BAO evolved from Ubi SB and are conceptually quite similar, see that first. @@ -1532,7 +1648,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { * - 0x50000000: stream audio (in .spk/.sbao) * - 0x60000000: unused? * - 0x70000000: info? has a count+table of id-things - * - 0x80000000: unknown (some id/info?) + * - 0x80000000: unknown (some floats?) * Class 1/2/3 are roughly equivalent to Ubi SB's section1/2/3, and class 4 is * basically .spN project files. * @@ -1581,7 +1697,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { switch(bao->version) { case 0x001B0100: /* Assassin's Creed (PS3/X360/PC)-atomic-forge */ - config_bao_entry(bao, 0xA4, 0x28); + config_bao_entry(bao, 0xA4, 0x28); /* PC: 0xA8, PS3/X360: 0xA4 */ config_bao_audio_b(bao, 0x08, 0x1c, 0x28, 0x34, 1, 1); /* 0x2c: prefetch flag? */ config_bao_audio_m(bao, 0x44, 0x4c, 0x50, 0x58, 0x64, 0x74); @@ -1593,6 +1709,8 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { config_bao_layer_m(bao, 0x4c, 0x20, 0x2c, 0x44, 0x00, 0x50, 0x00, 0x00, 1); /* stream size: 0x48? */ config_bao_layer_e(bao, 0x30, 0x00, 0x04, 0x08, 0x10); + config_bao_silence_f(bao, 0x1c); + bao->cfg.codec_map[0x02] = RAW_PSX; bao->cfg.codec_map[0x03] = UBI_IMA; bao->cfg.codec_map[0x04] = FMT_OGG; @@ -1606,7 +1724,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { case 0x001F0011: /* Naruto: The Broken Bond (X360)-package */ case 0x0022000D: /* Just Dance (Wii)-package */ case 0x0022001B: /* Prince of Persia: The Forgotten Sands (Wii)-package */ - config_bao_entry(bao, 0xA4, 0x28); + config_bao_entry(bao, 0xA4, 0x28); /* PC/Wii: 0xA8 */ config_bao_audio_b(bao, 0x08, 0x1c, 0x28, 0x34, 1, 1); config_bao_audio_m(bao, 0x44, 0x4c, 0x54, 0x5c, 0x64, 0x74); /* cues: 0x68, 0x6c */ @@ -1616,6 +1734,8 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { config_bao_layer_m(bao, 0x00, 0x20, 0x2c, 0x44, 0x4c, 0x50, 0x54, 0x58, 1); /* 0x1c: id-like, 0x3c: prefetch flag? */ config_bao_layer_e(bao, 0x28, 0x00, 0x04, 0x08, 0x10); + config_bao_silence_f(bao, 0x1c); + bao->cfg.codec_map[0x01] = RAW_PCM; bao->cfg.codec_map[0x03] = UBI_IMA; bao->cfg.codec_map[0x04] = FMT_OGG; @@ -1628,7 +1748,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { case 0x00220015: /* James Cameron's Avatar: The Game (PSP)-package */ case 0x0022001E: /* Prince of Persia: The Forgotten Sands (PSP)-package */ - config_bao_entry(bao, 0x84, 0x28); + config_bao_entry(bao, 0x84, 0x28); /* PSP: 0x84 */ config_bao_audio_b(bao, 0x08, 0x1c, 0x20, 0x20, (1 << 2), (1 << 5)); /* (1 << 4): prefetch flag? */ config_bao_audio_m(bao, 0x28, 0x30, 0x38, 0x40, 0x48, 0x58); @@ -1642,7 +1762,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { return 1; case 0x00230008: /* Splinter Cell: Conviction (X360/PC)-package */ - config_bao_entry(bao, 0xB4, 0x28); + config_bao_entry(bao, 0xB4, 0x28); /* PC: 0xB8, X360: 0xB4 */ config_bao_audio_b(bao, 0x08, 0x24, 0x38, 0x44, 1, 1); config_bao_audio_m(bao, 0x54, 0x5c, 0x64, 0x6c, 0x74, 0x84); @@ -1663,17 +1783,24 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { case 0x00250108: /* Scott Pilgrim vs the World (PS3/X360)-package */ case 0x0025010A: /* Prince of Persia: The Forgotten Sands (PS3/X360)-atomic-forge */ case 0x00250119: /* Shaun White Skateboarding (Wii)-package */ - case 0x0025011D: /* Shaun White Skateboarding (PS3)-atomic-forge */ - config_bao_entry(bao, 0xB4, 0x28); + case 0x0025011D: /* Shaun White Skateboarding (PC/PS3)-atomic-forge */ + config_bao_entry(bao, 0xB4, 0x28); /* PC: 0xB0, PS3/X360: 0xB4, Wii: 0xB8 */ + + if (bao->version == 0x0025011D) + bao->cfg.header_less_le_flag = 1; config_bao_audio_b(bao, 0x08, 0x24, 0x2c, 0x38, 1, 1); config_bao_audio_m(bao, 0x48, 0x50, 0x58, 0x60, 0x68, 0x78); + bao->cfg.audio_interleave = 0x10; config_bao_sequence(bao, 0x34, 0x28, 0x24, 0x14); config_bao_layer_m(bao, 0x00, 0x28, 0x30, 0x48, 0x50, 0x54, 0x58, 0x5c, 1); /* 0x24: id-like */ config_bao_layer_e(bao, 0x30, 0x00, 0x04, 0x08, 0x18); //todo some SPvsTW layers look like should loop (0x30 flag?) + //todo some POP layers have different sample rates (ambience) + + config_bao_silence_f(bao, 0x24); bao->cfg.codec_map[0x01] = RAW_PCM; bao->cfg.codec_map[0x02] = UBI_IMA; From 98587ec8e747dac53653672da754759baa84eafd Mon Sep 17 00:00:00 2001 From: bnnm Date: Fri, 15 Feb 2019 22:28:20 +0100 Subject: [PATCH 04/18] Fix memory corruption when forcing loops and clean internals --- src/layout/layered.c | 6 +- src/layout/segmented.c | 8 +- src/meta/aix.c | 4 +- src/streamfile.h | 2 +- src/vgmstream.c | 326 +++++++++++++++++++++-------------------- src/vgmstream.h | 55 +++---- 6 files changed, 201 insertions(+), 200 deletions(-) diff --git a/src/layout/layered.c b/src/layout/layered.c index 2dcae87b..d1f7d584 100644 --- a/src/layout/layered.c +++ b/src/layout/layered.c @@ -96,11 +96,9 @@ int setup_layout_layered(layered_layout_data* data) { } } - //todo could check if layers'd loop match vs main, etc + /* loops and other values could be mismatched but hopefully not */ - /* save start things so we can restart for seeking/looping */ - memcpy(data->layers[i]->start_ch,data->layers[i]->ch,sizeof(VGMSTREAMCHANNEL)*data->layers[i]->channels); - memcpy(data->layers[i]->start_vgmstream,data->layers[i],sizeof(VGMSTREAM)); + setup_vgmstream(data->layers[i]); /* final setup in case the VGMSTREAM was created manually */ } return 1; diff --git a/src/layout/segmented.c b/src/layout/segmented.c index 4350e717..03748f24 100644 --- a/src/layout/segmented.c +++ b/src/layout/segmented.c @@ -24,8 +24,8 @@ void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM while (total_samples < vgmstream->num_samples) { int32_t segment_samples = data->segments[loop_segment]->num_samples; - if (vgmstream->loop_start_sample >= total_samples && vgmstream->loop_start_sample < total_samples + segment_samples) { - loop_samples_skip = vgmstream->loop_start_sample - total_samples; + if (vgmstream->loop_sample >= total_samples && vgmstream->loop_sample < total_samples + segment_samples) { + loop_samples_skip = vgmstream->loop_sample - total_samples; break; /* loop_start falls within loop_segment's samples */ } total_samples += segment_samples; @@ -134,9 +134,7 @@ int setup_layout_segmented(segmented_layout_data* data) { } - /* save start things so we can restart for seeking/looping */ - memcpy(data->segments[i]->start_ch,data->segments[i]->ch,sizeof(VGMSTREAMCHANNEL)*data->segments[i]->channels); - memcpy(data->segments[i]->start_vgmstream,data->segments[i],sizeof(VGMSTREAM)); + setup_vgmstream(data->segments[i]); /* final setup in case the VGMSTREAM was created manually */ } diff --git a/src/meta/aix.c b/src/meta/aix.c index 57b429eb..c93dca85 100644 --- a/src/meta/aix.c +++ b/src/meta/aix.c @@ -136,8 +136,8 @@ VGMSTREAM * init_vgmstream_aix(STREAMFILE *streamFile) { /* setup layers */ if (temp_vgmstream->num_samples != data->sample_counts[i] || temp_vgmstream->loop_flag != 0) goto fail; - memcpy(temp_vgmstream->start_ch,temp_vgmstream->ch,sizeof(VGMSTREAMCHANNEL)*temp_vgmstream->channels); - memcpy(temp_vgmstream->start_vgmstream,temp_vgmstream,sizeof(VGMSTREAM)); + + setup_vgmstream(temp_vgmstream); /* final setup as the VGMSTREAM was created manually */ } } diff --git a/src/streamfile.h b/src/streamfile.h index a69da42f..d35968be 100644 --- a/src/streamfile.h +++ b/src/streamfile.h @@ -61,7 +61,7 @@ typedef struct _STREAMFILE { size_t (*read)(struct _STREAMFILE *,uint8_t * dest, off_t offset, size_t length); size_t (*get_size)(struct _STREAMFILE *); - off_t (*get_offset)(struct _STREAMFILE *); + off_t (*get_offset)(struct _STREAMFILE *); //todo: DO NOT USE, NOT RESET PROPERLY (remove?) /* for dual-file support */ void (*get_name)(struct _STREAMFILE *,char *name,size_t length); struct _STREAMFILE * (*open)(struct _STREAMFILE *,const char * const filename,size_t buffersize); diff --git a/src/vgmstream.c b/src/vgmstream.c index cbc643c4..63622f89 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -13,7 +13,7 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *streamFile, VGMSTREAM* (*init_vgmstream_function)(STREAMFILE*)); -/* List of functions that will recognize files */ +/* list of metadata parser functions that will recognize files, used on init */ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_adx, init_vgmstream_brstm, @@ -487,7 +487,7 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { fcns_size = (sizeof(init_vgmstream_functions)/sizeof(init_vgmstream_functions[0])); /* try a series of formats, see which works */ - for (i=0; i < fcns_size; i++) { + for (i =0; i < fcns_size; i++) { /* call init function and see if valid VGMSTREAM was returned */ VGMSTREAM * vgmstream = (init_vgmstream_functions[i])(streamFile); if (!vgmstream) @@ -509,11 +509,12 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { /* Sanify loops! */ if (vgmstream->loop_flag) { - if ((vgmstream->loop_end_sample <= vgmstream->loop_start_sample) - || (vgmstream->loop_end_sample > vgmstream->num_samples) - || (vgmstream->loop_start_sample < 0) ) { + if (vgmstream->loop_end_sample <= vgmstream->loop_start_sample + || vgmstream->loop_end_sample > vgmstream->num_samples + || vgmstream->loop_start_sample < 0) { vgmstream->loop_flag = 0; - VGM_LOG("VGMSTREAM: wrong loops ignored (lss=%i, lse=%i, ns=%i)\n", vgmstream->loop_start_sample, vgmstream->loop_end_sample, vgmstream->num_samples); + VGM_LOG("VGMSTREAM: wrong loops ignored (lss=%i, lse=%i, ns=%i)\n", + vgmstream->loop_start_sample, vgmstream->loop_end_sample, vgmstream->num_samples); } } @@ -522,7 +523,6 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { try_dual_file_stereo(vgmstream, streamFile, init_vgmstream_functions[i]); } - #ifdef VGM_USE_FFMPEG /* check FFmpeg streams here, for lack of a better place */ if (vgmstream->coding_type == coding_FFmpeg) { @@ -540,15 +540,13 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { continue; } - /* save info */ /* stream_index 0 may be used by plugins to signal "vgmstream default" (IOW don't force to 1) */ - if (!vgmstream->stream_index) + if (vgmstream->stream_index == 0) vgmstream->stream_index = streamFile->stream_index; - /* save start things so we can restart for seeking */ - memcpy(vgmstream->start_ch,vgmstream->ch,sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); - memcpy(vgmstream->start_vgmstream,vgmstream,sizeof(VGMSTREAM)); + + setup_vgmstream(vgmstream); /* final setup */ return vgmstream; } @@ -557,6 +555,16 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { return NULL; } +void setup_vgmstream(VGMSTREAM * vgmstream) { + /* save start things so we can restart when seeking */ + memcpy(vgmstream->start_ch, vgmstream->ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); + memcpy(vgmstream->start_vgmstream, vgmstream, sizeof(VGMSTREAM)); + + /* layout's sub-VGMSTREAM are expected to setup externally and maybe call this, + * as they can be created using init_vgmstream or manually */ +} + + /* format detection and VGMSTREAM setup, uses default parameters */ VGMSTREAM * init_vgmstream(const char * const filename) { VGMSTREAM *vgmstream = NULL; @@ -572,92 +580,89 @@ VGMSTREAM * init_vgmstream_from_STREAMFILE(STREAMFILE *streamFile) { return init_vgmstream_internal(streamFile); } -/* Reset a VGMSTREAM to its state at the start of playback - * (when a plugin needs to seek back to zero, for instance). - * Note that this does not reset the constituent STREAMFILES. */ +/* Reset a VGMSTREAM to its state at the start of playback (when a plugin seeks back to zero). */ void reset_vgmstream(VGMSTREAM * vgmstream) { - /* copy the vgmstream back into itself */ - memcpy(vgmstream,vgmstream->start_vgmstream,sizeof(VGMSTREAM)); - /* copy the initial channels */ - memcpy(vgmstream->ch,vgmstream->start_ch,sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); - - /* loop_ch is not zeroed here because there is a possibility of the + /* reset the VGMSTREAM and channels back to their original state */ + memcpy(vgmstream, vgmstream->start_vgmstream, sizeof(VGMSTREAM)); + memcpy(vgmstream->ch, vgmstream->start_ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); + /* loop_ch is not reset here because there is a possibility of the * init_vgmstream_* function doing something tricky and precomputing it. * Otherwise hit_loop will be 0 and it will be copied over anyway when we * really hit the loop start. */ + /* reset custom codec and layout data */ #ifdef VGM_USE_VORBIS - if (vgmstream->coding_type==coding_OGG_VORBIS) { + if (vgmstream->coding_type == coding_OGG_VORBIS) { reset_ogg_vorbis(vgmstream); } - if (vgmstream->coding_type==coding_VORBIS_custom) { + if (vgmstream->coding_type == coding_VORBIS_custom) { reset_vorbis_custom(vgmstream); } #endif - if (vgmstream->coding_type==coding_CRI_HCA) { + if (vgmstream->coding_type == coding_CRI_HCA) { reset_hca(vgmstream->codec_data); } - if (vgmstream->coding_type==coding_EA_MT) { + if (vgmstream->coding_type == coding_EA_MT) { reset_ea_mt(vgmstream); } #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) - if (vgmstream->coding_type==coding_MP4_AAC) { + if (vgmstream->coding_type == coding_MP4_AAC) { reset_mp4_aac(vgmstream); } #endif #ifdef VGM_USE_MPEG - if (vgmstream->coding_type==coding_MPEG_custom || - vgmstream->coding_type==coding_MPEG_ealayer3 || - vgmstream->coding_type==coding_MPEG_layer1 || - vgmstream->coding_type==coding_MPEG_layer2 || - vgmstream->coding_type==coding_MPEG_layer3) { + if (vgmstream->coding_type == coding_MPEG_custom || + vgmstream->coding_type == coding_MPEG_ealayer3 || + vgmstream->coding_type == coding_MPEG_layer1 || + vgmstream->coding_type == coding_MPEG_layer2 || + vgmstream->coding_type == coding_MPEG_layer3) { reset_mpeg(vgmstream); } #endif #ifdef VGM_USE_G7221 - if (vgmstream->coding_type==coding_G7221C) { + if (vgmstream->coding_type == coding_G7221C) { reset_g7221(vgmstream); } #endif #ifdef VGM_USE_G719 - if (vgmstream->coding_type==coding_G719) { + if (vgmstream->coding_type == coding_G719) { reset_g719(vgmstream->codec_data, vgmstream->channels); } #endif #ifdef VGM_USE_MAIATRAC3PLUS - if (vgmstream->coding_type==coding_AT3plus) { + if (vgmstream->coding_type == coding_AT3plus) { reset_at3plus(vgmstream); } #endif #ifdef VGM_USE_ATRAC9 - if (vgmstream->coding_type==coding_ATRAC9) { + if (vgmstream->coding_type == coding_ATRAC9) { reset_atrac9(vgmstream); } #endif #ifdef VGM_USE_CELT - if (vgmstream->coding_type==coding_CELT_FSB) { + if (vgmstream->coding_type == coding_CELT_FSB) { reset_celt_fsb(vgmstream); } #endif #ifdef VGM_USE_FFMPEG - if (vgmstream->coding_type==coding_FFmpeg) { + if (vgmstream->coding_type == coding_FFmpeg) { reset_ffmpeg(vgmstream); } #endif - if (vgmstream->coding_type==coding_ACM) { + if (vgmstream->coding_type == coding_ACM) { reset_acm(vgmstream->codec_data); } @@ -668,137 +673,135 @@ void reset_vgmstream(VGMSTREAM * vgmstream) { } - if (vgmstream->layout_type==layout_aix) { + if (vgmstream->layout_type == layout_aix) { aix_codec_data *data = vgmstream->codec_data; int i; data->current_segment = 0; - for (i=0;isegment_count*data->stream_count;i++) { + for (i = 0; i < data->segment_count*data->stream_count; i++) { reset_vgmstream(data->adxs[i]); } } - if (vgmstream->layout_type==layout_segmented) { + if (vgmstream->layout_type == layout_segmented) { reset_layout_segmented(vgmstream->layout_data); } - if (vgmstream->layout_type==layout_layered) { + if (vgmstream->layout_type == layout_layered) { reset_layout_layered(vgmstream->layout_data); } + + /* note that this does not reset the constituent STREAMFILES + * (ch's streamfiles init in metas, nor their internal state) */ } /* Allocate memory and setup a VGMSTREAM */ -VGMSTREAM * allocate_vgmstream(int channel_count, int looped) { +VGMSTREAM * allocate_vgmstream(int channel_count, int loop_flag) { VGMSTREAM * vgmstream; - VGMSTREAM * start_vgmstream; - VGMSTREAMCHANNEL * channels; - VGMSTREAMCHANNEL * start_channels; - VGMSTREAMCHANNEL * loop_channels; - /* up to ~16 aren't too rare for multilayered files, more is probably a bug */ + /* up to ~16-24 aren't too rare for multilayered files, more is probably a bug */ if (channel_count <= 0 || channel_count > 64) { VGM_LOG("VGMSTREAM: error allocating %i channels\n", channel_count); return NULL; } + /* VGMSTREAM's alloc'ed internals work as follows: + * - vgmstream: main struct (config+state) modified by metas, layouts and codings as needed + * - ch: config+state per channel, also modified by those + * - start_vgmstream: vgmstream clone copied on init_vgmstream and restored on reset_vgmstream + * - start_ch: ch clone copied on init_vgmstream and restored on reset_vgmstream + * - loop_ch: ch clone copied on loop start and restored on loop end (vgmstream_do_loop) + * - codec/layout_data: custom state for complex codecs or layouts, handled externally + * + * Here we only create the basic structs to be filled, and only after init_vgmstream it + * can be considered ready. Clones are shallow copies, in that they share alloc'ed struts + * (like, vgmstream->ch and start_vgmstream->ch will be the same after init_vgmstream, or + * start_vgmstream->start_vgmstream will end up pointing to itself) + * + * This is all a bit too brittle, so code alloc'ing or changing anything sensitive should + * take care clones are properly synced. + */ + + /* create vgmstream + main structs (other data is 0'ed) */ vgmstream = calloc(1,sizeof(VGMSTREAM)); if (!vgmstream) return NULL; - vgmstream->ch = NULL; - vgmstream->start_ch = NULL; - vgmstream->loop_ch = NULL; - vgmstream->start_vgmstream = NULL; - vgmstream->codec_data = NULL; + vgmstream->start_vgmstream = calloc(1,sizeof(VGMSTREAM)); + if (!vgmstream->start_vgmstream) goto fail; - start_vgmstream = calloc(1,sizeof(VGMSTREAM)); - if (!start_vgmstream) { - free(vgmstream); - return NULL; - } - vgmstream->start_vgmstream = start_vgmstream; - start_vgmstream->start_vgmstream = start_vgmstream; + vgmstream->ch = calloc(channel_count,sizeof(VGMSTREAMCHANNEL)); + if (!vgmstream->ch) goto fail; - channels = calloc(channel_count,sizeof(VGMSTREAMCHANNEL)); - if (!channels) { - free(vgmstream); - free(start_vgmstream); - return NULL; + vgmstream->start_ch = calloc(channel_count,sizeof(VGMSTREAMCHANNEL)); + if (!vgmstream->start_ch) goto fail; + + if (loop_flag) { + vgmstream->loop_ch = calloc(channel_count,sizeof(VGMSTREAMCHANNEL)); + if (!vgmstream->loop_ch) goto fail; } - vgmstream->ch = channels; + vgmstream->channels = channel_count; - - start_channels = calloc(channel_count,sizeof(VGMSTREAMCHANNEL)); - if (!start_channels) { - free(vgmstream); - free(start_vgmstream); - free(channels); - return NULL; - } - vgmstream->start_ch = start_channels; - - if (looped) { - loop_channels = calloc(channel_count,sizeof(VGMSTREAMCHANNEL)); - if (!loop_channels) { - free(vgmstream); - free(start_vgmstream); - free(channels); - free(start_channels); - return NULL; - } - vgmstream->loop_ch = loop_channels; - } - - vgmstream->loop_flag = looped; + vgmstream->loop_flag = loop_flag; return vgmstream; +fail: + if (vgmstream) { + free(vgmstream->ch); + free(vgmstream->start_ch); + free(vgmstream->loop_ch); + free(vgmstream->start_vgmstream); + } + free(vgmstream); + return NULL; } void close_vgmstream(VGMSTREAM * vgmstream) { if (!vgmstream) return; + /* free custom codecs */ #ifdef VGM_USE_VORBIS - if (vgmstream->coding_type==coding_OGG_VORBIS) { + if (vgmstream->coding_type == coding_OGG_VORBIS) { free_ogg_vorbis(vgmstream->codec_data); vgmstream->codec_data = NULL; } - if (vgmstream->coding_type==coding_VORBIS_custom) { + if (vgmstream->coding_type == coding_VORBIS_custom) { free_vorbis_custom(vgmstream->codec_data); vgmstream->codec_data = NULL; } #endif - if (vgmstream->coding_type==coding_CRI_HCA) { + if (vgmstream->coding_type == coding_CRI_HCA) { free_hca(vgmstream->codec_data); vgmstream->codec_data = NULL; } - if (vgmstream->coding_type==coding_EA_MT) { + if (vgmstream->coding_type == coding_EA_MT) { free_ea_mt(vgmstream->codec_data, vgmstream->channels); vgmstream->codec_data = NULL; } #ifdef VGM_USE_FFMPEG - if (vgmstream->coding_type==coding_FFmpeg) { + if (vgmstream->coding_type == coding_FFmpeg) { free_ffmpeg(vgmstream->codec_data); vgmstream->codec_data = NULL; } #endif #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) - if (vgmstream->coding_type==coding_MP4_AAC) { + if (vgmstream->coding_type == coding_MP4_AAC) { free_mp4_aac(vgmstream->codec_data); vgmstream->codec_data = NULL; } #endif #ifdef VGM_USE_MPEG - if (vgmstream->coding_type==coding_MPEG_custom || - vgmstream->coding_type==coding_MPEG_ealayer3 || - vgmstream->coding_type==coding_MPEG_layer1 || - vgmstream->coding_type==coding_MPEG_layer2 || - vgmstream->coding_type==coding_MPEG_layer3) { + if (vgmstream->coding_type == coding_MPEG_custom || + vgmstream->coding_type == coding_MPEG_ealayer3 || + vgmstream->coding_type == coding_MPEG_layer1 || + vgmstream->coding_type == coding_MPEG_layer2 || + vgmstream->coding_type == coding_MPEG_layer3) { free_mpeg(vgmstream->codec_data); vgmstream->codec_data = NULL; } @@ -839,7 +842,7 @@ void close_vgmstream(VGMSTREAM * vgmstream) { } #endif - if (vgmstream->coding_type==coding_ACM) { + if (vgmstream->coding_type == coding_ACM) { free_acm(vgmstream->codec_data); vgmstream->codec_data = NULL; } @@ -855,13 +858,14 @@ void close_vgmstream(VGMSTREAM * vgmstream) { } - if (vgmstream->layout_type==layout_aix) { + /* free custom layouts */ + if (vgmstream->layout_type == layout_aix) { aix_codec_data *data = (aix_codec_data *) vgmstream->codec_data; if (data) { if (data->adxs) { int i; - for (i=0;isegment_count*data->stream_count;i++) { + for (i = 0; i < data->segment_count*data->stream_count; i++) { /* note that the close_streamfile won't do anything but deallocate itself, * there is only one open file in vgmstream->ch[0].streamfile */ close_vgmstream(data->adxs[i]); @@ -877,12 +881,12 @@ void close_vgmstream(VGMSTREAM * vgmstream) { vgmstream->codec_data = NULL; } - if (vgmstream->layout_type==layout_segmented) { + if (vgmstream->layout_type == layout_segmented) { free_layout_segmented(vgmstream->layout_data); vgmstream->layout_data = NULL; } - if (vgmstream->layout_type==layout_layered) { + if (vgmstream->layout_type == layout_layered) { free_layout_layered(vgmstream->layout_data); vgmstream->layout_data = NULL; } @@ -892,15 +896,13 @@ void close_vgmstream(VGMSTREAM * vgmstream) { { int i,j; - for (i=0;ichannels;i++) { + for (i = 0; i < vgmstream->channels; i++) { if (vgmstream->ch[i].streamfile) { close_streamfile(vgmstream->ch[i].streamfile); /* Multiple channels might have the same streamfile. Find the others - * that are the same as this and clear them so they won't be closed - * again. */ - for (j=0;jchannels;j++) { - if (i!=j && vgmstream->ch[j].streamfile == - vgmstream->ch[i].streamfile) { + * that are the same as this and clear them so they won't be closed again. */ + for (j = 0; j < vgmstream->channels; j++) { + if (i != j && vgmstream->ch[j].streamfile == vgmstream->ch[i].streamfile) { vgmstream->ch[j].streamfile = NULL; } } @@ -909,12 +911,10 @@ void close_vgmstream(VGMSTREAM * vgmstream) { } } - if (vgmstream->loop_ch) free(vgmstream->loop_ch); - if (vgmstream->start_ch) free(vgmstream->start_ch); - if (vgmstream->ch) free(vgmstream->ch); - /* the start_vgmstream is considered just data */ - if (vgmstream->start_vgmstream) free(vgmstream->start_vgmstream); - + free(vgmstream->ch); + free(vgmstream->start_ch); + free(vgmstream->loop_ch); + free(vgmstream->start_vgmstream); free(vgmstream); } @@ -947,11 +947,10 @@ void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sa /* this requires a bit more messing with the VGMSTREAM than I'm comfortable with... */ if (loop_flag && !vgmstream->loop_flag && !vgmstream->loop_ch) { vgmstream->loop_ch = calloc(vgmstream->channels,sizeof(VGMSTREAMCHANNEL)); - /* loop_ch will be populated when decoded samples reach loop start */ + if (!vgmstream->loop_ch) loop_flag = 0; /* ??? */ } else if (!loop_flag && vgmstream->loop_flag) { - /* not important though */ - free(vgmstream->loop_ch); + free(vgmstream->loop_ch); /* not important though */ vgmstream->loop_ch = NULL; } @@ -964,15 +963,21 @@ void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sa vgmstream->loop_end_sample = 0; } + /* propagate changes to layouts that need them */ if (vgmstream->layout_type == layout_layered) { int i; layered_layout_data *data = vgmstream->layout_data; for (i = 0; i < data->layer_count; i++) { vgmstream_force_loop(data->layers[i], loop_flag, loop_start_sample, loop_end_sample); + /* layer's force_loop also calls setup_vgmstream, no need to do it here */ } } - /* segmented layout only works (ATM) with exact/header loop, full loop or no loop */ + + /* segmented layout loops with standard loop start/end values and works ok */ + + /* notify of new initial state */ + setup_vgmstream(vgmstream); } void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target) { @@ -2102,22 +2107,22 @@ int vgmstream_samples_to_do(int samples_this_block, int samples_per_frame, VGMST /* Why did I think this would be any simpler? */ if (vgmstream->loop_flag) { /* are we going to hit the loop end during this block? */ - if (vgmstream->current_sample+samples_left_this_block > vgmstream->loop_end_sample) { + if (vgmstream->current_sample + samples_left_this_block > vgmstream->loop_end_sample) { /* only do to just before it */ - samples_to_do = vgmstream->loop_end_sample-vgmstream->current_sample; + samples_to_do = vgmstream->loop_end_sample - vgmstream->current_sample; } /* are we going to hit the loop start during this block? */ - if (!vgmstream->hit_loop && vgmstream->current_sample+samples_left_this_block > vgmstream->loop_start_sample) { + if (!vgmstream->hit_loop && vgmstream->current_sample + samples_left_this_block > vgmstream->loop_start_sample) { /* only do to just before it */ - samples_to_do = vgmstream->loop_start_sample-vgmstream->current_sample; + samples_to_do = vgmstream->loop_start_sample - vgmstream->current_sample; } } /* if it's a framed encoding don't do more than one frame */ - if (samples_per_frame>1 && (vgmstream->samples_into_block%samples_per_frame)+samples_to_do>samples_per_frame) - samples_to_do = samples_per_frame - (vgmstream->samples_into_block%samples_per_frame); + if (samples_per_frame > 1 && (vgmstream->samples_into_block % samples_per_frame) + samples_to_do > samples_per_frame) + samples_to_do = samples_per_frame - (vgmstream->samples_into_block % samples_per_frame); return samples_to_do; } @@ -2133,7 +2138,7 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { * (only needed with the "play stream end after looping N times" option enabled) */ vgmstream->loop_count++; if (vgmstream->loop_target && vgmstream->loop_target == vgmstream->loop_count) { - vgmstream->loop_flag = 0; /* could be improved but works ok */ + vgmstream->loop_flag = 0; /* could be improved but works ok, will be restored on resets */ return 0; } @@ -2145,7 +2150,7 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { vgmstream->coding_type == coding_PSX || vgmstream->coding_type == coding_PSX_badflags) { int i; - for (i=0;ichannels;i++) { + for (i = 0; i < vgmstream->channels; i++) { vgmstream->loop_ch[i].adpcm_history1_16 = vgmstream->ch[i].adpcm_history1_16; vgmstream->loop_ch[i].adpcm_history2_16 = vgmstream->ch[i].adpcm_history2_16; vgmstream->loop_ch[i].adpcm_history1_32 = vgmstream->ch[i].adpcm_history1_32; @@ -2156,60 +2161,60 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { /* prepare certain codecs' internal state for looping */ - if (vgmstream->coding_type==coding_CRI_HCA) { + if (vgmstream->coding_type == coding_CRI_HCA) { loop_hca(vgmstream->codec_data); } - if (vgmstream->coding_type==coding_EA_MT) { - seek_ea_mt(vgmstream, vgmstream->loop_start_sample); + if (vgmstream->coding_type == coding_EA_MT) { + seek_ea_mt(vgmstream, vgmstream->loop_sample); } #ifdef VGM_USE_VORBIS - if (vgmstream->coding_type==coding_OGG_VORBIS) { + if (vgmstream->coding_type == coding_OGG_VORBIS) { seek_ogg_vorbis(vgmstream, vgmstream->loop_sample); } - if (vgmstream->coding_type==coding_VORBIS_custom) { - seek_vorbis_custom(vgmstream, vgmstream->loop_start_sample); + if (vgmstream->coding_type == coding_VORBIS_custom) { + seek_vorbis_custom(vgmstream, vgmstream->loop_sample); } #endif #ifdef VGM_USE_FFMPEG - if (vgmstream->coding_type==coding_FFmpeg) { - seek_ffmpeg(vgmstream, vgmstream->loop_start_sample); + if (vgmstream->coding_type == coding_FFmpeg) { + seek_ffmpeg(vgmstream, vgmstream->loop_sample); } #endif #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) - if (vgmstream->coding_type==coding_MP4_AAC) { + if (vgmstream->coding_type == coding_MP4_AAC) { seek_mp4_aac(vgmstream, vgmstream->loop_sample); } #endif #ifdef VGM_USE_MAIATRAC3PLUS - if (vgmstream->coding_type==coding_AT3plus) { + if (vgmstream->coding_type == coding_AT3plus) { seek_at3plus(vgmstream, vgmstream->loop_sample); } #endif #ifdef VGM_USE_ATRAC9 - if (vgmstream->coding_type==coding_ATRAC9) { + if (vgmstream->coding_type == coding_ATRAC9) { seek_atrac9(vgmstream, vgmstream->loop_sample); } #endif #ifdef VGM_USE_CELT - if (vgmstream->coding_type==coding_CELT_FSB) { + if (vgmstream->coding_type == coding_CELT_FSB) { seek_celt_fsb(vgmstream, vgmstream->loop_sample); } #endif #ifdef VGM_USE_MPEG - if (vgmstream->coding_type==coding_MPEG_custom || - vgmstream->coding_type==coding_MPEG_ealayer3 || - vgmstream->coding_type==coding_MPEG_layer1 || - vgmstream->coding_type==coding_MPEG_layer2 || - vgmstream->coding_type==coding_MPEG_layer3) { + if (vgmstream->coding_type == coding_MPEG_custom || + vgmstream->coding_type == coding_MPEG_ealayer3 || + vgmstream->coding_type == coding_MPEG_layer1 || + vgmstream->coding_type == coding_MPEG_layer2 || + vgmstream->coding_type == coding_MPEG_layer3) { seek_mpeg(vgmstream, vgmstream->loop_sample); } #endif @@ -2221,7 +2226,7 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { } /* restore! */ - memcpy(vgmstream->ch,vgmstream->loop_ch,sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); + memcpy(vgmstream->ch, vgmstream->loop_ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); vgmstream->current_sample = vgmstream->loop_sample; vgmstream->samples_into_block = vgmstream->loop_samples_into_block; vgmstream->current_block_size = vgmstream->loop_block_size; @@ -2234,10 +2239,9 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { /* is this the loop start? */ - if (!vgmstream->hit_loop && vgmstream->current_sample==vgmstream->loop_start_sample) { + if (!vgmstream->hit_loop && vgmstream->current_sample == vgmstream->loop_start_sample) { /* save! */ - memcpy(vgmstream->loop_ch,vgmstream->ch,sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); - + memcpy(vgmstream->loop_ch, vgmstream->ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); vgmstream->loop_sample = vgmstream->current_sample; vgmstream->loop_samples_into_block = vgmstream->samples_into_block; vgmstream->loop_block_size = vgmstream->current_block_size; @@ -2465,15 +2469,15 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *strea //todo force layout_none if layout_interleave? streamFile->get_name(streamFile,new_filename,sizeof(new_filename)); - if (strlen(new_filename)<2) return; /* we need at least a base and a name ending to replace */ + if (strlen(new_filename) < 2) return; /* we need at least a base and a name ending to replace */ ext = (char *)filename_extension(new_filename); if (ext-new_filename >= 1 && ext[-1]=='.') ext--; /* including "." */ /* find pair from base name and modify new_filename with the opposite */ dfs_pair_count = (sizeof(dfs_pairs)/sizeof(dfs_pairs[0])); - for (i = 0; dfs_pair == -1 && i< dfs_pair_count; i++) { - for (j=0; dfs_pair==-1 && j<2; j++) { + for (i = 0; dfs_pair == -1 && i < dfs_pair_count; i++) { + for (j = 0; dfs_pair == -1 && j < 2; j++) { const char * this_suffix = dfs_pairs[i][j]; size_t this_suffix_len = strlen(dfs_pairs[i][j]); const char * other_suffix = dfs_pairs[i][j^1]; @@ -2597,34 +2601,34 @@ fail: /* average bitrate helper to get STREAMFILE for a channel, since some codecs may use their own */ static STREAMFILE * get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM * vgmstream, int channel) { - if (vgmstream->coding_type==coding_NWA) { + if (vgmstream->coding_type == coding_NWA) { nwa_codec_data *data = vgmstream->codec_data; return (data && data->nwa) ? data->nwa->file : NULL; } - if (vgmstream->coding_type==coding_ACM) { + if (vgmstream->coding_type == coding_ACM) { acm_codec_data *data = vgmstream->codec_data; return (data && data->handle) ? data->streamfile : NULL; } #ifdef VGM_USE_VORBIS - if (vgmstream->coding_type==coding_OGG_VORBIS) { + if (vgmstream->coding_type == coding_OGG_VORBIS) { ogg_vorbis_codec_data *data = vgmstream->codec_data; return data ? data->ov_streamfile.streamfile : NULL; } #endif - if (vgmstream->coding_type==coding_CRI_HCA) { + if (vgmstream->coding_type == coding_CRI_HCA) { hca_codec_data *data = vgmstream->codec_data; return data ? data->streamfile : NULL; } #ifdef VGM_USE_FFMPEG - if (vgmstream->coding_type==coding_FFmpeg) { + if (vgmstream->coding_type == coding_FFmpeg) { ffmpeg_codec_data *data = vgmstream->codec_data; return data ? data->streamfile : NULL; } #endif #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) - if (vgmstream->coding_type==coding_MP4_AAC) { + if (vgmstream->coding_type == coding_MP4_AAC) { mp4_aac_codec_data *data = vgmstream->codec_data; return data ? data->if_file.streamfile : NULL; } @@ -2662,7 +2666,7 @@ int get_vgmstream_average_bitrate(VGMSTREAM * vgmstream) { //todo bitrate bugs with layout inside layouts (ex. TXTP) /* make a list of used streamfiles (repeats will be filtered below) */ - if (vgmstream->layout_type==layout_segmented) { + if (vgmstream->layout_type == layout_segmented) { segmented_layout_data *data = (segmented_layout_data *) vgmstream->layout_data; for (sub = 0; sub < data->segment_count; sub++) { streams_size += data->segments[sub]->stream_size; @@ -2673,7 +2677,7 @@ int get_vgmstream_average_bitrate(VGMSTREAM * vgmstream) { } } } - else if (vgmstream->layout_type==layout_layered) { + else if (vgmstream->layout_type == layout_layered) { layered_layout_data *data = vgmstream->layout_data; for (sub = 0; sub < data->layer_count; sub++) { streams_size += data->layers[sub]->stream_size; diff --git a/src/vgmstream.h b/src/vgmstream.h index 899b8f9d..dbbba226 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -723,9 +723,9 @@ typedef enum { /* info for a single vgmstream channel */ typedef struct { - STREAMFILE * streamfile; /* file used by this channel */ + STREAMFILE * streamfile; /* file used by this channel */ off_t channel_start_offset; /* where data for this channel begins */ - off_t offset; /* current location in the file */ + off_t offset; /* current location in the file */ off_t frame_header_offset; /* offset of the current frame header (for WS) */ int samples_left_in_frame; /* for WS */ @@ -733,7 +733,7 @@ typedef struct { /* format specific */ /* adpcm */ - int16_t adpcm_coef[16]; /* for formats with decode coefficients built in */ + int16_t adpcm_coef[16]; /* for formats with decode coefficients built in */ int32_t adpcm_coef_3by32[0x60]; /* for Level-5 0x555 */ union { int16_t adpcm_history1_16; /* previous sample */ @@ -771,7 +771,7 @@ typedef struct { /* main vgmstream info */ typedef struct { - /* basics */ + /* basic config */ int32_t num_samples; /* the actual max number of samples */ int32_t sample_rate; /* sample rate in Hz */ int channels; /* number of channels */ @@ -779,22 +779,22 @@ typedef struct { layout_t layout_type; /* type of layout */ meta_t meta_type; /* type of metadata */ - /* looping */ + /* loopin config */ int 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) */ - /* layouts/block */ + /* layouts/block config */ size_t interleave_block_size; /* interleave, or block/frame size (depending on the codec) */ size_t interleave_last_block_size; /* smaller interleave for last block */ - /* subsongs */ + /* subsong config */ int num_streams; /* for multi-stream formats (0=not set/one stream, 1=one stream) */ int stream_index; /* selected subsong (also 1-based) */ size_t stream_size; /* info to properly calculate bitrate in case of subsongs */ char stream_name[STREAM_NAME_SIZE]; /* name of the current stream (info), if the file stores it and it's filled */ - /* config */ + /* other config */ int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */ uint32_t channel_mask; /* to silence crossfading subsongs/layers */ int channel_mappings_on; /* channel mappings are active */ @@ -809,11 +809,6 @@ typedef struct { int config_ignore_fade; - /* channel state */ - VGMSTREAMCHANNEL * ch; /* pointer to array of channels */ - VGMSTREAMCHANNEL * start_ch; /* copies of channel status as they were at the beginning of the stream */ - VGMSTREAMCHANNEL * loop_ch; /* copies of channel status as they were at the loop point */ - /* layout/block state */ size_t full_block_size; /* actual data size of an entire block (ie. may be fixed, include padding/headers, etc) */ int32_t current_sample; /* number of samples we've passed (for loop detection) */ @@ -823,7 +818,7 @@ typedef struct { size_t current_block_samples; /* size in samples of the block we're in now (used over current_block_size if possible) */ off_t next_block_offset; /* offset of header of the next block */ /* layout/block loop state */ - int32_t loop_sample; /* saved from current_sample, should be loop_start_sample... */ + int32_t loop_sample; /* saved from current_sample (same as loop_start_sample, but more state-like) */ int32_t loop_samples_into_block;/* saved from samples_into_block */ off_t loop_block_offset; /* saved from current_block_offset */ size_t loop_block_size; /* saved from current_block_size */ @@ -835,17 +830,20 @@ typedef struct { int loop_count; /* counter of complete loops (1=looped once) */ int loop_target; /* max loops before continuing with the stream end (loops forever if not set) */ - /* decoder specific */ + /* decoder config/state */ int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */ - int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to the codec */ - + int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to them */ int32_t ws_output_size; /* WS ADPCM: output bytes for this block */ - void * start_vgmstream; /* a copy of the VGMSTREAM as it was at the beginning of the stream (for custom layouts) */ + + /* main state */ + VGMSTREAMCHANNEL * ch; /* array of channels */ + VGMSTREAMCHANNEL * start_ch; /* shallow copy of channels as they were at the beginning of the stream (for resets) */ + VGMSTREAMCHANNEL * loop_ch; /* shallow copy of channels as they were at the loop point (for loops) */ + void* start_vgmstream; /* shallow copy of the VGMSTREAM as it was at the beginning of the stream (for resets) */ /* Data the codec needs for the whole stream. This is for codecs too - * different from vgmstream's structure to be reasonably shoehorned into - * using the ch structures. + * different from vgmstream's structure to be reasonably shoehorned. * Note also that support must be added for resetting, looping and * closing for every codec that uses this, as it will not be handled. */ void * codec_data; @@ -1092,14 +1090,14 @@ typedef struct { VGMSTREAM **adxs; } aix_codec_data; -/* for files made of "vertical" segments, one per section of a song (using a complete sub-VGMSTREAM) */ +/* for files made of "continuous" segments, one per section of a song (using a complete sub-VGMSTREAM) */ typedef struct { int segment_count; VGMSTREAM **segments; int current_segment; } segmented_layout_data; -/* for files made of "horizontal" layers, one per group of channels (using a complete sub-VGMSTREAM) */ +/* for files made of "parallel" layers, one per group of channels (using a complete sub-VGMSTREAM) */ typedef struct { int layer_count; VGMSTREAM **layers; @@ -1282,7 +1280,7 @@ int get_vgmstream_average_bitrate(VGMSTREAM * vgmstream); * The list disables some common formats that may conflict (.wav, .ogg, etc). */ const char ** vgmstream_get_formats(size_t * size); -/* Force enable/disable internal looping. Should be done before playing anything, +/* Force enable/disable internal looping. Should be done before playing anything (or after reset), * and not all codecs support arbitrary loop values ATM. */ void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sample, int loop_end_sample); @@ -1293,9 +1291,12 @@ void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target); /* vgmstream "private" API */ /* -------------------------------------------------------------------------*/ -/* Allocate memory and setup a VGMSTREAM */ +/* Allocate initial memory for the VGMSTREAM */ VGMSTREAM * allocate_vgmstream(int channel_count, int looped); +/* Prepare the VGMSTREAM's initial state once parsed and ready, but before playing. */ +void setup_vgmstream(VGMSTREAM * vgmstream); + /* Get the number of samples of a single frame (smallest self-contained sample group, 1/N channels) */ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream); /* Get the number of bytes of a single frame (smallest self-contained byte group, 1/N channels) */ @@ -1314,11 +1315,11 @@ int vgmstream_samples_to_do(int samples_this_block, int samples_per_frame, VGMST /* Detect loop start and save values, or detect loop end and restore (loop back). Returns 1 if loop was done. */ int vgmstream_do_loop(VGMSTREAM * vgmstream); -/* Open the stream for reading at offset (standarized taking into account layouts, channels and so on). - * returns 0 on failure */ +/* Open the stream for reading at offset (taking into account layouts, channels and so on). + * Returns 0 on failure */ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t start_offset); -/* get description info */ +/* Get description info */ const char * get_vgmstream_coding_description(coding_t coding_type); const char * get_vgmstream_layout_description(layout_t layout_type); const char * get_vgmstream_meta_description(meta_t meta_type); From ba0345ceb61c6aa4faccdb5c27cd0d1ea8bb09b6 Mon Sep 17 00:00:00 2001 From: bnnm Date: Fri, 15 Feb 2019 23:46:21 +0100 Subject: [PATCH 05/18] Show loop info even when disabling loops in plugin's config --- fb2k/foo_vgmstream.cpp | 16 ++++++++-------- fb2k/foo_vgmstream.h | 2 +- src/vgmstream.c | 21 ++++++++++++++------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/fb2k/foo_vgmstream.cpp b/fb2k/foo_vgmstream.cpp index 609c8486..624d48e0 100644 --- a/fb2k/foo_vgmstream.cpp +++ b/fb2k/foo_vgmstream.cpp @@ -131,11 +131,11 @@ void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_cal int length_in_ms=0, channels = 0, samplerate = 0; int total_samples = -1; int bitrate = 0; - int loop_start = -1, loop_end = -1; + int loop_flag = -1, loop_start = -1, loop_end = -1; pfc::string8 description; pfc::string8_fast temp; - get_subsong_info(p_subsong, temp, &length_in_ms, &total_samples, &loop_start, &loop_end, &samplerate, &channels, &bitrate, description, p_abort); + get_subsong_info(p_subsong, temp, &length_in_ms, &total_samples, &loop_flag, &loop_start, &loop_end, &samplerate, &channels, &bitrate, description, p_abort); /* set tag info (metadata tab in file properties) */ @@ -193,7 +193,8 @@ void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_cal p_info.info_set_bitrate(bitrate / 1000); if (total_samples > 0) p_info.info_set_int("stream_total_samples", total_samples); - if (loop_start >= 0 && loop_end >= loop_start) { + if (loop_start >= 0 && loop_end > loop_start) { + p_info.info_set("looping", loop_flag > 0 ? "enabled" : "disabled"); p_info.info_set_int("loop_start", loop_start); p_info.info_set_int("loop_end", loop_end); } @@ -440,7 +441,7 @@ void input_vgmstream::setup_vgmstream(abort_callback & p_abort) { fade_samples = (int)(config.song_fade_time * vgmstream->sample_rate); } -void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & title, int *length_in_ms, int *total_samples, int *loop_start, int *loop_end, int *sample_rate, int *channels, int *bitrate, pfc::string_base & description, abort_callback & p_abort) { +void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & title, int *length_in_ms, int *total_samples, int *loop_flag, int *loop_start, int *loop_end, int *sample_rate, int *channels, int *bitrate, pfc::string_base & description, abort_callback & p_abort) { VGMSTREAM * infostream = NULL; bool is_infostream = false; foobar_song_config infoconfig; @@ -473,10 +474,9 @@ void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & ti *channels = infostream->channels; *total_samples = infostream->num_samples; *bitrate = get_vgmstream_average_bitrate(infostream); - if (infostream->loop_flag) { - *loop_start = infostream->loop_start_sample; - *loop_end = infostream->loop_end_sample; - } + *loop_flag = infostream->loop_flag; + *loop_start = infostream->loop_start_sample; + *loop_end = infostream->loop_end_sample; char temp[1024]; describe_vgmstream(infostream, temp, 1024); diff --git a/fb2k/foo_vgmstream.h b/fb2k/foo_vgmstream.h index 52933247..a72dbf11 100644 --- a/fb2k/foo_vgmstream.h +++ b/fb2k/foo_vgmstream.h @@ -85,7 +85,7 @@ class input_vgmstream : public input_stubs { VGMSTREAM * init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort); void setup_vgmstream(abort_callback & p_abort); void load_settings(); - void get_subsong_info(t_uint32 p_subsong, pfc::string_base & title, int *length_in_ms, int *total_samples, int *loop_start, int *loop_end, int *sample_rate, int *channels, int *bitrate, pfc::string_base & description, abort_callback & p_abort); + void get_subsong_info(t_uint32 p_subsong, pfc::string_base & title, int *length_in_ms, int *total_samples, int *loop_flag, int *loop_start, int *loop_end, int *sample_rate, int *channels, int *bitrate, pfc::string_base & description, abort_callback & p_abort); bool get_description_tag(pfc::string_base & temp, pfc::string_base const& description, const char *tag, char delimiter = '\n'); void set_config_defaults(foobar_song_config *current); void apply_config(VGMSTREAM * vgmstream, foobar_song_config *current); diff --git a/src/vgmstream.c b/src/vgmstream.c index 63622f89..d24f5f43 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -506,15 +506,17 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { close_vgmstream(vgmstream); continue; } - - /* Sanify loops! */ + + /* sanify loops and remove bad metadata */ if (vgmstream->loop_flag) { if (vgmstream->loop_end_sample <= vgmstream->loop_start_sample || vgmstream->loop_end_sample > vgmstream->num_samples || vgmstream->loop_start_sample < 0) { - vgmstream->loop_flag = 0; VGM_LOG("VGMSTREAM: wrong loops ignored (lss=%i, lse=%i, ns=%i)\n", vgmstream->loop_start_sample, vgmstream->loop_end_sample, vgmstream->num_samples); + vgmstream->loop_flag = 0; + vgmstream->loop_start_sample = 0; + vgmstream->loop_end_sample = 0; } } @@ -542,8 +544,9 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { /* save info */ /* stream_index 0 may be used by plugins to signal "vgmstream default" (IOW don't force to 1) */ - if (vgmstream->stream_index == 0) + if (vgmstream->stream_index == 0) { vgmstream->stream_index = streamFile->stream_index; + } setup_vgmstream(vgmstream); /* final setup */ @@ -958,11 +961,13 @@ void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sa if (loop_flag) { vgmstream->loop_start_sample = loop_start_sample; vgmstream->loop_end_sample = loop_end_sample; - } else { + } +#if 0 /* keep metadata as it's may be shown (with 'loop disabled' info) */ + else { vgmstream->loop_start_sample = 0; vgmstream->loop_end_sample = 0; } - +#endif /* propagate changes to layouts that need them */ if (vgmstream->layout_type == layout_layered) { @@ -2275,10 +2280,12 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { vgmstream->channels); concatn(length,desc,temp); - if (vgmstream->loop_flag) { + if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) { snprintf(temp,TEMPSIZE, + "looping: %s\n" "loop start: %d samples (%.4f seconds)\n" "loop end: %d samples (%.4f seconds)\n", + vgmstream->loop_flag ? "enabled" : "disabled", vgmstream->loop_start_sample, (double)vgmstream->loop_start_sample/vgmstream->sample_rate, vgmstream->loop_end_sample, From ed281e1c2eeb49039b3f11e7d41f9f3db80b27ed Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 16 Feb 2019 01:23:43 +0100 Subject: [PATCH 06/18] Fix mpeg state not properly resetting in some cases --- src/coding/mpeg_decoder.c | 43 +++++++++++---------------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/src/coding/mpeg_decoder.c b/src/coding/mpeg_decoder.c index 81c2dc7c..77047e05 100644 --- a/src/coding/mpeg_decoder.c +++ b/src/coding/mpeg_decoder.c @@ -509,42 +509,37 @@ void free_mpeg(mpeg_codec_data *data) { /* seeks stream to 0 */ void reset_mpeg(VGMSTREAM *vgmstream) { - off_t input_offset; mpeg_codec_data *data = vgmstream->codec_data; if (!data) return; + flush_mpeg(data); +#if 0 + /* flush_mpeg properly resets mpg123 with mpg123_open_feed, and + * offsets are reset in the VGMSTREAM externally, but for posterity: */ if (!data->custom) { + off_t input_offset = 0; mpg123_feedseek(data->m,0,SEEK_SET,&input_offset); - /* input_offset is ignored as we can assume it will be 0 for a seek to sample 0 */ } else { + off_t input_offset = 0; int i; - /* re-start from 0 */ for (i = 0; i < data->streams_size; i++) { mpg123_feedseek(data->streams[i]->m,0,SEEK_SET,&input_offset); - data->streams[i]->bytes_in_buffer = 0; - data->streams[i]->buffer_full = 0; - data->streams[i]->buffer_used = 0; - data->streams[i]->samples_filled = 0; - data->streams[i]->samples_used = 0; - data->streams[i]->current_size_count = 0; - data->streams[i]->current_size_target = 0; - data->streams[i]->decode_to_discard = 0; } - - data->samples_to_discard = data->skip_samples; /* initial delay */ } +#endif } /* seeks to a point */ void seek_mpeg(VGMSTREAM *vgmstream, int32_t num_sample) { - off_t input_offset; mpeg_codec_data *data = vgmstream->codec_data; if (!data) return; + flush_mpeg(data); if (!data->custom) { + off_t input_offset = 0; mpg123_feedseek(data->m, num_sample,SEEK_SET,&input_offset); /* adjust loop with mpg123's offset (useful?) */ @@ -553,31 +548,17 @@ void seek_mpeg(VGMSTREAM *vgmstream, int32_t num_sample) { } else { int i; - /* re-start from 0 */ + /* restart from 0 and manually discard samples, since we don't really know the correct offset */ for (i = 0; i < data->streams_size; i++) { - mpg123_feedseek(data->streams[i]->m,0,SEEK_SET,&input_offset); - data->streams[i]->bytes_in_buffer = 0; - data->streams[i]->buffer_full = 0; - data->streams[i]->buffer_used = 0; - data->streams[i]->samples_filled = 0; - data->streams[i]->samples_used = 0; - data->streams[i]->current_size_count = 0; - data->streams[i]->current_size_target = 0; - data->streams[i]->decode_to_discard = 0; + //mpg123_feedseek(data->streams[i]->m,0,SEEK_SET,&input_offset); /* already reset */ /* force first offset as discard-looping needs to start from the beginning */ if (vgmstream->loop_ch) vgmstream->loop_ch[i].offset = vgmstream->loop_ch[i].channel_start_offset; } - /* manually discard samples, since we don't really know the correct offset */ - data->samples_to_discard = num_sample; - data->samples_to_discard += data->skip_samples; + data->samples_to_discard += num_sample; } - - data->bytes_in_buffer = 0; - data->buffer_full = 0; - data->buffer_used = 0; } /* resets mpg123 decoder and its internals without seeking, useful when a new MPEG substream starts */ From fa4e56a6b1fc782a367fd444b4d02e5a97229e94 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 17 Feb 2019 20:28:27 +0100 Subject: [PATCH 07/18] Prepare parts for mixing --- cli/vgmstream_cli.c | 58 +++++++++-------- src/meta/txtp.c | 150 ++++++++++++++++++++++++++++++++++++++++++++ src/vgmstream.c | 12 +++- src/vgmstream.h | 33 +++++++++- 4 files changed, 223 insertions(+), 30 deletions(-) diff --git a/cli/vgmstream_cli.c b/cli/vgmstream_cli.c index 6ca4d8b4..5f0caca3 100644 --- a/cli/vgmstream_cli.c +++ b/cli/vgmstream_cli.c @@ -216,6 +216,7 @@ fail: } static void print_info(VGMSTREAM * vgmstream, cli_config *cfg) { + int channels = vgmstream->channels; if (!cfg->play_sdtout) { if (cfg->print_adxencd) { printf("adxencd"); @@ -236,7 +237,7 @@ static void print_info(VGMSTREAM * vgmstream, cli_config *cfg) { 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, vgmstream->channels); + 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); else @@ -312,16 +313,18 @@ static void apply_config(VGMSTREAM * vgmstream, cli_config *cfg) { } } -void apply_fade(sample * buf, VGMSTREAM * vgmstream, int to_get, int i, int len_samples, int fade_samples) { - if (vgmstream->loop_flag && fade_samples > 0) { +void apply_fade(sample * buf, VGMSTREAM * vgmstream, int to_get, int i, int len_samples, int fade_samples, int channels) { + int is_fade_on = vgmstream->loop_flag; + + if (is_fade_on && fade_samples > 0) { int samples_into_fade = i - (len_samples - fade_samples); if (samples_into_fade + to_get > 0) { int j, k; for (j = 0; j < to_get; j++, samples_into_fade++) { if (samples_into_fade > 0) { double fadedness = (double)(fade_samples - samples_into_fade) / fade_samples; - for (k = 0; k < vgmstream->channels; k++) { - buf[j*vgmstream->channels+k] = (sample)buf[j*vgmstream->channels+k]*fadedness; + for (k = 0; k < channels; k++) { + buf[j*channels + k] = (sample)buf[j*channels + k] * fadedness; } } } @@ -337,6 +340,7 @@ int main(int argc, char ** argv) { char outfilename_temp[PATH_LIMIT]; sample * buf = NULL; + int channels; int32_t len_samples; int32_t fade_samples; int i, j; @@ -453,16 +457,18 @@ int main(int argc, char ** argv) { /* last init */ - buf = malloc(BUFFER_SAMPLES*sizeof(sample)*vgmstream->channels); + channels = vgmstream->channels; + + buf = malloc(BUFFER_SAMPLES * sizeof(sample) * channels); if (!buf) { fprintf(stderr,"failed allocating output buffer\n"); - goto fail;; + goto fail; } /* slap on a .wav header */ { uint8_t wav_buf[0x100]; - int channels = (cfg.only_stereo != -1) ? 2 : vgmstream->channels; + int channels = (cfg.only_stereo != -1) ? 2 : channels; size_t bytes_done; bytes_done = make_wav_header(wav_buf,0x100, @@ -477,15 +483,15 @@ int main(int argc, char ** argv) { while (cfg.play_forever) { int to_get = BUFFER_SAMPLES; - render_vgmstream(buf,to_get,vgmstream); + render_vgmstream(buf, to_get, vgmstream); - swap_samples_le(buf,vgmstream->channels*to_get); /* write PC endian */ + 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*vgmstream->channels+(cfg.only_stereo*2),sizeof(sample),2,outfile); + fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample), 2, outfile); } } else { - fwrite(buf,sizeof(sample)*vgmstream->channels,to_get,outfile); + fwrite(buf, sizeof(sample) * channels, to_get, outfile); } } @@ -496,17 +502,17 @@ int main(int argc, char ** argv) { if (i + BUFFER_SAMPLES > len_samples) to_get = len_samples - i; - render_vgmstream(buf,to_get,vgmstream); + render_vgmstream(buf, to_get, vgmstream); - apply_fade(buf, vgmstream, to_get, i, len_samples, fade_samples); + apply_fade(buf, vgmstream, to_get, i, len_samples, fade_samples, channels); - swap_samples_le(buf,vgmstream->channels*to_get); /* write PC endian */ + 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*vgmstream->channels+(cfg.only_stereo*2),sizeof(sample),2,outfile); + fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample), 2, outfile); } } else { - fwrite(buf,sizeof(sample)*vgmstream->channels,to_get,outfile); + fwrite(buf, sizeof(sample) * channels, to_get, outfile); } } @@ -535,7 +541,7 @@ int main(int argc, char ** argv) { /* slap on a .wav header */ { uint8_t wav_buf[0x100]; - int channels = (cfg.only_stereo != -1) ? 2 : vgmstream->channels; + int channels = (cfg.only_stereo != -1) ? 2 : channels; size_t bytes_done; bytes_done = make_wav_header(wav_buf,0x100, @@ -551,17 +557,17 @@ int main(int argc, char ** argv) { if (i + BUFFER_SAMPLES > len_samples) to_get = len_samples - i; - render_vgmstream(buf,to_get,vgmstream); + render_vgmstream(buf, to_get, vgmstream); - apply_fade(buf, vgmstream, to_get, i, len_samples, fade_samples); + apply_fade(buf, vgmstream, to_get, i, len_samples, fade_samples, channels); - swap_samples_le(buf,vgmstream->channels*to_get); /* write PC endian */ + 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*vgmstream->channels+(cfg.only_stereo*2),sizeof(sample),2,outfile); + fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample), 2, outfile); } } else { - fwrite(buf,sizeof(sample)*vgmstream->channels,to_get,outfile); + fwrite(buf, sizeof(sample) * channels, to_get, outfile); } } fclose(outfile); @@ -574,14 +580,12 @@ int main(int argc, char ** argv) { return EXIT_SUCCESS; fail: - if (!cfg.play_sdtout) - { + if (!cfg.play_sdtout) { if (outfile != NULL) - { fclose(outfile); - } } close_vgmstream(vgmstream); + free(buf); return EXIT_FAILURE; } diff --git a/src/meta/txtp.c b/src/meta/txtp.c index a54fa6d5..3142ef0a 100644 --- a/src/meta/txtp.c +++ b/src/meta/txtp.c @@ -4,6 +4,9 @@ #define TXT_LINE_MAX 0x2000 +#ifdef VGMSTREAM_MIXING +#define TXTP_MIXING_MAX 64 +#endif typedef struct { char filename[TXT_LINE_MAX]; @@ -12,6 +15,11 @@ typedef struct { int channel_mappings_on; int channel_mappings[32]; +#if VGMSTREAM_MIXING + int mixing_count; + mix_config_data mixing[TXTP_MIXING_MAX]; +#endif + double config_loop_count; double config_fade_time; double config_fade_delay; @@ -216,6 +224,48 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) { } } +#ifdef VGMSTREAM_MIXING + if (current->mixing_count > 0) { + int i, ch_max_cur; + if (vgmstream->mixing_count + current->mixing_count > vgmstream->mixing_size) { + VGM_LOG("TXTP: ignored mixing\n"); + return; + } + + ch_max_cur = vgmstream->channels; + + for (i = 0; i < current->mixing_count; i++) { + mix_config_data mix = current->mixing[i]; + + vgmstream->mixing[vgmstream->mixing_count] = mix; + vgmstream->mixing_count++; + + /* some mixes change output channels */ + switch(vgmstream->mixing[i].command) { + case MIX_DOWNMIX: + if (mix.ch_a < 0 || mix.ch_a >= ch_max_cur || ch_max_cur - 1 == 0) break; + ch_max_cur--; + break; + + case MIX_DOWNMIX_REST: + if (mix.ch_a < 0 || mix.ch_a >= ch_max_cur) break; + ch_max_cur = mix.ch_a + 1; /* simply clamp channels */ + break; + + case MIX_UPMIX: + if (mix.ch_a < 0 || mix.ch_a > ch_max_cur) break; /* ch_a can be == max_cur, since we are inserting */ + ch_max_cur++; + break; + + default: + break; + } + } + + vgmstream->output_channels = ch_max_cur; + } +#endif + vgmstream->config_loop_count = current->config_loop_count; vgmstream->config_fade_time = current->config_fade_time; vgmstream->config_fade_delay = current->config_fade_delay; @@ -253,6 +303,24 @@ static void get_double(const char * config, double *value) { } } +#ifdef VGMSTREAM_MIXING +static void add_mixing(txtp_entry* cfg, mix_config_data* mix, mix_command_t command) { + if (cfg->mixing_count + 1 > TXTP_MIXING_MAX) { + VGM_LOG("TXTP: too many mixes\n"); + return; + } + + /* parsers reads ch1 = first, but for mixing code ch0 = first + * (if parser reads ch0 here it'll becode -1 and ignored in code) */ + mix->ch_a--; + mix->ch_b--; + mix->command = command; + cfg->mixing[cfg->mixing_count] = *mix; /* memcpy'ed */ + cfg->mixing_count++; +} +#endif + + static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filename) { strcpy(current->filename, filename); @@ -268,6 +336,18 @@ static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filenam } } +#ifdef VGMSTREAM_MIXING + //*current = *cfg; /* don't memcopy to allow list additions */ + + if (cfg->mixing_count > 0) { + int i; + for (i = 0; i < cfg->mixing_count; i++) { + current->mixing[current->mixing_count] = cfg->mixing[i]; + current->mixing_count++; + } + } +#endif + current->config_loop_count = cfg->config_loop_count; current->config_fade_time = cfg->config_fade_time; current->config_fade_delay = cfg->config_fade_delay; @@ -357,6 +437,76 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) { } } } +#ifdef VGMSTREAM_MIXING + else if (config[0] == 'm') { + /* channel mixing: file.ext#m(sub-command),(sub-command),etc */ + char cmd; + + config++; + + while (config[0] != '\0') { + mix_config_data mix = {0}; + + if (config[0]== ',') { + config++; + continue; + } + + if (sscanf(config, "%d-%d%n", &mix.ch_a, &mix.ch_b, &n) == 2 && n != 0) { + add_mixing(&cfg, &mix, MIX_SWAP); /* N-M: swaps M with N */ + config += n; + continue; + } + + if ((sscanf(config, "%d+%d*%f%n", &mix.ch_a, &mix.ch_b, &mix.vol_a, &n) == 3 && n != 0) || + (sscanf(config, "%d+%dx%f%n", &mix.ch_a, &mix.ch_b, &mix.vol_a, &n) == 3 && n != 0)) { + add_mixing(&cfg, &mix, MIX_ADD_VOLUME); /* N+M*V: mixes M * volume to N */ + config += n; + continue; + } + + if (sscanf(config, "%d+%d%n", &mix.ch_a, &mix.ch_b, &n) == 2 && n != 0) { + add_mixing(&cfg, &mix, MIX_ADD); /* N+M: mixes M to N */ + config += n; + continue; + } + + if ((sscanf(config, "%d*%f~%f@%f~%f%n", &mix.ch_a, &mix.vol_a, &mix.vol_b, &mix.pos_a, &mix.pos_b, &n) == 5 && n != 0) || + (sscanf(config, "%dx%f~%f@%f~%f%n", &mix.ch_a, &mix.vol_a, &mix.vol_b, &mix.pos_a, &mix.pos_b, &n) == 5 && n != 0)) { + add_mixing(&cfg, &mix, MIX_CROSSFADE); /* N*V1~V2@P1~P2: fades from volume1 to 2 between position1 to 2 */ + config += n; + continue; + } + + if ((sscanf(config, "%d*%f%n", &mix.ch_a, &mix.vol_a, &n) == 2 && n != 0) || + (sscanf(config, "%dx%f%n", &mix.ch_a, &mix.vol_a, &n) == 2 && n != 0)) { + add_mixing(&cfg, &mix, MIX_VOLUME); /* N*V: changes volume of N */ + config += n; + continue; + } + + if (sscanf(config, "%d%c%n", &mix.ch_a, &cmd, &n) == 2 && n != 0 && cmd == 'D') { + add_mixing(&cfg, &mix, MIX_DOWNMIX_REST); /* ND: downmix N and all following channels */ + config += n; + continue; + } + + if (sscanf(config, "%d%c%n", &mix.ch_a, &cmd, &n) == 2 && n != 0 && cmd == 'd') { + add_mixing(&cfg, &mix, MIX_DOWNMIX);/* Nd: downmix N only */ + config += n; + continue; + } + + if (sscanf(config, "%d%c%n", &mix.ch_a, &cmd, &n) == 2 && n != 0 && cmd == 'u') { + add_mixing(&cfg, &mix, MIX_UPMIX); /* Nu: upmix N */ + config += n; + continue; + } + + break; /* unknown/mix end */ + } + } +#endif else if (config[0] == 's' || (config[0] >= '0' && config[0] <= '9')) { /* subsongs: file.ext#s2 = play subsong 2, file.ext#2~10 = play subsong range */ int subsong_start = 0, subsong_end = 0; diff --git a/src/vgmstream.c b/src/vgmstream.c index d24f5f43..4692b62f 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -746,6 +746,11 @@ VGMSTREAM * allocate_vgmstream(int channel_count, int loop_flag) { vgmstream->channels = channel_count; vgmstream->loop_flag = loop_flag; +#ifdef VGMSTREAM_MIXING + /* fixed arrays, for now */ + vgmstream->mixing_size = 64; + vgmstream->stream_name_size = STREAM_NAME_SIZE; +#endif return vgmstream; fail: if (vgmstream) { @@ -2274,9 +2279,12 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { } snprintf(temp,TEMPSIZE, - "sample rate: %d Hz\n" + "sample rate: %d Hz\n", + vgmstream->sample_rate); + concatn(length,desc,temp); + + snprintf(temp,TEMPSIZE, "channels: %d\n", - vgmstream->sample_rate, vgmstream->channels); concatn(length,desc,temp); diff --git a/src/vgmstream.h b/src/vgmstream.h index dbbba226..438c4eec 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -5,8 +5,9 @@ #ifndef _VGMSTREAM_H #define _VGMSTREAM_H + /* reasonable maxs */ enum { PATH_LIMIT = 32768 }; -enum { STREAM_NAME_SIZE = 255 }; /* reasonable max */ +enum { STREAM_NAME_SIZE = 255 }; #include "streamfile.h" @@ -720,6 +721,29 @@ typedef enum { } meta_t; +#ifdef VGMSTREAM_MIXING +/* mixing info */ +typedef enum { + MIX_SWAP, + MIX_ADD, + MIX_ADD_VOLUME, + MIX_VOLUME, + MIX_CROSSFADE, + MIX_DOWNMIX, + MIX_DOWNMIX_REST, + MIX_UPMIX +} mix_command_t; + +typedef struct { + mix_command_t command; + int ch_a; + int ch_b; + float vol_a; + float vol_b; + float pos_a; + float pos_b; +} mix_config_data; +#endif /* info for a single vgmstream channel */ typedef struct { @@ -799,6 +823,13 @@ typedef struct { uint32_t channel_mask; /* to silence crossfading subsongs/layers */ int channel_mappings_on; /* channel mappings are active */ int channel_mappings[32]; /* swap channel "i" with "[i]" */ +#ifdef VGMSTREAM_MIXING + int output_channels; /* resulting channels after mixing (may be ignored if plugin doesn't support it) */ + int mixing_on; /* mixing allowed */ + int mixing_count; /* mixing number */ + mix_config_data mixing[64]; /* applies transformation to output samples (could be alloc'ed but to simplify...) */ + size_t mixing_size; /* mixing max */ +#endif /* config requests, players must read and honor these values */ /* (ideally internally would work as a player, but for now player must do it manually) */ double config_loop_count; From 18593837adda3268fae797090e11781d7de8e094 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 17 Feb 2019 20:47:57 +0100 Subject: [PATCH 08/18] Add TXTP "#h(rate)" to force sample rate --- src/meta/txtp.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/meta/txtp.c b/src/meta/txtp.c index 3142ef0a..ba56090b 100644 --- a/src/meta/txtp.c +++ b/src/meta/txtp.c @@ -26,6 +26,9 @@ typedef struct { int config_ignore_loop; int config_force_loop; int config_ignore_fade; + + int sample_rate; + } txtp_entry; typedef struct { @@ -266,6 +269,9 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) { } #endif + if (current->sample_rate > 0) + vgmstream->sample_rate = current->sample_rate; + vgmstream->config_loop_count = current->config_loop_count; vgmstream->config_fade_time = current->config_fade_time; vgmstream->config_fade_delay = current->config_fade_delay; @@ -296,11 +302,21 @@ static void clean_filename(char * filename) { } -static void get_double(const char * config, double *value) { +static int get_double(const char * config, double *value) { int n; if (sscanf(config, "%lf%n", value,&n) != 1) { *value = 0; + return 0; } + return n; +} +static int get_int(const char * config, int *value) { + int n; + if (sscanf(config, "%i%n", value,&n) != 1) { + *value = 0; + return 0; + } + return n; } #ifdef VGMSTREAM_MIXING @@ -354,6 +370,9 @@ static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filenam current->config_ignore_loop = cfg->config_ignore_loop; current->config_force_loop = cfg->config_force_loop; current->config_ignore_fade = cfg->config_ignore_fade; + + current->sample_rate = cfg->sample_rate; + } static int add_filename(txtp_header * txtp, char *filename, int is_default) { @@ -543,15 +562,19 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) { } else if (config[0] == 'l') { config++; - get_double(config, &cfg.config_loop_count); + config += get_double(config, &cfg.config_loop_count); } else if (config[0] == 'f') { config++; - get_double(config, &cfg.config_fade_time); + config += get_double(config, &cfg.config_fade_time); } else if (config[0] == 'd') { config++; - get_double(config, &cfg.config_fade_delay); + config += get_double(config, &cfg.config_fade_delay); + } + else if (config[0] == 'h') { + config++; + config += get_int(config, &cfg.sample_rate); } else if (config[0] == ' ') { continue; /* likely a comment, find next # */ From ab4805407ab87866b95ecdd451da5869db25488f Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 17 Feb 2019 20:48:18 +0100 Subject: [PATCH 09/18] Fix some .bnk [Manhunt 2 (PSP)] --- src/meta/bnk_sony.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/meta/bnk_sony.c b/src/meta/bnk_sony.c index b5437b69..36e34363 100644 --- a/src/meta/bnk_sony.c +++ b/src/meta/bnk_sony.c @@ -176,8 +176,15 @@ VGMSTREAM * init_vgmstream_bnk_sony(STREAMFILE *streamFile) { case 0xC2: sample_rate = 44100; break; case 0xBC: sample_rate = 36000; break; //? case 0xBA: sample_rate = 32000; break; //? + case 0xB9: sample_rate = 30000; break; //? + case 0xB8: sample_rate = 28000; break; //? case 0xB6: sample_rate = 22050; break; + case 0xB0: sample_rate = 15000; break; //? + case 0xAF: sample_rate = 14000; break; //? + case 0xAE: sample_rate = 13000; break; //? + case 0xAC: sample_rate = 12000; break; //? case 0xAA: sample_rate = 11025; break; + case 0xA9: sample_rate = 10000; break; //? default: VGM_LOG("BNK: unknown pitch %x\n", pitch); goto fail; From fa214e6852fff5ee06736351194ea3ea1cc1132a Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 17 Feb 2019 20:52:34 +0100 Subject: [PATCH 10/18] Add .dc2 TXTH extension [Star Wars Bounty Hunter (GC)] --- src/formats.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/formats.c b/src/formats.c index 03903b8e..9f9b154d 100644 --- a/src/formats.c +++ b/src/formats.c @@ -123,6 +123,7 @@ static const char* extension_list[] = { "de2", "dec", "dmsg", + "ds2", //txth/reserved [Star Wars Bounty Hunter (GC)] "dsf", "dsp", "dspw", From 8056ba37f401e9fba9f48c95e9ad68f2f77bc30b Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 17 Feb 2019 20:53:02 +0100 Subject: [PATCH 11/18] Update doc --- src/meta/ubi_bao.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/meta/ubi_bao.c b/src/meta/ubi_bao.c index 5b3393fa..42aeb17d 100644 --- a/src/meta/ubi_bao.c +++ b/src/meta/ubi_bao.c @@ -203,12 +203,12 @@ VGMSTREAM * init_vgmstream_ubi_bao_spk(STREAMFILE *streamFile) { /* Variation of .pk: * - 0x00: 0x014B5053 ("SPK\01" LE) * - 0x04: BAO count - * - 0x08 * count: BAO ids inside + * - 0x08: BAO ids inside (0x04 * BAO count) * - per BAO count - * - 0x00: 1? - * - 0x04: id that references this? (ex. id of an event BAO) - * - 0x08: BAO size - * - 0x0c+: BAO data + * - 0x00: table count + * - 0x04: ids related to this BAO? (0x04 * table count) + * - 0x08/NN: BAO size + * - 0x0c/NN+: BAO data up to size * * BAOs reference .sbao by name (are considered atomic) so perhaps could * be considered a type of bigfile. @@ -690,12 +690,12 @@ static VGMSTREAM * init_vgmstream_ubi_bao_header(ubi_bao_header * bao, STREAMFIL goto fail; /* not uncommon */ } - ;VGM_LOG("UBI BAO: target at %x, h_id=%08x, s_id=%08x, p_id=%08x\n", - (uint32_t)bao->header_offset, bao->header_id, bao->stream_id, bao->prefetch_id); - ;VGM_LOG("UBI BAO: stream=%x, size=%x, res=%s\n", - (uint32_t)bao->stream_offset, bao->stream_size, (bao->is_external ? bao->resource_name : "internal")); - ;VGM_LOG("UBI BAO: type=%i, header=%x, extra=%x, prefetch=%x, size=%x\n", - bao->header_type, bao->header_size, bao->extra_size, (uint32_t)bao->prefetch_offset, bao->prefetch_size); + //;VGM_LOG("UBI BAO: target at %x, h_id=%08x, s_id=%08x, p_id=%08x\n", + // (uint32_t)bao->header_offset, bao->header_id, bao->stream_id, bao->prefetch_id); + //;VGM_LOG("UBI BAO: stream=%x, size=%x, res=%s\n", + // (uint32_t)bao->stream_offset, bao->stream_size, (bao->is_external ? bao->resource_name : "internal")); + //;VGM_LOG("UBI BAO: type=%i, header=%x, extra=%x, prefetch=%x, size=%x\n", + // bao->header_type, bao->header_size, bao->extra_size, (uint32_t)bao->prefetch_offset, bao->prefetch_size); switch(bao->type) { @@ -800,8 +800,8 @@ static int parse_pk(ubi_bao_header * bao, STREAMFILE *streamFile) { bao_offset += bao_size; /* files simply concat BAOs */ } - ;VGM_LOG("UBI BAO: class "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(bao->classes[i],"%02x=%i ",i,bao->classes[i]); }} VGM_LOG("\n"); - ;VGM_LOG("UBI BAO: types "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(bao->types[i],"%02x=%i ",i,bao->types[i]); }} VGM_LOG("\n"); + //;VGM_LOG("UBI BAO: class "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(bao->classes[i],"%02x=%i ",i,bao->classes[i]); }} VGM_LOG("\n"); + //;VGM_LOG("UBI BAO: types "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(bao->types[i],"%02x=%i ",i,bao->types[i]); }} VGM_LOG("\n"); close_streamfile(streamIndex); close_streamfile(streamTest); @@ -1821,6 +1821,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { /* same as 0x001B0100 except: * - base 0xA0, skip 0x24, name style %08x.bao (not .sbao?) */ case 0x001D0A00: /* Shaun White Snowboarding (PSP)-atomic-opal */ + case 0x00220017: /* Avatar (PS3)-atomic/spk */ case 0x00220018: /* Avatar (PS3)-atomic/spk */ case 0x00260102: /* Prince of Persia Trilogy HD (PS3)-package-gear */ /* similar to 0x00250108 but most values are moved +4 From a4d4e4a5b7aacf1612e179a265a40404565939c1 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 17 Feb 2019 20:53:30 +0100 Subject: [PATCH 12/18] Fix minor Ubi SB issues --- src/meta/ubi_sb.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/meta/ubi_sb.c b/src/meta/ubi_sb.c index 296c5b6b..05d3974b 100644 --- a/src/meta/ubi_sb.c +++ b/src/meta/ubi_sb.c @@ -89,7 +89,7 @@ typedef struct { uint32_t map_zero; off_t map_offset; off_t map_size; - char map_name[0x24]; + char map_name[0x28]; uint32_t map_unknown; /* SB info (some values are derived depending if it's standard sbX or map sbX) */ @@ -145,7 +145,7 @@ typedef struct { float duration; /* silence duration */ int is_external; /* stream is in a external file */ - char resource_name[0x24]; /* filename to the external stream, or internal stream info for some games */ + char resource_name[0x28]; /* filename to the external stream, or internal stream info for some games */ char readable_name[255]; /* final subsong name */ int types[16]; /* counts each header types, for debugging */ @@ -1115,13 +1115,13 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream sb->num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile); for (i = 0; i < sb->layer_count; i++) { - int channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */ - (uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, streamFile) : - (uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, streamFile); + //int channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */ + // (uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, streamFile) : + // (uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, streamFile); int sample_rate = read_32bit(table_offset + sb->cfg.layer_sample_rate, streamFile); int stream_type = read_32bit(table_offset + sb->cfg.layer_stream_type, streamFile); int num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile); - if (sb->channels != channels || sb->sample_rate != sample_rate || sb->stream_type != stream_type) { + if (sb->sample_rate != sample_rate || sb->stream_type != stream_type) { VGM_LOG("Ubi SB: %i layer headers don't match at %x\n", sb->layer_count, (uint32_t)table_offset); if (sb->cfg.ignore_layer_error) { @@ -1132,6 +1132,9 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream goto fail; } + /* unusual but happens, layers handle it fine [Brothers in Arms 2 (PS2) ex. MP_B01_NL.SB1] */ + //;VGM_ASSERT_ONCE(sb->channels != channels, "UBI SB: layer channels don't match at %x\n", (uint32_t)table_offset); + /* can be +-1 */ if (sb->num_samples != num_samples && sb->num_samples + 1 == num_samples) { sb->num_samples -= 1; @@ -1781,8 +1784,8 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { /* games <= 0x00100000 seem to use old types, rest new types */ - /* common */ - sb->cfg.resource_name_size = 0x24; /* maybe 0x20/0x28 for some but ok enough (null terminated) */ + /* maybe 0x20/0x24 for some but ok enough (null terminated) */ + sb->cfg.resource_name_size = 0x28; /* min for Brother in Arms 2 (PS2) */ /* represents map style (1=first, 2=mid, 3=latest) */ if (sb->version <= 0x00000007) @@ -2547,6 +2550,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { /* Splinter Cell Classic Trilogy HD (2011)(PS3)-map */ if (sb->version == 0x001D0000 && sb->platform == UBI_PS3) { config_sb_entry(sb, 0x5c, 0x80); + sb->cfg.audio_interleave = 0x10; config_sb_audio_fs(sb, 0x28, 0x30, 0x34); config_sb_audio_he(sb, 0x44, 0x4c, 0x54, 0x5c, 0x64, 0x68); From f2d37f28cd09d8798a4dacd8c7d9dda1779bef5e Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 17 Feb 2019 21:04:43 +0100 Subject: [PATCH 13/18] Add txtp_maker.py companion CLI tool --- cli/txtp_maker.py | 544 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 544 insertions(+) create mode 100644 cli/txtp_maker.py diff --git a/cli/txtp_maker.py b/cli/txtp_maker.py new file mode 100644 index 00000000..f970e328 --- /dev/null +++ b/cli/txtp_maker.py @@ -0,0 +1,544 @@ +#!/usr/bin/env python3 + +# ########################################################################### # +# TXTP MAKER +# ########################################################################### # + +from __future__ import division +import subprocess +import zlib +import os.path +import os +import re +import sys +import fnmatch + +def print_usage(appname): + print("Usage: {} (filename) [options]".format(appname)+"\n" + "\n" + "Creates (filename)_(subsong).txtp for every subsong in (filename).\n" + " (filename) can be a * or *.ext wildcard too (works with dupe filters).\n" + "Works with files with no subsongs (unless filtered) too.\n" + "\n" + "Use -h to print [options]. Examples:\n" + "\n" + "{} bgm.fsb -in -fcm 2 -fms 5.0 ".format(appname)+"\n" + " make TXTP for subsongs with at least 2 channels and 5 seconds\n" + "{} *.scd -r -fd -l 2".format(appname)+"\n" + " all .scd in subdirs, ignoring dupes and making per 2ch layers\n" + "{} *.sm1 -fne .+STREAM[.]SS[0-9]$ ".format(appname)+"\n" + " all .sm1 excluding those subsong names that ends with 'STREAM.SS0..9'\n" + "{} samples.bnk -fni ^bgm.? ".format(appname)+"\n" + " in .bnk including only subsong names that start with 'bgm'\n" + "{} * -r -fss 1".format(appname)+"\n" + " all files in subdirs with at least 1 subsong (ignoring formats without them)\n" + ) + +def print_help(appname): + print("Options:\n" + " -r: find recursive (writes files to current dir, with dir in TXTP)\n" + " -c (name): set path to CLI (default: test.exe)\n" + " -n (name): use (name)_(subsong).txtp format\n" + " You can put '{filename}' somewhere to get it substituted by the base name\n" + " -z N: zero-fill subsong number (default: auto fill up to total subsongs)\n" + " -d (dir): add dir in TXTP (if the file will reside in a subdir)\n" + " -m: create mini-txtp\n" + " -o: overwrite existing .txtp (beware when using with internal names)\n" + " -in: name TXTP using the subsong's internal name if found\n" + " -ie: remove internal name's extension\n" + " -ii: add subsong number when using internal name\n" + " -l N: create multiple TXTP per subsong layers, every N channels\n" + " -fd: filter duplicates (slower)\n" + " -fcm N: filter min channels\n" + " -fcM N: filter by max channels\n" + " -frm N: filter by min sample rate\n" + " -frM N: filter by max sample rate\n" + " -fsm N.N: filter by min seconds\n" + " -fsM N.N: filter by max seconds\n" + " -fss N: filter min subsongs (1 filters formats incapable of subsongs)\n" + " -fni (regex): filter by subsong name, include files that match\n" + " -fne (regex): filter by subsong name, exclude files that match\n" + " -v (name): verbose level (off|trace|debug|info, default: info)\n" + " -h N: show this help\n" + ) + +# ########################################################################### # + +def find_files(dir, pattern, recursive): + files = [] + for root, dirnames, filenames in os.walk(dir): + for filename in fnmatch.filter(filenames, pattern): + files.append(os.path.join(root, filename)) + + if not recursive: + break + + return files + +def make_cmd(cfg, fname_in, fname_out, target_subsong): + if (cfg.test_dupes): + cmd = "{} -s {} -i -o {} {}".format(cfg.cli, target_subsong, fname_out, fname_in) + else: + cmd = "{} -s {} -m -i -o {} {}".format(cfg.cli, target_subsong, fname_out, fname_in) + return cmd + +class LogHelper(object): + + def __init__(self, cfg): + self.cfg = cfg + + def trace(self, msg): + v = self.cfg.verbose + if v == "trace": + print(msg) + + def debug(self, msg): + v = self.cfg.verbose + if v == "trace" or v == "debug": + print(msg) + + def info(self, msg): + v = self.cfg.verbose + if v == "trace" or v == "debug" or v == "info": + print(msg) + +class ConfigHelper(object): + show_help = False + cli = "test.exe" + + recursive = False + base_name = '' + zero_fill = -1 + subdir = '' + mini_txtp = False + overwrite = False + layers = 0 + + use_internal_name = False + use_internal_ext = False + use_internal_index = False + + test_dupes = False + min_channels = 0 + max_channels = 0 + min_sample_rate = 0 + max_sample_rate = 0 + min_seconds = 0.0 + max_seconds = 0.0 + min_subsongs = 0 + include_regex = "" + exclude_regex = "" + + verbose = "info" + + argv_len = 0 + index = 0 + + + def read_bool(self, command, default): + if self.index > self.argv_len - 1: + return default + if self.argv[self.index] == command: + val = True + self.index += 1 + return val + return default + + def read_value(self, command, default): + if self.index > self.argv_len - 2: + return default + if self.argv[self.index] == command: + val = self.argv[self.index+1] + self.index += 2 + return val + return default + + def read_string(self, command, default): + return str(self.read_value(command, default)) + + def read_int(self, command, default): + return int(self.read_value(command, default)) + + def read_float(self, command, default): + return float(self.read_value(command, default)) + + #todo improve this poop + def __init__(self, argv): + self.index = 2 #after file + self.argv = argv + self.argv_len = len(argv) + + if argv[1] == '-h': + self.show_help = True + + prev_index = self.index + while self.index < len(self.argv): + self.show_help = self.read_bool('-h', self.show_help) + self.cli = self.read_string('-c', self.cli) + self.recursive = self.read_bool('-r', self.recursive) + self.base_name = self.read_string('-n', self.base_name) + self.zero_fill = self.read_int('-z', self.zero_fill) + self.subdir = self.read_string('-d', self.subdir) + + self.test_dupes = self.read_bool('-fd', self.test_dupes) + self.min_channels = self.read_int('-fcm', self.min_channels) + self.max_channels = self.read_int('-fcM', self.max_channels) + self.min_sample_rate = self.read_int('-frm', self.min_sample_rate) + self.max_sample_rate = self.read_int('-frM', self.max_sample_rate) + self.min_seconds = self.read_float('-fsm', self.min_seconds) + self.max_seconds = self.read_float('-fsM', self.max_seconds) + self.min_subsongs = self.read_int('-fss', self.min_subsongs) + self.include_regex = self.read_string('-fni', self.include_regex) + self.exclude_regex = self.read_string('-fne', self.exclude_regex) + + self.mini_txtp = self.read_bool('-m', self.mini_txtp) + self.overwrite = self.read_bool('-o', self.overwrite) + self.layers = self.read_int('-l', self.layers) + + self.use_internal_name = self.read_bool('-in', self.use_internal_name) + self.use_internal_ext = self.read_bool('-ie', self.use_internal_ext) + self.use_internal_index = self.read_bool('-ii', self.use_internal_index) + + self.verbose = self.read_string('-v', self.verbose) + + if prev_index == self.index: + self.index += 1 + prev_index = self.index + + if (self.subdir != '') and not (self.subdir.endswith('/') or self.subdir.endswith('\\')): + self.subdir += '/' + + def __str__(self): + return str(self.__dict__) + + +class Cr32Helper(object): + crc32_map = {} + dupe = False + cfg = None + + def __init__(self, cfg): + self.cfg = cfg + + def get_crc32(self, fname): + buf_size = 0x8000 + with open(fname, 'rb') as file: + buf = file.read(buf_size) + crc32 = 0 + while len(buf) > 0: + crc32 = zlib.crc32(buf, crc32) + buf = file.read(buf_size) + return crc32 & 0xFFFFFFFF + + def update(self, fname): + cfg = self.cfg + + self.dupe = False + if cfg.test_dupes == 0: + return + if not os.path.exists(fname): + return + + crc32_str = format(self.get_crc32(fname),'08x') + if (crc32_str in self.crc32_map): + self.dupe = True + return + self.crc32_map[crc32_str] = True + + return + + def is_dupe(self): + return self.dupe + + +class TxtpMaker(object): + channels = 0 + sample_rate = 0 + num_samples = 0 + stream_count = 0 + stream_index = 0 + stream_name = '' + stream_seconds = 0 + + def __init__(self, cfg, output_b, log): + self.cfg = cfg + self.log = log + + self.output = str(output_b).replace("\\r","").replace("\\n","\n") + self.channels = self.get_value("channels: ") + self.sample_rate = self.get_value("sample rate: ") + self.num_samples = self.get_value("stream total samples: ") + self.stream_count = self.get_value("stream count: ") + self.stream_index = self.get_value("stream index: ") + self.stream_name = self.get_string("stream name: ") + + if self.channels == 0: + raise ValueError('Incorrect command result') + + self.stream_seconds = self.num_samples / self.sample_rate + + def __str__(self): + return str(self.__dict__) + + def get_string(self, str): + find_pos = self.output.find(str) + if (find_pos == -1): + return '' + cut_pos = find_pos + len(str) + str_cut = self.output[cut_pos:] + return str_cut.split()[0] + + def get_value(self, str): + res = self.get_string(str) + if (res == ''): + return 0; + return int(res) + + def is_ignorable(self): + cfg = self.cfg + + if (self.channels < cfg.min_channels): + return True; + if (cfg.max_channels > 0 and self.channels > cfg.max_channels): + return True; + if (self.sample_rate < cfg.min_sample_rate): + return True; + if (cfg.max_sample_rate > 0 and self.sample_rate > cfg.max_sample_rate): + return True; + if (self.stream_seconds < cfg.min_seconds): + return True; + if (cfg.max_seconds > 0 and self.stream_seconds > cfg.max_seconds): + return True; + if (self.stream_count < cfg.min_subsongs): + return True; + if (cfg.exclude_regex != "" and self.stream_name != ""): + p = re.compile(cfg.exclude_regex) + if (p.match(self.stream_name) != None): + return True + if (cfg.include_regex != "" and self.stream_name != ""): + p = re.compile(cfg.include_regex) + if (p.match(self.stream_name) == None): + return True + + return False + + def get_stream_mask(self, layer): + cfg = self.cfg + + mask = '#c' + + loops = cfg.layers + if layer + cfg.layers > self.channels: + loops = self.channels - cfg.layers + for ch in range(0,loops): + mask += str(layer+ch) + ',' + + mask = mask[:-1] + return mask + + def get_stream_name(self): + cfg = self.cfg + + if not cfg.use_internal_name: + return '' + txt = self.stream_name + + # remove paths #todo maybe config/replace? + pos = txt.rfind("\\") + if (pos != -1): + txt = txt[pos+1:] + pos = txt.rfind("/") + if (pos != -1): + txt = txt[pos+1:] + # remove bad chars + txt = txt.replace("%", "_") + txt = txt.replace("*", "_") + txt = txt.replace("?", "_") + txt = txt.replace(":", "_") + txt = txt.replace("\"", "_") + txt = txt.replace("|", "_") + txt = txt.replace("<", "_") + txt = txt.replace(">", "_") + + if not cfg.use_internal_ext: + pos = txt.rfind(".") + if (pos != -1): + txt = txt[:pos] + return txt + + def write(self, outname, line): + cfg = self.cfg + + outname += '.txtp' + if not cfg.overwrite and os.path.exists(outname): + raise ValueError('TXTP exists in path: ' + outname) + ftxtp = open(outname,"w+") + if line != '': + ftxtp.write(line) + ftxtp.close() + + self.log.debug("created: " + outname) + return + + def make(self, fname_path, fname_clean): + cfg = self.cfg + total_done = 0 + + if self.is_ignorable(): + return total_done + + # write plain (name).txtp when no subsongs + if self.stream_count <= 1: + index = "" + else: + index = str(self.stream_index) + if cfg.zero_fill < 0: + index = index.zfill(len(str(self.stream_count))) + else: + index = index.zfill(cfg.zero_fill) + + if cfg.mini_txtp: + outname = fname_path + if index != "": + outname += "#" + index + + if cfg.layers > 0 and cfg.layers < self.channels: + for layer in range(0, self.channels, cfg.layers): + mask = self.get_stream_mask(layer) + self.write(outname + mask, '') + total_done += 1 + else: + self.write(outname, '') + total_done += 1 + + else: + stream_name = self.get_stream_name() + if stream_name != '': + outname = stream_name + if cfg.use_internal_index: + outname += "_{}".format(index) + else: + if cfg.base_name != '': + fname_base = os.path.basename(fname_path) + pos = fname_base.rfind(".") #remove ext + if (pos != -1 and pos > 1): + fname_base = fname_base[:pos] + + txt = cfg.base_name + txt = txt.replace("{filename}",fname_base) + + else: + txt = fname_path + pos = txt.rfind(".") #remove ext + if (pos != -1 and pos > 1): + txt = txt[:pos] + outname = "{}".format(txt) + if index != "": + outname += "_" + index + + line = '' + if cfg.subdir != '': + line += cfg.subdir + line += fname_clean + if index != "": + line += "#" + index + + if cfg.layers > 0 and cfg.layers < self.channels: + done = 0 + for layer in range(0, self.channels, cfg.layers): + sub = chr(ord('a') + done) + done += 1 + mask = self.get_stream_mask(layer) + self.write(outname + sub, line + mask) + total_done += 1 + else: + self.write(outname, line) + total_done += 1 + return total_done + + def has_more_subsongs(self, target_subsong): + return target_subsong < self.stream_count + +# ########################################################################### # + +def main(): + appname = os.path.basename(sys.argv[0]) + if (len(sys.argv) <= 1): + print_usage(appname) + return + + cfg = ConfigHelper(sys.argv) + crc32 = Cr32Helper(cfg) + log = LogHelper(cfg) + + if cfg.show_help: + print_help(appname) + return + + fname = sys.argv[1] + fnames_in = find_files('.', fname, cfg.recursive) + + total_created = 0 + total_dupes = 0 + total_errors = 0 + for fname_in in fnames_in: + fname_in_clean = fname_in.replace("\\", "/") + if fname_in_clean.startswith("./"): + fname_in_clean = fname_in_clean[2:] + + fname_in_base = os.path.basename(fname_in) + + if fname_in.startswith(".\\"): #skip starting dot for extensionless files + fname_in = fname_in[2:] + + fname_out = ".temp." + fname_in_base + ".wav" + created = 0 + dupes = 0 + errors = 0 + target_subsong = 1 + while 1: + + try: + cmd = make_cmd(cfg, fname_in, fname_out, target_subsong) + log.trace("calling: " + cmd) + output_b = subprocess.check_output(cmd, shell=False) #stderr=subprocess.STDOUT + except subprocess.CalledProcessError as e: + log.debug("ignoring CLI error in " + fname_in + "#"+str(target_subsong)+": " + e.output) + errors += 1 + break + + if target_subsong == 1: + log.debug("processing {}...".format(fname_in_clean)) + + maker = TxtpMaker(cfg, output_b, log) + + if not maker.is_ignorable(): + crc32.update(fname_out) + + if not crc32.is_dupe(): + created += maker.make(fname_in_base, fname_in_clean) + else: + dupes += 1 + log.debug("Dupe subsong {}".format(target_subsong)) + + if not maker.has_more_subsongs(target_subsong): + break + target_subsong += 1 + + if target_subsong % 200 == 0: + log.info("{}/{} subsongs... ".format(target_subsong, maker.stream_count) + + "({} dupes, {} errors)".format(dupes, errors) + ) + + if os.path.exists(fname_out): + os.remove(fname_out) + + total_created += created + total_dupes += dupes + total_errors += errors + + + log.info("done! ({} done, {} dupes, {} errors)".format(total_created, total_dupes, total_errors)) + +if __name__ == "__main__": + main() From 4b3ec51667007edfc434d2cf42996058a4884541 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 17 Feb 2019 23:17:14 +0100 Subject: [PATCH 14/18] Add .ds2 [Star Wars Bounty Hunter (GC)] --- src/formats.c | 1 + src/meta/meta.h | 1 + src/meta/ngc_dsp_std.c | 35 +++++++++++++++++++++++++++++++++++ src/vgmstream.c | 1 + src/vgmstream.h | 1 + 5 files changed, 39 insertions(+) diff --git a/src/formats.c b/src/formats.c index 9f9b154d..72f2176d 100644 --- a/src/formats.c +++ b/src/formats.c @@ -1171,6 +1171,7 @@ static const meta_info meta_info_list[] = { {meta_GIN, "Electronic Arts Gnsu header"}, {meta_DSF, "Ocean DSF header"}, {meta_208, "Ocean .208 header"}, + {meta_DSP_DS2, "LucasArts .DS2 header"}, }; diff --git a/src/meta/meta.h b/src/meta/meta.h index 50ce55b0..bae1a35d 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -49,6 +49,7 @@ VGMSTREAM * init_vgmstream_dsp_switch_audio(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_dsp_sps_n1(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_dsp_itl_ch(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_dsp_adpcmx(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_dsp_ds2(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_csmp(STREAMFILE *streamFile); diff --git a/src/meta/ngc_dsp_std.c b/src/meta/ngc_dsp_std.c index 010287e1..aca329ee 100644 --- a/src/meta/ngc_dsp_std.c +++ b/src/meta/ngc_dsp_std.c @@ -1196,3 +1196,38 @@ VGMSTREAM * init_vgmstream_dsp_adpcmx(STREAMFILE *streamFile) { fail: return NULL; } + +/* .ds2 - LucasArts wrapper [Star Wars: Bounty Hunter (GC)] */ +VGMSTREAM * init_vgmstream_dsp_ds2(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + size_t file_size, channel_offset; + + /* checks */ + /* .ds2: real extension, dsp: fake/renamed */ + if (!check_extensions(streamFile, "ds2")) + goto fail; + if (!(read_32bitBE(0x50,streamFile) == 0 && + read_32bitBE(0x54,streamFile) == 0 && + read_32bitBE(0x58,streamFile) == 0 && + read_32bitBE(0x5c,streamFile) != 0)) + goto fail; + file_size = get_streamfile_size(streamFile); + channel_offset = read_32bitBE(0x5c,streamFile); /* absolute offset to 2nd channel */ + /* just to make sure */ + if (channel_offset < file_size / 2 || channel_offset > file_size) + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.single_header = 1; + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x00; + dspm.start_offset = 0x60; + dspm.interleave = channel_offset - dspm.start_offset; + + dspm.meta_type = meta_DSP_DS2; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} diff --git a/src/vgmstream.c b/src/vgmstream.c index 4692b62f..2823ad1e 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -465,6 +465,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_gin, init_vgmstream_dsf, init_vgmstream_208, + init_vgmstream_dsp_ds2, /* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */ init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */ diff --git a/src/vgmstream.h b/src/vgmstream.h index 438c4eec..00870f7f 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -718,6 +718,7 @@ typedef enum { meta_GIN, meta_DSF, meta_208, + meta_DSP_DS2, } meta_t; From d938f60048b037e6e3480c02d88720e0d78db6f8 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 17 Feb 2019 23:53:37 +0100 Subject: [PATCH 15/18] Clean loops for readable metadata --- src/vgmstream.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vgmstream.c b/src/vgmstream.c index 2823ad1e..26d98852 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -526,6 +526,13 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { try_dual_file_stereo(vgmstream, streamFile, init_vgmstream_functions[i]); } + /* clean as loops are readable metadata but loop fields may contain garbage + * (done *after* dual stereo as it needs loop fields to match) */ + if (!vgmstream->loop_flag) { + vgmstream->loop_start_sample = 0; + vgmstream->loop_end_sample = 0; + } + #ifdef VGM_USE_FFMPEG /* check FFmpeg streams here, for lack of a better place */ if (vgmstream->coding_type == coding_FFmpeg) { From ac4aae7fcebc8ad413d9efebf5b634f11d147cef Mon Sep 17 00:00:00 2001 From: bnnm Date: Mon, 18 Feb 2019 00:53:08 +0100 Subject: [PATCH 16/18] Tweak CLI shadowing --- cli/vgmstream_cli.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/vgmstream_cli.c b/cli/vgmstream_cli.c index 5f0caca3..899110ff 100644 --- a/cli/vgmstream_cli.c +++ b/cli/vgmstream_cli.c @@ -468,11 +468,11 @@ int main(int argc, char ** argv) { /* slap on a .wav header */ { uint8_t wav_buf[0x100]; - int channels = (cfg.only_stereo != -1) ? 2 : channels; + 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, + 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); @@ -541,11 +541,11 @@ int main(int argc, char ** argv) { /* slap on a .wav header */ { uint8_t wav_buf[0x100]; - int channels = (cfg.only_stereo != -1) ? 2 : channels; + 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, + 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); From ce07e6af9e4bccfcad68f3d1841abae93d5a073c Mon Sep 17 00:00:00 2001 From: bnnm Date: Mon, 18 Feb 2019 00:53:59 +0100 Subject: [PATCH 17/18] Fix ds2 samples --- src/meta/ngc_dsp_std.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/meta/ngc_dsp_std.c b/src/meta/ngc_dsp_std.c index aca329ee..5e50f667 100644 --- a/src/meta/ngc_dsp_std.c +++ b/src/meta/ngc_dsp_std.c @@ -223,7 +223,7 @@ static VGMSTREAM * init_vgmstream_dsp_common(STREAMFILE *streamFile, dsp_meta *d if (dspm->fix_looping && vgmstream->loop_end_sample > vgmstream->num_samples) vgmstream->loop_end_sample = vgmstream->num_samples; - if (dspm->single_header) { + if (dspm->single_header == 2) { /* double the samples */ vgmstream->num_samples /= dspm->channel_count; vgmstream->loop_start_sample /= dspm->channel_count; vgmstream->loop_end_sample /= dspm->channel_count; @@ -517,14 +517,13 @@ VGMSTREAM * init_vgmstream_ngc_mpdsp(STREAMFILE *streamFile) { if (!check_extensions(streamFile, "mpdsp")) goto fail; - /* at 0x48 is extra data that could help differenciating these DSPs, but other games - * put similar stuff there, needs more checks (ex. Battallion Wars, Army Men) */ - //0x00005300 60A94000 64FF1200 00000000 00000000 00000000 + /* at 0x48 is extra data that could help differenciating these DSPs, but seems like + * memory garbage created by the encoder that other games also have */ /* 0x02(2): sample rate, 0x08+: channel sizes/loop offsets? */ dspm.channel_count = 2; dspm.max_channels = 2; - dspm.single_header = 1; + dspm.single_header = 2; dspm.header_offset = 0x00; dspm.header_spacing = 0x00; /* same header for both channels */ @@ -1204,7 +1203,7 @@ VGMSTREAM * init_vgmstream_dsp_ds2(STREAMFILE *streamFile) { /* checks */ /* .ds2: real extension, dsp: fake/renamed */ - if (!check_extensions(streamFile, "ds2")) + if (!check_extensions(streamFile, "ds2,dsp")) goto fail; if (!(read_32bitBE(0x50,streamFile) == 0 && read_32bitBE(0x54,streamFile) == 0 && @@ -1213,8 +1212,7 @@ VGMSTREAM * init_vgmstream_dsp_ds2(STREAMFILE *streamFile) { goto fail; file_size = get_streamfile_size(streamFile); channel_offset = read_32bitBE(0x5c,streamFile); /* absolute offset to 2nd channel */ - /* just to make sure */ - if (channel_offset < file_size / 2 || channel_offset > file_size) + if (channel_offset < file_size / 2 || channel_offset > file_size) /* just to make sure */ goto fail; dspm.channel_count = 2; From 4fe81d0ba18cc4b2f5bfc535b8318a0f0fcd8ea1 Mon Sep 17 00:00:00 2001 From: bnnm Date: Mon, 18 Feb 2019 00:54:15 +0100 Subject: [PATCH 18/18] Update doc --- doc/TXTP.md | 143 ++++++++++++++++++++++++++-------------------------- 1 file changed, 71 insertions(+), 72 deletions(-) diff --git a/doc/TXTP.md b/doc/TXTP.md index b4a8afb9..960786e6 100644 --- a/doc/TXTP.md +++ b/doc/TXTP.md @@ -5,29 +5,73 @@ TXTP is a text file with commands, to improve support for games using audio in c Simply create a file named `(filename).txtp`, and inside write the commands described below. -## TXTP features +## TXTP FEATURES ### Play separate intro + loop files together as a single track -- __Ratchet & Clank (PS2)__: _bgm01.txtp_ +Some games clumsily loop audio by using multiple full file "segments": + +__Ratchet & Clank (PS2)__: _bgm01.txtp_ ``` -# define several files to play as one (there is no limit) +# define 2 or more segments to play as one BGM01_BEGIN.VAG BGM01_LOOPED.VAG -# multi-files must define loops +# segments must define loops loop_start_segment = 2 # 2nd file start loop_end_segment = 2 # optional, default is last +``` +Channel number must be equal, mixing sample rates is ok (uses first). -#channel number must be equal, mixing sample rates is ok (uses first) +If your loop segment has proper loops you want to keep, you can use: +``` +BGM_SUMMON_0001_01-Intro.hca +BGM_SUMMON_0001_01-Intro2.hca +BGM_SUMMON_0001_01.hca + +loop_start_segment = 3 +loop_mode = keep # loops in 3rd file's loop_start to 3rd file's loop_end +``` +``` +bgm_intro.adx +bgm_main.adx +bgm_main2.adx + +loop_start_segment = 2 +loop_end_segment = 3 +loop_mode = keep # loops in 2nd file's loop_start to 3rd file's loop_end ``` +### Multilayered songs +TXTP "layers" play songs with channels/parts divided into files as one (for example main melody + vocal track). + +__Nier Automata__: _BGM_0_012_song2.txtp_ +``` +# mix dynamic sections (2ch * 2) +BGM_0_012_04.wem +BGM_0_012_07.wem + +mode = layers +``` + +__Life is Strange__: _BIK_E1_6A_DialEnd.txtp_ +``` +# bik multichannel isn't autodetectable so must mix manually (1ch * 3) +BIK_E1_6A_DialEnd_00000000.audio.multi.bik#1 +BIK_E1_6A_DialEnd_00000000.audio.multi.bik#2 +BIK_E1_6A_DialEnd_00000000.audio.multi.bik#3 + +mode = layers +``` +Note that the number of channels is the sum of all layers, so three 2ch layers play as a 6ch file. + + ### Minifiles for bank formats without splitters -- __Super Robot Taisen OG Saga - Masou Kishin III - Pride of Justice (Vita)__: _bgm_12.txtp_ +__Super Robot Taisen OG Saga - Masou Kishin III - Pride of Justice (Vita)__: _bgm_12.txtp_ ``` # select subsong 12 bigfiles/bgm.sxd2#12 #relative paths are ok too for TXTP -#bigfiles/bgm.sxd2#s12 # "sN" is al alt for subsong +#bigfiles/bgm.sxd2#s12 # "sN" is alt for subsong # single files loop normally by default # if loop segment is defined it forces a full loop (0..num_samples) @@ -35,7 +79,7 @@ bigfiles/bgm.sxd2#12 #relative paths are ok too for TXTP ``` ### Play segmented subsongs as one -- __Prince of Persia Sands of Time__: _song_01.txtp_ +__Prince of Persia Sands of Time__: _song_01.txtp_ ``` # can use ranges ~ to avoid so much C&P amb_fx.sb0#254 @@ -48,13 +92,13 @@ loop_start_segment = 3 ### Channel mask for channel subsongs/layers -- __Final Fantasy XIII-2__: _music_Home_01.ps3.txtp_ +__Final Fantasy XIII-2__: _music_Home_01.ps3.txtp_ ``` #plays channels 1 and 2 = 1st subsong music_Home.ps3.scd#c1,2 ``` -- __Final Fantasy XIII-2__: _music_Home_02.ps3.txtp_ +__Final Fantasy XIII-2__: _music_Home_02.ps3.txtp_ ``` #plays channels 3 and 4 = 2nd subsong music_Home.ps3.scd#c3,4 @@ -63,48 +107,10 @@ music_Home.ps3.scd#c3,4 ``` -### Multilayered songs - -TXTP "layers" play songs with channels/parts divided into files as one. - -- __Nier Automata__: _BGM_0_012_song2.txtp_ -``` -# mix dynamic sections (2ch * 2) -BGM_0_012_04.wem -BGM_0_012_07.wem - -mode = layers -``` - -- __Life is Strange__: _BIK_E1_6A_DialEnd.txtp_ -``` -# bik multichannel isn't autodetectable so must mix manually (1ch * 3) -BIK_E1_6A_DialEnd_00000000.audio.multi.bik#1 -BIK_E1_6A_DialEnd_00000000.audio.multi.bik#2 -BIK_E1_6A_DialEnd_00000000.audio.multi.bik#3 - -mode = layers -``` -Note that the number of channels is the sum of all layers, so three 2ch layers play as a 6ch file. - - - -### Channel swapping/mapping -TXTP can swap channels for custom channel mappings. It does "swapping" rather than simpler "mapping" since vgmstream can't read a format's mappings or guess which channel is which. Format is: -``` -#ch1 = first -file1.ext#m2-3 # "FL BL FR BR" to "FL FR BL BR" - -#do note the order specified affects swapping -file2.ext#m2-3,4-5,4-6 # ogg "FL CN FR BL BR SB" to wav "FL FR CN SB BL BR" -``` -Note that channel masking applies after mappings. - - ### Custom play settings Those setting should override player's defaults if set (except "loop forever"). They are equivalent to some test.exe options. -- __God Hand (PS2)__: _boss2_3ningumi_ver6.txtp_ (each line is a separate TXTP) +__God Hand (PS2)__: _boss2_3ningumi_ver6.txtp_ (each line is a separate TXTP) ``` # set number of loops boss2_3ningumi_ver6.adx#l3 @@ -136,6 +142,19 @@ boss2_3ningumi_ver6.adx#l1.5#d1#f5 For segments and layers the first file defines looping options. +### Force sample rate +A few games set a sample rate value in the header but actually play with other (applying some of pitch or just forcing it) + +__Super Paper Mario (Wii)__ +``` +btl_koopa1_44k_lp.brstm#h22050 #in hz +``` +__Patapon (PSP)__ +``` +ptp_btl_bgm_voice.sgd#s1#h11050 +``` + + ### Force plugin extensions vgmstream supports a few common extensions that confuse plugins, like .wav/ogg/aac/opus/etc, so for them those extensions are disabled and are expected to be renamed to .lwav/logg/laac/lopus/etc. TXTP can make plugins play those disabled extensions, since it calls files directly by filename. @@ -175,7 +194,7 @@ commands = #s12 ``` -## TXTP parsing issues +## TXTP PARSING ISSUES *Commands* can be chained, but must not be separated by a space (everything after space may be ignored): ``` bgm bank.sxd2#s12#c1,2 #spaces + comment after commands is ignored @@ -193,34 +212,14 @@ loop_start_segment = 1 #spaces surrounding value are ignored ``` ``` bgm.sxd2 -config = #s12#c1,2 #must not have spaces once value starts until end +commands = #s12#c1,2 #must not have spaces once value starts until end ``` The parser is very simplistic and fairly lax, though may be erratic with edge cases or behave unexpectedly due to unforeseen use-cases and bugs. As filenames may contain spaces or #, certain name patterns could fool it too. Keep in mind this while making .txtp files. -## Mini TXTP - +## MINI-TXTP To simplify TXTP creation, if the .txtp is empty (0 bytes) its filename is used directly as a command. Note that extension is also included (since vgmstream needs a full filename). - _bgm.sxd2#12.txtp_: plays subsong 12 - _Ryoshima Coast 1 & 2.aix#c1,2.txtp_: channel mask - _boss2_3ningumi_ver6.adx#l2#F.txtp_: loop twice then play song end file normally - etc - - -## Other examples - -_Join "segments" (intro+body):_ -``` -#files must have same number of channels -Song001_intro.ogg -Song001_body.ogg -loop_start_segment = 2 -``` - -_Join "layers" (ex. main+vocals):_ -``` -#files must have same number of samples -Song001_main.ogg -Song001_vocals.ogg -mode = layers -```