mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-12 01:30:49 +01:00
Add TXTH operations (value_*/+-) and field values, ignore -1 loop flag
This commit is contained in:
parent
976e1f3efe
commit
befa17dc2e
110
doc/TXTH.md
110
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
|
||||
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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
Loading…
Reference in New Issue
Block a user