diff --git a/doc/TXTH.md b/doc/TXTH.md index 84ba6854..3190f031 100644 --- a/doc/TXTH.md +++ b/doc/TXTH.md @@ -31,22 +31,27 @@ A text file with the above commands must be saved as ".vag.txth" or ".txth", not ``` ###################################################### -# The file is made lines of "key = value" describing a header. -# Comments start with #, can be inlined. +# The file is made of lines like "key = value" commands describing a header. +# Comments start with #, can be inlined. keys and commands are all case sensitive. # Spaces are optional: key=value, key = value, and so on are all ok. # The parser is fairly simple and may be buggy or unexpected in some cases. # The order of keys is variable but some things won't work if others aren't defined # (ex. bytes-to-samples may not work without channels or interleave). # Common values: -# - (number): constant number in dec/hex. +# - (number): constant number in dec/hex, unsigned (no +10 or -10). # Examples: 44100, 40, 0x40 (decimal=64) # - (offset): format is @(number)[:LE|BE][$1|2|3|4] # * @(number): value at offset (required) # * :LE|BE: value is little/big endian (optional, defaults to LE) # * $1|2|3|4: value has size of 8/16/24/32 bit (optional, defaults to 4) # Examples: @0x10:BE$2 (get big endian 16b value at 0x10) -# - {string}: special values for certain keys, described below +# - (field): uses current value of a field. Accepted strings: +# - interleave, channels, sample_rate +# - start_offset, data_size +# - num_samples, loop_start_sample, loop_end_sample +# - subsong_count, subsong_offset +# - {string}: other special values for certain keys, described below # Codec used to encode the data [REQUIRED] # Accepted codec strings: @@ -84,11 +89,14 @@ codec = (codec string) # - others: ignored codec_mode = (number) -# Multiplies any next read values [OPTIONAL] -# Will change to "(key) = (number)|(offset) * value_multiplier", -# useful if a size is given in 0x800 sectors or such. -# Should be set to 0 when done using, as it affects ANY value. -value_multiplier = (number)|(offset) +# Modifies next values [OPTIONAL] +# Values will be "(key) = (number)|(offset)|(field) */+- value_(op)" +# Useful when a size or such needs adjustments (like given in 0x800 sectors). +# Set to 0 when done using, as it affects ANY value. Priority is as listed. +value_mul|value_* = (number)|(offset)|(field) +value_div|value_/ = (number)|(offset)|(field) +value_add|value_+ = (number)|(offset)|(field) +value_sub|value_- = (number)|(offset)|(field) # Interleave or block size [REQUIRED/OPTIONAL, depends on codec] # - half_size: sets interleave as data_size / channels @@ -96,30 +104,30 @@ value_multiplier = (number)|(offset) # and for codecs with variable-sized frames (MSADPCM, MS-IMA, ATRAC3/plus) # means block size (size of a single frame). # Interleave 0 means "stereo mode" for some codecs (IMA, AICA, etc). -interleave = (number)|(offset)|half_size +interleave = (number)|(offset)|(field)|half_size # Validate that id_value matches value at id_offset [OPTIONAL] # Can be redefined several times, it's checked whenever a new id_offset is found. -id_value = (number)|(offset) -id_offset = (number)|(offset) +id_value = (number)|(offset)|(field) +id_offset = (number)|(offset)|(field) # Number of channels [REQUIRED] -channels = (number)|(offset) +channels = (number)|(offset)|(field) # Music frequency in hz [REQUIRED] -sample_rate = (number)|(offset) +sample_rate = (number)|(offset)|(field) # Data start [OPTIONAL, default to 0] -start_offset = (number)|(offset) +start_offset = (number)|(offset)|(field) # Variable that can be used in sample values [OPTIONAL] # Defaults to (file_size - start_offset), re-calculated when start_offset # is set (won't recalculate if data_size is set then start_offset changes). -data_size = (number)|(offset) +data_size = (number)|(offset)|(field) # Modifies the meaning of sample fields when set *before* them [OPTIONAL, defaults to samples] # - samples: exact sample -# - bytes: automatically converts bytes/offset to samples +# - bytes: automatically converts bytes/offset to samples (applies after */+- modifiers) # - blocks: same as bytes, but value is given in blocks/frames # Value is internally converted from blocks to bytes first: bytes = (value * interleave*channels) # Some codecs can't convert bytes-to-samples at the moment: MPEG/FFMPEG @@ -127,34 +135,36 @@ data_size = (number)|(offset) sample_type = samples|bytes|blocks # Various sample values [REQUIRED (num_samples) / OPTIONAL (rest)] -num_samples = (number)|(offset)|data_size -loop_start_sample = (number)|(offset) -loop_end_sample = (number)|(offset)|data_size +# - data_size: automatically converts bytes-to-samples +num_samples = (number)|(offset)|(field)|data_size +loop_start_sample = (number)|(offset)|(field) +loop_end_sample = (number)|(offset)|(field)|data_size # Force loop, on (>0) or off (0), as loop start/end may be defined but not used [OPTIONAL] # - auto: tries to autodetect loop points for PS-ADPCM data, which may include loop flags. -# By default it loops when loop_end_sample is defined -loop_flag = (number)|(offset)|auto +# Ignores values 0xFFFF/0xFFFFFFFF (-1) as they are often used to disable loops. +# By default it loops when loop_end_sample is defined and less than num_samples. +loop_flag = (number)|(offset)|(field)|auto # Loop start/end modifier [OPTIONAL] # For XMA1/2 + sample_type=bytes it means loop subregion, if read after loop values. # For other codecs its added to loop start/end, if read before loop values # (a format may rarely have rough loop offset/bytes, then a loop adjust in samples). -loop_adjust = (number)|(offset) +loop_adjust = (number)|(offset)|(field) # Beginning samples to skip (encoder delay) [OPTIONAL] # Only some codecs use them (ATRAC3/ATRAC3PLUS/XMA/FFMPEG/AC3) -skip_samples = (number)|(offset) +skip_samples = (number)|(offset)|(field) # DSP decoding coefficients [REQUIRED for NGC_DSP] # These coefs are a list of 8*2 16-bit values per channel, starting from offset. -coef_offset = (number)|(offset) +coef_offset = (number)|(offset)|(field) # Offset separation per channel, usually 0x20 (16 values * 2 bytes) # Channel N coefs are read at coef_offset + coef_spacing * N -coef_spacing = (number)|(offset) +coef_spacing = (number)|(offset)|(field) # Format, usually BE; with (offset): 0=LE, >0=BE -coef_endianness = BE|LE|(offset) +coef_endianness = BE|LE|(offset)|(field) # Split/normal coefs [NOT IMPLEMENTED YET] #coef_mode = (number)|(offset) @@ -181,8 +191,8 @@ body_file = (filename)|*.(extension)|null # "value = @(offset) + subsong_offset*N". Mainly for bigfiles with consecutive # headers per subsong, set subsong_offset to 0 when done as it affects any reads. # The current subsong number is handled externally by plugins or TXTP. -subsong_count = (number)|(offset) -subsong_offset = (number)|(offset) +subsong_count = (number)|(offset)|(field) +subsong_offset = (number)|(offset)|(field) ``` ## Usages @@ -204,6 +214,7 @@ sample_type = bytes num_samples = @0x10 #calculated from channel_size channels = 2 #change once calculations are done ``` +This can be done with value modifiers too (see below). ### Redefining values Some commands alter the function of all next commands and can be redefined as needed: @@ -244,11 +255,44 @@ sample_rate = 0x04 # sample rate is the same for all subsongs # Nth subsong ch: 0x04+0x00*N: 0x08 ``` -### Multipliers +### Modifiers Sometimes header values are in "sectors" or similar concepts (typical in DVD games), and need to be adjusted to a real value. ``` -value_multiplier = 0x800 # offsets are in DVD sector size -start_offset = @0x10 # 2*0x800, for example -value_multiplier = 0 # next values don't need to be multiplied -start_offset = @0x14 +value_multiply = 0x800 # offsets are in DVD sector size +start_offset = @0x10 # 0x15*0x800, for example +value_multiply = 0 # next values don't need to be multiplied +start_offset = @0x14 ``` + +You can also use certain fields' values: +``` +value_add = 1 +channels = @0x08 # may be 1 + 1 = 2 +value_add = 0 + +value_multiply = channels # now set to 2 +sample_type = bytes +num_samples = @0x10 # channel_size * channels +value_multiply = 0 +``` +num_samples and loop_end_sample will always convert "data_size" field as bytes-to-samples though. + +Priority is fixed to */+-: +``` +value_add = 0x10 +value_mul = 0x800 +start_offset = @0x10 # (0x15*0x800) + 0x10 = 0xA810 +``` + +But with some creativity you can do fairly involved stuff: +``` +value_add = 0x10 +start_offset = @0x10 # (0x15+0x10) = 0x25 +value_add = 0 + +value_mul = 0x800 +start_offset = start_offset # (0x25*0x800) = 0x12800 +value_mul = 0 +``` + +If a TXTH needs too many complex calculations it may be better to implement directly in vgmstream though. diff --git a/src/meta/txth.c b/src/meta/txth.c index 3cdd7170..53a4b5a6 100644 --- a/src/meta/txth.c +++ b/src/meta/txth.c @@ -35,12 +35,16 @@ typedef enum { typedef struct { txth_type codec; uint32_t codec_mode; - uint32_t value_multiplier; - uint32_t interleave; + + uint32_t value_mul; + uint32_t value_div; + uint32_t value_add; + uint32_t value_sub; uint32_t id_value; uint32_t id_offset; + uint32_t interleave; uint32_t channels; uint32_t sample_rate; @@ -170,10 +174,10 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { goto fail; } + /* try to autodetect PS-ADPCM loop data */ if (txth.loop_flag_auto && coding == coding_PSX) { - size_t data_size = get_streamfile_size(txth.streamBody) - txth.start_offset; - txth.loop_flag = ps_find_loop_offsets(txth.streamBody, txth.start_offset, data_size, txth.channels, txth.interleave, + txth.loop_flag = ps_find_loop_offsets(txth.streamBody, txth.start_offset, txth.data_size, txth.channels, txth.interleave, (int32_t*)&txth.loop_start_sample, (int32_t*)&txth.loop_end_sample); } @@ -186,10 +190,8 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { vgmstream->num_samples = txth.num_samples; vgmstream->loop_start_sample = txth.loop_start_sample; vgmstream->loop_end_sample = txth.loop_end_sample; - if (txth.subsong_count) { - vgmstream->num_streams = txth.subsong_count; - vgmstream->stream_size = txth.data_size; - } + vgmstream->num_streams = txth.subsong_count; + vgmstream->stream_size = txth.data_size; /* codec specific (taken from GENH with minimal changes) */ switch (coding) { @@ -524,6 +526,9 @@ static int parse_txth(txth_header * txth) { if (!txth->streamBody) goto fail; + if (txth->data_size > get_streamfile_size(txth->streamBody) - txth->start_offset || txth->data_size == 0) + txth->data_size = get_streamfile_size(txth->streamBody) - txth->start_offset; + return 1; fail: return 0; @@ -564,8 +569,25 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char else if (0==strcmp(key,"codec_mode")) { if (!parse_num(txth->streamHead,txth,val, &txth->codec_mode)) goto fail; } - else if (0==strcmp(key,"value_multiplier")) { - if (!parse_num(txth->streamHead,txth,val, &txth->value_multiplier)) goto fail; + else if (0==strcmp(key,"value_mul") || 0==strcmp(key,"value_*")) { + if (!parse_num(txth->streamHead,txth,val, &txth->value_mul)) goto fail; + } + else if (0==strcmp(key,"value_div") || 0==strcmp(key,"value_/")) { + if (!parse_num(txth->streamHead,txth,val, &txth->value_div)) goto fail; + } + else if (0==strcmp(key,"value_add") || 0==strcmp(key,"value_+")) { + if (!parse_num(txth->streamHead,txth,val, &txth->value_add)) goto fail; + } + else if (0==strcmp(key,"value_sub") || 0==strcmp(key,"value_-")) { + if (!parse_num(txth->streamHead,txth,val, &txth->value_sub)) goto fail; + } + else if (0==strcmp(key,"id_value")) { + if (!parse_num(txth->streamHead,txth,val, &txth->id_value)) goto fail; + } + else if (0==strcmp(key,"id_offset")) { + if (!parse_num(txth->streamHead,txth,val, &txth->id_offset)) goto fail; + if (txth->id_value != txth->id_offset) /* evaluate current ID */ + goto fail; } else if (0==strcmp(key,"interleave")) { if (0==strcmp(val,"half_size")) { @@ -576,14 +598,6 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char if (!parse_num(txth->streamHead,txth,val, &txth->interleave)) goto fail; } } - else if (0==strcmp(key,"id_value")) { - if (!parse_num(txth->streamHead,txth,val, &txth->id_value)) goto fail; - } - else if (0==strcmp(key,"id_offset")) { - if (!parse_num(txth->streamHead,txth,val, &txth->id_offset)) goto fail; - if (txth->id_value != txth->id_offset) /* evaluate current ID */ - goto fail; - } else if (0==strcmp(key,"channels")) { if (!parse_num(txth->streamHead,txth,val, &txth->channels)) goto fail; } @@ -665,6 +679,9 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char else { if (!parse_num(txth->streamHead,txth,val, &txth->loop_flag)) goto fail; txth->loop_flag_set = 1; + if (txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) { /* normally -1 = no loop */ + txth->loop_flag = 0; + } } } else if (0==strcmp(key,"coef_offset")) { @@ -756,8 +773,11 @@ fail: } static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value) { - /* out_value can be these values, save before modifying */ - uint32_t value_multiplier = txth->value_multiplier; + /* out_value can be these, save before modifying */ + uint32_t value_mul = txth->value_mul; + uint32_t value_div = txth->value_div; + uint32_t value_add = txth->value_add; + uint32_t value_sub = txth->value_sub; uint32_t subsong_offset = txth->subsong_offset; if (val[0] == '@') { /* offset */ @@ -801,15 +821,34 @@ static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * v default: goto fail; } } - else { /* constant */ + else if (val[0] >= '0' && val[0] <= '9') { /* unsigned constant */ int hex = (val[0]=='0' && val[1]=='x'); if (sscanf(val, hex ? "%x" : "%u", out_value)!=1) goto fail; } + else { /* known field */ + if (0==strcmp(val,"interleave")) *out_value = txth->interleave; + else if (0==strcmp(val,"channels")) *out_value = txth->channels; + else if (0==strcmp(val,"sample_rate")) *out_value = txth->sample_rate; + else if (0==strcmp(val,"start_offset")) *out_value = txth->start_offset; + else if (0==strcmp(val,"data_size")) *out_value = txth->data_size; + else if (0==strcmp(val,"num_samples")) *out_value = txth->num_samples; + else if (0==strcmp(val,"loop_start_sample")) *out_value = txth->loop_start_sample; + else if (0==strcmp(val,"loop_end_sample")) *out_value = txth->loop_end_sample; + else if (0==strcmp(val,"subsong_count")) *out_value = txth->subsong_count; + else if (0==strcmp(val,"subsong_offset")) *out_value = txth->subsong_offset; + else goto fail; + } - if (value_multiplier) - *out_value = (*out_value) * value_multiplier; + if (value_mul) + *out_value = (*out_value) * value_mul; + if (value_div) + *out_value = (*out_value) / value_div; + if (value_add) + *out_value = (*out_value) + value_add; + if (value_sub) + *out_value = (*out_value) - value_sub; //;VGM_LOG("TXTH: val=%s, read %u (0x%x)\n", val, *out_value, *out_value); return 1;