mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-17 23:36:41 +01:00
Merge pull request #365 from bnnm/txtp-aix-bao-fixes
txtp aix bao fixes
This commit is contained in:
commit
450657f033
@ -313,7 +313,7 @@ static void apply_config(VGMSTREAM * vgmstream, cli_config *cfg) {
|
||||
}
|
||||
}
|
||||
|
||||
void apply_fade(sample * buf, VGMSTREAM * vgmstream, int to_get, int i, int len_samples, int fade_samples, int channels) {
|
||||
void apply_fade(sample_t * buf, VGMSTREAM * vgmstream, int to_get, int i, int len_samples, int fade_samples, int channels) {
|
||||
int is_fade_on = vgmstream->loop_flag;
|
||||
|
||||
if (is_fade_on && fade_samples > 0) {
|
||||
@ -324,7 +324,7 @@ void apply_fade(sample * buf, VGMSTREAM * vgmstream, int to_get, int i, int len_
|
||||
if (samples_into_fade > 0) {
|
||||
double fadedness = (double)(fade_samples - samples_into_fade) / fade_samples;
|
||||
for (k = 0; k < channels; k++) {
|
||||
buf[j*channels + k] = (sample)buf[j*channels + k] * fadedness;
|
||||
buf[j*channels + k] = (sample_t)buf[j*channels + k] * fadedness;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -339,8 +339,8 @@ int main(int argc, char ** argv) {
|
||||
FILE * outfile = NULL;
|
||||
char outfilename_temp[PATH_LIMIT];
|
||||
|
||||
sample * buf = NULL;
|
||||
int channels;
|
||||
sample_t * buf = NULL;
|
||||
int channels, input_channels;
|
||||
int32_t len_samples;
|
||||
int32_t fade_samples;
|
||||
int i, j;
|
||||
@ -458,8 +458,19 @@ int main(int argc, char ** argv) {
|
||||
|
||||
/* last init */
|
||||
channels = vgmstream->channels;
|
||||
input_channels = vgmstream->channels;
|
||||
|
||||
buf = malloc(BUFFER_SAMPLES * sizeof(sample) * channels);
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
/* enable after all config but before outbuf */
|
||||
{
|
||||
vgmstream_enable_mixing(vgmstream, BUFFER_SAMPLES);
|
||||
|
||||
channels = vgmstream->output_channels;
|
||||
input_channels = vgmstream->input_channels;
|
||||
}
|
||||
#endif
|
||||
|
||||
buf = malloc(BUFFER_SAMPLES * sizeof(sample_t) * input_channels);
|
||||
if (!buf) {
|
||||
fprintf(stderr,"failed allocating output buffer\n");
|
||||
goto fail;
|
||||
@ -488,10 +499,10 @@ int main(int argc, char ** argv) {
|
||||
swap_samples_le(buf, channels * to_get); /* write PC endian */
|
||||
if (cfg.only_stereo != -1) {
|
||||
for (j = 0; j < to_get; j++) {
|
||||
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample), 2, outfile);
|
||||
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample_t), 2, outfile);
|
||||
}
|
||||
} else {
|
||||
fwrite(buf, sizeof(sample) * channels, to_get, outfile);
|
||||
fwrite(buf, sizeof(sample_t) * channels, to_get, outfile);
|
||||
}
|
||||
}
|
||||
|
||||
@ -509,10 +520,10 @@ int main(int argc, char ** argv) {
|
||||
swap_samples_le(buf, channels * to_get); /* write PC endian */
|
||||
if (cfg.only_stereo != -1) {
|
||||
for (j = 0; j < to_get; j++) {
|
||||
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample), 2, outfile);
|
||||
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample_t), 2, outfile);
|
||||
}
|
||||
} else {
|
||||
fwrite(buf, sizeof(sample) * channels, to_get, outfile);
|
||||
fwrite(buf, sizeof(sample_t) * channels, to_get, outfile);
|
||||
}
|
||||
}
|
||||
|
||||
@ -522,13 +533,13 @@ int main(int argc, char ** argv) {
|
||||
|
||||
/* try again with (for testing reset_vgmstream, simulates a seek to 0) */
|
||||
if (cfg.test_reset) {
|
||||
char outfilename_temp[PATH_LIMIT];
|
||||
strcpy(outfilename_temp, cfg.outfilename);
|
||||
strcat(outfilename_temp, ".reset.wav");
|
||||
char outfilename_reset[PATH_LIMIT];
|
||||
strcpy(outfilename_reset, cfg.outfilename);
|
||||
strcat(outfilename_reset, ".reset.wav");
|
||||
|
||||
outfile = fopen(outfilename_temp,"wb");
|
||||
outfile = fopen(outfilename_reset,"wb");
|
||||
if (!outfile) {
|
||||
fprintf(stderr,"failed to open %s for output\n",outfilename_temp);
|
||||
fprintf(stderr,"failed to open %s for output\n",outfilename_reset);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@ -564,10 +575,10 @@ int main(int argc, char ** argv) {
|
||||
swap_samples_le(buf, channels * to_get); /* write PC endian */
|
||||
if (cfg.only_stereo != -1) {
|
||||
for (j = 0; j < to_get; j++) {
|
||||
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample), 2, outfile);
|
||||
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample_t), 2, outfile);
|
||||
}
|
||||
} else {
|
||||
fwrite(buf, sizeof(sample) * channels, to_get, outfile);
|
||||
fwrite(buf, sizeof(sample_t) * channels, to_get, outfile);
|
||||
}
|
||||
}
|
||||
fclose(outfile);
|
||||
@ -615,7 +626,7 @@ static void make_smpl_chunk(uint8_t * buf, int32_t loop_start, int32_t loop_end)
|
||||
static size_t make_wav_header(uint8_t * buf, size_t buf_size, int32_t sample_count, int32_t sample_rate, int channels, int smpl_chunk, int32_t loop_start, int32_t loop_end) {
|
||||
size_t data_size, header_size;
|
||||
|
||||
data_size = sample_count*channels*sizeof(sample);
|
||||
data_size = sample_count * channels * sizeof(sample_t);
|
||||
header_size = 0x2c;
|
||||
if (smpl_chunk && loop_end)
|
||||
header_size += 0x3c+ 0x08;
|
||||
@ -633,9 +644,9 @@ static size_t make_wav_header(uint8_t * buf, size_t buf_size, int32_t sample_cou
|
||||
put_16bitLE(buf+0x14, 1); /* compression code 1=PCM */
|
||||
put_16bitLE(buf+0x16, channels); /* channel count */
|
||||
put_32bitLE(buf+0x18, sample_rate); /* sample rate */
|
||||
put_32bitLE(buf+0x1c, sample_rate*channels*sizeof(sample)); /* bytes per second */
|
||||
put_16bitLE(buf+0x20, (int16_t)(channels*sizeof(sample))); /* block align */
|
||||
put_16bitLE(buf+0x22, sizeof(sample)*8); /* significant bits per sample */
|
||||
put_32bitLE(buf+0x1c, sample_rate*channels*sizeof(sample_t)); /* bytes per second */
|
||||
put_16bitLE(buf+0x20, (int16_t)(channels*sizeof(sample_t))); /* block align */
|
||||
put_16bitLE(buf+0x22, sizeof(sample_t)*8); /* significant bits per sample */
|
||||
|
||||
if (smpl_chunk && loop_end) {
|
||||
make_smpl_chunk(buf+0x24, loop_start, loop_end);
|
||||
|
82
doc/TXTH.md
82
doc/TXTH.md
@ -60,33 +60,77 @@ A text file with the above commands must be saved as ".vag.txth" or ".txth", not
|
||||
# Codec used to encode the data [REQUIRED]
|
||||
# Accepted codec strings:
|
||||
# - PSX PlayStation ADPCM
|
||||
# - XBOX Xbox IMA ADPCM
|
||||
# - NGC_DTK|DTK Nintendo ADP/DTK ADPCM
|
||||
# - PCM16BE PCM 16-bit big endian
|
||||
# - PCM16LE PCM 16-bit little endian
|
||||
# - PCM8 PCM 8-bit signed
|
||||
# - 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|DSP Nintendo GameCube ADPCM
|
||||
# - PCM8_U_int PCM RAW 8bit unsigned (interleaved)
|
||||
# * For many PS1/PS2/PS3 games
|
||||
# * Interleave is multiple of 0x10, often +0x1000
|
||||
# - PSX_bf PlayStation ADPCM with bad flags
|
||||
# - MS_IMA Microsoft IMA ADPCM
|
||||
# * Variation with garbage data, for rare PS2 games
|
||||
# - XBOX Xbox IMA ADPCM (mono/stereo)
|
||||
# * For many XBOX games, and some PC games
|
||||
# * Special interleave is multiple of 0x24 (mono) or 0x48 (stereo)
|
||||
# - DSP|NGC_DSP Nintendo GameCube ADPCM
|
||||
# * For many GC/Wii/3DS games
|
||||
# * Interleave is multiple of 0x08, often +0x1000
|
||||
# * Must set decoding coefficients (coef_offset/spacing/etc)
|
||||
# - DTK|NGC_DTK Nintendo ADP/DTK ADPCM
|
||||
# * For rare GC games
|
||||
# - PCM16LE PCM 16-bit little endian
|
||||
# * For many games (usually on PC)
|
||||
# * Interleave is multiple of 0x2
|
||||
# - PCM16BE PCM 16-bit big endian
|
||||
# * Variation for certain consoles (GC/Wii/PS3/X360/etc)
|
||||
# - PCM8 PCM 8-bit signed
|
||||
# * For some games (usually on PC)
|
||||
# * Interleave is multiple of 0x1
|
||||
# - PCM8_U PCM 8-bit unsigned
|
||||
# * Variation with modified encoding
|
||||
# - PCM8_U_int PCM 8-bit unsigned (interleave block)
|
||||
# * Variation with modified encoding
|
||||
# - IMA IMA ADPCM (mono/stereo)
|
||||
# * For some PC games, and rarely consoles
|
||||
# * Special interleave is multiple of 0x1, often +0x80
|
||||
# - DVI_IMA IMA ADPCM (DVI order)
|
||||
# * Variation with modified encoding
|
||||
# - YAMAHA|AICA Yamaha ADPCM (mono/stereo)
|
||||
# * For some Dreamcast games, and some arcade games
|
||||
# * Special interleave is multiple of 0x1
|
||||
# - APPLE_IMA4 Apple Quicktime IMA ADPCM
|
||||
# * For some Mac/iOS games
|
||||
# - MS_IMA Microsoft IMA ADPCM
|
||||
# * For some PC games
|
||||
# * Interleave (frame size) varies, often multiple of 0x100 [required]
|
||||
# - MSADPCM Microsoft ADPCM (mono/stereo)
|
||||
# * For some PC games
|
||||
# * Interleave (frame size) varies, often multiple of 0x100 [required]
|
||||
# - SDX2 Squareroot-delta-exact 8-bit DPCM (3DO games)
|
||||
# * For many 3DO games
|
||||
# - MPEG MPEG Audio Layer file (MP1/2/3)
|
||||
# * For some games (usually PC/PS3)
|
||||
# - ATRAC3 Sony ATRAC3
|
||||
# * For some PS2 and PS3 games
|
||||
# * Interleave (frame size) can be 0x60/0x98/0xC0 * channels [required]
|
||||
# - ATRAC3PLUS Sony ATRAC3plus
|
||||
# * For many PSP games and rare PS3 games
|
||||
# * Interleave (frame size) can be: [required]
|
||||
# Mono: 0x0118|0178|0230|02E8
|
||||
# Stereo: 0x0118|0178|0230|02E8|03A8|0460|05D0|0748|0800
|
||||
# - XMA1 Microsoft XMA1
|
||||
# * For early X360 games
|
||||
# - XMA2 Microsoft XMA2
|
||||
# * For later X360 games
|
||||
# - FFMPEG Any headered FFmpeg format
|
||||
# * For uncommon games
|
||||
# - AC3 AC3/SPDIF
|
||||
# * For few PS2 games
|
||||
# - PCFX PC-FX ADPCM
|
||||
# * For many PC-FX games
|
||||
# * Interleave is multiple of 0x1, often +0x8000
|
||||
# * Sample rate may be ~31468/~15734/~10489/~7867
|
||||
# - PCM4 PCM 4-bit signed
|
||||
# * For early consoles
|
||||
# - PCM4_U PCM 4-bit unsigned
|
||||
# - OKI16 OKI ADPCM with 16-bit output (not std/VOX/Dialogic 12-bit)
|
||||
# * Variation with modified encoding
|
||||
# - OKI16 OKI ADPCM with 16-bit output (not VOX/Dialogic 12-bit)
|
||||
# * For few PS2 games (Sweet Legacy, Hooligan)
|
||||
codec = (codec string)
|
||||
|
||||
# Codec variations [OPTIONAL, depends on codec]
|
||||
@ -111,9 +155,11 @@ value_sub|value_- = (number)|(offset)|(field)
|
||||
# Interleave or block size [REQUIRED/OPTIONAL, depends on codec]
|
||||
# - half_size: sets interleave as data_size / channels
|
||||
# For mono/interleaved codecs it's the amount of data between channels,
|
||||
# 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).
|
||||
# and while optional you'll often need to set it to get proper sound.
|
||||
# For codecs with custom frame sizes (MSADPCM, MS-IMA, ATRAC3/plus)
|
||||
# means frame size and it's required.
|
||||
# Interleave 0 means "stereo mode" for codecs marked as "mono/stereo",
|
||||
# and setting it will usually force mono-interleaved mode.
|
||||
interleave = (number)|(offset)|(field)|half_size
|
||||
|
||||
# Interleave in the last block [OPTIONAL]
|
||||
|
162
doc/TXTP.md
162
doc/TXTP.md
@ -1,14 +1,24 @@
|
||||
# TXTP FORMAT
|
||||
|
||||
TXTP is a text file with commands, to improve support for games using audio in certain uncommon or undesirable ways. It's in the form of a mini-playlist or a wrapper with play settings.
|
||||
TXTP is a text file with commands, to improve support for games using audio in certain uncommon or undesirable ways. It's in the form of a mini-playlist or a wrapper with play settings, meant to do post-processing over playable files.
|
||||
|
||||
Simply create a file named `(filename).txtp`, and inside write the commands described below.
|
||||
|
||||
|
||||
## TXTP FEATURES
|
||||
## TXTP MODES
|
||||
TXTP can join and play together multiple songs in various ways by setting a file list and mode:
|
||||
```
|
||||
file1
|
||||
...
|
||||
fileN
|
||||
|
||||
### Play separate intro + loop files together as a single track
|
||||
Some games clumsily loop audio by using multiple full file "segments":
|
||||
mode = (mode) # "segments" is the default if not set
|
||||
```
|
||||
You can set commands to alter how files play (described later). Having a single file is ok too.
|
||||
|
||||
|
||||
### Segments mode
|
||||
Some games clumsily loop audio by using multiple full file "segments", so you can play separate intro + loop files together as a single track. Channel number must be equal, mixing sample rates is ok (uses first).
|
||||
|
||||
__Ratchet & Clank (PS2)__: _bgm01.txtp_
|
||||
```
|
||||
@ -20,7 +30,6 @@ BGM01_LOOPED.VAG
|
||||
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).
|
||||
|
||||
If your loop segment has proper loops you want to keep, you can use:
|
||||
```
|
||||
@ -41,8 +50,8 @@ loop_end_segment = 3
|
||||
loop_mode = keep # loops in 2nd file's loop_start to 3rd file's loop_end
|
||||
```
|
||||
|
||||
### Multilayered songs
|
||||
TXTP "layers" play songs with channels/parts divided into files as one (for example main melody + vocal track).
|
||||
### Layers mode
|
||||
Some games layer channels or dynamic parts that must play at the same time, for example main melody + vocal track.
|
||||
|
||||
__Nier Automata__: _BGM_0_012_song2.txtp_
|
||||
```
|
||||
@ -62,10 +71,15 @@ BIK_E1_6A_DialEnd_00000000.audio.multi.bik#3
|
||||
|
||||
mode = layers
|
||||
```
|
||||
Note that the number of channels is the sum of all layers, so three 2ch layers play as a 6ch file.
|
||||
Note that the number of channels is the sum of all layers, so three 2ch layers play as a 6ch file. If all layers share loop points they are automatically kept.
|
||||
|
||||
|
||||
### Minifiles for bank formats without splitters
|
||||
## TXTP COMMANDS
|
||||
You can set file commands by adding multiple `#(command)` after the name. `# (anything)` is considered a comment and ignored, as well as any command not understood.
|
||||
|
||||
### Subsong selection for bank formats
|
||||
**`#(number)` or `#s(number)`**: set subsong (number)
|
||||
|
||||
__Super Robot Taisen OG Saga - Masou Kishin III - Pride of Justice (Vita)__: _bgm_12.txtp_
|
||||
```
|
||||
# select subsong 12
|
||||
@ -78,36 +92,48 @@ bigfiles/bgm.sxd2#12 #relative paths are ok too for TXTP
|
||||
#loop_start_segment = 1
|
||||
```
|
||||
|
||||
### Play segmented subsongs as one
|
||||
### Play segmented subsong ranges as one
|
||||
**`#m(number)~(number)` or `#ms(number)~(number)`**: set multiple subsong segments at a time, to avoid so much C&P
|
||||
|
||||
__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
|
||||
amb_fx.sb0#121
|
||||
|
||||
#3rd segment = subsong 123, not 3rd subsong
|
||||
loop_start_segment = 3
|
||||
```
|
||||
This is just a shorthand, so `song#1~3#h22050` is equivalent to:
|
||||
```
|
||||
song#1#h22050
|
||||
song#2#h22050
|
||||
song#3#h22050
|
||||
```
|
||||
|
||||
|
||||
### Channel mask for channel subsongs/layers
|
||||
**`#c(number)`** (single) or **`#c(number)~(number)`** (range): set number of channels to play. You can add multiple comma-separated numbers, or use ` ` space or `-` as separator and combine multiple ranges with single channels too.
|
||||
|
||||
__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
|
||||
music_Home.ps3.scd#c3 4
|
||||
|
||||
# song still has 4 channels, just mutes some
|
||||
#plays 1 to 3
|
||||
music_Home.ps3.scd#c1~3
|
||||
```
|
||||
Doesn't change the final number of channels though, just mutes non-selected channels.
|
||||
|
||||
|
||||
### Custom play settings
|
||||
**`#l(loops)`**, **`#f(fade)`**, **`#d(fade-delay)`**, **`#i(ignore loop)`**, **`#F(ignore fade)`**, **`#E(end-to-end loop)`**
|
||||
|
||||
Those setting should override player's defaults if set (except "loop forever"). They are equivalent to some test.exe options.
|
||||
|
||||
__God Hand (PS2)__: _boss2_3ningumi_ver6.txtp_ (each line is a separate TXTP)
|
||||
@ -139,11 +165,9 @@ boss2_3ningumi_ver6.adx#l1.5#d1#f5
|
||||
# boss2_3ningumi_ver6.adx#l1.0#F # this is equivalent to #i
|
||||
```
|
||||
|
||||
For segments and layers the first file defines looping options.
|
||||
|
||||
|
||||
### Force sample rate
|
||||
A few games set a sample rate value in the header but actually play with other (applying some of pitch or just forcing it)
|
||||
**`#h(sample rate)`**: for a few games that set a sample rate value in the header but actually play with other (applying some of pitch or just forcing it).
|
||||
|
||||
__Super Paper Mario (Wii)__
|
||||
```
|
||||
@ -155,15 +179,7 @@ ptp_btl_bgm_voice.sgd#s1#h11050
|
||||
```
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
## OTHER FEATURES
|
||||
|
||||
### Default commands
|
||||
You can set defaults that apply to the *resulting* file. This has subtle differences vs per-file config:
|
||||
@ -189,32 +205,94 @@ As it applies at the end, some options with ambiguous or technically hard to han
|
||||
bgm.sxd2
|
||||
bgm.sxd2
|
||||
|
||||
# ignored (resulting file has no subsongs, should apply to all?)
|
||||
# ignored (resulting file has no subsongs, or should apply to all?)
|
||||
commands = #s12
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
## TXTP PARSING ISSUES
|
||||
*Commands* can be chained, but must not be separated by a space (everything after space may be ignored):
|
||||
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.
|
||||
|
||||
|
||||
### TXTP parsing
|
||||
*Filenames* may be anything accepted by the file system, including spaces and symbols, and multiple *commands* can be chained:
|
||||
```
|
||||
bgm bank.sxd2#s12#c1,2 #spaces + comment after commands is ignored
|
||||
```
|
||||
```
|
||||
#commands after spaces are seen as comments and ignored
|
||||
BGM01_BEGIN.VAG #c1,2
|
||||
BGM01_LOOPED.VAG #c1,2
|
||||
bgm bank#s2#c1,2
|
||||
```
|
||||
|
||||
However *values* found after *=* allow spaces until value start, and until next space:
|
||||
You may add spaces as needed (but try to keep it simple and don't go overboard), though commands *must* start with `#(command)` (`#(space)(anything)` is a comment). Commands without corresponding file are ignored too (seen as comments too), while incorrect commands are ignored and skip to next, though the parser may try to make something usable of them (this may be change anytime without warning):
|
||||
```
|
||||
bgm.sxd2#s12
|
||||
loop_start_segment = 1 #spaces surrounding value are ignored
|
||||
# those are all equivalent
|
||||
song#s2#c1,2
|
||||
song #s2#c1,2 # comment
|
||||
song #s 2 #c1,2# comment
|
||||
song #s 2 #c 1 , 2# comment
|
||||
|
||||
#s2 #ignores rogue commands/comments
|
||||
|
||||
# seen as incorrect and ignored
|
||||
song #s TWO
|
||||
song #E enable
|
||||
song #E 1
|
||||
song #Enable
|
||||
song #h -48000
|
||||
|
||||
# accepted
|
||||
song #E # comment
|
||||
song #c1, 2, 3
|
||||
song #c 1 2 3
|
||||
|
||||
# ignores first and reads second
|
||||
song #s TWO#c1,2
|
||||
|
||||
# seen as #s1#c1,2
|
||||
song #s 1,2 #c1,2
|
||||
|
||||
# all seen as #h48000
|
||||
song #h48000
|
||||
song #h 48000hz
|
||||
song #h 48000mhz
|
||||
|
||||
# ignored
|
||||
song #h hz48000
|
||||
|
||||
# ignored as channels don't go that high (may be modified on request)
|
||||
song #c32,41
|
||||
|
||||
# swaps 1 with 2
|
||||
song #m1-2
|
||||
song #m 1 - 2
|
||||
|
||||
# swaps 1 with "-2", ignored
|
||||
song #m1 -2
|
||||
```
|
||||
|
||||
*Values* found after *=* allow spaces as well:
|
||||
```
|
||||
bgm.sxd2
|
||||
commands = #s12#c1,2 #must not have spaces once value starts until end
|
||||
song#s2
|
||||
loop_start_segment = 1 #s2# #commands here are ignored
|
||||
|
||||
song
|
||||
commands=#s2 # commands here are allowed
|
||||
commands= #c1,2
|
||||
```
|
||||
The parser is very simplistic and fairly lax, though may be erratic with edge cases or behave unexpectedly due to unforeseen use-cases and bugs. As filenames may contain spaces or #, certain name patterns could fool it too. Keep in mind this while making .txtp files.
|
||||
|
||||
Repeated commands overwrite previous setting, except comma-separated commands that are additive:
|
||||
```
|
||||
# overwrites, equivalent to #s2
|
||||
song#s1#s2
|
||||
|
||||
# adds, equivalent to #m1-2,3-4,5-6
|
||||
song#m1-2#m3-4
|
||||
commands = #m5-6
|
||||
```
|
||||
|
||||
The parser is fairly simplistic and lax, and may be erratic with edge cases or behave unexpectedly due to unforeseen use-cases and bugs. As filenames may contain spaces or #, certain name patterns could fool it too. Keep in mind this while making .txtp files.
|
||||
|
||||
|
||||
## MINI-TXTP
|
||||
|
@ -22,6 +22,7 @@ void decode_otns_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample *
|
||||
void decode_wv6_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_alp_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_ffta2_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_blitz_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
|
||||
void decode_ms_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel);
|
||||
void decode_ref_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel);
|
||||
@ -295,7 +296,7 @@ ffmpeg_codec_data * init_ffmpeg_ue4_opus(STREAMFILE *streamFile, off_t start_off
|
||||
ffmpeg_codec_data * init_ffmpeg_ea_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
|
||||
ffmpeg_codec_data * init_ffmpeg_x_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
|
||||
|
||||
size_t switch_opus_get_samples(off_t offset, size_t data_size, STREAMFILE *streamFile);
|
||||
size_t switch_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *streamFile);
|
||||
|
||||
size_t switch_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile);
|
||||
size_t ue4_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile);
|
||||
|
@ -523,9 +523,9 @@ static size_t get_xopus_packet_size(int packet, STREAMFILE * streamfile) {
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
|
||||
static size_t custom_opus_get_samples(off_t offset, size_t data_size, STREAMFILE *streamFile, opus_type_t type) {
|
||||
static size_t custom_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *streamFile, opus_type_t type) {
|
||||
size_t num_samples = 0;
|
||||
off_t end_offset = offset + data_size;
|
||||
off_t end_offset = offset + stream_size;
|
||||
int packet = 0;
|
||||
|
||||
if (end_offset > get_streamfile_size(streamFile)) {
|
||||
@ -569,8 +569,8 @@ static size_t custom_opus_get_samples(off_t offset, size_t data_size, STREAMFILE
|
||||
return num_samples;
|
||||
}
|
||||
|
||||
size_t switch_opus_get_samples(off_t offset, size_t data_size, STREAMFILE *streamFile) {
|
||||
return custom_opus_get_samples(offset, data_size, streamFile, OPUS_SWITCH);
|
||||
size_t switch_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *streamFile) {
|
||||
return custom_opus_get_samples(offset, stream_size, streamFile, OPUS_SWITCH);
|
||||
}
|
||||
|
||||
|
||||
|
@ -236,6 +236,33 @@ static void ffta2_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset
|
||||
if (*step_index > 88) *step_index=88;
|
||||
}
|
||||
|
||||
/* Yet another IMA expansion, from the exe */
|
||||
static void blitz_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index) {
|
||||
int sample_nibble, sample_decoded, step, delta;
|
||||
|
||||
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf; /* ADPCM code */
|
||||
sample_decoded = *hist1; /* predictor value */
|
||||
step = ADPCMTable[*step_index]; /* current step */
|
||||
|
||||
/* table has 2 different values, not enough to bother adding the full table */
|
||||
if (step == 22385)
|
||||
step = 22358;
|
||||
else if (step == 24623)
|
||||
step = 24633;
|
||||
|
||||
delta = (sample_nibble & 0x07);
|
||||
if (sample_nibble & 8) delta = -delta;
|
||||
delta = (step >> 1) + delta * step; /* custom */
|
||||
sample_decoded += delta;
|
||||
|
||||
/* somehow the exe tries to clamp hist, but actually doesn't (bug?),
|
||||
* not sure if pcm buffer would be clamped outside though */
|
||||
*hist1 = sample_decoded;//clamp16(sample_decoded);
|
||||
*step_index += IMA_IndexTable[sample_nibble];
|
||||
if (*step_index < 0) *step_index=0;
|
||||
if (*step_index > 88) *step_index=88;
|
||||
}
|
||||
|
||||
/* ************************************ */
|
||||
/* DVI/IMA */
|
||||
/* ************************************ */
|
||||
@ -403,6 +430,28 @@ void decode_ffta2_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspa
|
||||
stream->adpcm_step_index = step_index;
|
||||
}
|
||||
|
||||
/* Blitz IMA, IMA with custom nibble expand */
|
||||
void decode_blitz_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
int i, sample_count;
|
||||
int32_t hist1 = stream->adpcm_history1_32;
|
||||
int step_index = stream->adpcm_step_index;
|
||||
|
||||
//external interleave
|
||||
|
||||
//no header
|
||||
|
||||
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
|
||||
off_t byte_offset = stream->offset + i/2;
|
||||
int nibble_shift = (i&1?4:0); //low nibble first
|
||||
|
||||
blitz_ima_expand_nibble(stream, byte_offset,nibble_shift, &hist1, &step_index);
|
||||
outbuf[sample_count] = (short)(hist1);
|
||||
}
|
||||
|
||||
stream->adpcm_history1_32 = hist1;
|
||||
stream->adpcm_step_index = step_index;
|
||||
}
|
||||
|
||||
/* ************************************ */
|
||||
/* MS-IMA */
|
||||
/* ************************************ */
|
||||
@ -948,8 +997,8 @@ void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci
|
||||
offset += 0x10 + 0x08;
|
||||
if (version >= 3)
|
||||
offset += 0x04;
|
||||
//if (version >= 6) /* supposedly this exists, maybe in later BAOs */
|
||||
// offset += 0x08;
|
||||
if (version >= 6) /* later BAOs */
|
||||
offset += 0x08;
|
||||
|
||||
/* write PCM samples, must be written to match header's num_samples (hist mustn't) */
|
||||
max_samples_to_do = ((samples_to_do > header_samples) ? header_samples : samples_to_do);
|
||||
|
@ -506,7 +506,6 @@ static int ealayer3_rebuild_mpeg_frame(vgm_bitstream* is_0, ealayer3_frame_info*
|
||||
is_0->b_off = eaf_0->data_offset_b;
|
||||
for (i = 0; i < eaf_0->channels; i++) { /* granule0 */
|
||||
for (j = 0; j < eaf_0->main_data_size[i]; j++) {
|
||||
uint32_t c = 0;
|
||||
r_bits(is_0, 1, &c);
|
||||
w_bits(os, 1, c);
|
||||
}
|
||||
|
@ -221,5 +221,5 @@ void decode_pcmfloat(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspac
|
||||
}
|
||||
|
||||
size_t pcm_bytes_to_samples(size_t bytes, int channels, int bits_per_sample) {
|
||||
return (bytes * 8) / channels / bits_per_sample;
|
||||
return ((int64_t)bytes * 8) / channels / bits_per_sample;
|
||||
}
|
||||
|
@ -609,6 +609,7 @@ static const coding_info coding_info_list[] = {
|
||||
{coding_WV6_IMA, "Gorilla Systems WV6 4-bit IMA ADPCM"},
|
||||
{coding_ALP_IMA, "High Voltage ALP 4-bit IMA ADPCM"},
|
||||
{coding_FFTA2_IMA, "Final Fantasy Tactics A2 4-bit IMA ADPCM"},
|
||||
{coding_BLITZ_IMA, "Blitz Games 4-bit IMA ADPCM"},
|
||||
|
||||
{coding_MS_IMA, "Microsoft 4-bit IMA ADPCM"},
|
||||
{coding_XBOX_IMA, "XBOX 4-bit IMA ADPCM"},
|
||||
@ -699,7 +700,6 @@ static const layout_info layout_info_list[] = {
|
||||
|
||||
{layout_segmented, "segmented"},
|
||||
{layout_layered, "layered"},
|
||||
{layout_aix, "AIX"},
|
||||
|
||||
{layout_blocked_mxch, "blocked (MxCh)"},
|
||||
{layout_blocked_ast, "blocked (AST)"},
|
||||
@ -902,8 +902,8 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_RSD6WMA, "Radical RSD6/WMA header"},
|
||||
{meta_DC_ASD, "ASD Header"},
|
||||
{meta_NAOMI_SPSD, "Naomi SPSD header"},
|
||||
{meta_FFXI_BGW, "BGW BGMStream header"},
|
||||
{meta_FFXI_SPW, "SPW SeWave header"},
|
||||
{meta_FFXI_BGW, "Square Enix .BGW header"},
|
||||
{meta_FFXI_SPW, "Square Enix .SPW header"},
|
||||
{meta_PS2_ASS, "SystemSoft .ASS header"},
|
||||
{meta_NUB_IDSP, "Namco NUB IDSP header"},
|
||||
{meta_IDSP_NL, "Next Level IDSP header"},
|
||||
@ -996,7 +996,7 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_PS2_B1S, "B1S header"},
|
||||
{meta_PS2_WAD, "WAD header"},
|
||||
{meta_DSP_XIII, "XIII dsp header"},
|
||||
{meta_DSP_CABELAS, "Cabelas games dsp header"},
|
||||
{meta_DSP_CABELAS, "Cabelas games .DSP header"},
|
||||
{meta_PS2_ADM, "Dragon Quest V .ADM raw header"},
|
||||
{meta_PS2_LPCM, "LPCM header"},
|
||||
{meta_PS2_VMS, "VMS Header"},
|
||||
@ -1172,6 +1172,7 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_DSF, "Ocean DSF header"},
|
||||
{meta_208, "Ocean .208 header"},
|
||||
{meta_DSP_DS2, "LucasArts .DS2 header"},
|
||||
{meta_MUS_VC, "Vicious Cycle .MUS header"},
|
||||
|
||||
};
|
||||
|
||||
|
@ -1,92 +0,0 @@
|
||||
#include "layout.h"
|
||||
#include "../vgmstream.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
void render_vgmstream_aix(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
|
||||
int samples_written=0;
|
||||
aix_codec_data *data = vgmstream->codec_data;
|
||||
|
||||
while (samples_written<sample_count) {
|
||||
int samples_to_do;
|
||||
int samples_this_block = data->sample_counts[data->current_segment];
|
||||
int current_stream;
|
||||
int channels_sofar = 0;
|
||||
|
||||
if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) {
|
||||
data->current_segment = 1;
|
||||
for (current_stream = 0; current_stream < data->stream_count; current_stream++)
|
||||
{
|
||||
int i;
|
||||
reset_vgmstream(data->adxs[data->current_segment*data->stream_count+current_stream]);
|
||||
|
||||
/* carry over the history from the loop point */
|
||||
for (i=0;i<data->adxs[data->stream_count+current_stream]->channels;i++)
|
||||
{
|
||||
data->adxs[1*data->stream_count+current_stream]->ch[i].adpcm_history1_32 =
|
||||
data->adxs[0+current_stream]->ch[i].adpcm_history1_32;
|
||||
data->adxs[1*data->stream_count+current_stream]->ch[i].adpcm_history2_32 =
|
||||
data->adxs[0+current_stream]->ch[i].adpcm_history2_32;
|
||||
}
|
||||
}
|
||||
vgmstream->samples_into_block = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
samples_to_do = vgmstream_samples_to_do(samples_this_block, 1, vgmstream);
|
||||
|
||||
/*printf("samples_to_do=%d,samples_this_block=%d,samples_written=%d,sample_count=%d\n",samples_to_do,samples_this_block,samples_written,sample_count);*/
|
||||
|
||||
if (samples_written+samples_to_do > sample_count)
|
||||
samples_to_do=sample_count-samples_written;
|
||||
|
||||
if (samples_to_do == 0)
|
||||
{
|
||||
int i;
|
||||
data->current_segment++;
|
||||
/*printf("next %d, %d samples\n",data->current_file,data->files[data->current_file]->total_values/data->files[data->current_file]->info.channels);*/
|
||||
for (current_stream = 0; current_stream < data->stream_count; current_stream++)
|
||||
{
|
||||
reset_vgmstream(data->adxs[data->current_segment*data->stream_count+current_stream]);
|
||||
|
||||
/* carry over the history from the previous segment */
|
||||
for (i=0;i<data->adxs[data->current_segment*data->stream_count+current_stream]->channels;i++)
|
||||
{
|
||||
data->adxs[data->current_segment*data->stream_count+current_stream]->ch[i].adpcm_history1_32 =
|
||||
data->adxs[(data->current_segment-1)*data->stream_count+current_stream]->ch[i].adpcm_history1_32;
|
||||
data->adxs[data->current_segment*data->stream_count+current_stream]->ch[i].adpcm_history2_32 =
|
||||
data->adxs[(data->current_segment-1)*data->stream_count+current_stream]->ch[i].adpcm_history2_32;
|
||||
}
|
||||
}
|
||||
vgmstream->samples_into_block = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*printf("decode %d samples file %d\n",samples_to_do,data->current_file);*/
|
||||
if (samples_to_do > AIX_BUFFER_SIZE/2)
|
||||
{
|
||||
samples_to_do = AIX_BUFFER_SIZE/2;
|
||||
}
|
||||
|
||||
for (current_stream = 0; current_stream < data->stream_count; current_stream++)
|
||||
{
|
||||
int i,j;
|
||||
VGMSTREAM *adx = data->adxs[data->current_segment*data->stream_count+current_stream];
|
||||
|
||||
render_vgmstream(data->buffer,samples_to_do,adx);
|
||||
|
||||
for (i = 0; i < samples_to_do; i++)
|
||||
{
|
||||
for (j = 0; j < adx->channels; j++)
|
||||
{
|
||||
buffer[(i+samples_written)*vgmstream->channels+channels_sofar+j] = data->buffer[i*adx->channels+j];
|
||||
}
|
||||
}
|
||||
|
||||
channels_sofar += adx->channels;
|
||||
}
|
||||
|
||||
samples_written += samples_to_do;
|
||||
vgmstream->current_sample += samples_to_do;
|
||||
vgmstream->samples_into_block+=samples_to_do;
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
/* Decodes samples for blocked streams.
|
||||
* Data is divided into headered blocks with a bunch of data. The layout calls external helper functions
|
||||
* when a block is decoded, and those must parse the new block and move offsets accordingly. */
|
||||
void render_vgmstream_blocked(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
|
||||
void render_vgmstream_blocked(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
|
||||
int samples_written = 0;
|
||||
int frame_size, samples_per_frame, samples_this_block;
|
||||
|
||||
@ -41,14 +41,14 @@ void render_vgmstream_blocked(sample * buffer, int32_t sample_count, VGMSTREAM *
|
||||
if (samples_this_block < 0) {
|
||||
/* probably block bug or EOF, next calcs would give wrong values/segfaults/infinite loop */
|
||||
VGM_LOG("layout_blocked: wrong block samples at 0x%x\n", (uint32_t)vgmstream->current_block_offset);
|
||||
memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample));
|
||||
memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t));
|
||||
break;
|
||||
}
|
||||
|
||||
if (vgmstream->current_block_offset < 0 || vgmstream->current_block_offset == 0xFFFFFFFF) {
|
||||
/* probably block bug or EOF, block functions won't be able to read anything useful/infinite loop */
|
||||
VGM_LOG("layout_blocked: wrong block offset found\n");
|
||||
memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample));
|
||||
memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@ void block_update_wsi(off_t block_offset, VGMSTREAM * vgmstream) {
|
||||
STREAMFILE* streamFile = vgmstream->ch[0].streamfile;
|
||||
int i;
|
||||
off_t channel_block_size;
|
||||
//int is_first_offset =
|
||||
|
||||
|
||||
/* assume that all channels have the same size for this block */
|
||||
@ -22,8 +21,6 @@ void block_update_wsi(off_t block_offset, VGMSTREAM * vgmstream) {
|
||||
|
||||
/* first block has DSP header, remove */
|
||||
if (block_offset == vgmstream->ch[0].channel_start_offset) {
|
||||
int i;
|
||||
|
||||
vgmstream->current_block_size -= 0x60;
|
||||
for (i = 0; i < vgmstream->channels; i++) {
|
||||
vgmstream->ch[i].offset += 0x60;
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
/* Decodes samples for flat streams.
|
||||
* Data forms a single stream, and the decoder may internally skip chunks and move offsets as needed. */
|
||||
void render_vgmstream_flat(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
|
||||
void render_vgmstream_flat(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
|
||||
int samples_written = 0;
|
||||
int samples_per_frame, samples_this_block;
|
||||
|
||||
@ -26,7 +26,7 @@ void render_vgmstream_flat(sample * buffer, int32_t sample_count, VGMSTREAM * vg
|
||||
|
||||
if (samples_to_do == 0) {
|
||||
VGM_LOG("layout_flat: wrong samples_to_do found\n");
|
||||
memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample));
|
||||
memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
* Data has interleaved chunks per channel, and once one is decoded the layout moves offsets,
|
||||
* skipping other chunks (essentially a simplified variety of blocked layout).
|
||||
* Incompatible with decoders that move offsets. */
|
||||
void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
|
||||
void render_vgmstream_interleave(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
|
||||
int samples_written = 0;
|
||||
int frame_size, samples_per_frame, samples_this_block;
|
||||
int has_interleave_last = vgmstream->interleave_last_block_size && vgmstream->channels > 1;
|
||||
@ -49,7 +49,7 @@ void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREA
|
||||
|
||||
if (samples_to_do == 0) { /* happens when interleave is not set */
|
||||
VGM_LOG("layout_interleave: wrong samples_to_do found\n");
|
||||
memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample));
|
||||
memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -5,15 +5,17 @@
|
||||
/* NOTE: if loop settings change the layered vgmstreams must be notified (preferably using vgmstream_force_loop) */
|
||||
#define LAYER_BUF_SIZE 512
|
||||
#define LAYER_MAX_CHANNELS 6 /* at least 2, but let's be generous */
|
||||
#define VGMSTREAM_MAX_LAYERS 255
|
||||
|
||||
|
||||
/* Decodes samples for layered streams.
|
||||
* Similar to interleave layout, but decodec samples are mixed from complete vgmstreams, each
|
||||
* with custom codecs and different number of channels, creating a single super-vgmstream.
|
||||
* Usually combined with custom streamfiles to handle data interleaved in weird ways. */
|
||||
void render_vgmstream_layered(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
|
||||
void render_vgmstream_layered(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
|
||||
int samples_written = 0;
|
||||
layered_layout_data *data = vgmstream->layout_data;
|
||||
sample interleave_buf[LAYER_BUF_SIZE*LAYER_MAX_CHANNELS];
|
||||
sample_t interleave_buf[LAYER_BUF_SIZE*LAYER_MAX_CHANNELS];
|
||||
|
||||
|
||||
while (samples_written < sample_count) {
|
||||
@ -53,7 +55,7 @@ void render_vgmstream_layered(sample * buffer, int32_t sample_count, VGMSTREAM *
|
||||
layered_layout_data* init_layout_layered(int layer_count) {
|
||||
layered_layout_data *data = NULL;
|
||||
|
||||
if (layer_count <= 0 || layer_count > 255)
|
||||
if (layer_count <= 0 || layer_count > VGMSTREAM_MAX_LAYERS)
|
||||
goto fail;
|
||||
|
||||
data = calloc(1, sizeof(layered_layout_data));
|
||||
@ -131,3 +133,41 @@ void reset_layout_layered(layered_layout_data *data) {
|
||||
reset_vgmstream(data->layers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* helper for easier creation of layers */
|
||||
VGMSTREAM *allocate_layered_vgmstream(layered_layout_data* data) {
|
||||
VGMSTREAM *vgmstream;
|
||||
int i, channels, loop_flag;
|
||||
|
||||
/* get data */
|
||||
channels = 0;
|
||||
loop_flag = 1;
|
||||
for (i = 0; i < data->layer_count; i++) {
|
||||
channels += data->layers[i]->channels;
|
||||
|
||||
if (loop_flag && !data->layers[i]->loop_flag)
|
||||
loop_flag = 0;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = data->layers[0]->meta_type;
|
||||
vgmstream->sample_rate = data->layers[0]->sample_rate;
|
||||
vgmstream->num_samples = data->layers[0]->num_samples;
|
||||
vgmstream->loop_start_sample = data->layers[0]->loop_start_sample;
|
||||
vgmstream->loop_end_sample = data->layers[0]->loop_end_sample;
|
||||
vgmstream->coding_type = data->layers[0]->coding_type;
|
||||
|
||||
vgmstream->layout_type = layout_layered;
|
||||
vgmstream->layout_data = data;
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
if (vgmstream) vgmstream->layout_data = NULL;
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include "../vgmstream.h"
|
||||
|
||||
/* blocked layouts */
|
||||
void render_vgmstream_blocked(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
|
||||
void render_vgmstream_blocked(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
|
||||
void block_update(off_t block_offset, VGMSTREAM * vgmstream);
|
||||
|
||||
void block_update_ast(off_t block_ofset, VGMSTREAM * vgmstream);
|
||||
@ -48,22 +48,22 @@ void block_update_xa_aiff(off_t block_offset, VGMSTREAM * vgmstream);
|
||||
void block_update_vs_square(off_t block_offset, VGMSTREAM * vgmstream);
|
||||
|
||||
/* other layouts */
|
||||
void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
|
||||
void render_vgmstream_interleave(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
|
||||
|
||||
void render_vgmstream_flat(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
|
||||
void render_vgmstream_flat(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
|
||||
|
||||
void render_vgmstream_aix(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
|
||||
|
||||
void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
|
||||
void render_vgmstream_segmented(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
|
||||
segmented_layout_data* init_layout_segmented(int segment_count);
|
||||
int setup_layout_segmented(segmented_layout_data* data);
|
||||
void free_layout_segmented(segmented_layout_data *data);
|
||||
void reset_layout_segmented(segmented_layout_data *data);
|
||||
VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment);
|
||||
|
||||
void render_vgmstream_layered(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
|
||||
void render_vgmstream_layered(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
|
||||
layered_layout_data* init_layout_layered(int layer_count);
|
||||
int setup_layout_layered(layered_layout_data* data);
|
||||
void free_layout_layered(layered_layout_data *data);
|
||||
void reset_layout_layered(layered_layout_data *data);
|
||||
VGMSTREAM *allocate_layered_vgmstream(layered_layout_data* data);
|
||||
|
||||
#endif
|
||||
|
@ -2,10 +2,13 @@
|
||||
#include "../vgmstream.h"
|
||||
|
||||
|
||||
#define VGMSTREAM_MAX_SEGMENTS 255
|
||||
|
||||
|
||||
/* Decodes samples for segmented streams.
|
||||
* Chains together sequential vgmstreams, for data divided into separate sections or files
|
||||
* (like one part for intro and other for loop segments, which may even use different codecs). */
|
||||
void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
|
||||
void render_vgmstream_segmented(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
|
||||
int samples_written = 0, loop_samples_skip = 0;
|
||||
segmented_layout_data *data = vgmstream->layout_data;
|
||||
|
||||
@ -85,7 +88,7 @@ void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM
|
||||
segmented_layout_data* init_layout_segmented(int segment_count) {
|
||||
segmented_layout_data *data = NULL;
|
||||
|
||||
if (segment_count <= 0 || segment_count > 255)
|
||||
if (segment_count <= 0 || segment_count > VGMSTREAM_MAX_SEGMENTS)
|
||||
goto fail;
|
||||
|
||||
data = calloc(1, sizeof(segmented_layout_data));
|
||||
@ -169,3 +172,46 @@ void reset_layout_segmented(segmented_layout_data *data) {
|
||||
reset_vgmstream(data->segments[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* helper for easier creation of segments */
|
||||
VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment) {
|
||||
VGMSTREAM *vgmstream;
|
||||
int i, num_samples, loop_start, loop_end;
|
||||
|
||||
/* get data */
|
||||
num_samples = 0;
|
||||
loop_start = 0;
|
||||
loop_end = 0;
|
||||
for (i = 0; i < data->segment_count; i++) {
|
||||
if (loop_flag && i == loop_start_segment)
|
||||
loop_start = num_samples;
|
||||
|
||||
num_samples += data->segments[i]->num_samples;
|
||||
|
||||
if (loop_flag && i == loop_end_segment)
|
||||
loop_end = num_samples;
|
||||
}
|
||||
|
||||
/* respect loop_flag even when no loop_end found as it's possible file loops are set outside */
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(data->segments[0]->channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = data->segments[0]->meta_type;
|
||||
vgmstream->sample_rate = data->segments[0]->sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
vgmstream->coding_type = data->segments[0]->coding_type;
|
||||
|
||||
vgmstream->layout_type = layout_segmented;
|
||||
vgmstream->layout_data = data;
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
if (vgmstream) vgmstream->layout_data = NULL;
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -707,6 +707,10 @@
|
||||
<File
|
||||
RelativePath=".\meta\msvp.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\mus_vc.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\mus_acm.c"
|
||||
@ -1305,6 +1309,10 @@
|
||||
<File
|
||||
RelativePath=".\meta\fag.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\ffdl.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\psx_gms.c"
|
||||
@ -1990,10 +1998,6 @@
|
||||
RelativePath=".\layout\segmented.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\layout\aix_layout.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\layout\blocked_ast.c"
|
||||
>
|
||||
|
@ -288,6 +288,7 @@
|
||||
<ClCompile Include="meta\mc3.c" />
|
||||
<ClCompile Include="meta\mca.c" />
|
||||
<ClCompile Include="meta\msvp.c" />
|
||||
<ClCompile Include="meta\mus_vc.c" />
|
||||
<ClCompile Include="meta\mus_acm.c">
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)1.xdc</XMLDocumentationFileName>
|
||||
@ -415,6 +416,7 @@
|
||||
<ClCompile Include="meta\ps3_mta2.c" />
|
||||
<ClCompile Include="meta\xa.c" />
|
||||
<ClCompile Include="meta\fag.c" />
|
||||
<ClCompile Include="meta\ffdl.c" />
|
||||
<ClCompile Include="meta\psx_gms.c" />
|
||||
<ClCompile Include="meta\ea_swvr.c" />
|
||||
<ClCompile Include="meta\raw.c" />
|
||||
@ -547,7 +549,6 @@
|
||||
<ClCompile Include="coding\xa_decoder.c" />
|
||||
<ClCompile Include="coding\xmd_decoder.c" />
|
||||
<ClCompile Include="layout\segmented.c" />
|
||||
<ClCompile Include="layout\aix_layout.c" />
|
||||
<ClCompile Include="layout\blocked_ast.c" />
|
||||
<ClCompile Include="layout\blocked_bdsp.c" />
|
||||
<ClCompile Include="layout\blocked.c" />
|
||||
|
@ -439,6 +439,9 @@
|
||||
<ClCompile Include="meta\mus_acm.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\mus_vc.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\musc.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -802,6 +805,9 @@
|
||||
<ClCompile Include="meta\fag.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\ffdl.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\psx_gms.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -1180,9 +1186,6 @@
|
||||
<ClCompile Include="layout\segmented.c">
|
||||
<Filter>layout\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="layout\aix_layout.c">
|
||||
<Filter>layout\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="layout\blocked_ast.c">
|
||||
<Filter>layout\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
305
src/meta/aix.c
305
src/meta/aix.c
@ -1,200 +1,195 @@
|
||||
#include "meta.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "aix_streamfile.h"
|
||||
|
||||
/* AIX - interleaved AAX, N segments with M layers (1/2ch) inside [SoulCalibur IV (PS3), Dragon Ball Z: Burst Limit (PS3)] */
|
||||
VGMSTREAM * init_vgmstream_aix(STREAMFILE *streamFile) {
|
||||
|
||||
#define MAX_SEGMENTS 50 /* usually segment0=intro, segment1=loop/main, sometimes ~5, rarely ~40 */
|
||||
|
||||
static VGMSTREAM *build_segmented_vgmstream(STREAMFILE *streamFile, off_t *segment_offsets, size_t *segment_sizes, int32_t *segment_samples, int segment_count, int layer_count);
|
||||
|
||||
/* AIX - N segments with M layers (2ch ADX) inside [SoulCalibur IV (PS3), Dragon Ball Z: Burst Limit (PS3)] */
|
||||
VGMSTREAM * init_vgmstream_aix(STREAMFILE *sf) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE * streamFileAIX = NULL;
|
||||
int loop_flag = 0, channel_count, sample_rate;
|
||||
int32_t sample_count, loop_start_sample = 0, loop_end_sample = 0;
|
||||
|
||||
off_t *segment_offset = NULL;
|
||||
int32_t *segment_samples = NULL;
|
||||
aix_codec_data *data = NULL;
|
||||
off_t data_offset;
|
||||
off_t layer_list_offset;
|
||||
off_t layer_list_end;
|
||||
const off_t segment_list_offset = 0x20;
|
||||
const size_t segment_list_entry_size = 0x10;
|
||||
const size_t layer_list_entry_size = 0x08;
|
||||
off_t segment_offsets[MAX_SEGMENTS] = {0};
|
||||
size_t segment_sizes[MAX_SEGMENTS] = {0};
|
||||
int32_t segment_samples[MAX_SEGMENTS] = {0};
|
||||
int segment_rates[MAX_SEGMENTS] = {0};
|
||||
|
||||
off_t data_offset, subtable_offset;
|
||||
int segment_count, layer_count;
|
||||
int i, j;
|
||||
int i;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "aix"))
|
||||
if (!check_extensions(sf, "aix"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x41495846 || /* "AIXF" */
|
||||
read_32bitBE(0x08,streamFile) != 0x01000014 || /* version? */
|
||||
read_32bitBE(0x0c,streamFile) != 0x00000800)
|
||||
if (read_u32be(0x00,sf) != 0x41495846 || /* "AIXF" */
|
||||
read_u32be(0x08,sf) != 0x01000014 || /* version? */
|
||||
read_u32be(0x0c,sf) != 0x00000800)
|
||||
goto fail;
|
||||
|
||||
/* AIX combine layers for multichannel and segments for looping, all very hacky.
|
||||
* For some reason AIX with 1 layer and 1 segment exist (equivalent to a single ADX). */
|
||||
|
||||
/* base segment header */
|
||||
data_offset = read_32bitBE(0x04,streamFile)+0x08;
|
||||
|
||||
segment_count = (uint16_t)read_16bitBE(0x18,streamFile);
|
||||
if (segment_count < 1) goto fail;
|
||||
|
||||
layer_list_offset = segment_list_offset + segment_count*segment_list_entry_size + 0x10;
|
||||
if (layer_list_offset >= data_offset) goto fail;
|
||||
|
||||
segment_samples = calloc(segment_count,sizeof(int32_t));
|
||||
if (!segment_samples) goto fail;
|
||||
segment_offset = calloc(segment_count,sizeof(off_t));
|
||||
if (!segment_offset) goto fail;
|
||||
data_offset = read_s32be(0x04,sf) + 0x08;
|
||||
|
||||
/* parse segments table */
|
||||
{
|
||||
sample_rate = read_32bitBE(layer_list_offset+0x08,streamFile); /* first layer's sample rate */
|
||||
const off_t segment_list_offset = 0x20;
|
||||
const size_t segment_list_entry_size = 0x10;
|
||||
|
||||
segment_count = read_u16be(0x18,sf);
|
||||
if (segment_count < 1 || segment_count > MAX_SEGMENTS) goto fail;
|
||||
|
||||
subtable_offset = segment_list_offset + segment_count*segment_list_entry_size;
|
||||
if (subtable_offset >= data_offset) goto fail;
|
||||
|
||||
for (i = 0; i < segment_count; i++) {
|
||||
segment_offset[i] = read_32bitBE(segment_list_offset + segment_list_entry_size*i + 0x00,streamFile);
|
||||
/* 0x04: segment size */
|
||||
segment_samples[i] = read_32bitBE(segment_list_offset + segment_list_entry_size*i + 0x08,streamFile);
|
||||
segment_offsets[i] = read_s32be(segment_list_offset + segment_list_entry_size*i + 0x00,sf);
|
||||
segment_sizes[i] = read_u32be(segment_list_offset + segment_list_entry_size*i + 0x04,sf);
|
||||
segment_samples[i] = read_s32be(segment_list_offset + segment_list_entry_size*i + 0x08,sf);
|
||||
segment_rates[i] = read_s32be(segment_list_offset + segment_list_entry_size*i + 0x0c,sf);
|
||||
|
||||
/* segments > 0 can have 0 sample rate, seems to indicate same as first
|
||||
* [Ryu ga Gotoku: Kenzan! (PS3) tenkei_sng1.aix] */
|
||||
if (i > 0 && segment_rates[i] == 0)
|
||||
segment_rates[i] = segment_rates[0];
|
||||
|
||||
/* all segments must have equal sample rate */
|
||||
if (read_32bitBE(segment_list_offset + segment_list_entry_size*i + 0x0c,streamFile) != sample_rate) {
|
||||
/* segments > 0 can have 0 sample rate (Ryu ga gotoku: Kenzan! tenkei_sng1.aix),
|
||||
seems to indicate same sample rate as first */
|
||||
if (!(i > 0 && read_32bitBE(segment_list_offset + segment_list_entry_size*i + 0x0c,streamFile) == 0))
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (segment_offset[0] != data_offset)
|
||||
if (segment_rates[i] != segment_rates[0])
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* base layer header */
|
||||
layer_count = (uint8_t)read_8bit(layer_list_offset,streamFile);
|
||||
if (segment_offsets[0] != data_offset)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* between the segment and layer table some kind of 0x10 subtable? */
|
||||
if (read_u8(subtable_offset,sf) != 0x01)
|
||||
goto fail;
|
||||
|
||||
/* parse layers table */
|
||||
{
|
||||
const size_t layer_list_entry_size = 0x08;
|
||||
off_t layer_list_offset, layer_list_end;
|
||||
|
||||
layer_list_offset = subtable_offset + 0x10;
|
||||
if (layer_list_offset >= data_offset) goto fail;
|
||||
|
||||
layer_count = read_u8(layer_list_offset,sf);
|
||||
if (layer_count < 1) goto fail;
|
||||
|
||||
layer_list_end = layer_list_offset + 0x08 + layer_count*layer_list_entry_size;
|
||||
if (layer_list_end >= data_offset) goto fail;
|
||||
|
||||
/* parse layers table */
|
||||
channel_count = 0;
|
||||
for (i = 0; i < layer_count; i++) {
|
||||
/* all streams must have same samplerate as segments */
|
||||
if (read_32bitBE(layer_list_offset + 0x08 + i*layer_list_entry_size + 0x00,streamFile) != sample_rate)
|
||||
/* all layers must have same sample rate as segments */
|
||||
if (read_s32be(layer_list_offset + 0x08 + i*layer_list_entry_size + 0x00,sf) != segment_rates[0])
|
||||
goto fail;
|
||||
channel_count += read_8bit(layer_list_offset + 0x08 + i*layer_list_entry_size + 0x04,streamFile);
|
||||
}
|
||||
|
||||
/* check for existence of segments */
|
||||
for (i = 0; i < segment_count; i++) {
|
||||
off_t aixp_offset = segment_offset[i];
|
||||
for (j = 0; j < layer_count; j++) {
|
||||
if (read_32bitBE(aixp_offset,streamFile) != 0x41495850) /* "AIXP" */
|
||||
goto fail;
|
||||
if (read_8bit(aixp_offset+0x08,streamFile) != j)
|
||||
goto fail;
|
||||
aixp_offset += read_32bitBE(aixp_offset+0x04,streamFile) + 0x08;
|
||||
/* 0x04: layer channels */
|
||||
}
|
||||
}
|
||||
|
||||
/* open base streamfile, that will be shared by all open_aix_with_STREAMFILE */
|
||||
{
|
||||
char filename[PATH_LIMIT];
|
||||
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
streamFileAIX = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); //todo simplify
|
||||
if (!streamFileAIX) goto fail;
|
||||
}
|
||||
|
||||
/* init layout */
|
||||
{
|
||||
data = malloc(sizeof(aix_codec_data));
|
||||
if (!data) goto fail;
|
||||
|
||||
data->segment_count = segment_count;
|
||||
data->stream_count = layer_count;
|
||||
data->adxs = calloc(segment_count * layer_count, sizeof(VGMSTREAM*));
|
||||
if (!data->adxs) goto fail;
|
||||
|
||||
data->sample_counts = calloc(segment_count,sizeof(int32_t));
|
||||
if (!data->sample_counts) goto fail;
|
||||
|
||||
memcpy(data->sample_counts,segment_samples,segment_count*sizeof(int32_t));
|
||||
}
|
||||
|
||||
/* open each segment / layer subfile */
|
||||
for (i = 0; i < segment_count; i++) {
|
||||
for (j = 0; j < layer_count; j++) {
|
||||
//;VGM_LOG("AIX: opening segment %d/%d stream %d/%d %x\n",i,segment_count,j,stream_count,segment_offset[i]);
|
||||
VGMSTREAM *temp_vgmstream;
|
||||
STREAMFILE * temp_streamFile = open_aix_with_STREAMFILE(streamFileAIX,segment_offset[i],j);
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
temp_vgmstream = data->adxs[i*layer_count+j] = init_vgmstream_adx(temp_streamFile);
|
||||
|
||||
close_streamfile(temp_streamFile);
|
||||
|
||||
if (!temp_vgmstream) goto fail;
|
||||
|
||||
/* setup layers */
|
||||
if (temp_vgmstream->num_samples != data->sample_counts[i] || temp_vgmstream->loop_flag != 0)
|
||||
goto fail;
|
||||
|
||||
setup_vgmstream(temp_vgmstream); /* final setup as the VGMSTREAM was created manually */
|
||||
}
|
||||
}
|
||||
|
||||
/* get looping and samples */
|
||||
sample_count = 0;
|
||||
loop_flag = (segment_count > 1);
|
||||
for (i = 0; i < segment_count; i++) {
|
||||
sample_count += data->sample_counts[i];
|
||||
|
||||
if (i == 0)
|
||||
loop_start_sample = sample_count;
|
||||
if (i == 1)
|
||||
loop_end_sample = sample_count;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
/* build combo layers + segments VGMSTREAM */
|
||||
vgmstream = build_segmented_vgmstream(sf, segment_offsets, segment_sizes, segment_samples, segment_count, layer_count);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = sample_count;
|
||||
vgmstream->loop_start_sample = loop_start_sample;
|
||||
vgmstream->loop_end_sample = loop_end_sample;
|
||||
|
||||
vgmstream->meta_type = meta_AIX;
|
||||
vgmstream->coding_type = data->adxs[0]->coding_type;
|
||||
vgmstream->layout_type = layout_aix;
|
||||
|
||||
vgmstream->ch[0].streamfile = streamFileAIX;
|
||||
data->current_segment = 0;
|
||||
|
||||
vgmstream->codec_data = data;
|
||||
free(segment_offset);
|
||||
free(segment_samples);
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(streamFileAIX);
|
||||
close_vgmstream(vgmstream);
|
||||
free(segment_samples);
|
||||
free(segment_offset);
|
||||
|
||||
/* free aix layout */
|
||||
if (data) {
|
||||
if (data->adxs) {
|
||||
int i;
|
||||
for (i = 0; i < data->segment_count*data->stream_count; i++) {
|
||||
close_vgmstream(data->adxs[i]);
|
||||
}
|
||||
free(data->adxs);
|
||||
}
|
||||
if (data->sample_counts) {
|
||||
free(data->sample_counts);
|
||||
}
|
||||
free(data);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static VGMSTREAM *build_layered_vgmstream(STREAMFILE *streamFile, off_t segment_offset, size_t segment_size, int layer_count) {
|
||||
VGMSTREAM *vgmstream;
|
||||
layered_layout_data* data = NULL;
|
||||
int i;
|
||||
STREAMFILE* temp_streamFile = NULL;
|
||||
|
||||
|
||||
/* build layers */
|
||||
data = init_layout_layered(layer_count);
|
||||
if (!data) goto fail;
|
||||
|
||||
for (i = 0; i < layer_count; i++) {
|
||||
/* build the layer STREAMFILE */
|
||||
temp_streamFile = setup_aix_streamfile(streamFile, segment_offset, segment_size, i, "adx");
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
/* build the sub-VGMSTREAM */
|
||||
data->layers[i] = init_vgmstream_adx(temp_streamFile);
|
||||
if (!data->layers[i]) goto fail;
|
||||
|
||||
data->layers[i]->stream_size = get_streamfile_size(temp_streamFile);
|
||||
|
||||
close_streamfile(temp_streamFile);
|
||||
temp_streamFile = NULL;
|
||||
}
|
||||
|
||||
if (!setup_layout_layered(data))
|
||||
goto fail;
|
||||
|
||||
|
||||
/* build the layered VGMSTREAM */
|
||||
vgmstream = allocate_layered_vgmstream(data);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
if (!vgmstream) free_layout_layered(data);
|
||||
close_vgmstream(vgmstream);
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static VGMSTREAM *build_segmented_vgmstream(STREAMFILE *streamFile, off_t *segment_offsets, size_t *segment_sizes, int32_t *segment_samples, int segment_count, int layer_count) {
|
||||
VGMSTREAM *vgmstream;
|
||||
segmented_layout_data *data = NULL;
|
||||
int i, loop_flag, loop_start_segment, loop_end_segment;
|
||||
|
||||
|
||||
/* build segments */
|
||||
data = init_layout_segmented(segment_count);
|
||||
if (!data) goto fail;
|
||||
|
||||
for (i = 0; i < segment_count; i++) {
|
||||
/* build the layered sub-VGMSTREAM */
|
||||
data->segments[i] = build_layered_vgmstream(streamFile, segment_offsets[i], segment_sizes[i], layer_count);
|
||||
if (!data->segments[i]) goto fail;
|
||||
|
||||
data->segments[i]->num_samples = segment_samples[i]; /* just in case */
|
||||
|
||||
data->segments[i]->stream_size = segment_sizes[i];
|
||||
}
|
||||
|
||||
if (!setup_layout_segmented(data))
|
||||
goto fail;
|
||||
|
||||
/* known loop cases:
|
||||
* - 1 segment: main/no loop [Hatsune Miku: Project Diva (PSP)]
|
||||
* - 2 segments: intro + loop [SoulCalibur IV (PS3)]
|
||||
* - 3 segments: intro + loop + end [Dragon Ball Z: Burst Limit (PS3), Metroid: Other M (Wii)]
|
||||
* - 4/5 segments: intros + loop + ends [Danball Senki (PSP)]
|
||||
* - 39 segments: no loops but multiple segments for dynamic parts? [Tetris Collection (PS2)] */
|
||||
loop_flag = (segment_count > 0 && segment_count <= 5);
|
||||
loop_start_segment = (segment_count > 3) ? 2 : 1;
|
||||
loop_end_segment = (segment_count > 3) ? (segment_count - 2) : 1;
|
||||
|
||||
/* build the segmented VGMSTREAM */
|
||||
vgmstream = allocate_segmented_vgmstream(data, loop_flag, loop_start_segment, loop_end_segment);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
if (!vgmstream) free_layout_segmented(data);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -2,163 +2,150 @@
|
||||
#define _AIX_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
/* a streamfile representing a subfile inside another, in blocked AIX format */
|
||||
|
||||
typedef struct _AIXSTREAMFILE {
|
||||
STREAMFILE sf;
|
||||
STREAMFILE *real_file;
|
||||
off_t start_physical_offset;
|
||||
off_t current_physical_offset;
|
||||
off_t current_logical_offset;
|
||||
off_t current_block_size;
|
||||
int stream_id;
|
||||
} AIXSTREAMFILE;
|
||||
typedef struct {
|
||||
/* config */
|
||||
off_t stream_offset;
|
||||
size_t stream_size;
|
||||
int layer_number;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* fake offset */
|
||||
off_t physical_offset; /* actual offset */
|
||||
size_t block_size; /* current size */
|
||||
size_t skip_size; /* size from block start to reach data */
|
||||
size_t data_size; /* usable size in a block */
|
||||
|
||||
size_t logical_size;
|
||||
} aix_io_data;
|
||||
|
||||
|
||||
/*static*/ STREAMFILE *open_aix_with_STREAMFILE(STREAMFILE *file, off_t start_offset, int stream_id);
|
||||
static size_t aix_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, aix_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
|
||||
static size_t read_aix(AIXSTREAMFILE *streamfile,uint8_t *dest,off_t offset,size_t length) {
|
||||
size_t sz = 0;
|
||||
/* re-start when previous offset (can't map logical<>physical offsets) */
|
||||
if (data->logical_offset < 0 || offset < data->logical_offset) {
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->data_size = 0;
|
||||
}
|
||||
|
||||
/*printf("trying to read %x bytes from %x (str%d)\n",length,offset,streamfile->stream_id);*/
|
||||
/* read blocks */
|
||||
while (length > 0) {
|
||||
int read_something = 0;
|
||||
|
||||
/* read the beginning of the requested block, if we can */
|
||||
if (offset >= streamfile->current_logical_offset) {
|
||||
off_t to_read;
|
||||
off_t length_available;
|
||||
/* ignore EOF */
|
||||
if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
length_available = (streamfile->current_logical_offset + streamfile->current_block_size) - offset;
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
uint32_t block_id = read_u32be(data->physical_offset+0x00, streamfile);
|
||||
data->block_size = read_u32be(data->physical_offset+0x04, streamfile) + 0x08;
|
||||
|
||||
if (length < length_available) {
|
||||
/* check valid block "AIXP" id, knowing that AIX segments end with "AIXE" block too */
|
||||
if (block_id != 0x41495850 || data->block_size == 0 || data->block_size == 0xFFFFFFFF) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* read target layer, otherwise skip to next block and try again */
|
||||
if (read_s8(data->physical_offset+0x08, streamfile) == data->layer_number) {
|
||||
/* 0x09(1): layer count */
|
||||
data->data_size = read_s16be(data->physical_offset+0x0a, streamfile);
|
||||
/* 0x0c: -1 */
|
||||
data->skip_size = 0x10;
|
||||
}
|
||||
|
||||
/* strange AIX in Tetris Collection (PS2) with padding before ADX start (no known flag) */
|
||||
if (data->logical_offset == 0x00 &&
|
||||
read_u32be(data->physical_offset + 0x10, streamfile) == 0 &&
|
||||
read_u16be(data->physical_offset + data->block_size - 0x28, streamfile) == 0x8000) {
|
||||
data->data_size = 0x28;
|
||||
data->skip_size = data->block_size - 0x28;
|
||||
}
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
else {
|
||||
to_read = length_available;
|
||||
}
|
||||
|
||||
if (to_read > 0) {
|
||||
size_t bytes_read;
|
||||
|
||||
bytes_read = read_streamfile(dest,
|
||||
streamfile->current_physical_offset+0x10 + (offset-streamfile->current_logical_offset),
|
||||
to_read,streamfile->real_file);
|
||||
|
||||
sz += bytes_read;
|
||||
if (bytes_read != to_read) {
|
||||
return sz; /* an error which we will not attempt to handle here */
|
||||
}
|
||||
|
||||
read_something = 1;
|
||||
|
||||
dest += bytes_read;
|
||||
offset += bytes_read;
|
||||
length -= bytes_read;
|
||||
}
|
||||
}
|
||||
|
||||
if (!read_something) {
|
||||
/* couldn't read anything, must seek */
|
||||
int found_block = 0;
|
||||
|
||||
/* as we have no memory we must start seeking from the beginning */
|
||||
if (offset < streamfile->current_logical_offset) {
|
||||
streamfile->current_logical_offset = 0;
|
||||
streamfile->current_block_size = 0;
|
||||
streamfile->current_physical_offset = streamfile->start_physical_offset;
|
||||
return total_read;
|
||||
}
|
||||
|
||||
/* seek ye forwards */
|
||||
while (!found_block) {
|
||||
/*printf("seek looks at %x\n",streamfile->current_physical_offset);*/
|
||||
switch (read_32bitBE(streamfile->current_physical_offset, streamfile->real_file)) {
|
||||
case 0x41495850: /* AIXP */
|
||||
if (read_8bit(streamfile->current_physical_offset+8, streamfile->real_file) == streamfile->stream_id) {
|
||||
streamfile->current_block_size = (uint16_t)read_16bitBE(streamfile->current_physical_offset+0x0a, streamfile->real_file);
|
||||
static size_t aix_io_size(STREAMFILE *streamfile, aix_io_data* data) {
|
||||
uint8_t buf[1];
|
||||
|
||||
if (offset >= streamfile->current_logical_offset+ streamfile->current_block_size) {
|
||||
streamfile->current_logical_offset += streamfile->current_block_size;
|
||||
}
|
||||
else {
|
||||
found_block = 1;
|
||||
}
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
|
||||
aix_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
|
||||
data->logical_size = data->logical_offset;
|
||||
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
if (!found_block) {
|
||||
streamfile->current_physical_offset += read_32bitBE(streamfile->current_physical_offset+0x04, streamfile->real_file) + 8;
|
||||
/* Handles deinterleaving of AIX blocked layer streams */
|
||||
static STREAMFILE* setup_aix_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, int layer_number, const char* extension) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
aix_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(aix_io_data);
|
||||
|
||||
io_data.stream_offset = stream_offset;
|
||||
io_data.stream_size = stream_size;
|
||||
io_data.layer_number = layer_number;
|
||||
io_data.logical_offset = -1; /* force reset */
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, aix_io_read,aix_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_buffer_streamfile(new_streamFile,0);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
if (extension) {
|
||||
new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,extension);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
}
|
||||
|
||||
break;
|
||||
case 0x41495846: /* AIXF */
|
||||
/* shouldn't ever see this */
|
||||
case 0x41495845: /* AIXE */
|
||||
/* shouldn't have reached the end o' the line... */
|
||||
default:
|
||||
return sz;
|
||||
break;
|
||||
} /* end block/chunk type select */
|
||||
} /* end while !found_block */
|
||||
} /* end if !read_something */
|
||||
} /* end while length > 0 */
|
||||
return temp_streamFile;
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
static void close_aix(AIXSTREAMFILE *streamfile) {
|
||||
free(streamfile);
|
||||
return;
|
||||
}
|
||||
|
||||
static size_t get_size_aix(AIXSTREAMFILE *streamfile) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t get_offset_aix(AIXSTREAMFILE *streamfile) {
|
||||
return streamfile->current_logical_offset;
|
||||
}
|
||||
|
||||
static void get_name_aix(AIXSTREAMFILE *streamfile,char *buffer,size_t length) {
|
||||
strncpy(buffer,"ARBITRARY.ADX",length);
|
||||
buffer[length-1]='\0';
|
||||
}
|
||||
|
||||
static STREAMFILE *open_aix_impl(AIXSTREAMFILE *streamfile,const char * const filename,size_t buffersize) {
|
||||
AIXSTREAMFILE *newfile;
|
||||
if (strcmp(filename,"ARBITRARY.ADX"))
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
|
||||
newfile = malloc(sizeof(AIXSTREAMFILE));
|
||||
if (!newfile)
|
||||
return NULL;
|
||||
memcpy(newfile,streamfile,sizeof(AIXSTREAMFILE));
|
||||
return &newfile->sf;
|
||||
}
|
||||
|
||||
/*static*/ STREAMFILE *open_aix_with_STREAMFILE(STREAMFILE *file, off_t start_offset, int stream_id) {
|
||||
AIXSTREAMFILE *streamfile = malloc(sizeof(AIXSTREAMFILE));
|
||||
|
||||
if (!streamfile)
|
||||
return NULL;
|
||||
|
||||
/* success, set our pointers */
|
||||
|
||||
streamfile->sf.read = (void*)read_aix;
|
||||
streamfile->sf.get_size = (void*)get_size_aix;
|
||||
streamfile->sf.get_offset = (void*)get_offset_aix;
|
||||
streamfile->sf.get_name = (void*)get_name_aix;
|
||||
streamfile->sf.open = (void*)open_aix_impl;
|
||||
streamfile->sf.close = (void*)close_aix;
|
||||
|
||||
streamfile->real_file = file;
|
||||
streamfile->current_physical_offset = start_offset;
|
||||
streamfile->start_physical_offset = start_offset;
|
||||
streamfile->current_logical_offset = 0;
|
||||
streamfile->current_block_size = 0;
|
||||
streamfile->stream_id = stream_id;
|
||||
|
||||
return &streamfile->sf;
|
||||
}
|
||||
|
||||
#endif /* _AIX_STREAMFILE_H_ */
|
||||
|
@ -7,7 +7,7 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset, header_offset, name_offset;
|
||||
size_t stream_size;
|
||||
int loop_flag, channel_count, sample_rate, version, codec;
|
||||
int loop_flag, channel_count, sample_rate, version, codec, tracks;
|
||||
int total_subsongs, target_subsong = streamFile->stream_index;
|
||||
int32_t (*read_32bit)(off_t,STREAMFILE*);
|
||||
|
||||
@ -65,6 +65,7 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *streamFile) {
|
||||
name_offset = header_offset + 0x0c;
|
||||
start_offset = read_32bit(header_offset+0x2c, streamFile);
|
||||
stream_size = read_32bit(header_offset+0x30, streamFile);
|
||||
tracks = 0;
|
||||
|
||||
switch(codec) {
|
||||
case 0x03:
|
||||
@ -93,7 +94,12 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *streamFile) {
|
||||
case 0x05: /* James Bond 007: Blood Stone (X360) */
|
||||
sample_rate = read_32bit(header_offset+0x40, streamFile);
|
||||
loop_flag = read_8bit(header_offset+0x48, streamFile);
|
||||
tracks = read_8bit(header_offset+0x49, streamFile);
|
||||
channel_count = read_8bit(header_offset+0x4b, streamFile);
|
||||
|
||||
if (tracks) {
|
||||
channel_count = channel_count * tracks;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -1054,6 +1054,9 @@ static size_t calculate_eaac_size(VGMSTREAM *vgmstream, STREAMFILE *streamFile,
|
||||
uint32_t total_samples;
|
||||
size_t stream_size, file_size;
|
||||
|
||||
if (streamFile == NULL)
|
||||
return 0;
|
||||
|
||||
switch (eaac->codec) {
|
||||
case EAAC_CODEC_EAXMA:
|
||||
case EAAC_CODEC_EALAYER3_V1:
|
||||
@ -1154,6 +1157,7 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st
|
||||
if (!vgmstream_open_stream(data->segments[i],temp_streamFile[i],0x00))
|
||||
goto fail;
|
||||
|
||||
//todo temp_streamFile doesn't contain EAXMA's streamfile
|
||||
data->segments[i]->stream_size = calculate_eaac_size(data->segments[i], temp_streamFile[i], eaac, 0x00);
|
||||
}
|
||||
|
||||
@ -1212,6 +1216,7 @@ static layered_layout_data* build_layered_eaaudiocore_eaxma(STREAMFILE *streamDa
|
||||
|
||||
data->layers[i]->coding_type = coding_FFmpeg;
|
||||
data->layers[i]->layout_type = layout_none;
|
||||
data->layers[i]->stream_size = get_streamfile_size(temp_streamFile);
|
||||
|
||||
xma_fix_raw_samples(data->layers[i], temp_streamFile, 0x00,stream_size, 0, 0,0); /* samples are ok? */
|
||||
}
|
||||
|
97
src/meta/ffdl.c
Normal file
97
src/meta/ffdl.c
Normal file
@ -0,0 +1,97 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* FFDL - Matrix Software wrapper [Final Fantasy Dimensions (Android/iOS)] */
|
||||
VGMSTREAM * init_vgmstream_ffdl(STREAMFILE *sf) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE *temp_sf = NULL;
|
||||
int loop_flag = 0, is_ffdl = 0;
|
||||
int32_t num_samples = 0, loop_start_sample = 0, loop_end_sample = 0;
|
||||
off_t start_offset;
|
||||
size_t file_size;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .ogg/logg: probable extension for Android
|
||||
* .mp4/lmp4: probable extension for iOS
|
||||
* .bin: iOS FFDL extension
|
||||
* (extensionless): for FFDL files without names in Android .obb bigfile */
|
||||
if (!check_extensions(sf, "ogg,logg,mp4,lmp4,bin,"))
|
||||
goto fail;
|
||||
|
||||
/* "FFDL" is a wrapper used in all of the game's files, that may contain standard
|
||||
* Ogg/MP4 or "mtxs" w/ loops + Ogg/MP4, and may concatenate multiple of them
|
||||
* (without size in sight), so they should be split externally first. */
|
||||
|
||||
start_offset = 0x00;
|
||||
|
||||
/* may start with wrapper (not split) */
|
||||
if (read_u32be(0x00,sf) == 0x4646444C) { /* "FFDL" */
|
||||
is_ffdl = 1;
|
||||
start_offset += 0x04;
|
||||
}
|
||||
|
||||
/* may start with sample info (split) or after "FFDL" */
|
||||
if (read_u32be(start_offset+0x00,sf) == 0x6D747873) { /* "mtxs" */
|
||||
is_ffdl = 1;
|
||||
|
||||
num_samples = read_s32le(start_offset + 0x04,sf);
|
||||
loop_start_sample = read_s32le(start_offset + 0x08,sf);
|
||||
loop_end_sample = read_s32le(start_offset + 0x0c,sf);
|
||||
loop_flag = !(loop_start_sample==0 && loop_end_sample==num_samples);
|
||||
|
||||
start_offset += 0x10;
|
||||
}
|
||||
|
||||
/* don't parse regular files */
|
||||
if (!is_ffdl)
|
||||
goto fail;
|
||||
|
||||
file_size = get_streamfile_size(sf) - start_offset;
|
||||
|
||||
if (read_u32be(start_offset + 0x00,sf) == 0x4F676753) { /* "OggS" */
|
||||
#ifdef VGM_USE_VORBIS
|
||||
temp_sf = setup_subfile_streamfile(sf, start_offset, file_size, "ogg");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_ogg_vorbis(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
}
|
||||
else if (read_u32be(start_offset + 0x04,sf) == 0x66747970) { /* "ftyp" after atom size */
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
temp_sf = setup_subfile_streamfile(sf, start_offset, file_size, "mp4");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_mp4_aac_ffmpeg(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* install loops */
|
||||
if (loop_flag) {
|
||||
/* num_samples is erratic (can be bigger = padded, or smaller = cut; doesn't matter for looping though) */
|
||||
//;VGM_ASSERT(vgmstream->num_samples != num_samples,
|
||||
// "FFDL: mtxs samples = %i vs num_samples = %i\n", num_samples, vgmstream->num_samples);
|
||||
//vgmstream->num_samples = num_samples;
|
||||
|
||||
/* loop samples are within num_samples, and don't have encoder delay (loop_start=0 starts from encoder_delay) */
|
||||
vgmstream_force_loop(vgmstream, 1, loop_start_sample, loop_end_sample);
|
||||
}
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
@ -189,9 +189,9 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) {
|
||||
|
||||
/* setup adpcm */
|
||||
if (coding == coding_AICA || coding == coding_AICA_int) {
|
||||
int i;
|
||||
for (i=0;i<vgmstream->channels;i++) {
|
||||
vgmstream->ch[i].adpcm_step_index = 0x7f;
|
||||
int ch;
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
vgmstream->ch[ch].adpcm_step_index = 0x7f;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -831,4 +831,8 @@ VGMSTREAM * init_vgmstream_dsf(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_208(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ffdl(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_mus_vc(STREAMFILE * streamFile);
|
||||
|
||||
#endif /*_META_H*/
|
||||
|
@ -171,11 +171,10 @@ VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset = 0;
|
||||
int loop_flag = 0;
|
||||
int32_t num_samples = 0, loop_start_sample = 0, loop_end_sample = 0;
|
||||
int32_t loop_start_sample = 0, loop_end_sample = 0;
|
||||
size_t filesize;
|
||||
off_t atom_offset;
|
||||
size_t atom_size;
|
||||
int is_ffdl = 0;
|
||||
|
||||
ffmpeg_codec_data *ffmpeg_data = NULL;
|
||||
|
||||
@ -188,31 +187,6 @@ VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE *streamFile) {
|
||||
|
||||
filesize = streamFile->get_size(streamFile);
|
||||
|
||||
/* check header for Final Fantasy Dimensions */
|
||||
if (read_32bitBE(0x00,streamFile) == 0x4646444C) { /* "FFDL" (any kind of file) */
|
||||
is_ffdl = 1;
|
||||
if (read_32bitBE(0x04,streamFile) == 0x6D747873) { /* "mtxs" (bgm file) */
|
||||
/* this value is erratic so we'll use FFmpeg's num_samples
|
||||
* (can be bigger = silence-padded, or smaller = cut; doesn't matter for looping though)*/
|
||||
num_samples = read_32bitLE(0x08,streamFile);
|
||||
/* loop samples are within num_samples, and don't have encoder delay (loop_start=0 starts from encoder_delay) */
|
||||
loop_start_sample = read_32bitLE(0x0c,streamFile);
|
||||
loop_end_sample = read_32bitLE(0x10,streamFile);
|
||||
loop_flag = !(loop_start_sample==0 && loop_end_sample==num_samples);
|
||||
start_offset = 0x14;
|
||||
|
||||
/* some FFDL have muxed streams ("FFDL" + "mtxs" data1 + mp4 data1 + "mtxs" data2 + mp4 data2 + etc)
|
||||
* check if there is anything after the first mp4 data */
|
||||
if (!find_atom_be(streamFile, 0x6D646174, start_offset, &atom_offset, &atom_size)) goto fail; /* "mdat" */
|
||||
if (atom_offset-8 + atom_size < filesize && read_32bitBE(atom_offset-8 + atom_size,streamFile) == 0x6D747873) { /*"mtxs"*/
|
||||
VGM_LOG("FFDL: multiple streams found\n");
|
||||
filesize = atom_offset-8 + atom_size; /* clamp size, though FFmpeg will ignore the extra data anyway */
|
||||
}
|
||||
} else {
|
||||
start_offset = 0x4; /* some SEs contain "ftyp" after "FFDL" */
|
||||
}
|
||||
}
|
||||
|
||||
/* check header */
|
||||
if ( read_32bitBE(start_offset+0x04,streamFile) != 0x66747970) /* atom size @0x00 + "ftyp" @0x04 */
|
||||
goto fail;
|
||||
@ -221,7 +195,7 @@ VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE *streamFile) {
|
||||
if ( !ffmpeg_data ) goto fail;
|
||||
|
||||
/* Tales of Hearts iOS has loop info in the first "free" atom */
|
||||
if (!is_ffdl && find_atom_be(streamFile, 0x66726565, start_offset, &atom_offset, &atom_size)) { /* "free" */
|
||||
if (find_atom_be(streamFile, 0x66726565, start_offset, &atom_offset, &atom_size)) { /* "free" */
|
||||
if (read_32bitBE(atom_offset,streamFile) == 0x4F700002
|
||||
&& (atom_size == 0x38 || atom_size == 0x40)) { /* make sure it's ToHr "free" */
|
||||
/* 0x00: id? 0x04/8: s_rate; 0x10: num_samples (without padding, same as FFmpeg's) */
|
||||
|
79
src/meta/mus_vc.c
Normal file
79
src/meta/mus_vc.c
Normal file
@ -0,0 +1,79 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* .MUS - Vicious Cycle games [Dinotopia: The Sunstone Odyssey (GC/Xbox), Robotech: Battlecry (PS2/Xbox)] */
|
||||
VGMSTREAM * init_vgmstream_mus_vc(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count, sample_rate;
|
||||
int big_endian, type;
|
||||
int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "mus"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x08,streamFile) != 0xBBBBBBBB &&
|
||||
read_32bitBE(0x14,streamFile) != 0xBBBBBBBB &&
|
||||
read_32bitBE(0x2c,streamFile) != 0xBEBEBEBE)
|
||||
goto fail;
|
||||
|
||||
big_endian = (read_32bitBE(0x00,streamFile) == 0xFBBFFBBF);
|
||||
read_32bit = big_endian ? read_32bitBE : read_32bitLE;
|
||||
|
||||
type = read_32bit(0x04, streamFile);
|
||||
/* 0x08: pseudo size? */
|
||||
/* other fields may be chunk sizes and lesser stuff */
|
||||
/* 0x88: codec header */
|
||||
|
||||
channel_count = read_32bit(0x54,streamFile); /* assumed */
|
||||
if (channel_count != 1) goto fail;
|
||||
sample_rate = read_32bit(0x58,streamFile);
|
||||
loop_flag = 1; /* most files repeat except small jingles, but smaller ambient tracks also repeat */
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_MUS_VC;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
|
||||
switch(type) {
|
||||
case 0x01:
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->num_samples = dsp_bytes_to_samples(read_32bit(0xB0,streamFile), vgmstream->channels);
|
||||
vgmstream->loop_start_sample = 0;
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
|
||||
start_offset = 0xB8;
|
||||
dsp_read_coefs_be(vgmstream,streamFile,0x88,0x00);
|
||||
dsp_read_hist_be (vgmstream,streamFile,0xac,0x00);
|
||||
break;
|
||||
|
||||
case 0x02:
|
||||
vgmstream->coding_type = coding_XBOX_IMA;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->num_samples = xbox_ima_bytes_to_samples(read_32bit(0x9a,streamFile), vgmstream->channels);
|
||||
vgmstream->loop_start_sample = 0;
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
|
||||
start_offset = 0x9e;
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
read_string(vgmstream->stream_name,0x14, 0x34,streamFile); /* repeated at 0x64, size at 0x30/0x60 */
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
@ -39,7 +39,7 @@ VGMSTREAM * init_vgmstream_scd_sscf(STREAMFILE *streamFile) {
|
||||
total_subsongs = 0;
|
||||
for (i = 0; i < entries; i++) {
|
||||
off_t entry_offset = 0x20 + (0x20*i);
|
||||
off_t stream_offset;
|
||||
off_t entry_stream_offset;
|
||||
|
||||
/* skip dummies */
|
||||
if (read_32bitLE(entry_offset+0x08,streamFile) == 0) /* size 0 */
|
||||
@ -49,16 +49,16 @@ VGMSTREAM * init_vgmstream_scd_sscf(STREAMFILE *streamFile) {
|
||||
|
||||
/* skip repeated sounds */
|
||||
is_dupe = 0;
|
||||
stream_offset = read_32bitLE(entry_offset+0x04,streamFile);
|
||||
entry_stream_offset = read_32bitLE(entry_offset+0x04,streamFile);
|
||||
for (j = 0; j < total_subsongs; j++) {
|
||||
if (stream_offset == stream_offsets[j]) {
|
||||
if (entry_stream_offset == stream_offsets[j]) {
|
||||
is_dupe = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_dupe)
|
||||
continue;
|
||||
stream_offsets[total_subsongs] = stream_offset;
|
||||
stream_offsets[total_subsongs] = entry_stream_offset;
|
||||
|
||||
/* ok */
|
||||
total_subsongs++;
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
typedef enum { PSX, DSP, XBOX, WMA } strwav_codec;
|
||||
typedef enum { PSX, DSP, XBOX, WMA, IMA } strwav_codec;
|
||||
typedef struct {
|
||||
int32_t channels;
|
||||
int32_t sample_rate;
|
||||
@ -126,6 +126,12 @@ VGMSTREAM * init_vgmstream_str_wav(STREAMFILE *streamFile) {
|
||||
goto fail; /* only 2ch+..+2ch layout is known */
|
||||
break;
|
||||
|
||||
case IMA:
|
||||
vgmstream->coding_type = coding_BLITZ_IMA;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = strwav.interleave;
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case WMA: {
|
||||
ffmpeg_codec_data *ffmpeg_data = NULL;
|
||||
@ -330,6 +336,27 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Zapper: One Wicked Cricket! (PC)[2005] */
|
||||
if ( read_32bitBE(0x04,streamHeader) == 0x00000900 &&
|
||||
read_32bitLE(0x24,streamHeader) == read_32bitLE(0x114,streamHeader) && /* sample rate repeat */
|
||||
read_32bitLE(0x28,streamHeader) == 0x10 &&
|
||||
read_32bitLE(0x12c,streamHeader) == header_size /* ~0x130 */
|
||||
) {
|
||||
strwav->num_samples = read_32bitLE(0x20,streamHeader);
|
||||
strwav->sample_rate = read_32bitLE(0x24,streamHeader);
|
||||
strwav->flags = read_32bitLE(0x2c,streamHeader);
|
||||
strwav->loop_start = read_32bitLE(0x54,streamHeader);
|
||||
strwav->loop_end = read_32bitLE(0x30,streamHeader);
|
||||
|
||||
strwav->channels = read_32bitLE(0xF8,streamHeader) * (strwav->flags & 0x02 ? 2 : 1); /* tracks of 2/1ch */
|
||||
strwav->loop_flag = strwav->flags & 0x01;
|
||||
strwav->interleave = strwav->channels > 2 ? 0x8000 : 0x10000;
|
||||
|
||||
strwav->codec = IMA;
|
||||
//;VGM_LOG("STR+WAV: header Zapper (PC)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Pac-Man World 3 (GC)[2005] */
|
||||
/* SpongeBob SquarePants: Creature from the Krusty Krab (GC)[2006] */
|
||||
if ( read_32bitBE(0x04,streamHeader) == 0x00000800 &&
|
||||
|
@ -265,9 +265,9 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
|
||||
|
||||
/* setup adpcm */
|
||||
if (coding == coding_AICA || coding == coding_AICA_int) {
|
||||
int i;
|
||||
for (i=0;i<vgmstream->channels;i++) {
|
||||
vgmstream->ch[i].adpcm_step_index = 0x7f;
|
||||
int ch;
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
vgmstream->ch[ch].adpcm_step_index = 0x7f;
|
||||
}
|
||||
}
|
||||
|
||||
@ -317,10 +317,12 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
|
||||
goto fail; /* only 2ch+..+2ch layout is known */
|
||||
}
|
||||
break;
|
||||
|
||||
case coding_NGC_DTK:
|
||||
if (vgmstream->channels != 2) goto fail;
|
||||
vgmstream->layout_type = layout_none;
|
||||
break;
|
||||
|
||||
case coding_NGC_DSP:
|
||||
if (txth.channels > 1 && txth.codec_mode == 0) {
|
||||
if (!txth.interleave) goto fail;
|
||||
@ -360,6 +362,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_MPEG
|
||||
case coding_MPEG_layer3:
|
||||
vgmstream->layout_type = layout_none;
|
||||
@ -608,6 +611,7 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char
|
||||
else if (0==strcmp(val,"DVI_IMA")) txth->codec = DVI_IMA;
|
||||
else if (0==strcmp(val,"MPEG")) txth->codec = MPEG;
|
||||
else if (0==strcmp(val,"IMA")) txth->codec = IMA;
|
||||
else if (0==strcmp(val,"YAMAHA")) txth->codec = AICA;
|
||||
else if (0==strcmp(val,"AICA")) txth->codec = AICA;
|
||||
else if (0==strcmp(val,"MSADPCM")) txth->codec = MSADPCM;
|
||||
else if (0==strcmp(val,"NGC_DSP")) txth->codec = NGC_DSP;
|
||||
@ -628,6 +632,21 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char
|
||||
else if (0==strcmp(val,"PCM4_U")) txth->codec = PCM4_U;
|
||||
else if (0==strcmp(val,"OKI16")) txth->codec = OKI16;
|
||||
else goto fail;
|
||||
|
||||
/* set common interleaves to simplify usage
|
||||
* (do it here to in case it's overwritten later, possibly with 0 on purpose) */
|
||||
if (txth->interleave == 0) {
|
||||
switch(txth->codec) {
|
||||
case PSX: txth->interleave = 0x10; break;
|
||||
case PSX_bf: txth->interleave = 0x10; break;
|
||||
case NGC_DSP: txth->interleave = 0x08; break;
|
||||
case PCM16LE: txth->interleave = 0x02; break;
|
||||
case PCM16BE: txth->interleave = 0x02; break;
|
||||
case PCM8: txth->interleave = 0x01; break;
|
||||
case PCM8_U: txth->interleave = 0x01; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (0==strcmp(key,"codec_mode")) {
|
||||
if (!parse_num(txth->streamHead,txth,val, &txth->codec_mode)) goto fail;
|
||||
|
428
src/meta/txtp.c
428
src/meta/txtp.c
@ -3,21 +3,20 @@
|
||||
#include "../layout/layout.h"
|
||||
|
||||
|
||||
#define TXT_LINE_MAX 0x2000
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
#define TXTP_MIXING_MAX 64
|
||||
#endif
|
||||
#define TXTP_LINE_MAX 1024
|
||||
|
||||
|
||||
typedef struct {
|
||||
char filename[TXT_LINE_MAX];
|
||||
char filename[TXTP_LINE_MAX];
|
||||
int subsong;
|
||||
uint32_t channel_mask;
|
||||
#ifndef VGMSTREAM_MIXING
|
||||
int channel_mappings_on;
|
||||
int channel_mappings[32];
|
||||
|
||||
#if VGMSTREAM_MIXING
|
||||
#endif
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
int mixing_count;
|
||||
mix_config_data mixing[TXTP_MIXING_MAX];
|
||||
mix_config_data mixing[VGMSTREAM_MAX_MIXING];
|
||||
#endif
|
||||
|
||||
double config_loop_count;
|
||||
@ -49,7 +48,9 @@ typedef struct {
|
||||
static txtp_header* parse_txtp(STREAMFILE* streamFile);
|
||||
static void clean_txtp(txtp_header* txtp);
|
||||
static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current);
|
||||
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
void add_mixing(txtp_entry* cfg, mix_config_data* mix, mix_command_t command);
|
||||
#endif
|
||||
|
||||
/* TXTP - an artificial playlist-like format to play files with segments/layers/config */
|
||||
VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) {
|
||||
@ -217,6 +218,7 @@ fail:
|
||||
}
|
||||
|
||||
static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) {
|
||||
#ifndef VGMSTREAM_MIXING
|
||||
vgmstream->channel_mask = current->channel_mask;
|
||||
|
||||
vgmstream->channel_mappings_on = current->channel_mappings_on;
|
||||
@ -226,47 +228,6 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) {
|
||||
vgmstream->channel_mappings[ch] = current->channel_mappings[ch];
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
if (current->mixing_count > 0) {
|
||||
int i, ch_max_cur;
|
||||
if (vgmstream->mixing_count + current->mixing_count > vgmstream->mixing_size) {
|
||||
VGM_LOG("TXTP: ignored mixing\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ch_max_cur = vgmstream->channels;
|
||||
|
||||
for (i = 0; i < current->mixing_count; i++) {
|
||||
mix_config_data mix = current->mixing[i];
|
||||
|
||||
vgmstream->mixing[vgmstream->mixing_count] = mix;
|
||||
vgmstream->mixing_count++;
|
||||
|
||||
/* some mixes change output channels */
|
||||
switch(vgmstream->mixing[i].command) {
|
||||
case MIX_DOWNMIX:
|
||||
if (mix.ch_a < 0 || mix.ch_a >= ch_max_cur || ch_max_cur - 1 == 0) break;
|
||||
ch_max_cur--;
|
||||
break;
|
||||
|
||||
case MIX_DOWNMIX_REST:
|
||||
if (mix.ch_a < 0 || mix.ch_a >= ch_max_cur) break;
|
||||
ch_max_cur = mix.ch_a + 1; /* simply clamp channels */
|
||||
break;
|
||||
|
||||
case MIX_UPMIX:
|
||||
if (mix.ch_a < 0 || mix.ch_a > ch_max_cur) break; /* ch_a can be == max_cur, since we are inserting */
|
||||
ch_max_cur++;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
vgmstream->output_channels = ch_max_cur;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (current->sample_rate > 0)
|
||||
@ -278,6 +239,30 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) {
|
||||
vgmstream->config_ignore_loop = current->config_ignore_loop;
|
||||
vgmstream->config_force_loop = current->config_force_loop;
|
||||
vgmstream->config_ignore_fade = current->config_ignore_fade;
|
||||
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
/* add macro to mixing list */
|
||||
if (current->channel_mask) {
|
||||
int ch;
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
if (!((current->channel_mask >> ch) & 1)) {
|
||||
mix_config_data mix = {0};
|
||||
mix.ch_dst = ch;
|
||||
mix.vol = 0.0f;
|
||||
add_mixing(current, &mix, MIX_VOLUME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* copy mixing list (should be done last as some mixes depend on config) */
|
||||
if (current->mixing_count > 0) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < current->mixing_count; i++) {
|
||||
vgmstream_add_mixing(vgmstream, current->mixing[i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ********************************** */
|
||||
@ -302,34 +287,136 @@ static void clean_filename(char * filename) {
|
||||
|
||||
}
|
||||
|
||||
/* sscanf 101:
|
||||
* - reads linearly and matches "%" commands to input parameters
|
||||
* - returns number of matched % parameters until stop
|
||||
* - reads until string end or not being able to match
|
||||
* - %n: number of chars consumed until that point (can appear and set multiple times)
|
||||
* - %d/f: reads number until end or *non-number* (so "%d" reads "5t" as "5")
|
||||
* - %[^(chars)] reads string with chars not in the list
|
||||
* - %*(command) is read but skipped (match not set to parameter)
|
||||
* - " ": ignores all spaces until next non-space
|
||||
* - other chars in string must exist: ("%dt t%dt" reads "5t t5t" as "5" and "5", while "t5t 5t" matches only first "5")
|
||||
*/
|
||||
|
||||
|
||||
static int get_double(const char * config, double *value) {
|
||||
int n;
|
||||
if (sscanf(config, "%lf%n", value,&n) != 1) {
|
||||
*value = 0;
|
||||
int n, m;
|
||||
double temp;
|
||||
|
||||
m = sscanf(config, " %lf%n", &temp,&n);
|
||||
if (m != 1 || temp < 0)
|
||||
return 0;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
static int get_int(const char * config, int *value) {
|
||||
int n;
|
||||
if (sscanf(config, "%i%n", value,&n) != 1) {
|
||||
*value = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
*value = temp;
|
||||
return n;
|
||||
}
|
||||
|
||||
static int get_int(const char * config, int *value) {
|
||||
int n,m;
|
||||
int temp;
|
||||
|
||||
m = sscanf(config, " %i%n", &temp,&n);
|
||||
if (m != 1 || temp < 0)
|
||||
return 0;
|
||||
|
||||
*value = temp;
|
||||
return n;
|
||||
}
|
||||
|
||||
static int get_bool(const char * config, int *value) {
|
||||
int n,m;
|
||||
char temp;
|
||||
|
||||
m = sscanf(config, " %c%n", &temp, &n);
|
||||
if (m >= 1 && !(temp == '#' || temp == '\r' || temp == '\n'))
|
||||
return 0; /* ignore if anything non-space/comment matched */
|
||||
|
||||
if (temp == '#') n--; /* don't consume separator */
|
||||
*value = 1;
|
||||
return n;
|
||||
}
|
||||
|
||||
static int get_mask(const char * config, uint32_t *value) {
|
||||
int n, m, total_n = 0;
|
||||
int temp1,temp2, r1, r2;
|
||||
int i;
|
||||
char cmd;
|
||||
uint32_t mask = *value;
|
||||
|
||||
while (config[0] != '\0') {
|
||||
m = sscanf(config, " %c%n", &cmd,&n); /* consume comma */
|
||||
if (m == 1 && (cmd == ',' || cmd == '-')) { /* '-' is alt separator (space is ok too, implicitly) */
|
||||
config += n;
|
||||
continue;
|
||||
}
|
||||
|
||||
m = sscanf(config, " %d%n ~ %d%n", &temp1,&n, &temp2,&n);
|
||||
if (m == 1) { /* single values */
|
||||
r1 = temp1 - 1;
|
||||
r2 = temp1 - 1;
|
||||
}
|
||||
else if (m == 2) { /* range */
|
||||
r1 = temp1 - 1;
|
||||
r2 = temp2 - 1;
|
||||
}
|
||||
else { /* no more matches */
|
||||
break;
|
||||
}
|
||||
|
||||
if (n == 0 || r1 < 0 || r1 > 31 || r2 < 0 || r2 > 31)
|
||||
break;
|
||||
|
||||
for (i = r1; i < r2 + 1; i++) {
|
||||
mask |= (1 << i);
|
||||
}
|
||||
|
||||
config += n;
|
||||
total_n += n;
|
||||
|
||||
if (config[0]== ',' || config[0]== '-')
|
||||
config++;
|
||||
}
|
||||
|
||||
*value = mask;
|
||||
return total_n;
|
||||
}
|
||||
|
||||
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
static void add_mixing(txtp_entry* cfg, mix_config_data* mix, mix_command_t command) {
|
||||
if (cfg->mixing_count + 1 > TXTP_MIXING_MAX) {
|
||||
static int get_fade(const char * config, mix_config_data *mix, int *out_n) {
|
||||
int n, m;
|
||||
|
||||
//todo add { } shortcuts / time / etc
|
||||
|
||||
m = sscanf(config, " %d ^ %f ~ %f = %c @ %f ~ %f + %f ~ %f%n",
|
||||
&mix->ch_dst,
|
||||
&mix->vol_start, &mix->vol_end, &mix->shape,
|
||||
&mix->time_pre, &mix->time_start, &mix->time_end, &mix->time_post,
|
||||
&n);
|
||||
|
||||
VGM_LOG("curve m=%i, n=%i\n", m,n);
|
||||
if (m == 8 && n != 0) {
|
||||
mix->time_end += mix->time_start;
|
||||
*out_n = n;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
void add_mixing(txtp_entry* cfg, mix_config_data* mix, mix_command_t command) {
|
||||
if (cfg->mixing_count + 1 > VGMSTREAM_MAX_MIXING) {
|
||||
VGM_LOG("TXTP: too many mixes\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* parsers reads ch1 = first, but for mixing code ch0 = first
|
||||
* (if parser reads ch0 here it'll becode -1 and ignored in code) */
|
||||
mix->ch_a--;
|
||||
mix->ch_b--;
|
||||
* (if parser reads ch0 here it'll become -1 with special meaning in code) */
|
||||
mix->ch_dst--;
|
||||
mix->ch_src--;
|
||||
mix->command = command;
|
||||
cfg->mixing[cfg->mixing_count] = *mix; /* memcpy'ed */
|
||||
cfg->mixing_count++;
|
||||
@ -344,6 +431,7 @@ static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filenam
|
||||
|
||||
current->channel_mask = cfg->channel_mask;
|
||||
|
||||
#ifndef VGMSTREAM_MIXING
|
||||
if (cfg->channel_mappings_on) {
|
||||
int ch;
|
||||
current->channel_mappings_on = cfg->channel_mappings_on;
|
||||
@ -351,7 +439,7 @@ static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filenam
|
||||
current->channel_mappings[ch] = cfg->channel_mappings[ch];
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
//*current = *cfg; /* don't memcopy to allow list additions */
|
||||
|
||||
@ -376,15 +464,15 @@ static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filenam
|
||||
}
|
||||
|
||||
static int add_filename(txtp_header * txtp, char *filename, int is_default) {
|
||||
int i, n;
|
||||
int i, n, nc, mc;
|
||||
txtp_entry cfg = {0};
|
||||
size_t range_start, range_end;
|
||||
const char separator = '#';
|
||||
char command[TXTP_LINE_MAX] = {0};
|
||||
|
||||
|
||||
//;VGM_LOG("TXTP: filename=%s\n", filename);
|
||||
|
||||
/* parse config: file.ext#(command) */
|
||||
/* parse config: file.ext#(commands) */
|
||||
{
|
||||
char *config;
|
||||
|
||||
@ -393,200 +481,209 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) {
|
||||
config = strchr(filename, '.'); /* first dot (may be a false positive) */
|
||||
if (!config) /* extensionless */
|
||||
config = filename;
|
||||
config = strchr(config,separator); /* next should be config (hopefully right after extension) */
|
||||
config = strchr(config, '#'); /* next should be config */
|
||||
if (!config) /* no config */
|
||||
config = filename;
|
||||
config = filename; //todo if no config just exit?
|
||||
|
||||
|
||||
range_start = 0;
|
||||
range_end = 1;
|
||||
do {
|
||||
/* get config pointer but remove config from filename */
|
||||
config = strchr(config, separator);
|
||||
if (!config)
|
||||
continue;
|
||||
//;VGM_LOG("TXTP: config=%s\n", config);
|
||||
|
||||
config[0] = '\0';
|
||||
config++;
|
||||
while (config != NULL) {
|
||||
/* position in next #(command) */
|
||||
config = strchr(config, '#');
|
||||
if (!config) break;
|
||||
//;VGM_LOG("TXTP: config='%s'\n", config);
|
||||
|
||||
/* get command until next space/number/comment/end */
|
||||
command[0] = '\0';
|
||||
mc = sscanf(config, "#%n%[^ #0-9\r\n]%n", &nc, command, &nc);
|
||||
//;VGM_LOG("TXTP: command='%s', nc=%i, mc=%i\n", command, nc, mc);
|
||||
if (mc == 0 && nc == 0) break;
|
||||
|
||||
if (config[0] == 'c') {
|
||||
config[0] = '\0'; //todo don't modify input string and properly calculate filename end
|
||||
|
||||
config += nc; /* skip '#' and command */
|
||||
|
||||
/* check command string (though at the moment we only use single letters) */
|
||||
if (strcmp(command,"c") == 0) {
|
||||
/* channel mask: file.ext#c1,2 = play channels 1,2 and mutes rest */
|
||||
int ch;
|
||||
|
||||
config++;
|
||||
cfg.channel_mask = 0;
|
||||
while (sscanf(config, "%d%n", &ch,&n) == 1) {
|
||||
if (ch > 0 && ch <= 32)
|
||||
cfg.channel_mask |= (1 << (ch-1));
|
||||
|
||||
config += n;
|
||||
if (config[0]== ',' || config[0]== '-') /* "-" for PowerShell, may have problems with "," */
|
||||
config++;
|
||||
else if (config[0] != '\0')
|
||||
break;
|
||||
};
|
||||
config += get_mask(config, &cfg.channel_mask);
|
||||
//;VGM_LOG("TXTP: channel_mask ");{int i; for (i=0;i<16;i++)VGM_LOG("%i ",(cfg.channel_mask>>i)&1);}VGM_LOG("\n");
|
||||
}
|
||||
else if (config[0] == 'm') {
|
||||
#ifndef VGMSTREAM_MIXING
|
||||
else if (strcmp(command,"m") == 0) {
|
||||
/* channel mappings: file.ext#m1-2,3-4 = swaps channels 1<>2 and 3<>4 */
|
||||
int ch_from = 0, ch_to = 0;
|
||||
|
||||
config++;
|
||||
cfg.channel_mappings_on = 1;
|
||||
|
||||
while (config[0] != '\0') {
|
||||
if (sscanf(config, " %d%n", &ch_from, &n) != 1)
|
||||
break;
|
||||
config += n;
|
||||
if (config[0]== ',' || config[0]== '-')
|
||||
config++;
|
||||
else if (config[0] != '\0')
|
||||
break;
|
||||
|
||||
if (sscanf(config, " %d%n", &ch_to, &n) != 1)
|
||||
break;
|
||||
config += n;
|
||||
if (config[0]== ',' || config[0]== '-')
|
||||
config++;
|
||||
else if (config[0] != '\0')
|
||||
break;
|
||||
|
||||
if (ch_from > 0 && ch_from <= 32 && ch_to > 0 && ch_to <= 32) {
|
||||
cfg.channel_mappings[ch_from-1] = ch_to-1;
|
||||
}
|
||||
//;VGM_LOG("TXTP: channel_swap %i-%i\n", ch_from, ch_to);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
else if (config[0] == 'm') {
|
||||
else if (strcmp(command,"m") == 0) {
|
||||
/* channel mixing: file.ext#m(sub-command),(sub-command),etc */
|
||||
char cmd;
|
||||
|
||||
config++;
|
||||
|
||||
while (config[0] != '\0') {
|
||||
mix_config_data mix = {0};
|
||||
|
||||
if (config[0]== ',') {
|
||||
config++;
|
||||
//;VGM_LOG("TXTP: subcommand='%s'\n", config);
|
||||
|
||||
if (sscanf(config, " %c%n", &cmd, &n) == 1 && n != 0 && cmd == ',') {
|
||||
config += n;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sscanf(config, "%d-%d%n", &mix.ch_a, &mix.ch_b, &n) == 2 && n != 0) {
|
||||
if (sscanf(config, " %d - %d%n", &mix.ch_dst, &mix.ch_src, &n) == 2 && n != 0) {
|
||||
//;VGM_LOG("TXTP: mix %i-%i\n", mix.ch_dst, mix.ch_src);
|
||||
add_mixing(&cfg, &mix, MIX_SWAP); /* N-M: swaps M with N */
|
||||
config += n;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((sscanf(config, "%d+%d*%f%n", &mix.ch_a, &mix.ch_b, &mix.vol_a, &n) == 3 && n != 0) ||
|
||||
(sscanf(config, "%d+%dx%f%n", &mix.ch_a, &mix.ch_b, &mix.vol_a, &n) == 3 && n != 0)) {
|
||||
if ((sscanf(config, " %d + %d * %f%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0) ||
|
||||
(sscanf(config, " %d + %d x %f%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0)) {
|
||||
//;VGM_LOG("TXTP: mix %i+%i*%f\n", mix.ch_dst, mix.ch_src, mix.vol);
|
||||
add_mixing(&cfg, &mix, MIX_ADD_VOLUME); /* N+M*V: mixes M*volume to N */
|
||||
config += n;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sscanf(config, "%d+%d%n", &mix.ch_a, &mix.ch_b, &n) == 2 && n != 0) {
|
||||
if (sscanf(config, " %d + %d%n", &mix.ch_dst, &mix.ch_src, &n) == 2 && n != 0) {
|
||||
//;VGM_LOG("TXTP: mix %i+%i\n", mix.ch_dst, mix.ch_src);
|
||||
add_mixing(&cfg, &mix, MIX_ADD); /* N+M: mixes M to N */
|
||||
config += n;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((sscanf(config, "%d*%f~%f@%f~%f%n", &mix.ch_a, &mix.vol_a, &mix.vol_b, &mix.pos_a, &mix.pos_b, &n) == 5 && n != 0) ||
|
||||
(sscanf(config, "%dx%f~%f@%f~%f%n", &mix.ch_a, &mix.vol_a, &mix.vol_b, &mix.pos_a, &mix.pos_b, &n) == 5 && n != 0)) {
|
||||
add_mixing(&cfg, &mix, MIX_CROSSFADE); /* N*V1~V2@P1~P2: fades from volume1 to 2 between position1 to 2 */
|
||||
config += n;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((sscanf(config, "%d*%f%n", &mix.ch_a, &mix.vol_a, &n) == 2 && n != 0) ||
|
||||
(sscanf(config, "%dx%f%n", &mix.ch_a, &mix.vol_a, &n) == 2 && n != 0)) {
|
||||
if ((sscanf(config, " %d * %f%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0) ||
|
||||
(sscanf(config, " %d x %f%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) {
|
||||
//;VGM_LOG("TXTP: mix %i*%f\n", mix.ch_dst, mix.vol);
|
||||
add_mixing(&cfg, &mix, MIX_VOLUME); /* N*V: changes volume of N */
|
||||
config += n;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sscanf(config, "%d%c%n", &mix.ch_a, &cmd, &n) == 2 && n != 0 && cmd == 'D') {
|
||||
if ((sscanf(config, " %d = %f%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) {
|
||||
//;VGM_LOG("TXTP: mix %i=%f\n", mix.ch_dst, mix.vol);
|
||||
add_mixing(&cfg, &mix, MIX_LIMIT); /* N=V: limits volume of N */
|
||||
config += n;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sscanf(config, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'D') {
|
||||
//;VGM_LOG("TXTP: mix %iD\n", mix.ch_dst);
|
||||
add_mixing(&cfg, &mix, MIX_DOWNMIX_REST); /* ND: downmix N and all following channels */
|
||||
config += n;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sscanf(config, "%d%c%n", &mix.ch_a, &cmd, &n) == 2 && n != 0 && cmd == 'd') {
|
||||
if (sscanf(config, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'd') {
|
||||
//;VGM_LOG("TXTP: mix %id\n", mix.ch_dst);
|
||||
add_mixing(&cfg, &mix, MIX_DOWNMIX);/* Nd: downmix N only */
|
||||
config += n;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sscanf(config, "%d%c%n", &mix.ch_a, &cmd, &n) == 2 && n != 0 && cmd == 'u') {
|
||||
if (sscanf(config, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'u') {
|
||||
//;VGM_LOG("TXTP: mix %iu\n", mix.ch_dst);
|
||||
add_mixing(&cfg, &mix, MIX_UPMIX); /* Nu: upmix N */
|
||||
config += n;
|
||||
continue;
|
||||
}
|
||||
|
||||
break; /* unknown/mix end */
|
||||
if (get_fade(config, &mix, &n) != 0) {
|
||||
//;VGM_LOG("TXTP: fade %d^%f~%f=%c@%f~%f+%f~%f\n",
|
||||
mix.ch_dst, mix.vol_start, mix.vol_end, mix.shape,
|
||||
mix.time_pre, mix.time_start, mix.time_end, mix.time_post);
|
||||
add_mixing(&cfg, &mix, MIX_FADE); /* N^V1~V2@T1~T2+T3~T4: fades volumes between positions */
|
||||
config += n;
|
||||
continue;
|
||||
}
|
||||
|
||||
break; /* unknown mix/new command/end */
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (config[0] == 's' || (config[0] >= '0' && config[0] <= '9')) {
|
||||
else if (strcmp(command,"s") == 0 || (nc == 1 && config[0] >= '0' && config[0] <= '9')) {
|
||||
/* subsongs: file.ext#s2 = play subsong 2, file.ext#2~10 = play subsong range */
|
||||
int subsong_start = 0, subsong_end = 0;
|
||||
|
||||
if (config[0]== 's')
|
||||
config++;
|
||||
//todo also advance config?
|
||||
if (sscanf(config, " %d ~ %d", &subsong_start, &subsong_end) == 2) {
|
||||
if (subsong_start > 0 && subsong_end > 0) {
|
||||
range_start = subsong_start-1;
|
||||
range_end = subsong_end;
|
||||
}
|
||||
//;VGM_LOG("TXTP: subsong range %i~%i\n", range_start, range_end);
|
||||
}
|
||||
else if (sscanf(config, "%u", &subsong_start) == 1) {
|
||||
else if (sscanf(config, " %d", &subsong_start) == 1) {
|
||||
if (subsong_start > 0) {
|
||||
range_start = subsong_start-1;
|
||||
range_end = subsong_start;
|
||||
}
|
||||
//;VGM_LOG("TXTP: subsong single %i-%i\n", range_start, range_end);
|
||||
}
|
||||
else {
|
||||
config = NULL; /* wrong config, ignore */
|
||||
else { /* wrong config, ignore */
|
||||
//;VGM_LOG("TXTP: subsong none\n");
|
||||
}
|
||||
}
|
||||
else if (config[0] == 'i') {
|
||||
config++;
|
||||
cfg.config_ignore_loop = 1;
|
||||
else if (strcmp(command,"i") == 0) {
|
||||
config += get_bool(config, &cfg.config_ignore_loop);
|
||||
//;VGM_LOG("TXTP: ignore_loop=%i\n", cfg.config_ignore_loop);
|
||||
}
|
||||
else if (config[0] == 'E') {
|
||||
config++;
|
||||
cfg.config_force_loop = 1;
|
||||
else if (strcmp(command,"E") == 0) {
|
||||
config += get_bool(config, &cfg.config_force_loop);
|
||||
//;VGM_LOG("TXTP: force_loop=%i\n", cfg.config_force_loop);
|
||||
}
|
||||
else if (config[0] == 'F') {
|
||||
config++;
|
||||
cfg.config_ignore_fade = 1;
|
||||
else if (strcmp(command,"F") == 0) {
|
||||
config += get_bool(config, &cfg.config_ignore_fade);
|
||||
//;VGM_LOG("TXTP: ignore_fade=%i\n", cfg.config_ignore_fade);
|
||||
}
|
||||
else if (config[0] == 'l') {
|
||||
config++;
|
||||
else if (strcmp(command,"l") == 0) {
|
||||
config += get_double(config, &cfg.config_loop_count);
|
||||
//;VGM_LOG("TXTP: loop_count=%f\n", cfg.config_loop_count);
|
||||
}
|
||||
else if (config[0] == 'f') {
|
||||
config++;
|
||||
else if (strcmp(command,"f") == 0) {
|
||||
config += get_double(config, &cfg.config_fade_time);
|
||||
//;VGM_LOG("TXTP: fade_time=%f\n", cfg.config_fade_time);
|
||||
}
|
||||
else if (config[0] == 'd') {
|
||||
config++;
|
||||
else if (strcmp(command,"d") == 0) {
|
||||
config += get_double(config, &cfg.config_fade_delay);
|
||||
//;VGM_LOG("TXTP: fade_delay %f\n", cfg.config_fade_delay);
|
||||
}
|
||||
else if (config[0] == 'h') {
|
||||
config++;
|
||||
else if (strcmp(command,"h") == 0) {
|
||||
config += get_int(config, &cfg.sample_rate);
|
||||
//;VGM_LOG("TXTP: sample_rate %i\n", cfg.sample_rate);
|
||||
}
|
||||
else if (config[0] == ' ') {
|
||||
continue; /* likely a comment, find next # */
|
||||
else if (config[nc] == ' ') {
|
||||
//;VGM_LOG("TXTP: comment\n");
|
||||
break; /* comment, ignore rest */
|
||||
}
|
||||
else {
|
||||
//;VGM_LOG("TXTP: unknown command '%c'\n", config[0]);
|
||||
break; /* also possibly a comment too */
|
||||
//;VGM_LOG("TXTP: unknown command\n");
|
||||
break; /* end, incorrect command, or possibly a comment or double ## comment too */
|
||||
}
|
||||
}
|
||||
|
||||
} while (config != NULL);
|
||||
|
||||
//;VGM_LOG("TXTP: config: range %i~%i, mask=%x\n", range_start, range_end, channel_mask);
|
||||
}
|
||||
|
||||
|
||||
@ -652,6 +749,9 @@ static int parse_keyval(txtp_header * txtp, const char * key, const char * val)
|
||||
if (0==strcmp(val,"layers")) {
|
||||
txtp->is_layered = 1;
|
||||
}
|
||||
else if (0==strcmp(val,"segments")) {
|
||||
txtp->is_layered = 0;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
@ -665,7 +765,7 @@ static int parse_keyval(txtp_header * txtp, const char * key, const char * val)
|
||||
}
|
||||
}
|
||||
else if (0==strcmp(key,"commands")) {
|
||||
char val2[TXT_LINE_MAX];
|
||||
char val2[TXTP_LINE_MAX];
|
||||
strcpy(val2, val); /* copy since val is modified here but probably not important */
|
||||
if (!add_filename(txtp, val2, 1)) goto fail;
|
||||
}
|
||||
@ -713,23 +813,29 @@ static txtp_header* parse_txtp(STREAMFILE* streamFile) {
|
||||
|
||||
/* read lines */
|
||||
while (txt_offset < file_size) {
|
||||
char line[TXT_LINE_MAX] = {0};
|
||||
char key[TXT_LINE_MAX] = {0}, val[TXT_LINE_MAX] = {0}; /* at least as big as a line to avoid overflows (I hope) */
|
||||
char filename[TXT_LINE_MAX] = {0};
|
||||
char line[TXTP_LINE_MAX] = {0};
|
||||
char key[TXTP_LINE_MAX] = {0}, val[TXTP_LINE_MAX] = {0}; /* at least as big as a line to avoid overflows (I hope) */
|
||||
char filename[TXTP_LINE_MAX] = {0};
|
||||
int ok, bytes_read, line_done;
|
||||
|
||||
bytes_read = get_streamfile_text_line(TXT_LINE_MAX,line, txt_offset,streamFile, &line_done);
|
||||
bytes_read = get_streamfile_text_line(TXTP_LINE_MAX,line, txt_offset,streamFile, &line_done);
|
||||
if (!line_done) goto fail;
|
||||
|
||||
txt_offset += bytes_read;
|
||||
|
||||
/* get key/val (ignores lead/trail spaces, stops at space/separator) */
|
||||
/* get key/val (ignores lead/trail spaces, # may be commands or comments) */
|
||||
ok = sscanf(line, " %[^ \t#=] = %[^\t\r\n] ", key,val);
|
||||
if (ok == 2) { /* no key=val */
|
||||
if (val[0] != '#') {
|
||||
/* val is not command, re-parse skipping comments and trailing spaces */
|
||||
ok = sscanf(line, " %[^ \t#=] = %[^ #\t\r\n] ", key,val);
|
||||
}
|
||||
if (ok == 2) {
|
||||
if (!parse_keyval(txtp, key, val)) /* read key/val */
|
||||
goto fail;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* must be a filename (only remove spaces from start/end, as filenames con contain mid spaces/#/etc) */
|
||||
ok = sscanf(line, " %[^\t\r\n] ", filename);
|
||||
|
@ -108,6 +108,7 @@ typedef struct {
|
||||
int stream_type;
|
||||
|
||||
int layer_count;
|
||||
int layer_channels[BAO_MAX_LAYER_COUNT];
|
||||
int sequence_count;
|
||||
uint32_t sequence_chain[BAO_MAX_CHAIN_COUNT];
|
||||
int sequence_loop;
|
||||
@ -398,7 +399,8 @@ static VGMSTREAM * init_vgmstream_ubi_bao_base(ubi_bao_header * bao, STREAMFILE
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
vgmstream->num_samples = bao->num_samples; /* ffmpeg_data->totalSamples */
|
||||
VGM_ASSERT(bao->num_samples != ffmpeg_data->totalSamples, "UBI BAO: header samples differ\n");
|
||||
VGM_ASSERT(bao->num_samples != ffmpeg_data->totalSamples,
|
||||
"UBI BAO: header samples %i vs ffmpeg %i differ\n", bao->num_samples, (uint32_t)ffmpeg_data->totalSamples);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -425,7 +427,7 @@ static VGMSTREAM * init_vgmstream_ubi_bao_audio(ubi_bao_header * bao, STREAMFILE
|
||||
|
||||
streamData = setup_bao_streamfile(bao, streamFile);
|
||||
if (!streamData) goto fail;
|
||||
//dump_streamfile(streamData, "test.out");
|
||||
|
||||
vgmstream = init_vgmstream_ubi_bao_base(bao, streamFile, streamData);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
@ -446,7 +448,7 @@ static VGMSTREAM * init_vgmstream_ubi_bao_layer(ubi_bao_header *bao, STREAMFILE
|
||||
STREAMFILE* temp_streamFile = NULL;
|
||||
STREAMFILE * streamData = NULL;
|
||||
size_t full_stream_size = bao->stream_size;
|
||||
int i;
|
||||
int i, total_channels = 0;
|
||||
|
||||
streamData = setup_bao_streamfile(bao, streamFile);
|
||||
if (!streamData) goto fail;
|
||||
@ -463,6 +465,8 @@ static VGMSTREAM * init_vgmstream_ubi_bao_layer(ubi_bao_header *bao, STREAMFILE
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
bao->stream_size = get_streamfile_size(temp_streamFile);
|
||||
bao->channels = bao->layer_channels[i];
|
||||
total_channels += bao->layer_channels[i];
|
||||
|
||||
/* build the layer VGMSTREAM (standard sb with custom streamfile) */
|
||||
data->layers[i] = init_vgmstream_ubi_bao_base(bao, streamFile, temp_streamFile);
|
||||
@ -476,7 +480,7 @@ static VGMSTREAM * init_vgmstream_ubi_bao_layer(ubi_bao_header *bao, STREAMFILE
|
||||
goto fail;
|
||||
|
||||
/* build the base VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(bao->channels * bao->layer_count, bao->loop_flag);
|
||||
vgmstream = allocate_vgmstream(total_channels, bao->loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_UBI_BAO;
|
||||
@ -898,10 +902,6 @@ static int parse_type_audio(ubi_bao_header * bao, off_t offset, STREAMFILE* stre
|
||||
|
||||
bao->stream_type = read_32bit(h_offset + bao->cfg.audio_stream_type, streamFile);
|
||||
|
||||
if (bao->loop_flag && bao->cfg.audio_channel_samples) {
|
||||
bao->num_samples = bao->num_samples / bao->channels;
|
||||
}
|
||||
|
||||
return 1;
|
||||
//fail:
|
||||
// return 0;
|
||||
@ -990,13 +990,13 @@ static int parse_type_layer(ubi_bao_header * bao, off_t offset, STREAMFILE* stre
|
||||
|
||||
/* get 1st layer header in extra table and validate all headers match */
|
||||
table_offset = offset + bao->header_size + cues_size;
|
||||
bao->channels = read_32bit(table_offset + bao->cfg.layer_channels, streamFile);
|
||||
//bao->channels = read_32bit(table_offset + bao->cfg.layer_channels, streamFile);
|
||||
bao->sample_rate = read_32bit(table_offset + bao->cfg.layer_sample_rate, streamFile);
|
||||
bao->stream_type = read_32bit(table_offset + bao->cfg.layer_stream_type, streamFile);
|
||||
bao->num_samples = read_32bit(table_offset + bao->cfg.layer_num_samples, streamFile);
|
||||
|
||||
for (i = 0; i < bao->layer_count; i++) {
|
||||
//int channels = read_32bit(table_offset + bao->cfg.layer_channels, streamFile);
|
||||
int channels = read_32bit(table_offset + bao->cfg.layer_channels, streamFile);
|
||||
int sample_rate = read_32bit(table_offset + bao->cfg.layer_sample_rate, streamFile);
|
||||
int stream_type = read_32bit(table_offset + bao->cfg.layer_stream_type, streamFile);
|
||||
int num_samples = read_32bit(table_offset + bao->cfg.layer_num_samples, streamFile);
|
||||
@ -1004,15 +1004,14 @@ static int parse_type_layer(ubi_bao_header * bao, off_t offset, STREAMFILE* stre
|
||||
VGM_LOG("UBI BAO: layer headers don't match at %x\n", (uint32_t)table_offset);
|
||||
|
||||
if (bao->cfg.layer_ignore_error) {
|
||||
bao->layer_count -= 1;
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* unusual but happens, layers handle it fine [Rayman Raving Rabbids: TV Party (Wii) ex. 0x22000cbc.pk] */
|
||||
//;VGM_ASSERT_ONCE(bao->channels != channels, "UBI BAO: layer channels don't match at %x\n", (uint32_t)table_offset);
|
||||
/* uncommonly channels may vary per layer [Rayman Raving Rabbids: TV Party (Wii) ex. 0x22000cbc.pk] */
|
||||
bao->layer_channels[i] = channels;
|
||||
|
||||
/* can be +-1 */
|
||||
if (bao->num_samples != num_samples && bao->num_samples + 1 == num_samples) {
|
||||
@ -1079,6 +1078,10 @@ static int parse_values(ubi_bao_header * bao, STREAMFILE *streamFile) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (bao->type == UBI_AUDIO && bao->codec == RAW_PSX && bao->loop_flag && bao->cfg.audio_channel_samples) {
|
||||
bao->num_samples = bao->num_samples / bao->channels;
|
||||
}
|
||||
|
||||
|
||||
/* set prefetch id */
|
||||
if (bao->is_prefetched) {
|
||||
@ -1641,7 +1644,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) {
|
||||
* - 0x04+: mini header (varies with version, see parse_header)
|
||||
*
|
||||
* Then are divided into "classes":
|
||||
* - 0x10000000: event (links by id to another event or header BAO)
|
||||
* - 0x10000000: event (links by id to another event or header BAO, may set volume/reverb/filters/etc)
|
||||
* - 0x20000000: header
|
||||
* - 0x30000000: memory audio (in .pk/.bao)
|
||||
* - 0x40000000: project info
|
||||
@ -1649,6 +1652,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) {
|
||||
* - 0x60000000: unused?
|
||||
* - 0x70000000: info? has a count+table of id-things
|
||||
* - 0x80000000: unknown (some floats?)
|
||||
* - 0x90000000: unknown (some kind of command config?), rare [Ghost Recon Future Soldier (PC)]
|
||||
* Class 1/2/3 are roughly equivalent to Ubi SB's section1/2/3, and class 4 is
|
||||
* basically .spN project files.
|
||||
*
|
||||
@ -1700,7 +1704,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) {
|
||||
config_bao_entry(bao, 0xA4, 0x28); /* PC: 0xA8, PS3/X360: 0xA4 */
|
||||
|
||||
config_bao_audio_b(bao, 0x08, 0x1c, 0x28, 0x34, 1, 1); /* 0x2c: prefetch flag? */
|
||||
config_bao_audio_m(bao, 0x44, 0x4c, 0x50, 0x58, 0x64, 0x74);
|
||||
config_bao_audio_m(bao, 0x44, 0x48, 0x50, 0x58, 0x64, 0x74);
|
||||
bao->cfg.audio_interleave = 0x10;
|
||||
bao->cfg.audio_channel_samples = 1; //todo check all looping ps-adpcm
|
||||
|
||||
@ -1814,6 +1818,29 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) {
|
||||
bao->cfg.file_type = UBI_FORGE_b;
|
||||
return 1;
|
||||
|
||||
case 0x00280303: /* Tom Clancy's Ghost Recon Future Soldier (PC/PS3)-pk */
|
||||
config_bao_entry(bao, 0xBC, 0x28); /* PC/PS3: 0xBC */
|
||||
|
||||
config_bao_audio_b(bao, 0x08, 0x38, 0x3c, 0x48, 1, 1);
|
||||
config_bao_audio_m(bao, 0x54, 0x5c, 0x64, 0x6c, 0x74, 0x80);
|
||||
|
||||
config_bao_sequence(bao, 0x48, 0x3c, 0x38, 0x14);
|
||||
|
||||
config_bao_layer_m(bao, 0x00, 0x3c, 0x44, 0x58, 0x60, 0x64, 0x00, 0x00, 1);
|
||||
config_bao_layer_e(bao, 0x2c, 0x00, 0x04, 0x08, 0x1c);
|
||||
bao->cfg.layer_ignore_error = 1; //todo some layer sample rates don't match
|
||||
//todo some files have strange prefetch+stream of same size (2 segments?), ex. CEND_30_VOX.lpk
|
||||
|
||||
config_bao_silence_f(bao, 0x38);
|
||||
|
||||
bao->cfg.codec_map[0x01] = RAW_PCM;
|
||||
bao->cfg.codec_map[0x02] = UBI_IMA; /* v6 */
|
||||
bao->cfg.codec_map[0x04] = FMT_OGG;
|
||||
bao->cfg.codec_map[0x07] = RAW_AT3; //todo some layers use AT3_105
|
||||
|
||||
bao->cfg.file_type = UBI_FORGE_b;
|
||||
return 1;
|
||||
|
||||
case 0x001B0200: /* Beowulf (PS3)-atomic-bin+fat */
|
||||
/* same as 0x001B0100 except:
|
||||
* - base 0xA0, skip 0x24, name style %08x (.bao/sbao?) */
|
||||
|
@ -137,6 +137,7 @@ typedef struct {
|
||||
off_t xma_header_offset; /* some XMA have extra header stuff */
|
||||
|
||||
int layer_count; /* number of layers in a layer type */
|
||||
int layer_channels[SB_MAX_LAYER_COUNT];
|
||||
int sequence_count; /* number of segments in a sequence type */
|
||||
int sequence_chain[SB_MAX_CHAIN_COUNT]; /* sequence of entry numbers */
|
||||
int sequence_loop; /* chain index to loop */
|
||||
@ -639,7 +640,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st
|
||||
STREAMFILE* temp_streamFile = NULL;
|
||||
STREAMFILE *streamData = NULL;
|
||||
size_t full_stream_size = sb->stream_size;
|
||||
int i;
|
||||
int i, total_channels = 0;
|
||||
|
||||
/* open external stream if needed */
|
||||
if (sb->is_external) {
|
||||
@ -664,6 +665,8 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
sb->stream_size = get_streamfile_size(temp_streamFile);
|
||||
sb->channels = sb->layer_channels[i];
|
||||
total_channels += sb->layer_channels[i];
|
||||
|
||||
/* build the layer VGMSTREAM (standard sb with custom streamfile) */
|
||||
data->layers[i] = init_vgmstream_ubi_sb_base(sb, streamTest, temp_streamFile, 0x00);
|
||||
@ -678,7 +681,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st
|
||||
|
||||
|
||||
/* build the base VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(sb->channels * sb->layer_count, sb->loop_flag);
|
||||
vgmstream = allocate_vgmstream(total_channels, sb->loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_UBI_SB;
|
||||
@ -1107,17 +1110,17 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream
|
||||
|
||||
/* get 1st layer header in extra table and validate all headers match */
|
||||
table_offset = sb->extra_offset;
|
||||
sb->channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */
|
||||
(uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, streamFile) :
|
||||
(uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, streamFile);
|
||||
//sb->channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */
|
||||
// (uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, streamFile) :
|
||||
// (uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, streamFile);
|
||||
sb->sample_rate = read_32bit(table_offset + sb->cfg.layer_sample_rate, streamFile);
|
||||
sb->stream_type = read_32bit(table_offset + sb->cfg.layer_stream_type, streamFile);
|
||||
sb->num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile);
|
||||
|
||||
for (i = 0; i < sb->layer_count; i++) {
|
||||
//int channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */
|
||||
// (uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, streamFile) :
|
||||
// (uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, streamFile);
|
||||
int channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */
|
||||
(uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, streamFile) :
|
||||
(uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, streamFile);
|
||||
int sample_rate = read_32bit(table_offset + sb->cfg.layer_sample_rate, streamFile);
|
||||
int stream_type = read_32bit(table_offset + sb->cfg.layer_stream_type, streamFile);
|
||||
int num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile);
|
||||
@ -1125,15 +1128,14 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream
|
||||
VGM_LOG("Ubi SB: %i layer headers don't match at %x\n", sb->layer_count, (uint32_t)table_offset);
|
||||
|
||||
if (sb->cfg.ignore_layer_error) {
|
||||
sb->layer_count = 1;
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* unusual but happens, layers handle it fine [Brothers in Arms 2 (PS2) ex. MP_B01_NL.SB1] */
|
||||
//;VGM_ASSERT_ONCE(sb->channels != channels, "UBI SB: layer channels don't match at %x\n", (uint32_t)table_offset);
|
||||
/* uncommonly channels may vary per layer [Brothers in Arms 2 (PS2) ex. MP_B01_NL.SB1] */
|
||||
sb->layer_channels[i] = channels;
|
||||
|
||||
/* can be +-1 */
|
||||
if (sb->num_samples != num_samples && sb->num_samples + 1 == num_samples) {
|
||||
|
@ -90,7 +90,7 @@ VGMSTREAM * init_vgmstream_vag(STREAMFILE *streamFile) {
|
||||
else if (read_32bitLE(0x1000,streamFile) == 0x56414770) /* "pGAV" */
|
||||
interleave = 0x1000; /* Jak X interleave, includes header */
|
||||
else
|
||||
goto fail;
|
||||
interleave = 0x2000; /* Jak 3 interleave in rare files, no header */
|
||||
//todo interleave_first = interleave - start_offset; /* interleave includes header */
|
||||
}
|
||||
else {
|
||||
|
@ -6,7 +6,7 @@ VGMSTREAM * init_vgmstream_xnb(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset, xma_chunk_offset = 0;
|
||||
int loop_flag = 0, channel_count, num_samples = 0, loop_start = 0, loop_end = 0;
|
||||
int big_endian, flags, codec, sample_rate, block_size, bps;
|
||||
int big_endian, flags, codec, sample_rate, block_align, bps;
|
||||
size_t data_size;
|
||||
char platform;
|
||||
|
||||
@ -76,7 +76,7 @@ VGMSTREAM * init_vgmstream_xnb(STREAMFILE *streamFile) {
|
||||
channel_count = read_16bit(current_offset+0x02, streamFile);
|
||||
sample_rate = read_32bit(current_offset+0x04, streamFile);
|
||||
/* 0x08: byte rate */
|
||||
block_size = read_16bit(current_offset+0x0c, streamFile);
|
||||
block_align = read_16bit(current_offset+0x0c, streamFile);
|
||||
bps = read_16bit(current_offset+0x0e, streamFile);
|
||||
|
||||
if (codec == 0x166) {
|
||||
@ -104,29 +104,29 @@ VGMSTREAM * init_vgmstream_xnb(STREAMFILE *streamFile) {
|
||||
switch (codec) {
|
||||
case 0x01: /* Dragon's Blade (Android) */
|
||||
/* null in Metagalactic Blitz (PC) */
|
||||
if (!block_size)
|
||||
block_size = (bps == 8 ? 0x01 : 0x02) * channel_count;
|
||||
if (!block_align)
|
||||
block_align = (bps == 8 ? 0x01 : 0x02) * channel_count;
|
||||
|
||||
vgmstream->coding_type = bps == 8 ? coding_PCM8_U_int : coding_PCM16LE;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = block_size / channel_count;
|
||||
vgmstream->interleave_block_size = block_align / channel_count;
|
||||
vgmstream->num_samples = pcm_bytes_to_samples(data_size, channel_count, bps);
|
||||
break;
|
||||
|
||||
case 0x02: /* White Noise Online (PC) */
|
||||
if (!block_size) goto fail;
|
||||
if (!block_align) goto fail;
|
||||
vgmstream->coding_type = coding_MSADPCM;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->interleave_block_size = block_size;
|
||||
vgmstream->num_samples = msadpcm_bytes_to_samples(data_size, block_size, channel_count);
|
||||
vgmstream->interleave_block_size = block_align;
|
||||
vgmstream->num_samples = msadpcm_bytes_to_samples(data_size, block_align, channel_count);
|
||||
break;
|
||||
|
||||
case 0x11:
|
||||
if (!block_size) goto fail;
|
||||
if (!block_align) goto fail;
|
||||
vgmstream->coding_type = coding_MS_IMA;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->interleave_block_size = block_size;
|
||||
vgmstream->num_samples = ms_ima_bytes_to_samples(data_size, block_size, channel_count);
|
||||
vgmstream->interleave_block_size = block_align;
|
||||
vgmstream->num_samples = ms_ima_bytes_to_samples(data_size, block_align, channel_count);
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
|
@ -741,8 +741,9 @@ static int get_xsb_name(char * buf, size_t maxsize, int target_subsong, xwb_head
|
||||
xsb.xsb_wavebanks = calloc(xsb.xsb_wavebanks_count, sizeof(xsb_wavebank));
|
||||
if (!xsb.xsb_wavebanks) goto fail;
|
||||
|
||||
/* The following is a bizarre soup of flags, tables, offsets to offsets and stuff, just to get the actual name.
|
||||
* info: https://wiki.multimedia.cx/index.php/XACT */
|
||||
/* The following is a bizarre soup of flags, tables, offsets to offsets and stuff, just to get the actual name. Info:
|
||||
* - https://wiki.multimedia.cx/index.php/XACT
|
||||
* - https://github.com/MonoGame/MonoGame/blob/master/MonoGame.Framework/Audio/Xact/SoundBank.cs*/
|
||||
|
||||
/* parse xsb sounds */
|
||||
off = xsb.xsb_sounds_offset;
|
||||
|
@ -1111,13 +1111,19 @@ void get_streamfile_ext(STREAMFILE *streamFile, char * filename, size_t size) {
|
||||
}
|
||||
|
||||
/* debug util, mainly for custom IO testing */
|
||||
void dump_streamfile(STREAMFILE *streamFile, const char* out) {
|
||||
void dump_streamfile(STREAMFILE *streamFile, int num) {
|
||||
#ifdef VGM_DEBUG_OUTPUT
|
||||
off_t offset = 0;
|
||||
FILE *f = NULL;
|
||||
|
||||
if (out) {
|
||||
f = fopen(out,"wb");
|
||||
if (num >= 0) {
|
||||
char filename[PATH_LIMIT];
|
||||
char dumpname[PATH_LIMIT];
|
||||
|
||||
get_streamfile_filename(streamFile, filename, PATH_LIMIT);
|
||||
snprintf(dumpname,PATH_LIMIT, "%s_%02i.dump", filename, num);
|
||||
|
||||
f = fopen(dumpname,"wb");
|
||||
if (!f) return;
|
||||
}
|
||||
|
||||
@ -1127,14 +1133,14 @@ void dump_streamfile(STREAMFILE *streamFile, const char* out) {
|
||||
size_t read;
|
||||
|
||||
read = read_streamfile(buffer,offset,0x8000,streamFile);
|
||||
if (out)
|
||||
if (f)
|
||||
fwrite(buffer,sizeof(uint8_t),read, f);
|
||||
else
|
||||
VGM_LOGB(buffer,read,0);
|
||||
offset += read;
|
||||
}
|
||||
|
||||
if (out) {
|
||||
if (f) {
|
||||
fclose(f);
|
||||
}
|
||||
#endif
|
||||
|
@ -191,6 +191,19 @@ static inline int8_t read_8bit(off_t offset, STREAMFILE * streamfile) {
|
||||
return buf[0];
|
||||
}
|
||||
|
||||
/* alias of the above */
|
||||
static inline int8_t read_s8(off_t offset, STREAMFILE * streamfile) { return read_8bit(offset, streamfile); }
|
||||
static inline uint8_t read_u8(off_t offset, STREAMFILE * streamfile) { return (uint8_t)read_8bit(offset, streamfile); }
|
||||
static inline int32_t read_s16le(off_t offset, STREAMFILE * streamfile) { return read_16bitLE(offset, streamfile); }
|
||||
static inline uint32_t read_u16le(off_t offset, STREAMFILE * streamfile) { return (uint16_t)read_16bitLE(offset, streamfile); }
|
||||
static inline int32_t read_s16be(off_t offset, STREAMFILE * streamfile) { return read_16bitBE(offset, streamfile); }
|
||||
static inline uint32_t read_u16be(off_t offset, STREAMFILE * streamfile) { return (uint16_t)read_16bitBE(offset, streamfile); }
|
||||
static inline int32_t read_s32le(off_t offset, STREAMFILE * streamfile) { return read_32bitLE(offset, streamfile); }
|
||||
static inline uint32_t read_u32le(off_t offset, STREAMFILE * streamfile) { return (uint32_t)read_32bitLE(offset, streamfile); }
|
||||
static inline int32_t read_s32be(off_t offset, STREAMFILE * streamfile) { return read_32bitBE(offset, streamfile); }
|
||||
static inline uint32_t read_u32be(off_t offset, STREAMFILE * streamfile) { return (uint32_t)read_32bitBE(offset, streamfile); }
|
||||
|
||||
|
||||
/* guess byte endianness from a given value, return true if big endian and false if little endian */
|
||||
static inline int guess_endianness16bit(off_t offset, STREAMFILE * streamfile) {
|
||||
uint8_t buf[0x02];
|
||||
@ -231,5 +244,5 @@ void get_streamfile_basename(STREAMFILE *streamFile, char * buffer, size_t size)
|
||||
void get_streamfile_path(STREAMFILE *streamFile, char * buffer, size_t size);
|
||||
void get_streamfile_ext(STREAMFILE *streamFile, char * filename, size_t size);
|
||||
|
||||
void dump_streamfile(STREAMFILE *streamFile, const char* out);
|
||||
void dump_streamfile(STREAMFILE *streamFile, int num);
|
||||
#endif
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <stdint.h>
|
||||
#endif /* _MSC_VER */
|
||||
|
||||
typedef int16_t sample;
|
||||
typedef int16_t sample; //TODO: deprecated, remove
|
||||
typedef int16_t sample_t;
|
||||
|
||||
#endif
|
||||
|
28
src/util.c
28
src/util.c
@ -6,22 +6,26 @@ const char * filename_extension(const char * pathname) {
|
||||
const char * filename;
|
||||
const char * extension;
|
||||
|
||||
/* get basename + extension */
|
||||
filename = pathname;
|
||||
#if 0
|
||||
//must detect empty extensions in folders with . in the name; not too important and DIR_SEPARATOR could improved
|
||||
filename = strrchr(pathname, DIR_SEPARATOR);
|
||||
if (filename == NULL)
|
||||
filename = pathname; /* pathname has no separators (single filename) */
|
||||
/* favor strrchr (optimized/aligned) rather than homemade loops */
|
||||
|
||||
/* find possible separator first to avoid misdetecting folders with dots + extensionless files
|
||||
* (allow both slashes as plugin could pass normalized '/') */
|
||||
filename = strrchr(pathname, '/');
|
||||
if (filename != NULL)
|
||||
filename++; /* skip separator */
|
||||
else {
|
||||
filename = strrchr(pathname, '\\');
|
||||
if (filename != NULL)
|
||||
filename++; /* skip separator */
|
||||
else
|
||||
filename++; /* skip the separator */
|
||||
#endif
|
||||
filename = pathname; /* pathname has no separators (single filename) */
|
||||
}
|
||||
|
||||
extension = strrchr(filename,'.');
|
||||
if (extension==NULL)
|
||||
extension = filename+strlen(filename); /* point to null, i.e. an empty string for the extension */
|
||||
if (extension != NULL)
|
||||
extension++; /* skip dot */
|
||||
else
|
||||
extension++; /* skip the dot */
|
||||
extension = filename + strlen(filename); /* point to null (empty "" string for extensionless files) */
|
||||
|
||||
return extension;
|
||||
}
|
||||
|
203
src/vgmstream.c
203
src/vgmstream.c
@ -466,6 +466,8 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
|
||||
init_vgmstream_dsf,
|
||||
init_vgmstream_208,
|
||||
init_vgmstream_dsp_ds2,
|
||||
init_vgmstream_ffdl,
|
||||
init_vgmstream_mus_vc,
|
||||
|
||||
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
|
||||
init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */
|
||||
@ -567,6 +569,16 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) {
|
||||
}
|
||||
|
||||
void setup_vgmstream(VGMSTREAM * vgmstream) {
|
||||
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
/* fill default config to simplify external code (mixing off will always happen
|
||||
* initially, and if they contain values it means mixing must be enabled) */
|
||||
if (!vgmstream->mixing_on || vgmstream->input_channels <= 0)
|
||||
vgmstream->input_channels = vgmstream->channels;
|
||||
if (!vgmstream->mixing_on || vgmstream->output_channels <= 0)
|
||||
vgmstream->output_channels = vgmstream->channels;
|
||||
#endif
|
||||
|
||||
/* save start things so we can restart when seeking */
|
||||
memcpy(vgmstream->start_ch, vgmstream->ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels);
|
||||
memcpy(vgmstream->start_vgmstream, vgmstream, sizeof(VGMSTREAM));
|
||||
@ -602,7 +614,7 @@ void reset_vgmstream(VGMSTREAM * vgmstream) {
|
||||
* Otherwise hit_loop will be 0 and it will be copied over anyway when we
|
||||
* really hit the loop start. */
|
||||
|
||||
/* reset custom codec and layout data */
|
||||
/* reset custom codec */
|
||||
#ifdef VGM_USE_VORBIS
|
||||
if (vgmstream->coding_type == coding_OGG_VORBIS) {
|
||||
reset_ogg_vorbis(vgmstream);
|
||||
@ -679,21 +691,10 @@ void reset_vgmstream(VGMSTREAM * vgmstream) {
|
||||
|
||||
if (vgmstream->coding_type == coding_NWA) {
|
||||
nwa_codec_data *data = vgmstream->codec_data;
|
||||
if (data)
|
||||
reset_nwa(data->nwa);
|
||||
}
|
||||
|
||||
|
||||
if (vgmstream->layout_type == layout_aix) {
|
||||
aix_codec_data *data = vgmstream->codec_data;
|
||||
int i;
|
||||
|
||||
data->current_segment = 0;
|
||||
for (i = 0; i < data->segment_count*data->stream_count; i++) {
|
||||
reset_vgmstream(data->adxs[i]);
|
||||
}
|
||||
if (data) reset_nwa(data->nwa);
|
||||
}
|
||||
|
||||
/* reset custom layouts */
|
||||
if (vgmstream->layout_type == layout_segmented) {
|
||||
reset_layout_segmented(vgmstream->layout_data);
|
||||
}
|
||||
@ -703,7 +704,7 @@ void reset_vgmstream(VGMSTREAM * vgmstream) {
|
||||
}
|
||||
|
||||
/* note that this does not reset the constituent STREAMFILES
|
||||
* (ch's streamfiles init in metas, nor their internal state) */
|
||||
* (vgmstream->ch[N].streamfiles' internal state, though shouldn't matter) */
|
||||
}
|
||||
|
||||
/* Allocate memory and setup a VGMSTREAM */
|
||||
@ -711,7 +712,7 @@ VGMSTREAM * allocate_vgmstream(int channel_count, int loop_flag) {
|
||||
VGMSTREAM * vgmstream;
|
||||
|
||||
/* up to ~16-24 aren't too rare for multilayered files, more is probably a bug */
|
||||
if (channel_count <= 0 || channel_count > 64) {
|
||||
if (channel_count <= 0 || channel_count > VGMSTREAM_MAX_CHANNELS) {
|
||||
VGM_LOG("VGMSTREAM: error allocating %i channels\n", channel_count);
|
||||
return NULL;
|
||||
}
|
||||
@ -756,9 +757,9 @@ VGMSTREAM * allocate_vgmstream(int channel_count, int loop_flag) {
|
||||
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
/* fixed arrays, for now */
|
||||
vgmstream->mixing_size = 64;
|
||||
vgmstream->stream_name_size = STREAM_NAME_SIZE;
|
||||
vgmstream->mixing_size = VGMSTREAM_MAX_MIXING;
|
||||
#endif
|
||||
//vgmstream->stream_name_size = STREAM_NAME_SIZE;
|
||||
return vgmstream;
|
||||
fail:
|
||||
if (vgmstream) {
|
||||
@ -875,28 +876,6 @@ void close_vgmstream(VGMSTREAM * vgmstream) {
|
||||
|
||||
|
||||
/* free custom layouts */
|
||||
if (vgmstream->layout_type == layout_aix) {
|
||||
aix_codec_data *data = (aix_codec_data *) vgmstream->codec_data;
|
||||
|
||||
if (data) {
|
||||
if (data->adxs) {
|
||||
int i;
|
||||
for (i = 0; i < data->segment_count*data->stream_count; i++) {
|
||||
/* note that the close_streamfile won't do anything but deallocate itself,
|
||||
* there is only one open file in vgmstream->ch[0].streamfile */
|
||||
close_vgmstream(data->adxs[i]);
|
||||
}
|
||||
free(data->adxs);
|
||||
}
|
||||
if (data->sample_counts) {
|
||||
free(data->sample_counts);
|
||||
}
|
||||
|
||||
free(data);
|
||||
}
|
||||
vgmstream->codec_data = NULL;
|
||||
}
|
||||
|
||||
if (vgmstream->layout_type == layout_segmented) {
|
||||
free_layout_segmented(vgmstream->layout_data);
|
||||
vgmstream->layout_data = NULL;
|
||||
@ -1011,6 +990,9 @@ void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target) {
|
||||
vgmstream_set_loop_target(data->layers[i], loop_target);
|
||||
}
|
||||
}
|
||||
|
||||
/* notify of new initial state */
|
||||
setup_vgmstream(vgmstream);
|
||||
}
|
||||
|
||||
|
||||
@ -1063,9 +1045,6 @@ void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstre
|
||||
case layout_blocked_vs_square:
|
||||
render_vgmstream_blocked(buffer,sample_count,vgmstream);
|
||||
break;
|
||||
case layout_aix:
|
||||
render_vgmstream_aix(buffer,sample_count,vgmstream);
|
||||
break;
|
||||
case layout_segmented:
|
||||
render_vgmstream_segmented(buffer,sample_count,vgmstream);
|
||||
break;
|
||||
@ -1076,11 +1055,11 @@ void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstre
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
#ifndef VGMSTREAM_MIXING
|
||||
/* swap channels if set, to create custom channel mappings */
|
||||
if (vgmstream->channel_mappings_on) {
|
||||
int ch_from,ch_to,s;
|
||||
sample temp;
|
||||
sample_t temp;
|
||||
for (s = 0; s < sample_count; s++) {
|
||||
for (ch_from = 0; ch_from < vgmstream->channels; ch_from++) {
|
||||
if (ch_from > 32)
|
||||
@ -1109,6 +1088,11 @@ void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstre
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
mix_vgmstream(buffer, sample_count, vgmstream);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Get the number of samples of a single frame (smallest self-contained sample group, 1/N channels) */
|
||||
@ -1178,6 +1162,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
|
||||
case coding_WV6_IMA:
|
||||
case coding_ALP_IMA:
|
||||
case coding_FFTA2_IMA:
|
||||
case coding_BLITZ_IMA:
|
||||
case coding_PCFX:
|
||||
return 2;
|
||||
case coding_XBOX_IMA:
|
||||
@ -1356,6 +1341,7 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
|
||||
case coding_WV6_IMA:
|
||||
case coding_ALP_IMA:
|
||||
case coding_FFTA2_IMA:
|
||||
case coding_BLITZ_IMA:
|
||||
case coding_PCFX:
|
||||
case coding_OKI16:
|
||||
return 0x01;
|
||||
@ -1490,7 +1476,7 @@ int get_vgmstream_shortframe_size(VGMSTREAM * vgmstream) {
|
||||
|
||||
/* Decode samples into the buffer. Assume that we have written samples_written into the
|
||||
* buffer already, and we have samples_to_do consecutive samples ahead of us. */
|
||||
void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to_do, sample * buffer) {
|
||||
void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to_do, sample_t * buffer) {
|
||||
int ch;
|
||||
|
||||
switch (vgmstream->coding_type) {
|
||||
@ -1860,6 +1846,12 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
|
||||
vgmstream->channels,vgmstream->samples_into_block,samples_to_do);
|
||||
}
|
||||
break;
|
||||
case coding_BLITZ_IMA:
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
decode_blitz_ima(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch,
|
||||
vgmstream->channels,vgmstream->samples_into_block,samples_to_do);
|
||||
}
|
||||
break;
|
||||
|
||||
case coding_APPLE_IMA4:
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
@ -2555,7 +2547,7 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *strea
|
||||
}
|
||||
|
||||
/* check these even if there is no loop, because they should then be zero in both
|
||||
* Homura PS2 right channel doesn't have loop points so it's ignored */
|
||||
* (Homura PS2 right channel doesn't have loop points so this check is ignored) */
|
||||
if (new_vgmstream->meta_type != meta_PS2_SMPL &&
|
||||
!(new_vgmstream->loop_flag == opened_vgmstream->loop_flag &&
|
||||
new_vgmstream->loop_start_sample== opened_vgmstream->loop_start_sample &&
|
||||
@ -2660,100 +2652,96 @@ static STREAMFILE * get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM *
|
||||
return vgmstream->ch[channel].streamfile;
|
||||
}
|
||||
|
||||
static int get_vgmstream_average_bitrate_from_size(size_t size, int sample_rate, int length_samples) {
|
||||
static int get_vgmstream_file_bitrate_from_size(size_t size, int sample_rate, int length_samples) {
|
||||
if (sample_rate == 0 || length_samples == 0) return 0;
|
||||
if (length_samples < 100) return 0; /* ignore stupid bitrates caused by some segments */
|
||||
return (int)((int64_t)size * 8 * sample_rate / length_samples);
|
||||
}
|
||||
static int get_vgmstream_average_bitrate_from_streamfile(STREAMFILE * streamfile, int sample_rate, int length_samples) {
|
||||
return get_vgmstream_average_bitrate_from_size(get_streamfile_size(streamfile), sample_rate, length_samples);
|
||||
static int get_vgmstream_file_bitrate_from_streamfile(STREAMFILE * streamfile, int sample_rate, int length_samples) {
|
||||
if (streamfile == NULL) return 0;
|
||||
return get_vgmstream_file_bitrate_from_size(get_streamfile_size(streamfile), sample_rate, length_samples);
|
||||
}
|
||||
|
||||
/* Return the average bitrate in bps of all unique files contained within this stream. */
|
||||
int get_vgmstream_average_bitrate(VGMSTREAM * vgmstream) {
|
||||
STREAMFILE *streamfiles[64];
|
||||
const size_t streamfiles_max = 64; /* arbitrary max, */
|
||||
size_t streamfiles_size = 0;
|
||||
size_t streams_size = 0;
|
||||
unsigned int ch, sub;
|
||||
|
||||
static int get_vgmstream_file_bitrate_main(VGMSTREAM * vgmstream, STREAMFILE **streamfile_pointers, int *pointers_count, int pointers_max) {
|
||||
int sub, ch;
|
||||
int bitrate = 0;
|
||||
int sample_rate = vgmstream->sample_rate;
|
||||
int length_samples = vgmstream->num_samples;
|
||||
|
||||
if (!sample_rate || !length_samples)
|
||||
return 0;
|
||||
/* Recursively get bitrate and fill the list of streamfiles if needed (to filter),
|
||||
* since layouts can include further vgmstreams that may also share streamfiles.
|
||||
*
|
||||
* Because of how data, layers and segments can be combined it's possible to
|
||||
* fool this in various ways; metas should report stream_size in complex cases
|
||||
* to get accurate bitrates (particularly for subsongs). */
|
||||
|
||||
/* subsongs need to report this to properly calculate */
|
||||
if (vgmstream->stream_size) {
|
||||
return get_vgmstream_average_bitrate_from_size(vgmstream->stream_size, sample_rate, length_samples);
|
||||
bitrate = get_vgmstream_file_bitrate_from_size(vgmstream->stream_size, vgmstream->sample_rate, vgmstream->num_samples);
|
||||
}
|
||||
|
||||
//todo bitrate bugs with layout inside layouts (ex. TXTP)
|
||||
/* make a list of used streamfiles (repeats will be filtered below) */
|
||||
if (vgmstream->layout_type == layout_segmented) {
|
||||
else if (vgmstream->layout_type == layout_segmented) {
|
||||
segmented_layout_data *data = (segmented_layout_data *) vgmstream->layout_data;
|
||||
for (sub = 0; sub < data->segment_count; sub++) {
|
||||
streams_size += data->segments[sub]->stream_size;
|
||||
for (ch = 0; ch < data->segments[sub]->channels; ch++) {
|
||||
if (streamfiles_size >= streamfiles_max) continue;
|
||||
streamfiles[streamfiles_size] = get_vgmstream_average_bitrate_channel_streamfile(data->segments[sub], ch);
|
||||
streamfiles_size++;
|
||||
}
|
||||
bitrate += get_vgmstream_file_bitrate_main(data->segments[sub], streamfile_pointers, pointers_count, pointers_max);
|
||||
}
|
||||
bitrate = bitrate / data->segment_count;
|
||||
}
|
||||
else if (vgmstream->layout_type == layout_layered) {
|
||||
layered_layout_data *data = vgmstream->layout_data;
|
||||
for (sub = 0; sub < data->layer_count; sub++) {
|
||||
streams_size += data->layers[sub]->stream_size;
|
||||
for (ch = 0; ch < data->layers[sub]->channels; ch++) {
|
||||
if (streamfiles_size >= streamfiles_max) continue;
|
||||
streamfiles[streamfiles_size] = get_vgmstream_average_bitrate_channel_streamfile(data->layers[sub], ch);
|
||||
streamfiles_size++;
|
||||
}
|
||||
bitrate += get_vgmstream_file_bitrate_main(data->layers[sub], streamfile_pointers, pointers_count, pointers_max);
|
||||
}
|
||||
bitrate = bitrate / data->layer_count;
|
||||
}
|
||||
else {
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
if (streamfiles_size >= streamfiles_max)
|
||||
continue;
|
||||
streamfiles[streamfiles_size] = get_vgmstream_average_bitrate_channel_streamfile(vgmstream, ch);
|
||||
streamfiles_size++;
|
||||
}
|
||||
}
|
||||
|
||||
/* could have a sum of all sub-VGMSTREAMs */
|
||||
if (streams_size) {
|
||||
return get_vgmstream_average_bitrate_from_size(streams_size, sample_rate, length_samples);
|
||||
}
|
||||
|
||||
/* compare files by absolute paths, so bitrate doesn't multiply when the same STREAMFILE is
|
||||
/* Add channel bitrate if streamfile hasn't been used before (comparing files
|
||||
* by absolute paths), so bitrate doesn't multiply when the same STREAMFILE is
|
||||
* reopened per channel, also skipping repeated pointers. */
|
||||
{
|
||||
char path_current[PATH_LIMIT];
|
||||
char path_compare[PATH_LIMIT];
|
||||
unsigned int i, j;
|
||||
int is_unique = 1;
|
||||
|
||||
for (i = 0; i < streamfiles_size; i++) {
|
||||
STREAMFILE * currentFile = streamfiles[i];
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
STREAMFILE * currentFile = get_vgmstream_average_bitrate_channel_streamfile(vgmstream, ch);
|
||||
if (!currentFile) continue;
|
||||
get_streamfile_name(currentFile, path_current, sizeof(path_current));
|
||||
|
||||
for (j = 0; j < i; j++) {
|
||||
STREAMFILE * compareFile = streamfiles[j];
|
||||
for (sub = 0; sub < *pointers_count; sub++) {
|
||||
STREAMFILE * compareFile = streamfile_pointers[sub];
|
||||
if (!compareFile) continue;
|
||||
if (currentFile == compareFile)
|
||||
break;
|
||||
get_streamfile_name(compareFile, path_compare, sizeof(path_compare));
|
||||
if (strcmp(path_current, path_compare) == 0)
|
||||
if (currentFile == compareFile) {
|
||||
is_unique = 0;
|
||||
break;
|
||||
}
|
||||
get_streamfile_name(compareFile, path_compare, sizeof(path_compare));
|
||||
if (strcmp(path_current, path_compare) == 0) {
|
||||
is_unique = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == j) { /* current STREAMFILE hasn't appeared previously */
|
||||
bitrate += get_vgmstream_average_bitrate_from_streamfile(currentFile, sample_rate, length_samples);
|
||||
if (is_unique) {
|
||||
if (*pointers_count >= pointers_max) goto fail;
|
||||
streamfile_pointers[*pointers_count] = currentFile;
|
||||
(*pointers_count)++;
|
||||
|
||||
bitrate += get_vgmstream_file_bitrate_from_streamfile(currentFile, vgmstream->sample_rate, vgmstream->num_samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bitrate;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return the average bitrate in bps of all unique data contained within this stream.
|
||||
* This is the bitrate of the *file*, as opposed to the bitrate of the *codec*, meaning
|
||||
* it counts extra data like block headers and padding. While this can be surprising
|
||||
* sometimes (as it's often higher than common codec bitrates) it isn't wrong per se. */
|
||||
int get_vgmstream_average_bitrate(VGMSTREAM * vgmstream) {
|
||||
const size_t pointers_max = 128; /* arbitrary max, but +100 segments have been observed */
|
||||
STREAMFILE *streamfile_pointers[128]; /* list already used streamfiles */
|
||||
int pointers_count = 0;
|
||||
|
||||
return get_vgmstream_file_bitrate_main(vgmstream, streamfile_pointers, &pointers_count, pointers_max);
|
||||
}
|
||||
|
||||
|
||||
@ -2773,8 +2761,7 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s
|
||||
|
||||
|
||||
/* stream/offsets not needed, managed by layout */
|
||||
if (vgmstream->layout_type == layout_aix ||
|
||||
vgmstream->layout_type == layout_segmented ||
|
||||
if (vgmstream->layout_type == layout_segmented ||
|
||||
vgmstream->layout_type == layout_layered)
|
||||
return 1;
|
||||
|
||||
|
@ -8,6 +8,10 @@
|
||||
/* reasonable maxs */
|
||||
enum { PATH_LIMIT = 32768 };
|
||||
enum { STREAM_NAME_SIZE = 255 };
|
||||
enum { VGMSTREAM_MAX_CHANNELS = 64 };
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
enum { VGMSTREAM_MAX_MIXING = 64 };
|
||||
#endif
|
||||
|
||||
#include "streamfile.h"
|
||||
|
||||
@ -120,6 +124,7 @@ typedef enum {
|
||||
coding_WV6_IMA, /* Gorilla Systems WV6 4-bit IMA ADPCM */
|
||||
coding_ALP_IMA, /* High Voltage ALP 4-bit IMA ADPCM */
|
||||
coding_FFTA2_IMA, /* Final Fantasy Tactics A2 4-bit IMA ADPCM */
|
||||
coding_BLITZ_IMA, /* Blitz Games 4-bit IMA ADPCM */
|
||||
|
||||
coding_MS_IMA, /* Microsoft IMA ADPCM */
|
||||
coding_XBOX_IMA, /* XBOX IMA ADPCM */
|
||||
@ -264,7 +269,6 @@ typedef enum {
|
||||
layout_blocked_vs_square,
|
||||
|
||||
/* otherwise odd */
|
||||
layout_aix, /* CRI AIX's wheels within wheels */
|
||||
layout_segmented, /* song divided in segments (song sections) */
|
||||
layout_layered, /* song divided in layers (song channels) */
|
||||
|
||||
@ -719,6 +723,7 @@ typedef enum {
|
||||
meta_DSF,
|
||||
meta_208,
|
||||
meta_DSP_DS2,
|
||||
meta_MUS_VC,
|
||||
|
||||
} meta_t;
|
||||
|
||||
@ -729,20 +734,28 @@ typedef enum {
|
||||
MIX_ADD,
|
||||
MIX_ADD_VOLUME,
|
||||
MIX_VOLUME,
|
||||
MIX_CROSSFADE,
|
||||
MIX_LIMIT,
|
||||
MIX_DOWNMIX,
|
||||
MIX_DOWNMIX_REST,
|
||||
MIX_UPMIX
|
||||
MIX_UPMIX,
|
||||
MIX_FADE
|
||||
} mix_command_t;
|
||||
|
||||
typedef struct {
|
||||
mix_command_t command;
|
||||
int ch_a;
|
||||
int ch_b;
|
||||
float vol_a;
|
||||
float vol_b;
|
||||
float pos_a;
|
||||
float pos_b;
|
||||
/* common */
|
||||
int ch_dst;
|
||||
int ch_src;
|
||||
float vol;
|
||||
|
||||
/* fade envelope */
|
||||
float vol_start; /* volume from pre to start */
|
||||
float vol_end; /* volume from end to post */
|
||||
char shape; /* curve type */
|
||||
float time_pre; /* position before curve where vol_str applies (-1 = beginning) */
|
||||
float time_start; /* curve start position where vol changes from src to dst */
|
||||
float time_end; /* curve end position where vol changes from src to dst */
|
||||
float time_post; /* position after curve where vol_dst applies (-1 = end) */
|
||||
} mix_config_data;
|
||||
#endif
|
||||
|
||||
@ -821,15 +834,19 @@ typedef struct {
|
||||
|
||||
/* other config */
|
||||
int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */
|
||||
#ifndef VGMSTREAM_MIXING
|
||||
uint32_t channel_mask; /* to silence crossfading subsongs/layers */
|
||||
int channel_mappings_on; /* channel mappings are active */
|
||||
int channel_mappings[32]; /* swap channel "i" with "[i]" */
|
||||
#endif
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
int output_channels; /* resulting channels after mixing (may be ignored if plugin doesn't support it) */
|
||||
/* may be ignored if plugin doesn't support it, but fields will be always set to simplify plugin's code */
|
||||
int input_channels; /* starting channels before mixing (outbuf must be this big) */
|
||||
int output_channels; /* resulting channels after mixing */
|
||||
int mixing_on; /* mixing allowed */
|
||||
int mixing_count; /* mixing number */
|
||||
mix_config_data mixing[64]; /* applies transformation to output samples (could be alloc'ed but to simplify...) */
|
||||
size_t mixing_size; /* mixing max */
|
||||
mix_config_data mixing_chain[VGMSTREAM_MAX_MIXING]; /* effects to apply (could be alloc'ed but to simplify...) */
|
||||
#endif
|
||||
/* config requests, players must read and honor these values */
|
||||
/* (ideally internally would work as a player, but for now player must do it manually) */
|
||||
@ -1065,21 +1082,21 @@ typedef struct {
|
||||
|
||||
#ifdef VGM_USE_G7221
|
||||
typedef struct {
|
||||
sample buffer[640];
|
||||
sample_t buffer[640];
|
||||
g7221_handle *handle;
|
||||
} g7221_codec_data;
|
||||
#endif
|
||||
|
||||
#ifdef VGM_USE_G719
|
||||
typedef struct {
|
||||
sample buffer[960];
|
||||
sample_t buffer[960];
|
||||
void *handle;
|
||||
} g719_codec_data;
|
||||
#endif
|
||||
|
||||
#ifdef VGM_USE_MAIATRAC3PLUS
|
||||
typedef struct {
|
||||
sample *buffer;
|
||||
sample_t *buffer;
|
||||
int channels;
|
||||
int samples_discard;
|
||||
void *handle;
|
||||
@ -1108,20 +1125,6 @@ typedef struct {
|
||||
void *io_config;
|
||||
} acm_codec_data;
|
||||
|
||||
#define AIX_BUFFER_SIZE 0x1000
|
||||
/* AIXery */
|
||||
typedef struct {
|
||||
sample buffer[AIX_BUFFER_SIZE];
|
||||
int segment_count;
|
||||
int stream_count;
|
||||
int current_segment;
|
||||
/* one per segment */
|
||||
int32_t *sample_counts;
|
||||
/* organized like:
|
||||
* segment1_stream1, segment1_stream2, segment2_stream1, segment2_stream2*/
|
||||
VGMSTREAM **adxs;
|
||||
} aix_codec_data;
|
||||
|
||||
/* for files made of "continuous" segments, one per section of a song (using a complete sub-VGMSTREAM) */
|
||||
typedef struct {
|
||||
int segment_count;
|
||||
@ -1329,6 +1332,15 @@ VGMSTREAM * allocate_vgmstream(int channel_count, int looped);
|
||||
/* Prepare the VGMSTREAM's initial state once parsed and ready, but before playing. */
|
||||
void setup_vgmstream(VGMSTREAM * vgmstream);
|
||||
|
||||
#ifdef VGMSTREAM_MIXING
|
||||
/* Applies mixing commands to the vgmstream to the sample buffer.
|
||||
* Mixing must be enabled and outbuf must be big enough for output_channels*samples_to_do big. */
|
||||
void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream);
|
||||
|
||||
/* Add a new internal mix. Always use this as it validates mixes. */
|
||||
void vgmstream_add_mixing(VGMSTREAM* vgmstream, mix_config_data mix);
|
||||
#endif
|
||||
|
||||
/* Get the number of samples of a single frame (smallest self-contained sample group, 1/N channels) */
|
||||
int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream);
|
||||
/* Get the number of bytes of a single frame (smallest self-contained byte group, 1/N channels) */
|
||||
@ -1339,7 +1351,7 @@ int get_vgmstream_shortframe_size(VGMSTREAM * vgmstream);
|
||||
|
||||
/* Decode samples into the buffer. Assume that we have written samples_written into the
|
||||
* buffer already, and we have samples_to_do consecutive samples ahead of us. */
|
||||
void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to_do, sample * buffer);
|
||||
void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to_do, sample_t * buffer);
|
||||
|
||||
/* Calculate number of consecutive samples to do (taking into account stopping for loop start and end) */
|
||||
int vgmstream_samples_to_do(int samples_this_block, int samples_per_frame, VGMSTREAM * vgmstream);
|
||||
|
@ -994,21 +994,26 @@ int winamp_IsOurFile(const in_char *fn) {
|
||||
const in_char *filename;
|
||||
const in_char *extension;
|
||||
|
||||
/* get basename + extension */
|
||||
filename = fn;
|
||||
#if 0
|
||||
//must detect empty extensions in folders with . in the name; doesn't work ok?
|
||||
/* favor strrchr (optimized/aligned) rather than homemade loops */
|
||||
|
||||
/* find possible separator first to avoid misdetecting folders with dots + extensionless files
|
||||
* (allow both slashes as plugin could pass normalized '/') */
|
||||
filename = wa_strrchr(fn, wa_L('\\'));
|
||||
if (filename == NULL)
|
||||
filename = fn;
|
||||
if (filename != NULL)
|
||||
filename++; /* skip separator */
|
||||
else {
|
||||
filename = wa_strrchr(fn, wa_L('/'));
|
||||
if (filename != NULL)
|
||||
filename++; /* skip separator */
|
||||
else
|
||||
filename = fn; /* pathname has no separators (single filename) */
|
||||
}
|
||||
|
||||
extension = wa_strrchr(filename,'.');
|
||||
if (extension != NULL)
|
||||
extension++; /* skip dot */
|
||||
else
|
||||
filename++;
|
||||
#endif
|
||||
extension = wa_strrchr(filename, wa_L('.'));
|
||||
if (extension == NULL)
|
||||
return 1; /* extensionless, try to play it */
|
||||
else
|
||||
extension++;
|
||||
|
||||
/* returning 0 here means it only accepts the extensions in working_extension_list */
|
||||
/* it's possible to ignore the list and manually accept extensions, like foobar's g_is_our_path */
|
||||
|
Loading…
x
Reference in New Issue
Block a user