A text file with the above commands must be saved as `.vag.txth` or `.txth` (preferably the former), notice it starts with a "." (dot). On some Windows versions files starting with a dot need to be created by appending a dot at the end when renaming: `.txth.`
While the main point is playing the file, many of TXTH's features are aimed towards keeping original data intact, for documentation and preservation purposes; try leaving data as untouched as possible and consider how the game plays the file, as there is a good chance some feature can mimic it.
The `.txth` may be rejected if incorrect commands are found. Errors are shown in the console log (see *USAGE* guide), better try starting with a simple case from examples then add more complex commands until it fully works.
Extension must be accepted/added to vgmstream (plugins like foobar2000 only load extensions from an accepted list in `formats.c`), or one could rename to any supported extension (like `.vgmstream`), or leave the file extensionless. Before renaming consider reporting the unknown extension so it can be added to the list (so similar games benefit, as long as the extension is a good fit). Some plugins allow playing unknown extensions too.
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.
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.
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) or need to be done in a certain order (due to technical reasons) as explained below.
To get a file playing you need to correctly set, at least: `codec` and sometimes `interleave`, `sample_rate`, `channels` and `num_samples`, or use the "subfile" feature.
### VALUES
The following can be used in place of `(value)` for `(key) = (value)` commands.
-`(number)`: constant number in dec/hex, unsigned (no +10 or -10).
* Examples: `44100, 40, 0x40 (decimal=64)`
-`(offset)`: read a value at offset inside the file, format being `@(number)[:LE|BE][$1|2|3|4]`
*`@(number)`: offset of the value (required)
* if `base_offset` is defined this value is modified (see later)
*`: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)
* Example: `@0x10:BE$2` means `get big endian 16b value at 0x10`
-`(field)`: uses current value of some fields (`interleave`, `channels`, `start_offset`, `data_size`, `num_samples`, `subsong_count`, `subfile_size`, `base_offset`, `name_valueX`, etc)
This value changes how data is read, and while optional (defaults described in the "codec" section) you'll often need it to get proper sound.
Roughly speaking interleave is the separation between data of each channel (or the size of a data chunk). For example `interleave = 0x02` means there are 2 bytes of data from channel 1, then 2 bytes from channel 2, then bytes for other channels if any (this is common for PCM16LE, where 2 bytes = 1 sample). While `interleave = 0x1000` works the same but means there is a lot more data of one channel before next channel:
Incorrect interleave will usually sound like audio is "fragmented" or noisy (since channel data is misinterpreted), but it's usually easy enough to try a few common values until it sounds right. Interleave needs to be a multiple of some value (PCM16LE is multiple of 0x02 so can't use interleave 0x05).
Depending on the codec itself interleave has certain implications:
- mono-interleaved codecs: uses default value or value in `interleave` as described above
- mono/stereo codecs: no default, setting interleave usually forces mono-interleaved mode (if known cases exists)
- codecs with frame sizes: if `interleave` is set but `frame_size` isn't set, it'll use the former as `frame_size` (see below)
- other codecs: ignored
Mono/stereo codecs are a bit particular in that they have two modes. For 1 channel files mono mode is always used. But for stereo files, different games may have either mono-interleave data or stereo data:
- stereo file uses mono data: set interleave (forces mono mode with interleaved chunks)
- stereo file uses stereo data: don't set interleave (forces stereo mode with linear chunks)
It's technically possible that a game could use stereo mode with interleave for multichannel, but isn't handled at the moment.
#### FRAME SIZE [REQUIRED depending on codec]
Codecs with custom frame sizes (MSADPCM, MS-IMA, ATRAC3/plus) need `frame_size`. "frame size" is the amount of data the decoder needs as a single unit. Conceptually it's similar to interleave, so to simplify usage you may set `interleave` instead of `frame_size`.
It's possible though rare (seen in some MSADPCM files) that a game needs a `frame_size` then sets another `interleave` value, for example has frame_size 0x100 with mono interleave 0x400. This means it reads multiple smaller 0x100 for one channel up to 0x400, then next channel, etc.
In some files with interleaved data the last block (`interleave * channels`) of data is smaller than normal, so `interleave` is smaller for that block. Setting this fixes decoding glitches at the end.
Note that this doesn't affect files with padding data in the last block (as the `interleave` itself is constant).
Special values:
-`auto`: calculate based on channels, interleave and data_size/start_offset
Similar to the above, in rare cases the file starts with a different interleave (bigger or smaller), then uses another value.
For example, file has `start_offset` at 0x100, first `interleave_first` of 0x800 then `interleave` of 0x400.
In trickier cases, file at 0x100 has 0x10 garbage (before each channel data), then data up to 0x800, then interleave of 0x800. So interleave sizes are consistent, but first block has less data. Here we need to set `interleave_first_skip = 0x10` so block sizes can be properly calculated and garbage skipped. Notice that if file was 4ch this means total garbage of 0x40 (`(0x10 garbage + 0x7F0 data) * 4`).
Be aware that certain features like autodetecting PS-ADPCM loop points may not handle interleave_first at the moment.
Validates that `id_value` (normally set as constant value) matches value read at `id_check`. The file will be rejected and won't play if values don't match.
Where encoded data actually starts, after the header part. Defaults to 0.
```
start_offset = (value)
```
#### DATA SIZE
Special variable that can be used in sample values. Defaults to `(file_size - start_offset)`, re-calculated when `start_offset` is set. With multiple subsongs, `block_size` or padding are set this it's recalculated as well.
If data_size is manually set it stays constant and won't be auto changed.
```
data_size = (value)
```
#### DATA PADDING
Some files have extra padding at the end that is meant to be ignored. This adjusts the padding in `data_size`, manually or auto-calculated.
Special values (for PS-ADPCM only):
-`auto`: discards null frames
-`auto-empty`: discards null and 'empty' frames (for games with weird padding)
```
padding_size = (value)|auto|auto-empty
```
#### SAMPLE MEANINGS
Modifies the meaning of sample fields when set *before* them.
Accepted values:
-`samples`: exact sample (default)
-`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: `FFMPEG`. For XMA1/2, bytes does special parsing, with loop values being bit offsets within data (as XMA has a peculiar way to loop).
To activate loops you need to define both `loop_start` and `loop_end` (rather than say, only defining `start` and defaulting `end` to `num_samples`). This is to make the *.txth* more descriptive, and avoid ambiguity in cases where `start` value is 0. See loop settings below to fine tune when to loop.
Force loop on or off, as loop start/end may be defined but not used. If not set, by default it loops when loop_end_sample is defined and not bigger than num_samples.
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 = (value)
```
#### ENCODER DELAY
Beginning samples to skip, a.k.a. priming samples or encoder delay, that some codecs use to "warm up" the decoder. This is needed for proper gapless support.
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.
`coef_offset` is adjusted by `base_offset` and `subsong_spacing`. If offset points to some absolute offset that doesn't depend on subsong, set first `offset_absolute = 1`.
Some ADPCM codecs need to set up their initial or "history" state, normally one or two 16-bit PCM samples per channel, starting from `hist_offset`.
Usually each channel uses its own state, so we may need to set separation per channel.
State values can be little or big endian (usually BE for DSP), set `hist_endianness` directly or in an offset value where ´0=LE, >0=BE´.
Normally audio starts with silence or hist samples are set to zero and can be ignored, but it does affect a bit resulting output.
Currently used by DSP.
```
hist_offset = (value)
hist_spacing = (value)
hist_endianness = BE|LE|(value)
```
#### HEADER/BODY SETTINGS
Changes internal header/body representation to external files.
TXTH commands are done on a "header", and decoding on "body". When loading an unsupported file it becomes the "base" file
that loads the .txth, and is both header and body.
You can alter those, mainly for files that split header and body in separate files (load base file and txth sets header on another file). It's also possible to load the .txth directly with a set body, as a sort of "reverse TXTH" (useful with bigfiles, as you could have one .txth per song).
Allowed values:
- (filename): open any file, subdirs also work (dir/filename)
- *.(extension): opens with same name as the "base" file (the one you open, not the .txth) plus another extension
- null: unloads file and goes back to defaults (body/header = base file).
Sets the number of subsongs in the file, adjusting reads per subsong N: `value = @(offset) + subsong_spacing*N`. Number/constants values aren't adjusted though.
Instead of `subsong_spacing` you can use `subsong_offset` (older alias).
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.
A experimental field is `subsong_sum = (value)`, that sums all subsong values up to current subsong. Mainly meant when offsets are the sum of subsong sizes: if you have a table of sizes at 0x10 for 3 subsongs, each of size 0x1000, then `subsong_sum = @0x10` for first subsong sums 0x0000, 0x1000 for second, 0x2000 for third (can be used later as `start_offset = subsong_sum`).
`name_offset` is adjusted by `base_offset` and `subsong_spacing`. If offset points to some absolute offset that doesn't depend on subsong, set first `offset_absolute = 1`.
Tells TXTH to parse a full file (ex. an Ogg) at `subfile_offset`, with size of `subfile_size` (defaults to `file size - subfile_offset` if not set). This is useful for files that are just container of other files, so you don't have to remove the extra data (since it could contain useful stuff like loop info).
Internal subfile extension can be changed to `subfile_extension` if needed, as vgmstream won't accept unknown extensions (for example if your file uses .vgmstream or .pogg you may need to set subfile_extension = ogg).
Setting any of those three will trigger this mode (it's ok to set offset 0). Once triggered most fields are ignored, but not all, explained later. This will also set some values like `channels` or `sample_rate` if not set for calculations/convenience.
Some files interleave data chunks, for example 3 stereo songs pasted together, alternating 0x10000 bytes of data each. Or maybe 0x100 of useless header + 0x10000 of valid data. Chunk settings allow vgmstream to play valid chunks while ignoring the rest (read 0x10000 data, skip rest).
File is first "dechunked" before being played, so other settings work over this final file (`start_offset` would be a point within the internal dechunked" file). Use combinations of chunk settings to make vgmstream "see" only actual codec data.
-`chunk_number`: first chunk to start (ex. 1=0x00000, 2=0x10000, 3=0x20000...)
- If you set `subsong_count` and `chunk_count` first, `chunk_number` will be auto-set per subsong (subsong 1 starts from chunk number 1, subsong 2 from chunk 2, etc)
-`chunk_value`: ignores chunks that don't match this value at chunk offset 0x00 (32-bit, in `chunk_endianness`). Can be used to ignore video chunks in movie files, for example.
-`chunk_size_offset`: reads chunk size at this offset, in header (32-bit in `chunk_endianness`). For chunks of dynamic sizes (no need to set `chunk_size` as will be ignored). Includes header size.
-`chunk_data_size_offset`: same, for data sizes (not including headers). Note that you can use header+data+size configs to handle padding between blocks.
-`chunk_endianness`: sets endianness of the above two settings.
For technical reasons, "dechunking" activates when setting all main settings, so set optional config first. Note that `chunk_size` is static (read once from a fixed offset) while `chunk_size_offset` is dynamic (read on every chunk).
Some games have headers for all files pasted together separate from the actual data, but this order may be hard-coded or even alphabetically ordered by filename. In those cases you can set a "name table" that assigns constant values (one or many) to filenames. This table is loaded from an external text file (for clarity) and can be set to any name, for example `name_table = .names.txt`
Then I'll find your current file name, and you can then reference its numbers from the list as a `name_value` field, like `base_offset = name_value`, `start_offset = 0x1000 + name_value1`, `interleave = name_value5`, etc. `(filename)` can be with or without extension (like `bgm01.vag` or just `bgm01`), and if the file's name isn't found it'll use default values, and if those aren't defined you'll get 0 instead. Being "values" they can use math or offsets too (`bgm05: 5*0x010`). You can also set a codec string too (`bgm01: PCM16LE`; `codec = name_value`).
While you can put anything in the values, this feature is meant to be used to store some number that points to the actual data inside a real multi-header, that could be set with `header_file`. If you feel the need to store many constant values per file, there is good chance it can be done in some better, simpler way.
You can set a default offset that affects next `@(offset)` reads making them `@(offset + base_offset)`, for cleaner parsing.
This is particularly interesting when combined with offsets to some long value. For example instead of `channels = @0x714` you could set `base_offset = 0x710, channels = @0x04`. Or values from the `name_table`, like `base_offset = name_value, channels = @0x04`.
It also allows parsing formats that set offsets to another offset, by "chaining" `base_offset`. With `base_offset = @0x10` (pointing to `0x40`) then `base_offset = @0x20`, it reads value at `0x60`. Set to 0 when you want to disable/reset the chain: `base_offset = @0x10` then `base_offset = 0` then `base_offset = @0x20` reads value at `0x20`
TXTH can't do conditions (`if`) but sometimes you have have variations of the same format in the same dir. You can set multiple `.txth` files to try until one works. Use `id_value/id_check` to reject wrong `.txth` (otherwise the first one will be selected).
As an interesting side-effect, you can use this to force load `.txth` in other paths. For example it can be useful if you have files in subdirs and want to point to a base `.txth` in root.
Most commands are evaluated and calculated immediately, every time they are found. This is by design, as it can be used to adjust and trick for certain calculations.
For example, normally you are given a data_size in bytes, that can be used to calculate num_samples for all channels.
```
channels = 2
sample_type = bytes
num_samples = @0x10#calculated from data_size
```
But sometimes this size is for a single channel only (even though the file may be stereo). You can set temporally change the channel number to force a correct calculation.
```
channels = 1 #not the actual number of channels
sample_type = bytes
num_samples = @0x10#calculated from channel_size
channels = 2 #change once calculations are done
```
You can also use:
```
channels = 2
sample_type = bytes
num_samples = @0x10 * channels # resulting bytes is transformed to samples
```
Do note when using special values/strings like `data_size` in `num_samples` and `loop_end_samples` they must be alone to trigger.
Priority is left-to-right only, due to technical reasons it doesn't handle proper math priority. Do add brackets though, they are accounted for and if they are implemented in the future your .txth *will* break with impunity.
Most fields can't be changed after parsing since doesn't make much sense technically, as the parsed subfile should supply them. You can set them to use bytes-to-samples conversions, though.
```
# parses subfile at start with some num_samples
subfile_offset = 0x20
# force recalculation of num_samples
codec = PSX
start_offset = 0x40
num_samples = data_size
```
### Chunks
Chunks affect some values (padding size, data size, etc) and are a bit sensitive to order at the moment, due to technical complexities:
```
# Street Fighter EX3 (PS2)
# base config is defined normally
codec = PSX
sample_rate = 44100
channels = 2
interleave = 0x8000
# set subsong number instead of chunk_number for subsongs
subsong_count = 26
#chunk_number = 1
chunk_start = 0
chunk_size = 0x10000
chunk_count = 26
# after setting chunks (sizes vary when 'dechunking')