mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-30 17:24:31 +01:00
Divide BUILD.md into DEV.md and add GENH/TXTH/TXTP.md doc
This commit is contained in:
parent
7a7bb3b12a
commit
488daca056
128
doc/BUILD.md
128
doc/BUILD.md
@ -1,4 +1,4 @@
|
|||||||
# vgmstream
|
# vgmstream build help
|
||||||
|
|
||||||
## Compilation requirements
|
## Compilation requirements
|
||||||
|
|
||||||
@ -130,129 +130,3 @@ git clean -fd
|
|||||||
Should be buildable with Autotools, much like the Audacious plugin, though requires libao (libao-dev).
|
Should be buildable with Autotools, much like the Audacious plugin, though requires libao (libao-dev).
|
||||||
|
|
||||||
Windows builds are possible with libao.dll and includes, but some features are disabled.
|
Windows builds are possible with libao.dll and includes, but some features are disabled.
|
||||||
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
### Code
|
|
||||||
vgmstream uses C (C89 when possible), and C++ for the foobar2000 and Audacious plugins.
|
|
||||||
|
|
||||||
C should be restricted to features VS2010 understands. This mainly means declaring variables at the start of a { .. } block (declare+initialize is fine, as long as it doesn't reference variables declared in the same block) and avoiding C99 features like variable-length arrays (but certain others like // comments are fine).
|
|
||||||
|
|
||||||
There are no hard coding rules but for consistency should follow general C conventions and the style used in most files: 4 spaces instead of tabs, underscore_and_lowercase_names, brackets starting in the same line (`if (..) { CRLF ... }`), etc. Some of the code may be a bit inefficient or duplicated at places, but it isn't much of a problem if gives clarity. vgmstream's performance is fast enough (as it mainly deals with playing songs in real time) so that favors clarity over optimization. Similarly some code may segfault or even cause infinite loops on bad data, but it's fixed as encountered rather than worrying to much about improbable cases.
|
|
||||||
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
./ docs, scripts
|
|
||||||
./audacious/ Audacious plugin
|
|
||||||
./cli/ CLI tools
|
|
||||||
./ext_includes/ external includes for compiling
|
|
||||||
./ext_libs/ external libs/DLLs for linking
|
|
||||||
./fb2k/ foobar2000 plugin
|
|
||||||
./src/ main vgmstream code and helpers
|
|
||||||
./src/coding/ format data decoders
|
|
||||||
./src/layout/ format data demuxers
|
|
||||||
./src/meta/ format header parsers
|
|
||||||
./winamp/ Winamp plugin
|
|
||||||
./xmplay/ XMPlay plugin
|
|
||||||
```
|
|
||||||
|
|
||||||
### Overview
|
|
||||||
vgmstream works by parsing a music stream header (*meta/*), preparing/controlling data and sample buffers (*layout/*) and decoding the compressed data into listenable PCM samples (*coding/*).
|
|
||||||
|
|
||||||
Very simplified it goes like this:
|
|
||||||
- player (test.exe, plugin, etc) opens a file stream (STREAMFILE) *[plugin's main/decode]*
|
|
||||||
- init tries all parsers (metas) until one works *[init_vgmstream]*
|
|
||||||
- parser reads header (channels, sample rate, loop points) and set ups the VGMSTREAM struct, if the format is correct *[init_vgmstream_(format-name)]*
|
|
||||||
- player finds total_samples to play, based on the number of loops and other settings *[get_vgmstream_play_samples]*
|
|
||||||
- player asks to fill a small sample buffer *[render_vgmstream]*
|
|
||||||
- layout prepares samples and offsets to read from the stream *[render_vgmstream_(layout)]*
|
|
||||||
- decoder reads and decodes bytes into PCM samples *[decode_vgmstream_(coding)]*
|
|
||||||
- player plays those samples, asks to fill sample buffer again, repeats (until total_samples)
|
|
||||||
- layout moves offsets back to loop_start when loop_end is reached *[vgmstream_do_loop]*
|
|
||||||
- player closes the VGMSTREAM once the stream is finished
|
|
||||||
|
|
||||||
### Components
|
|
||||||
|
|
||||||
#### STREAMFILEs
|
|
||||||
Structs with I/O callbacks that vgmstream uses in place of stdio/FILEs. All I/O must be done through STREAMFILEs as it lets plugins set up their own. This includes reading data or opening other STREAMFILEs (ex. when a header has companion files that need to be parsed, or during setup).
|
|
||||||
|
|
||||||
Players should open a base STREAMFILE and pass it to init_vgmstream. Once it's done this STREAMFILE must be closed, as internally vgmstream opens its own copy (using the base one's callbacks).
|
|
||||||
|
|
||||||
For optimization purposes vgmstream may open a copy of the FILE per channel, as each has its own I/O buffer, and channel data can be too separate to fit a single buffer.
|
|
||||||
|
|
||||||
Custom STREAMFILEs wrapping base STREAMFILEs may be used for complex I/O cases (ex. if data needs decryption, parts needs to be skipped on the fly, or a file is composed of multiple sub-files), as some codecs are not easy to feed chunked data.
|
|
||||||
|
|
||||||
#### VGMSTREAM
|
|
||||||
The VGMSTREAM (caps) is the main struct created during init when a file is successfully recognized and parsed. It holds the file's configuration (channels, sample rate, decoder, layout, samples, loop points, etc) and decoder state (STREAMFILEs, offsets per channel, current sample, etc), and is used to interact with the API.
|
|
||||||
|
|
||||||
#### metas
|
|
||||||
Metadata (header) parsers that identify and handle formats.
|
|
||||||
|
|
||||||
To add a new one:
|
|
||||||
- *src/meta/(format-name).c*: create new init_vgmstream_(format-name) parser that tests the extension and header id, reads all needed info from the stream header and sets up the VGMSTREAM
|
|
||||||
- *src/meta/meta.h*: define parser's init
|
|
||||||
- *src/vgmstream.h*: define meta type in the meta_t list
|
|
||||||
- *src/vgmstream.c*: add parser init to the init list
|
|
||||||
- *src/formats.c*: add new extension to the format list, add meta type description
|
|
||||||
- *src/libvgmstream.vcproj/vcxproj/filters*: add to compile new (format-name).c parser in VS
|
|
||||||
- if the format needs an external library don't forget to mark optional parts with: *#ifdef VGM_USE_X ... #endif*
|
|
||||||
|
|
||||||
Ultimately the meta must alloc the VGMSTREAM, set config and initial state. vgmstream needs the total of number samples to work, so at times must convert from data sizes to samples (doing calculations or using helpers).
|
|
||||||
|
|
||||||
It also needs to open and assign to the VGMSTREAM one or several STREAMFILEs (usually reopening the base one, but could be any other file) to do I/O during decode, as well as setting the starting offsets of each channel and other values; this gives metas full flexibility at the cost of some repetition. The STREAMFILE passed to the meta will be discarded and its pointer must not be reused.
|
|
||||||
|
|
||||||
The .c file is usually named after the format's main extension or header id, optionally with affixes. Each file should parse one format and/or its variations (regardless of accepted extensions or decoders used) for consistency, but deviations may be found in the codebase. Sometimes a format is already parsed but not accepted due to bugs though.
|
|
||||||
|
|
||||||
Different formats may use the same extension but this isn't a problem as long as the header id or some other validation tells them apart, and should be implemented in separate .c files. If the format is headerless and the extension isn't unique enough it probably needs a generic GENH/TXTH header instead of direct support.
|
|
||||||
|
|
||||||
If the format supports subsongs it should read the stream index (subsong number) in the passed STREAMFILE, and use it to parse a section of the file. Then it must report the number of subsongs in the VGMSTREAM, to signal this feature is enabled. The index is 1-based (first subsong is 1, 0 is default/first). This makes possible to directly use bank-like formats like .FSB, and while vgmstream could technically support any container (like generic bigfiles or even .zip) it should be restricted to files that actually are audio banks.
|
|
||||||
|
|
||||||
#### layouts
|
|
||||||
Layouts control most of the main logic:
|
|
||||||
- receive external buffer to fill with PCM samples
|
|
||||||
- detect when looping must be done
|
|
||||||
- find max number of samples to do next decoder call (usually one frame, less if loop starts/ends)
|
|
||||||
- call decoder
|
|
||||||
- do post-process if necessary (move offsets, check stuff, etc)
|
|
||||||
- repeat until buffer is filled
|
|
||||||
|
|
||||||
Available layouts, depending on how codec data is laid out:
|
|
||||||
- none/flat: straight data. Decoder should handle channel offsets and other details normally.
|
|
||||||
- interleave: one data block per channel, mixed in configurable sizes. Once one channel block is fully decoded this layout skips the other channels, so the decoder only handles one at a time.
|
|
||||||
- blocked: data is divided into blocks, often with a header. Layout detects when a block is done and asks a helper function to fix offsets (skipping the header and pointing to data per channel), depending on the block format.
|
|
||||||
- others: uncommon cases may need its own custom layout (ex.- multistream/subfiles)
|
|
||||||
|
|
||||||
The layout used mainly depends on the decoder. MP3 data (that may have 1 or 2 channels per frame) uses flat layout, while DSP ADPCM (that only decodes one channel at a time) is interleaved. In case of mono files either could be used as there won't be any actual difference.
|
|
||||||
|
|
||||||
Layouts expect the VGMSTREAM to be properly initialized during the meta processing (channel offsets must point to each channel start offset).
|
|
||||||
|
|
||||||
#### decoders
|
|
||||||
Decoders take a sample buffer, convert data to PCM samples and fill one or multiple channels at a time, depending on the decoder itself. Usually its data is divided into frames with a number of samples, and should only need to do one frame at a time (when size is fixed/informed; vgmstream gives flexibility to the decoder), but must take into account that the sample buffer may be smaller than the frame samples, and that may start some samples into the frame.
|
|
||||||
|
|
||||||
Every call the decoder will need to find out the current frame offset (usually per channel). This is usually done with a base channel offset (from the VGMSTREAM) plus deriving the frame number (thus sub-offset, but only if frames are fixed) through the current sample, or manually updating the channel offsets every frame. This second method is not suitable to use with the interleave layout as it advances the offsets assuming they didn't change (this is a limitation/bug at the moment). Similarly, the blocked layout cannot contain interleaved data, and must use alt decoders with internal interleave (also a current limitation). Thus, some decoders and layouts don't mix.
|
|
||||||
|
|
||||||
If the decoder needs to keep state between calls it may use the VGMSTREAM for common values (like ADPCM history), or alloc a custom data struct. In that case the decoder should provide init/free functions so the meta or vgmstream may use. This is the case with decoders implemented using external libraries (*ext_libs*), as seen in *#ifdef VGM_USE_X ... #endif* sections.
|
|
||||||
|
|
||||||
Adding a new decoder involves:
|
|
||||||
- *src/coding/(decoder-name).c*: create `decode_x` function that decodes stream data into the passed sample buffer. If the codec requires custom internals it may need `init/reset/seek/free_x`, or other helper functions.
|
|
||||||
- *src/coding/coding.h*: define decoder's functions.
|
|
||||||
- *src/vgmstream.h*: define new coding type in the list. If the codec requires custom internals, define new `x_codec_data` struct.
|
|
||||||
- *src/vgmstream.c: reset_vgmstream*: call `reset_x` if needed
|
|
||||||
- *src/vgmstream.c: close_vgmstream*: call `free_x` if needed
|
|
||||||
- *src/vgmstream.c: get_vgmstream_samples_per_frame*: define so vgmstream only asks for N samples per decode_x call. May return 0 if variable/unknown/etc (decoder must handle setting arbitrary number of samples)
|
|
||||||
- *src/vgmstream.c: get_vgmstream_frame_size*: define so vgmstream can do certain internal calculations. May return 0 if variable/unknown/etc, but blocked/interleave layouts will need to be used in a certain way.
|
|
||||||
- *src/vgmstream.c: decode_vgmstream*: call `decode_x`, possibly once per channel if the decoder works with a channel at a time.
|
|
||||||
- *src/vgmstream.c: vgmstream_do_loop*: call `seek_x` if needed
|
|
||||||
- *src/vgmstream.c: reset_vgmstream*: call `reset_x` if needed
|
|
||||||
- *src/formats.c*: add coding type description
|
|
||||||
- *src/libvgmstream.vcproj/vcxproj/filters*: add to compile new (decoder-name).c parser in VS
|
|
||||||
- *src/vgmstream.c*: add parser init to the init list
|
|
||||||
- if the codec depends on a external library don't forget to mark parts with: *#ifdef VGM_USE_X ... #endif*
|
|
||||||
|
|
||||||
#### core
|
|
||||||
The vgmstream core simply consists of functions gluing the above together and some helpers (ex.- extension list, loop adjust, etc).
|
|
||||||
|
|
||||||
The *Overview* section should give an idea about how it's used.
|
|
||||||
|
152
doc/DEV.md
Normal file
152
doc/DEV.md
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
# vgmstream development help
|
||||||
|
|
||||||
|
## Code
|
||||||
|
vgmstream uses C (C89 when possible), and C++ for the foobar2000 and Audacious plugins.
|
||||||
|
|
||||||
|
C should be restricted to features VS2010 understands. This mainly means declaring variables at the start of a { .. } block (declare+initialize is fine, as long as it doesn't reference variables declared in the same block) and avoiding C99 features like variable-length arrays (but certain others like // comments are fine).
|
||||||
|
|
||||||
|
There are no hard coding rules but for consistency one could follow the style used in most files:
|
||||||
|
- general C conventions
|
||||||
|
- 4 spaces instead of tabs
|
||||||
|
- underscore_and_lowercase_names instead of CamelCase
|
||||||
|
- /* C89 comments */ for general comments, //C99 comments for special comments (like disabling code but leaving it there for visibility)
|
||||||
|
- brackets starting in the same line
|
||||||
|
ex `if (..) { CRLF ... }`
|
||||||
|
- line length ~100, more is ok for 'noise code' (uninteresting calcs or function defs)
|
||||||
|
- offsets/sizes in hex, counts/numbers in decimal
|
||||||
|
- test functions may return 1=ok, 0=ko for simplicity.
|
||||||
|
- free(ptr) no need to NULL-check per standard, close_stuff(ptr) should follow when possible
|
||||||
|
- lowercase_helper_structs, UPPERCASE_MAIN_STRUCTS
|
||||||
|
- spaces in calcs/ifs/etc may be added as desired for clarity
|
||||||
|
ex. `if (simple_check)` or `if ( complex_and_important_stuff(weird + weird) )`
|
||||||
|
|
||||||
|
But other styles may be found, this isn't very important as most files are isolated.
|
||||||
|
|
||||||
|
Some of the code may be a bit inefficient or duplicated at places, but it isn't much of a problem if gives clarity. vgmstream's performance is fast enough (as it mainly deals with playing songs in real time) so that favors clarity over optimization. Similarly, some code may segfault or even cause infinite loops on bad data, but it's fixed as encountered rather than worrying too much about improbable cases.
|
||||||
|
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
./ scripts
|
||||||
|
./audacious/ Audacious plugin
|
||||||
|
./cli/ CLI tools
|
||||||
|
./doc/ docs
|
||||||
|
./ext_includes/ external includes for compiling
|
||||||
|
./ext_libs/ external libs/DLLs for linking
|
||||||
|
./fb2k/ foobar2000 plugin
|
||||||
|
./src/ main vgmstream code and helpers
|
||||||
|
./src/coding/ format data decoders
|
||||||
|
./src/layout/ format data demuxers
|
||||||
|
./src/meta/ format header parsers
|
||||||
|
./winamp/ Winamp plugin
|
||||||
|
./xmplay/ XMPlay plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
vgmstream works by parsing a music stream header (*meta/*), preparing/controlling data and sample buffers (*layout/*) and decoding the compressed data into listenable PCM samples (*coding/*).
|
||||||
|
|
||||||
|
Very simplified it goes like this:
|
||||||
|
- player (test.exe, plugin, etc) opens a file stream (STREAMFILE) *[plugin's main/decode]*
|
||||||
|
- init tries all parsers (metas) until one works *[init_vgmstream]*
|
||||||
|
- parser reads header (channels, sample rate, loop points) and set ups the VGMSTREAM struct, if the format is correct *[init_vgmstream_(format-name)]*
|
||||||
|
- player finds total_samples to play, based on the number of loops and other settings *[get_vgmstream_play_samples]*
|
||||||
|
- player asks to fill a small sample buffer *[render_vgmstream]*
|
||||||
|
- layout prepares samples and offsets to read from the stream *[render_vgmstream_(layout)]*
|
||||||
|
- decoder reads and decodes bytes into PCM samples *[decode_vgmstream_(coding)]*
|
||||||
|
- player plays those samples, asks to fill sample buffer again, repeats (until total_samples)
|
||||||
|
- layout moves offsets back to loop_start when loop_end is reached *[vgmstream_do_loop]*
|
||||||
|
- player closes the VGMSTREAM once the stream is finished
|
||||||
|
|
||||||
|
vgsmtream's main code (located in src) may be considered "libvgmstream", and plugins interface it through vgmstream.h, mainly the part commented as "vgmstream public API". There isn't a clean external API at the moment, this may be improved later.
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### STREAMFILEs
|
||||||
|
Structs with I/O callbacks that vgmstream uses in place of stdio/FILEs. All I/O must be done through STREAMFILEs as it lets plugins set up their own. This includes reading data or opening other STREAMFILEs (ex. when a header has companion files that need to be parsed, or during setup).
|
||||||
|
|
||||||
|
Players should open a base STREAMFILE and pass it to init_vgmstream. Once it's done this STREAMFILE must be closed, as internally vgmstream opens its own copy (using the base one's callbacks).
|
||||||
|
|
||||||
|
For optimization purposes vgmstream may open a copy of the FILE per channel, as each has its own I/O buffer, and channel data can be too separate to fit a single buffer.
|
||||||
|
|
||||||
|
Custom STREAMFILEs wrapping base STREAMFILEs may be used for complex I/O cases:
|
||||||
|
- file is a container of another format (`fakename/clamp_streamfile`)
|
||||||
|
- data needs decryption (`io_streamfile`)
|
||||||
|
- data must be expanded/reduced on the fly for codecs that are not easy to feed chunked data (`io_streamfile`)
|
||||||
|
- data is divided in multiple physical files, but must be read as a single (`multifile_streamfile`)
|
||||||
|
Certain metas combine those streamfiles together with special layouts to support very complex cases, that would require massive changes in vgmstream to support in a cleaner (possible undesirable) way.
|
||||||
|
|
||||||
|
|
||||||
|
### VGMSTREAM
|
||||||
|
The VGMSTREAM (caps) is the main struct created during init when a file is successfully recognized and parsed. It holds the file's configuration (channels, sample rate, decoder, layout, samples, loop points, etc) and decoder state (STREAMFILEs, offsets per channel, current sample, etc), and is used to interact with the API.
|
||||||
|
|
||||||
|
### metas
|
||||||
|
Metadata (header) parsers that identify and handle formats.
|
||||||
|
|
||||||
|
To add a new one:
|
||||||
|
- *src/meta/(format-name).c*: create new init_vgmstream_(format-name) parser that tests the extension and header id, reads all needed info from the stream header and sets up the VGMSTREAM
|
||||||
|
- *src/meta/meta.h*: define parser's init
|
||||||
|
- *src/vgmstream.h*: define meta type in the meta_t list
|
||||||
|
- *src/vgmstream.c*: add parser init to the init list
|
||||||
|
- *src/formats.c*: add new extension to the format list, add meta type description
|
||||||
|
- *src/libvgmstream.vcproj/vcxproj/filters*: add to compile new (format-name).c parser in VS
|
||||||
|
- if the format needs an external library don't forget to mark optional parts with: *#ifdef VGM_USE_X ... #endif*
|
||||||
|
|
||||||
|
Ultimately the meta must alloc the VGMSTREAM, set config and initial state. vgmstream needs the total of number samples to work, so at times must convert from data sizes to samples (doing calculations or using helpers).
|
||||||
|
|
||||||
|
It also needs to open and assign to the VGMSTREAM one or several STREAMFILEs (usually reopening the base one, but could be any other file) to do I/O during decode, as well as setting the starting offsets of each channel and other values; this gives metas full flexibility at the cost of some repetition. The STREAMFILE passed to the meta will be discarded and its pointer must not be reused.
|
||||||
|
|
||||||
|
The .c file is usually named after the format's main extension or header id, optionally with affixes. Each file should parse one format and/or its variations (regardless of accepted extensions or decoders used) for consistency, but deviations may be found in the codebase. Sometimes a format is already parsed but not accepted due to bugs though.
|
||||||
|
|
||||||
|
Different formats may use the same extension but this isn't a problem as long as the header id or some other validation tells them apart, and should be implemented in separate .c files. If the format is headerless and the extension isn't unique enough it probably needs a generic GENH/TXTH header instead of direct support.
|
||||||
|
|
||||||
|
If the format supports subsongs it should read the stream index (subsong number) in the passed STREAMFILE, and use it to parse a section of the file. Then it must report the number of subsongs in the VGMSTREAM, to signal this feature is enabled. The index is 1-based (first subsong is 1, 0 is default/first). This makes possible to directly use bank-like formats like .FSB, and while vgmstream could technically support any container (like generic bigfiles or even .zip) it should be restricted to files that actually are audio banks.
|
||||||
|
|
||||||
|
### layouts
|
||||||
|
Layouts control most of the main logic:
|
||||||
|
- receive external buffer to fill with PCM samples
|
||||||
|
- detect when looping must be done
|
||||||
|
- find max number of samples to do next decoder call (usually one frame, less if loop starts/ends)
|
||||||
|
- call decoder
|
||||||
|
- do post-process if necessary (move offsets, check stuff, etc)
|
||||||
|
- repeat until buffer is filled
|
||||||
|
|
||||||
|
Available layouts, depending on how codec data is laid out:
|
||||||
|
- flat: straight data. Decoder should handle channel offsets and other details normally.
|
||||||
|
- interleave: one data block per channel, mixed in configurable sizes. Once one channel block is fully decoded this layout skips the other channels, so the decoder only handles one at a time.
|
||||||
|
- blocked: data is divided into blocks, often with a header. Layout detects when a block is done and asks a helper function to fix offsets (skipping the header and pointing to data per channel), depending on the block format.
|
||||||
|
- segmented: file is divided into consecutive but separate segments, each one is setup as a fully separate VGMSTREAM.
|
||||||
|
- layered: file is divided into multichannel layers that play at the same time, each one is setup as a fully separate VGMSTREAM.
|
||||||
|
- others: uncommon cases may need its own custom layout, but may be dealt with using custom IO STREAMFILEs instead.
|
||||||
|
|
||||||
|
The layout used mainly depends on the decoder. MP3 data (that may have 1 or 2 channels per frame) uses flat layout, while DSP ADPCM (that only decodes one channel at a time) is interleaved. In case of mono files either could be used as there won't be any actual difference.
|
||||||
|
|
||||||
|
Layouts expect the VGMSTREAM to be properly initialized during the meta processing (channel offsets must point to each channel start offset).
|
||||||
|
|
||||||
|
### decoders
|
||||||
|
Decoders take a sample buffer, convert data to PCM samples and fill one or multiple channels at a time, depending on the decoder itself. Usually its data is divided into frames with a number of samples, and should only need to do one frame at a time (when size is fixed/informed; vgmstream gives flexibility to the decoder), but must take into account that the sample buffer may be smaller than the frame samples, and that may start some samples into the frame.
|
||||||
|
|
||||||
|
Every call the decoder will need to find out the current frame offset (usually per channel). This is usually done with a base channel offset (from the VGMSTREAM) plus deriving the frame number (thus sub-offset, but only if frames are fixed) through the current sample, or manually updating the channel offsets every frame. This second method is not suitable to use with the interleave layout as it advances the offsets assuming they didn't change (this is a limitation/bug at the moment). Similarly, the blocked layout cannot contain interleaved data, and must use alt decoders with internal interleave (also a current limitation). Thus, some decoders and layouts don't mix.
|
||||||
|
|
||||||
|
If the decoder needs to keep state between calls it may use the VGMSTREAM for common values (like ADPCM history), or alloc a custom data struct. In that case the decoder should provide init/free functions so the meta or vgmstream may use. This is the case with decoders implemented using external libraries (*ext_libs*), as seen in *#ifdef VGM_USE_X ... #endif* sections.
|
||||||
|
|
||||||
|
Adding a new decoder involves:
|
||||||
|
- *src/coding/(decoder-name).c*: create `decode_x` function that decodes stream data into the passed sample buffer. If the codec requires custom internals it may need `init/reset/seek/free_x`, or other helper functions.
|
||||||
|
- *src/coding/coding.h*: define decoder's functions.
|
||||||
|
- *src/vgmstream.h*: define new coding type in the list. If the codec requires custom internals, define new `x_codec_data` struct.
|
||||||
|
- *src/vgmstream.c: reset_vgmstream*: call `reset_x` if needed
|
||||||
|
- *src/vgmstream.c: close_vgmstream*: call `free_x` if needed
|
||||||
|
- *src/vgmstream.c: get_vgmstream_samples_per_frame*: define so vgmstream only asks for N samples per decode_x call. May return 0 if variable/unknown/etc (decoder must handle setting arbitrary number of samples)
|
||||||
|
- *src/vgmstream.c: get_vgmstream_frame_size*: define so vgmstream can do certain internal calculations. May return 0 if variable/unknown/etc, but blocked/interleave layouts will need to be used in a certain way.
|
||||||
|
- *src/vgmstream.c: decode_vgmstream*: call `decode_x`, possibly once per channel if the decoder works with a channel at a time.
|
||||||
|
- *src/vgmstream.c: vgmstream_do_loop*: call `seek_x` if needed
|
||||||
|
- *src/vgmstream.c: reset_vgmstream*: call `reset_x` if needed
|
||||||
|
- *src/formats.c*: add coding type description
|
||||||
|
- *src/libvgmstream.vcproj/vcxproj/filters*: add to compile new (decoder-name).c parser in VS
|
||||||
|
- *src/vgmstream.c*: add parser init to the init list
|
||||||
|
- if the codec depends on a external library don't forget to mark parts with: *#ifdef VGM_USE_X ... #endif*
|
||||||
|
|
||||||
|
### core
|
||||||
|
The vgmstream core simply consists of functions gluing the above together and some helpers (ex.- extension list, loop adjust, etc).
|
||||||
|
|
||||||
|
The *Overview* section should give an idea about how it's used.
|
9
doc/GENH.md
Normal file
9
doc/GENH.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# GENH FORMAT
|
||||||
|
|
||||||
|
GENH is a generic binary header with fixed values, to make unsupported files playable. This binary header is appended to the beginning of a file and file is renamed to .genh, so vgmstream will read the GENH header and play the file with the new info.
|
||||||
|
|
||||||
|
GENH as been mostly superseded by TXTH, as it can do anything that GENH does and more, plus it's cleaner and much simpler to create, so TXTH is the recommended way to make vgmstream play unsupported files.
|
||||||
|
|
||||||
|
If you still want to create files with GENH headers, the easiest way is to use VGMToolBox's GENH creator, that provides a simple Windows interface.
|
||||||
|
|
||||||
|
For programmers looking for a formal definition the place to check would be vgmstream's parser, located in `genh.c` (particularly `parse_genh`), as new features or fixes may be added anytime.
|
153
doc/TXTH.md
Normal file
153
doc/TXTH.md
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
# TXTH FORMAT
|
||||||
|
|
||||||
|
TXTH is a simple text file that uses text commands to simulate a header for files unsupported by vgmstream, mainly headerless audio.
|
||||||
|
|
||||||
|
When an unsupported file is loaded, vgmstream tries to find a TXTH header in the same dir, in this order:
|
||||||
|
- (filename.ext).txth
|
||||||
|
- .(ext).txth
|
||||||
|
- .txth
|
||||||
|
|
||||||
|
If found and parsed correctly (the TXTH may be rejected if incorrect commands are found) vgmstream will try to play the file as described. Extension must be accepted/added to vgmstream (plugins like foobar2000 only load extensions from a whitelist in formats.c), or one could rename to any supported extension (like .vgmstream), or leave the file extensionless.
|
||||||
|
|
||||||
|
|
||||||
|
## Example of a TXTH file
|
||||||
|
For an unsupported bgm01.vag this would be a simple TXTH for it:
|
||||||
|
```
|
||||||
|
id_value = 0x534E4420 #test that file starts with "SND "
|
||||||
|
id_offset = @0x00:BE #test is done at offset 0, big endian value
|
||||||
|
codec = PSX
|
||||||
|
sample_rate = @0x10$2 #get sample rate at offset 0x10, 16 bit value
|
||||||
|
channels = @0x14 #get number of channels at offset 14
|
||||||
|
interleave = 0x1000 #fixed value
|
||||||
|
start_offset = 0x100
|
||||||
|
num_samples = data_size #find automatically number of samples in the file
|
||||||
|
loop_flag = auto
|
||||||
|
```
|
||||||
|
A text file with the above commands must be saved as ".vag.txth" or ".txth", notice it starts with a "." (dot). On Windows files starting with a dot can be created by appending a dot at the end: ".txth."
|
||||||
|
|
||||||
|
|
||||||
|
## Available commands
|
||||||
|
|
||||||
|
```
|
||||||
|
######################################################
|
||||||
|
|
||||||
|
# comments start with #, can be inline
|
||||||
|
# The file is made lines of "key = value" describing a header.
|
||||||
|
# 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.
|
||||||
|
# Examples: 44100, 40, 0x40 (dec=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
|
||||||
|
|
||||||
|
# Codec used to encode the data [REQUIRED]
|
||||||
|
# Accepted codec strings:
|
||||||
|
# - PSX PlayStation ADPCM
|
||||||
|
# - XBOX Xbox IMA ADPCM
|
||||||
|
# - NGC_DTK Nintendo ADP/DTK ADPCM
|
||||||
|
# - PCM16BE PCM RAW 16bit big endian
|
||||||
|
# - PCM16LE PCM RAW 16bit little endian
|
||||||
|
# - PCM8 PCM RAW 8bit
|
||||||
|
# - SDX2 Squareroot-delta-exact 8-bit DPCM (3DO games)
|
||||||
|
# - DVI_IMA DVI IMA ADPCM
|
||||||
|
# - MPEG MPEG Audio Layer File (MP1/2/3)
|
||||||
|
# - IMA IMA ADPCM
|
||||||
|
# - AICA Yamaha AICA ADPCM (Dreamcast)
|
||||||
|
# - MSADPCM Microsoft ADPCM (Windows)
|
||||||
|
# - NGC_DSP Nintengo GameCube ADPCM
|
||||||
|
# - PCM8_U_int PCM RAW 8bit unsigned (interleaved)
|
||||||
|
# - PSX_bf PlayStation ADPCM with bad flags
|
||||||
|
# - MS_IMA Microsoft IMA ADPCM
|
||||||
|
# - PCM8_U PCM RAW 8bit unsigned
|
||||||
|
# - APPLE_IMA4 Apple Quicktime IMA ADPCM
|
||||||
|
# - ATRAC3 raw ATRAC3
|
||||||
|
# - ATRAC3PLUS raw ATRAC3PLUS
|
||||||
|
# - XMA1 raw XMA1
|
||||||
|
# - XMA2 raw XMA2
|
||||||
|
# - FFMPEG any headered FFmpeg format
|
||||||
|
codec = (codec string)
|
||||||
|
|
||||||
|
# Varies with codec:
|
||||||
|
# - NGC_DSP: 0=normal interleave, 1=byte interleave, 2=no interleave
|
||||||
|
# - ATRAC3: 0=autodetect joint stereo, 1=force joint stereo, 2=force normal stereo
|
||||||
|
# - XMA1|XMA2: 0=dual multichannel (2ch xN), 1=single multichannel (1ch xN)
|
||||||
|
# - XBOX: 0=standard, 1=force mono/interleave mode
|
||||||
|
# - others: ignored
|
||||||
|
codec_mode = (number)
|
||||||
|
|
||||||
|
# Interleave or block size [REQUIRED/OPTIONAL, depends on codec]
|
||||||
|
# Interleave 0 means "stereo mode" for some codecs (IMA, AICA, etc)
|
||||||
|
interleave = (number)|(offset)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# Number of channels [REQUIRED]
|
||||||
|
channels = (number)|(offset)
|
||||||
|
|
||||||
|
# Music frequency in hz [REQUIRED]
|
||||||
|
sample_rate = (number)|(offset)
|
||||||
|
|
||||||
|
# Data start [OPTIONAL, default to 0]
|
||||||
|
start_offset = (number)|(offset)
|
||||||
|
|
||||||
|
# Variable that can be used in sample values [OPTIONAL]
|
||||||
|
# Defaults to (file_size - start_offset), re-calculated when start_offset is set.
|
||||||
|
data_size = (number)|(offset)
|
||||||
|
|
||||||
|
|
||||||
|
# Modifies the meaning of sample fields when set *before* them [OPTIONAL, defaults to samples]
|
||||||
|
# - samples: exact sample
|
||||||
|
# - bytes: automatically converts bytes/offset to samples
|
||||||
|
# - blocks: same as bytes, but value is given in blocks/frames
|
||||||
|
# Value is internally converted from blocks to bytes first: bytes = (value * interleave*channels)
|
||||||
|
# It's possible to re-define values multiple times:
|
||||||
|
# * samples_type=bytes ... num_samples=@0x10
|
||||||
|
# * samples_type=sample ... loop_end_sample=@0x14
|
||||||
|
# Sometimes "bytes" values are given for a single channel only. In that case you can temporally set 1 channel
|
||||||
|
# * channels=1 ... sample_type=bytes ... num_samples=@0x10 ... channels=2
|
||||||
|
# Some codecs can't convert bytes-to-samples at the moment: MPEG/FFMPEG
|
||||||
|
# For XMA1/2 bytes does special parsing, with loop values being bit offsets within data.
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# (rarely a format may have rough loop offset/bytes, then a loop adjust in samples).
|
||||||
|
loop_adjust = (number)|(offset)
|
||||||
|
|
||||||
|
# Force loop, on (>0) or off (0), as loop start/end may be defined but not used [OPTIONAL]
|
||||||
|
# By default it loops when loop_end_sample is defined
|
||||||
|
# auto tries to autodetect loop points for PS-ADPCM data, which may include loop flags.
|
||||||
|
loop_flag = (number)|(offset)|auto
|
||||||
|
|
||||||
|
# beginning samples to skip (encoder delay), for codecs that need them (ATRAC3/XMA/etc)
|
||||||
|
skip_samples = (number)|(offset)
|
||||||
|
|
||||||
|
|
||||||
|
# DSP coefficients [REQUIRED for NGC_DSP]
|
||||||
|
# Coefs start
|
||||||
|
coef_offset = (number)|(offset)
|
||||||
|
# offset separation per channel, usually 0x20
|
||||||
|
# - Example: channel N coefs are read at coef_offset + coef_spacing * N
|
||||||
|
coef_spacing = (number)|(offset)
|
||||||
|
# Format, usually BE; with (offset): 0=LE, >0=BE
|
||||||
|
coef_endianness = BE|LE|(offset)
|
||||||
|
# Split/normal coefs [NOT IMPLEMENTED YET]
|
||||||
|
#coef_mode = (number)|(offset)
|
||||||
|
|
||||||
|
```
|
128
doc/TXTP.md
Normal file
128
doc/TXTP.md
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# TXTP FORMAT
|
||||||
|
|
||||||
|
TXTP is a mini-playlist file for various purposes, mainly to make more playable certain games that use uncommon ways to play their audio.
|
||||||
|
|
||||||
|
Simply create a file named `(filename).txtp`, and inside write the commands described below.
|
||||||
|
|
||||||
|
|
||||||
|
## TXTP features
|
||||||
|
|
||||||
|
### Play separate intro + loop files together as a single track
|
||||||
|
- __Ratchet & Clank (PS2)__: _bgm01.txtp_
|
||||||
|
```
|
||||||
|
# define several files to play as one (there is no limit)
|
||||||
|
BGM01_BEGIN.VAG
|
||||||
|
BGM01_LOOPED.VAG
|
||||||
|
|
||||||
|
# multi-files 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)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Minifiles for bank formats without splitters
|
||||||
|
- __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
|
||||||
|
|
||||||
|
# single files loop normally by default
|
||||||
|
# if loop segment is defined it forces a full loop (0..num_samples)
|
||||||
|
#loop_start_segment = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Play segmented subsongs as one
|
||||||
|
- __Prince of Persia Sands of Time__: _song_01.txtp_
|
||||||
|
```
|
||||||
|
# can use ranges ~ to avoid so much C&P
|
||||||
|
amb_fx.sb0#254
|
||||||
|
amb_fx.sb0#122~144
|
||||||
|
amb_fx.sb0#121 #notice "#" works as config or comment
|
||||||
|
|
||||||
|
#3rd segment = subsong 123, not 3rd subsong
|
||||||
|
loop_start_segment = 3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Channel mask for channel subsongs/layers
|
||||||
|
- __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_
|
||||||
|
```
|
||||||
|
#plays channels 3 and 4 = 2nd subsong
|
||||||
|
music_Home.ps3.scd#c3,4
|
||||||
|
|
||||||
|
# song still has 4 channels, just mutes some
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Channel mapping
|
||||||
|
TXTP can swap channels for custom channel mappings. Note that channel masking applies after mappings. 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"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
Combined with TXTH, this can also be used for extensions that aren't normally accepted by vgmstream.
|
||||||
|
|
||||||
|
### TXTP combos
|
||||||
|
TXTP may even reference other TXTP, or files that require TXTH, for extra complex cases. Each file defined in TXTP is internally parsed like it was a completely separate file, so there is a bunch of valid ways to mix them.
|
||||||
|
|
||||||
|
|
||||||
|
## Mini TXTP
|
||||||
|
|
||||||
|
To simplify TXTP creation, if the .txtp is empty (0 bytes) its filename is used directly as a command.
|
||||||
|
- _bgm.sxd2#12.txtp_: plays subsong 12
|
||||||
|
- _Ryoshima Coast 1 & 2.aix#c1,2.txtp_: channel mask
|
||||||
|
- 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
|
||||||
|
```
|
Loading…
Reference in New Issue
Block a user