Add TXTH "BE_split" coefs and "debug" flag

This commit is contained in:
bnnm 2022-08-21 11:07:26 +02:00
parent db5e09e126
commit e6df86923c
3 changed files with 62 additions and 30 deletions

View File

@ -34,6 +34,7 @@ Extension must be accepted/added to vgmstream (plugins like foobar2000 only load
Note that TXTH has *lower* priority than most (not all) vgmstream formats, by design. This means your `.txth` may be ignored if vgmstream thinks it can play your file better. If vgmstream plays your file somewhat off, rather renaming to force a `.txth`, report the bug so that and similar cases can be fixed. TXTH isn't meant to be a replacement of vgmstream's parsers, but a way to play cases that aren't a good fit be added directly to vgmstream.
If you put `debug = 1` on top of the TXTP, vgmstream will ouput to the plugin/CLI's console some info about values being read, useful while testing more complex cases.
## Available commands
The file is made of lines with `key = value` commands describing a header. Commands are all case sensitive and spaces are optional: `key=value`, `key = value`, and so on are all ok, while `Key = VaLuE` is not. Comments start with `#` and can be inlined.
@ -344,9 +345,10 @@ skip_samples = (value)
#### DSP DECODING COEFFICIENTS [REQUIRED for DSP]
DSP needs a "coefs" list to decode correctly. These are 8*2 16-bit values per channel, starting from `coef_offset`.
Usually each channel uses its own list, so we may need to set separation per channel, usually 0x20 (16 values * 2 bytes). So channel N coefs are read at `coef_offset + coef_spacing * N`
Those 16-bit coefs can be little or big endian (usually BE), set `coef_endianness` directly or in an offset value where `0=LE, >0=BE`.
They typically look like positive-negative values one after other (0x0nnn 0xFnnn 0x...). Usually each channel uses its own list, so we may need to set `coef_spacing` (separation per channel), often 0x20 (16 values * 2 bytes). Channel N coefs are read at offset `coef_offset + coef_spacing * ch`.
Those 16-bit coefs can be little or big endian (BE in GC/Wii, LE in 3DS/Switch). Set `coef_endianness` directly or in an offset value where `0=LE, >0=BE`. This also allows adding a `_split` suffix, that means coefs are divided into 8 positive then 8 negatives (instead of the usual 1 positive, 1 negative up to 16), as found in a few Capcom games.
While the coef table is almost always included per-file, some games have their coef table in the executable or precalculated somehow. You can set inline coefs instead of coef_offset. Format is a long string of bytes (optionally space-separated) like `coef_table = 0x1E02DE01 3C0C0EFA ...`. You still need to set `coef_spacing` and `coef_endianness` though.
@ -354,7 +356,7 @@ While the coef table is almost always included per-file, some games have their c
```
coef_offset = (value)
coef_spacing = (value)
coef_endianness = BE|LE|(value)
coef_endianness = BE|LE|BE_split|LE_split\(value)
coef_table = (string)
```

View File

@ -3,6 +3,7 @@
#include "../layout/layout.h"
#include "txth_streamfile.h"
#include "../util/text_reader.h"
#include "../util/endianness.h"
#define TXT_LINE_MAX 2048 /* probably ~1000 would be ok */
#define TXT_LINE_KEY_MAX 128
@ -154,6 +155,8 @@ typedef struct {
int sf_head_opened;
int sf_body_opened;
int debug;
} txth_header;
static VGMSTREAM* init_subfile(txth_header* txth);
@ -478,40 +481,37 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
/* get coefs */
{
int16_t (*read_16bit)(off_t, STREAMFILE*) = txth.coef_big_endian ? read_16bitBE : read_16bitLE;
int16_t (*get_16bit)(const uint8_t* p) = txth.coef_big_endian ? get_16bitBE : get_16bitLE;
read_s16_t read_s16 = txth.coef_big_endian ? read_s16be : read_s16le;
get_s16_t get_s16 =txth.coef_big_endian ? get_s16be : get_s16le;
for (i = 0; i < vgmstream->channels; i++) {
if (txth.coef_mode == 0) { /* normal coefs */
for (j = 0; j < 16; j++) {
int16_t coef;
if (txth.coef_table_set)
coef = get_16bit(txth.coef_table + i*txth.coef_spacing + j*2);
coef = get_s16(txth.coef_table + i*txth.coef_spacing + j*2);
else
coef = read_16bit(txth.coef_offset + i*txth.coef_spacing + j*2, txth.sf_head);
coef = read_s16(txth.coef_offset + i*txth.coef_spacing + j*2, txth.sf_head);
vgmstream->ch[i].adpcm_coef[j] = coef;
}
}
else { /* split coefs */
goto fail; //IDK what is this
/*
else { /* split coefs (first all 8 positive, then all 8 negative [P.N.03 (GC), Viewtiful Joe (GC)] */
for (j = 0; j < 8; j++) {
vgmstream->ch[i].adpcm_coef[j*2] = read_16bit(genh.coef_offset + i*genh.coef_spacing + j*2, txth.sf_head);
vgmstream->ch[i].adpcm_coef[j*2+1] = read_16bit(genh.coef_split_offset + i*genh.coef_split_spacing + j*2, txth.sf_head);
vgmstream->ch[i].adpcm_coef[j*2+0] = read_s16(txth.coef_offset + i*txth.coef_spacing + j*2 + 0x00, txth.sf_head);
vgmstream->ch[i].adpcm_coef[j*2+1] = read_s16(txth.coef_offset + i*txth.coef_spacing + j*2 + 0x10, txth.sf_head);
}
*/
}
}
}
/* get hist */
if (txth.hist_set) {
int16_t (*read_16bit)(off_t , STREAMFILE*) = txth.hist_big_endian ? read_16bitBE : read_16bitLE;
read_s16_t read_s16 = txth.coef_big_endian ? read_s16be : read_s16le;
for (i = 0; i < vgmstream->channels; i++) {
off_t offset = txth.hist_offset + i*txth.hist_spacing;
vgmstream->ch[i].adpcm_history1_16 = read_16bit(offset + 0x00, txth.sf_head);
vgmstream->ch[i].adpcm_history2_16 = read_16bit(offset + 0x02, txth.sf_head);
vgmstream->ch[i].adpcm_history1_16 = read_s16(offset + 0x00, txth.sf_head);
vgmstream->ch[i].adpcm_history2_16 = read_s16(offset + 0x02, txth.sf_head);
}
}
@ -993,21 +993,37 @@ static txth_codec_t parse_codec(txth_header* txth, const char* val) {
return UNKNOWN;
}
static int parse_be(txth_header* txth, const char* val, uint32_t* p_value) {
if (is_string(val, "BE"))
static int parse_endianness(txth_header* txth, const char* val, uint32_t* p_value, uint32_t* mode) {
if (is_string(val, "BE")) {
*p_value = 1;
else if (is_string(val, "LE"))
if (mode) *mode = 0;
}
else if (is_string(val, "LE")) {
*p_value = 0;
else
if (mode) *mode = 0;
}
else if (is_string(val, "BE_split")) {
*p_value = 1;
if (mode) *mode = 1;
}
else if (is_string(val, "LE_split")) {
*p_value = 0;
if (mode) *mode = 1;
}
else {
if (!parse_num(txth->sf_head,txth,val, p_value))
goto fail;
}
return 1;
fail:
return 0;
}
static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, char* val) {
//;VGM_LOG("TXTH: key=%s, val=%s\n", key, val);
if (txth->debug)
vgm_logi("TXTH: reading key=%s, val=%s\n", key, val);
/* CODEC */
if (is_string(key,"codec")) {
@ -1245,10 +1261,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
if (!parse_num(txth->sf_head,txth,val, &txth->coef_spacing)) goto fail;
}
else if (is_string(key,"coef_endianness")) {
if (!parse_be(txth, val, &txth->coef_big_endian)) goto fail;
}
else if (is_string(key,"coef_mode")) {
if (!parse_num(txth->sf_head,txth,val, &txth->coef_mode)) goto fail;
if (!parse_endianness(txth, val, &txth->coef_big_endian, &txth->coef_mode)) goto fail;
}
else if (is_string(key,"coef_table")) {
if (!parse_coef_table(txth->sf_head,txth,val, txth->coef_table, sizeof(txth->coef_table))) goto fail;
@ -1268,7 +1281,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
if (!parse_num(txth->sf_head,txth,val, &txth->hist_spacing)) goto fail;
}
else if (is_string(key,"hist_endianness")) {
if (!parse_be(txth, val, &txth->hist_big_endian)) goto fail;
if (!parse_endianness(txth, val, &txth->hist_big_endian, NULL)) goto fail;
}
/* SUBSONGS */
@ -1424,7 +1437,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
if (!parse_num(txth->sf_head,txth,val, &txth->chunk_size_offset)) goto fail;
}
else if (is_string(key,"chunk_endianness")) {
if (!parse_be(txth, val, &txth->chunk_be)) goto fail;
if (!parse_endianness(txth, val, &txth->chunk_be, NULL)) goto fail;
}
@ -1443,6 +1456,10 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
if (!parse_multi_txth(txth,val)) goto fail;
}
/* DEBUG */
else if (is_string(key,"debug")) {
txth->debug = 1;
}
/* DEFAULT */
else {
@ -1925,7 +1942,9 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char* val, uint32_
if (subsong_spacing)
offset = offset + subsong_spacing * (txth->target_subsong - 1);
//;VGM_LOG("TXTH: read at offset %x + %x\n", offset - txth->base_offset, txth->base_offset);
if (txth->debug)
vgm_logi("TXTH: use value at 0x%x (%s %ib)\n", offset, big_endian ? "BE" : "LE", size * 8);
switch(size) {
case 1: value = read_u8(offset,sf); break;
case 2: value = big_endian ? read_u16be(offset,sf) : read_u16le(offset,sf); break;
@ -1941,6 +1960,9 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char* val, uint32_
if (sscanf(val, hex ? "%x%n" : "%u%n", &value, &n) != 1)
goto fail;
value_read = 1;
if (txth->debug)
vgm_logi(hex ? "TXTH: use constant 0x%x\n" : "TXTH: use constant %i\n", value);
}
else { /* known field */
if ((n = is_string_field(val,"interleave"))) value = txth->interleave;
@ -1995,6 +2017,9 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char* val, uint32_
else if ((n = is_string_field(val,"name_value16"))) value = txth->name_values[15];
else goto fail;
value_read = 1;
if (txth->debug)
vgm_logi("TXTH: use field value 0x%x\n", value);
}
/* apply simple left-to-right math though, for now "(" ")" are counted and validated
@ -2036,10 +2061,13 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char* val, uint32_
*out_value = result;
//;VGM_LOG("TXTH: final result %u (0x%x)\n", result, result);
if (txth->debug)
vgm_logi("TXTH: final value: %u (0x%x)\n", result, result);
return 1;
fail:
//VGM_LOG("TXTH: error parsing num '%s'\n", val);
if (txth->debug)
vgm_logi("TXTH: error parsing num '%s'\n", val);
return 0;
}

View File

@ -9,6 +9,8 @@ typedef uint16_t (*read_u16_t)(off_t, STREAMFILE*);
typedef int16_t (*read_s16_t)(off_t, STREAMFILE*);
typedef float (*read_f32_t)(off_t, STREAMFILE*);
typedef int16_t (*get_s16_t)(const uint8_t*);
//todo move here
#define guess_endian32 guess_endianness32bit
#define guess_endian16 guess_endianness16bit