mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 16:30:54 +01:00
commit
6857e0cd7a
18
README.md
18
README.md
@ -278,6 +278,24 @@ simpler to make and cleaner: for example create a text file named `bgm01-loop.tx
|
||||
and inside write `bgm01.mp3 #I 10.0 90.0`. Open the `.txtp` to play the `.mp3`
|
||||
looping from 10 to 90 seconds.
|
||||
|
||||
#### OS case sensitiveness
|
||||
When using OS with case sensitive filesystem (mainly Linux), a known issue with
|
||||
companion files is that vgmstream generally tries to find them using lowercase
|
||||
extension.
|
||||
|
||||
This means that if the developer used uppercase instead (e.g. `bgm.ABK`+`bgm.AST`)
|
||||
loading will fail. It's technically complex to fix this, so for the time being
|
||||
the only option is renaming the companion extension to lowercase.
|
||||
|
||||
A particularly nasty variation of that is that some formats load files by full
|
||||
name (e.g. `STREAM.SS0`), but sometimes the actual filename is in other case
|
||||
(`Stream.ss0`), and some files could even point to that with another case. You
|
||||
could try adding *symlinks* in various upper/lower/mixed cases to handle this.
|
||||
Currently there isn't any way to know what exact name is needed (other than
|
||||
hex-editting), though only a few formats do this, mainly *Ubisoft* banks.
|
||||
|
||||
Regular formats without companion files should work fine in upper/lowercase.
|
||||
|
||||
### Decryption keys
|
||||
Certain formats have encrypted data, and need a key to decrypt. vgmstream
|
||||
will try to find the correct key from a list, but it can be provided by
|
||||
|
48
doc/TXTH.md
48
doc/TXTH.md
@ -440,9 +440,13 @@ While you can put anything in the values, this feature is meant to be used to st
|
||||
|
||||
|
||||
#### BASE OFFSET MODIFIER
|
||||
You can set a default offset that affects next `@(offset)` reads making them `@(offset + base_offset)`, for cleaner parsing (particularly interesting when combined with the `name_table`).
|
||||
You can set a default offset that affects next `@(offset)` reads making them `@(offset + base_offset)`, for cleaner parsing.
|
||||
|
||||
This is particularly interesting when combined with offsets to some long value. For example instead of `channels = @0x714` you could set `base_offset = 0x710, channels = @0x04`. Or values from the `name_table`, like `base_offset = name_value, channels = @0x04`.
|
||||
|
||||
It also allows parsing formats that set offsets to another offset, by "chaining" `base_offset`. With `base_offset = @0x10` (pointing to `0x40`) then `base_offset = @0x20`, it reads value at `0x60`. Set to 0 when you want to disable/reset the chain: `base_offset = @0x10` then `base_offset = 0` then `base_offset = @0x20` reads value at `0x20`
|
||||
|
||||
|
||||
For example instead of `channels = @0x714` you could set `base_offset = 0x710, channels = @0x04`. Set to 0 when you want to disable it.
|
||||
```
|
||||
base_offset = (value)
|
||||
```
|
||||
@ -1052,3 +1056,43 @@ loop_flag = auto
|
||||
#@0x10 is an absolute offset to another table, that shouldn't be affected by subsong_spacing
|
||||
name_offset_absolute = @0x10 + 0x270
|
||||
```
|
||||
|
||||
#### Fatal Frame (Xbox) .mwa.txth
|
||||
```
|
||||
#00: MWAV
|
||||
#04: flags?
|
||||
#08: subsongs
|
||||
#0c: data size
|
||||
#10: null
|
||||
#14: sizes offset
|
||||
#18: offsets table
|
||||
#1c: offset to tables?
|
||||
#20: header offset
|
||||
|
||||
subsong_count = @0x08
|
||||
|
||||
# size table
|
||||
subsong_spacing = 0
|
||||
base_offset = 0
|
||||
base_offset = @0x14
|
||||
subsong_spacing = 0x04
|
||||
data_size = @0x00
|
||||
|
||||
# offset table
|
||||
subsong_spacing = 0
|
||||
base_offset = 0
|
||||
base_offset = @0x18
|
||||
subsong_spacing = 0x04
|
||||
start_offset = @0x00
|
||||
|
||||
# header (standard "fmt")
|
||||
subsong_spacing = 0
|
||||
base_offset = 0
|
||||
base_offset = @0x20
|
||||
channels = @0x02$2
|
||||
sample_rate = @0x04
|
||||
|
||||
codec = XBOX
|
||||
num_samples = data_size
|
||||
#todo: there are dummy entries
|
||||
```
|
||||
|
116
doc/TXTP.md
116
doc/TXTP.md
@ -72,7 +72,7 @@ loop_start_segment = 2
|
||||
loop_end_segment = 3
|
||||
loop_mode = keep # loops in 2nd file's loop_start to 3rd file's loop_end
|
||||
```
|
||||
Mixing sample rates is ok (uses max) but channel number must be equal for all files. You can use mixing (explained later) to join segments of different channels though.
|
||||
Mixing sample rates is ok (uses max). Different number of channels is allowed, but you may need to use mixing (explained later) to improve results. 4ch + 2ch will sound ok, but 1ch + 2ch would need some upmixing first.
|
||||
|
||||
|
||||
### Layers mode
|
||||
@ -242,15 +242,16 @@ group = L #h44100
|
||||
commands = #h48000 #overwrites
|
||||
```
|
||||
|
||||
Segments and layer settings and rules still apply when making groups, so you can't group segments of files with different total channels. To do it you could use commands to "downmix" the group first:
|
||||
Segments and layer settings and rules still apply when making groups, so you may need to adjust groups a bit with commands:
|
||||
```
|
||||
# this doesn't need to be grouped
|
||||
intro_2ch.at3
|
||||
|
||||
# this is grouped into a single 4ch file, then downmixed to stereo
|
||||
# this is grouped into a single 4ch file, then auto-downmixed to stereo
|
||||
# (without downmixing may sound a bit strange since channels from mainB wouldn't mix with intro)
|
||||
mainA_2ch.at3
|
||||
mainB_2ch.at3
|
||||
group = 2L2 #@layer-v 2
|
||||
group = -L2 #@layer-v
|
||||
|
||||
# finally resulting layers are played as segments (2ch, 2ch)
|
||||
# (could set a group = S and ommit mode here, too)
|
||||
@ -489,6 +490,39 @@ Use this feature responsibly, though. If you find a format that should loop usin
|
||||
Note that a few codecs may not work with arbitrary loop values since they weren't tested with loops. Misaligned loops will cause audible "clicks" at loop point too.
|
||||
|
||||
|
||||
### Loop anchors
|
||||
**`#a`** (loop start segment), **`#A`** (loop end segment): mark looping parts in segmented layout.
|
||||
|
||||
For segmented layout normally you set loop points using `loop_start_segment` and `loop_end_segment`. It's clean in simpler cases but can be a hassle when lots of files exist. To simplify those cases you can set "loop anchors":
|
||||
```
|
||||
bgm01.adx
|
||||
bgm02.adx #a ##defines loop start
|
||||
```
|
||||
```
|
||||
bgm01.adx
|
||||
bgm02.adx #a ##defines loop start
|
||||
bgm03.adx
|
||||
bgm04.adx #A ##defines loop end
|
||||
bgm05.adx
|
||||
```
|
||||
You can also use `#@loop` to set loop start.
|
||||
|
||||
This setting also works in groups, which allows loops when using multiple segmented groups (not possible with `loop_start/end_segment`).
|
||||
```
|
||||
bgm01.adx
|
||||
bgm02.adx #a
|
||||
group -S2 #l 2.0
|
||||
bgm01.adx
|
||||
bgm02.adx #a
|
||||
bgm03.adx
|
||||
group -S2 #l 3.0
|
||||
group -S2
|
||||
#could use R groups to select one sub-groups that loops
|
||||
# (loop_start_segment doesn't make sense for both segments)
|
||||
```
|
||||
Loop anchors have priority over `loop_start_segment`, and are ignored in layered layouts.
|
||||
|
||||
|
||||
### Force sample rate
|
||||
**`#h(sample rate)`**: changes sample rate to selected value, changing play speed.
|
||||
|
||||
@ -566,44 +600,11 @@ song#m1-3,2-4,3D
|
||||
# - drop channel 1 then 2 (now 1)
|
||||
song#m1d,1d
|
||||
```
|
||||
Proper mixing requires some basic knowledge though, it's further explained later. Order matters and operations are applied sequentially, for extra flexibility at the cost of complexity and user-friendliness, and may result in surprising mixes. Try to stick to macros and simple combos, using later examples as a base.
|
||||
Proper mixing requires some basic knowledge though, it's further explained later. Order matters and operations are applied sequentially, for extra flexibility at the cost of complexity and user-friendliness, and may result in surprising mixes. Typical mixing operations are provided as *macros* (see below), so try to stick to macros and simple combos, using later examples as a base.
|
||||
|
||||
This can be applied to individual layers and segments, but normally you want to use `commands` to apply mixing to the resulting file (see examples). Per-segment mixing should be reserved to specific up/downmixings.
|
||||
|
||||
Mixing must be supported by the plugin, otherwise it's ignored (there is a negligible performance penalty per mix operation though).
|
||||
|
||||
|
||||
### Loop anchors
|
||||
**`#a`** (loop start segment), **`#A`** (loop end segment): mark looping parts in segmented layout.
|
||||
|
||||
For segmented layout normally you set loop points using `loop_start_segment` and `loop_end_segment`. It's clean in simpler cases but can be a hassle when lots of files exist. To simplify those cases you can set "loop anchors":
|
||||
```
|
||||
bgm01.adx
|
||||
bgm02.adx #a ##defines loop start
|
||||
```
|
||||
```
|
||||
bgm01.adx
|
||||
bgm02.adx #a ##defines loop start
|
||||
bgm03.adx
|
||||
bgm04.adx #A ##defines loop end
|
||||
bgm05.adx
|
||||
```
|
||||
You can also use `#@loop` to set loop start.
|
||||
|
||||
This setting also works in groups, which allows loops when using multiple segmented groups (not possible with `loop_start/end_segment`).
|
||||
```
|
||||
bgm01.adx
|
||||
bgm02.adx #a
|
||||
group -S2 #l 2.0
|
||||
bgm01.adx
|
||||
bgm02.adx #a
|
||||
bgm03.adx
|
||||
group -S2 #l 3.0
|
||||
group -S2
|
||||
#could use R groups to select one sub-groups that loops
|
||||
# (loop_start_segment doesn't make sense for both segments)
|
||||
```
|
||||
Loop anchors have priority over `loop_start_segment`, and are ignored in layered layouts.
|
||||
Mixing must be supported by the plugin, otherwise it's ignored (there is a negligible performance penalty per mix operation though, though having *a lot* will add up).
|
||||
|
||||
|
||||
### Macros
|
||||
@ -613,10 +614,10 @@ Manually setting values gets old, so TXTP supports a bunch of simple macros. The
|
||||
- `volume N (channels)`: sets volume V to selected channels. N.N = percent or NdB = decibels.
|
||||
- `1.0` or `0dB` = base volume, `2.0` or `6dB` = double volume, `0.5` or `-6dB` = half volume
|
||||
- `#v N` also works
|
||||
- `track (channels)`: makes a file of selected channels
|
||||
- `layer-v N (channels)`: mixes selected channels to N channels with default volume (for layered vocals). If N is 0 (or ommited), automatically sets highest channel count among all layers.
|
||||
- `layer-b N (channels)`: same, but adjusts volume depending on layers (for layered bgm)
|
||||
- `layer-e N (channels)`: same, but adjusts volume equally for all layers (for generic downmixing)
|
||||
- `track (channels)`: makes a file of selected channels (drops others)
|
||||
- `layer-v (N) (channels)`: for layered files, mixes selected channels to N channels with default volume (for layered vocals). If N is ommited (or 0), automatically sets highest channel count among all layers plus does some extra optimizations for (hopefully) better sounding results. May be applied to global commands or group config.
|
||||
- `layer-e (N) (channels)`: same, but adjusts volume equally for all layers (for generic downmixing)
|
||||
- `layer-b (N) (channels)`: same, but adjusts volume focusing on "main" layer (for layered bgm)
|
||||
- `remix N (channels)`: same, but mixes selected channels to N channels properly adjusting volume (for layered bgm)
|
||||
- `crosstrack N`: crossfades between Nch tracks after every loop (loop count is adjusted as needed)
|
||||
- `crosslayer-v/b/e N`: crossfades Nch layers to the main track after every loop (loop count is adjusted as needed)
|
||||
@ -731,7 +732,7 @@ Here is a rough look of how TXTP parses files, so you get a better idea of what'
|
||||
subdir name/bgm bank.fsb#s2#C1,2
|
||||
subdir name/bgm bank.fsb #s2 #C1,2 #comment
|
||||
```
|
||||
All defined files must exist and be parseable by vgmstream, and general config like `mode` must make sense (not `mde = layers` or `mode = laye`).
|
||||
All defined files must exist and be parseable by vgmstream, and general config like `mode` must make sense (not `mde = layers` or `mode = laye`). *subdir* may even be relative paths like `../file.adx`, provided your OS supports that.
|
||||
|
||||
Commands may add spaces as needed, but try to keep it simple. They *must* start with `#(command)`, as `#(space)(anything)` is a comment. Commands without corresponding file are ignored too (seen as comments), 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):
|
||||
```
|
||||
@ -796,7 +797,7 @@ You can also add spaces before files/commands, mainly to improve readability whe
|
||||
#segment x2
|
||||
song1
|
||||
song2
|
||||
group = 1S2 #E
|
||||
group = -S2 #E
|
||||
```
|
||||
|
||||
Repeated commands overwrite previous setting, except comma-separated commands that are additive:
|
||||
@ -844,7 +845,7 @@ All this means there is no simple, standard way to mix, so you must experiment a
|
||||
|
||||
|
||||
### Mixing examples
|
||||
TXTP has a few macros that help you handle most simpler cases (like `#C 1 2`, `#@layer-v 2`), that you should use when possible, but below is a full explanation of manual mixing (macros just automate these options using some standard formulas).
|
||||
TXTP has a few macros that help you handle most simpler cases (like `#C 1 2`, `#@layer-v`), that you should use when possible, but below is a full explanation of manual mixing (macros just automate these options using some standard formulas).
|
||||
```
|
||||
# boost volume of all channels by 30%
|
||||
song#m0*1.3
|
||||
@ -915,19 +916,22 @@ ffxiii2-eclipse.scd#m5u,6u,5+1,6+2#@crosstrack 2
|
||||
|
||||
Segment/layer downmixing is allowed but try to keep it simple, some mixes accomplish the same things but are a bit strange.
|
||||
```
|
||||
# mix one stereo segment with a mono segment
|
||||
# mix one stereo segment with a mono segment upmixed to stereo
|
||||
intro-stereo.hca
|
||||
loop-mono.hca#m2u
|
||||
|
||||
# this makes mono file
|
||||
intro-stereo.hca#m2u
|
||||
loop-stereo.hca#m2u
|
||||
|
||||
# but you normally should do this instead as it's more natural
|
||||
loop-mono.hca#m2u,2+1
|
||||
```
|
||||
```
|
||||
# this makes mono file from each stereo song
|
||||
intro-stereo.hca#m2d
|
||||
loop-stereo.hca#m2d
|
||||
```
|
||||
```
|
||||
# but you normally should do it on the final result as it's more natural
|
||||
intro-stereo.hca
|
||||
loop-stereo.hca
|
||||
commands = #m2u
|
||||
|
||||
commands = #m2d
|
||||
```
|
||||
```
|
||||
# fading segments work rather unexpectedly
|
||||
# fades out 1 minute into the _segment_ (could be 2 minutes into the resulting file)
|
||||
segment1.hca#m0{0:10+10.0
|
||||
|
@ -551,7 +551,7 @@ ffmpeg_codec_data* init_ffmpeg_ue4_opus(STREAMFILE* sf, off_t start_offset, size
|
||||
ffmpeg_codec_data* init_ffmpeg_ea_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
|
||||
ffmpeg_codec_data* init_ffmpeg_x_opus(STREAMFILE* sf, off_t table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip);
|
||||
ffmpeg_codec_data* init_ffmpeg_fsb_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
|
||||
ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip);
|
||||
ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t data_offset, size_t data_size, opus_config* cfg);
|
||||
|
||||
size_t switch_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE* sf);
|
||||
|
||||
|
@ -490,11 +490,11 @@ static size_t make_opus_header(uint8_t* buf, int buf_size, opus_config *cfg) {
|
||||
if (mapping_family > 0) {
|
||||
int i;
|
||||
|
||||
/* internal mono/stereo streams (N mono/stereo streams form M channels) */
|
||||
/* internal mono/stereo streams (N mono/stereo streams that make M channels) */
|
||||
put_u8(buf+0x13, cfg->stream_count);
|
||||
/* joint stereo streams (rest would be mono, so 6ch can be 2ch+2ch+1ch+1ch = 2 coupled */
|
||||
/* joint stereo streams (rest would be mono, so 6ch can be 2ch+2ch+1ch+1ch = 2 coupled in 4 streams */
|
||||
put_u8(buf+0x14, cfg->coupled_count);
|
||||
/* mapping bits per channel? */
|
||||
/* mapping per channel (order of channels, ex: 0x000104050203) */
|
||||
for (i = 0; i < cfg->channels; i++) {
|
||||
put_u8(buf+0x15+i, cfg->channel_mapping[i]);
|
||||
}
|
||||
@ -753,8 +753,8 @@ ffmpeg_codec_data* init_ffmpeg_x_opus(STREAMFILE* sf, off_t table_offset, int ta
|
||||
ffmpeg_codec_data* init_ffmpeg_fsb_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
|
||||
return init_ffmpeg_custom_opus(sf, start_offset, data_size, channels, skip, sample_rate, OPUS_FSB);
|
||||
}
|
||||
ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip) {
|
||||
return init_ffmpeg_custom_table_opus(sf, table_offset, table_count, data_offset, data_size, channels, skip, 0, OPUS_WWISE);
|
||||
ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t data_offset, size_t data_size, opus_config* cfg) {
|
||||
return init_ffmpeg_custom_opus_config(sf, data_offset, data_size, cfg, OPUS_WWISE);
|
||||
}
|
||||
|
||||
static opus_type_t get_ue4opus_version(STREAMFILE* sf, off_t offset) {
|
||||
|
@ -333,6 +333,7 @@ static const char* extension_list[] = {
|
||||
"musc",
|
||||
"musx",
|
||||
"mvb", //txth/reserved [Porsche Challenge (PS1)]
|
||||
"mwa", //txth/reserved [Fatal Frame (Xbox)]
|
||||
"mwv",
|
||||
"mxst",
|
||||
"myspd",
|
||||
|
@ -76,6 +76,18 @@ decode_fail:
|
||||
}
|
||||
|
||||
|
||||
void seek_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample) {
|
||||
int layer;
|
||||
layered_layout_data* data = vgmstream->layout_data;
|
||||
|
||||
for (layer = 0; layer < data->layer_count; layer++) {
|
||||
seek_vgmstream(data->layers[layer], seek_sample);
|
||||
}
|
||||
|
||||
vgmstream->current_sample = seek_sample;
|
||||
vgmstream->samples_into_block = seek_sample;
|
||||
}
|
||||
|
||||
void loop_layout_layered(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
||||
int layer;
|
||||
layered_layout_data* data = vgmstream->layout_data;
|
||||
|
@ -59,7 +59,8 @@ 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);
|
||||
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample);
|
||||
void seek_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample);
|
||||
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample);
|
||||
VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment);
|
||||
|
||||
void render_vgmstream_layered(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
|
||||
@ -67,7 +68,8 @@ 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);
|
||||
void loop_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample);
|
||||
void seek_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample);
|
||||
void loop_layout_layered(VGMSTREAM* vgmstream, int32_t loop_sample);
|
||||
VGMSTREAM *allocate_layered_vgmstream(layered_layout_data* data);
|
||||
|
||||
#endif
|
||||
|
@ -7,6 +7,7 @@
|
||||
#define VGMSTREAM_MAX_SEGMENTS 1024
|
||||
#define VGMSTREAM_SEGMENT_SAMPLE_BUFFER 8192
|
||||
|
||||
static inline void copy_samples(sample_t* outbuf, segmented_layout_data* data, int current_channels, int32_t samples_to_do, int32_t samples_written);
|
||||
|
||||
/* Decodes samples for segmented streams.
|
||||
* Chains together sequential vgmstreams, for data divided into separate sections or files
|
||||
@ -15,9 +16,10 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
int samples_written = 0, samples_this_block;
|
||||
segmented_layout_data* data = vgmstream->layout_data;
|
||||
int use_internal_buffer = 0;
|
||||
int current_channels = 0;
|
||||
|
||||
/* normally uses outbuf directly (faster?) but could need internal buffer if downmixing */
|
||||
if (vgmstream->channels != data->input_channels) {
|
||||
if (vgmstream->channels != data->input_channels || data->mixed_channels) {
|
||||
use_internal_buffer = 1;
|
||||
}
|
||||
|
||||
@ -27,6 +29,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
}
|
||||
|
||||
samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
|
||||
mixing_info(data->segments[data->current_segment], NULL, ¤t_channels);
|
||||
|
||||
while (samples_written < sample_count) {
|
||||
int samples_to_do;
|
||||
@ -34,6 +37,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) {
|
||||
/* handle looping (loop_layout has been called below, changes segments/state) */
|
||||
samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
|
||||
mixing_info(data->segments[data->current_segment], NULL, ¤t_channels);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -50,6 +54,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
reset_vgmstream(data->segments[data->current_segment]);
|
||||
|
||||
samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
|
||||
mixing_info(data->segments[data->current_segment], NULL, ¤t_channels);
|
||||
vgmstream->samples_into_block = 0;
|
||||
continue;
|
||||
}
|
||||
@ -73,10 +78,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
data->segments[data->current_segment]);
|
||||
|
||||
if (use_internal_buffer) {
|
||||
int s;
|
||||
for (s = 0; s < samples_to_do * data->output_channels; s++) {
|
||||
outbuf[samples_written * data->output_channels + s] = data->buffer[s];
|
||||
}
|
||||
copy_samples(outbuf, data, current_channels, samples_to_do, samples_written);
|
||||
}
|
||||
|
||||
samples_written += samples_to_do;
|
||||
@ -89,7 +91,31 @@ decode_fail:
|
||||
memset(outbuf + samples_written * data->output_channels, 0, (sample_count - samples_written) * data->output_channels * sizeof(sample_t));
|
||||
}
|
||||
|
||||
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
||||
static inline void copy_samples(sample_t* outbuf, segmented_layout_data* data, int current_channels, int32_t samples_to_do, int32_t samples_written) {
|
||||
int ch_out = data->output_channels;
|
||||
int ch_in = current_channels;
|
||||
int pos = samples_written * ch_out;
|
||||
int s;
|
||||
if (ch_in == ch_out) { /* most common and probably faster */
|
||||
for (s = 0; s < samples_to_do * ch_out; s++) {
|
||||
outbuf[pos + s] = data->buffer[s];
|
||||
}
|
||||
}
|
||||
else {
|
||||
int ch;
|
||||
for (s = 0; s < samples_to_do; s++) {
|
||||
for (ch = 0; ch < ch_in; ch++) {
|
||||
outbuf[pos + s*ch_out + ch] = data->buffer[s*ch_in + ch];
|
||||
}
|
||||
for (ch = ch_in; ch < ch_out; ch++) {
|
||||
outbuf[pos + s*ch_out + ch] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void seek_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample) {
|
||||
int segment, total_samples;
|
||||
segmented_layout_data* data = vgmstream->layout_data;
|
||||
|
||||
@ -98,13 +124,13 @@ void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
||||
while (total_samples < vgmstream->num_samples) {
|
||||
int32_t segment_samples = vgmstream_get_samples(data->segments[segment]);
|
||||
|
||||
/* find if loop falls within segment's samples */
|
||||
if (loop_sample >= total_samples && loop_sample < total_samples + segment_samples) {
|
||||
int32_t loop_relative = loop_sample - total_samples;
|
||||
/* find if sample falls within segment's samples */
|
||||
if (seek_sample >= total_samples && seek_sample < total_samples + segment_samples) {
|
||||
int32_t seek_relative = seek_sample - total_samples;
|
||||
|
||||
seek_vgmstream(data->segments[segment], loop_relative);
|
||||
seek_vgmstream(data->segments[segment], seek_relative);
|
||||
data->current_segment = segment;
|
||||
vgmstream->samples_into_block = loop_relative;
|
||||
vgmstream->samples_into_block = seek_relative;
|
||||
break;
|
||||
}
|
||||
total_samples += segment_samples;
|
||||
@ -112,10 +138,14 @@ void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
||||
}
|
||||
|
||||
if (segment == data->segment_count) {
|
||||
VGM_LOG("SEGMENTED: can't find loop segment\n");
|
||||
VGM_LOG("SEGMENTED: can't find seek segment\n");
|
||||
}
|
||||
}
|
||||
|
||||
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
||||
loop_layout_segmented(vgmstream, loop_sample);
|
||||
}
|
||||
|
||||
|
||||
segmented_layout_data* init_layout_segmented(int segment_count) {
|
||||
segmented_layout_data* data = NULL;
|
||||
@ -139,7 +169,7 @@ fail:
|
||||
}
|
||||
|
||||
int setup_layout_segmented(segmented_layout_data* data) {
|
||||
int i, max_input_channels = 0, max_output_channels = 0;
|
||||
int i, max_input_channels = 0, max_output_channels = 0, mixed_channels = 0;
|
||||
sample_t *outbuf_re = NULL;
|
||||
|
||||
|
||||
@ -170,8 +200,8 @@ int setup_layout_segmented(segmented_layout_data* data) {
|
||||
}
|
||||
}
|
||||
|
||||
/* different segments may have different input channels, though output should be
|
||||
* the same for all (ex. 2ch + 1ch segments, but 2ch segment is downmixed to 1ch) */
|
||||
/* different segments may have different input or output channels, we
|
||||
* need to know maxs to properly handle */
|
||||
mixing_info(data->segments[i], &segment_input_channels, &segment_output_channels);
|
||||
if (max_input_channels < segment_input_channels)
|
||||
max_input_channels = segment_input_channels;
|
||||
@ -183,11 +213,12 @@ int setup_layout_segmented(segmented_layout_data* data) {
|
||||
|
||||
mixing_info(data->segments[i-1], NULL, &prev_output_channels);
|
||||
if (segment_output_channels != prev_output_channels) {
|
||||
VGM_LOG("SEGMENTED: segment %i has wrong channels %i vs prev channels %i\n", i, segment_output_channels, prev_output_channels);
|
||||
goto fail;
|
||||
mixed_channels = 1;
|
||||
//VGM_LOG("SEGMENTED: segment %i has wrong channels %i vs prev channels %i\n", i, segment_output_channels, prev_output_channels);
|
||||
//goto fail;
|
||||
}
|
||||
|
||||
/* a bit weird, but no matter */
|
||||
/* a bit weird, but no matter (should resample) */
|
||||
if (data->segments[i]->sample_rate != data->segments[i-1]->sample_rate) {
|
||||
VGM_LOG("SEGMENTED: segment %i has different sample rate\n", i);
|
||||
}
|
||||
@ -214,6 +245,7 @@ int setup_layout_segmented(segmented_layout_data* data) {
|
||||
|
||||
data->input_channels = max_input_channels;
|
||||
data->output_channels = max_output_channels;
|
||||
data->mixed_channels = mixed_channels;
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
|
@ -105,7 +105,7 @@ VGMSTREAM * init_vgmstream_aax(STREAMFILE *streamFile) {
|
||||
}
|
||||
}
|
||||
|
||||
channel_count = data->segments[0]->channels;
|
||||
channel_count = data->output_channels;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
|
@ -78,7 +78,7 @@ VGMSTREAM * init_vgmstream_mus_acm(STREAMFILE *streamFile) {
|
||||
goto fail;
|
||||
|
||||
|
||||
channel_count = data->segments[0]->channels;
|
||||
channel_count = data->output_channels;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
|
@ -347,8 +347,9 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
|
||||
* .at9: standard ATRAC9
|
||||
* .saf: Whacked! (Xbox)
|
||||
* .mwv: Level-5 games [Dragon Quest VIII (PS2), Rogue Galaxy (PS2)]
|
||||
* .ima: Baja: Edge of Control (PS3/X360)
|
||||
*/
|
||||
if ( check_extensions(sf, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,str,at3,rws,aud,at9,saf") ) {
|
||||
if ( check_extensions(sf, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,str,at3,rws,aud,at9,saf,ima") ) {
|
||||
;
|
||||
}
|
||||
else if ( check_extensions(sf, "mwv") ) {
|
||||
|
@ -1,59 +1,92 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* sadl - from DS games with Procyon Studio audio driver [Professor Layton (DS), Soma Bringer (DS)] */
|
||||
VGMSTREAM* init_vgmstream_sadl(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
int channel_count, loop_flag;
|
||||
int channels, loop_flag;
|
||||
off_t start_offset;
|
||||
uint8_t flags;
|
||||
uint32_t loop_start, data_size;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "sad"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,sf) != 0x7361646c) /* "sadl" */
|
||||
goto fail;
|
||||
if (read_32bitLE(0x40,sf) != get_streamfile_size(sf))
|
||||
if (read_u32be(0x00,sf) != 0x7361646c) /* "sadl" */
|
||||
goto fail;
|
||||
/* 04: null */
|
||||
/* 08: data size, or null in later files */
|
||||
/* 0c: version? (x0410=Luminous Arc, 0x0411=Layton, 0x0415=rest) */
|
||||
/* 0e: file id (for .sad packed in .spd) */
|
||||
/* 14: name related? */
|
||||
/* 20: short filename (may be null or nor match full filename) */
|
||||
|
||||
/* 30: flags? (0/1/2) */
|
||||
loop_flag = read_u8(0x31,sf);
|
||||
channels = read_u8(0x32,sf);
|
||||
flags = read_u8(0x33,sf);
|
||||
/* 34: flags? */
|
||||
/* 38: flags? */
|
||||
/* 3c: null? */
|
||||
data_size = read_u32le(0x40,sf); //?
|
||||
start_offset = read_u32le(0x48,sf); /* usually 0x100, 0xc0 in LA */
|
||||
/* 4c: start offset again or 0x40 in LA */
|
||||
/* 50: size or samples? */
|
||||
loop_start = read_u32le(0x54,sf); //?
|
||||
/* others: sizes/samples/flags? */
|
||||
|
||||
data_size -= start_offset;
|
||||
loop_start -= start_offset;
|
||||
|
||||
loop_flag = read_8bit(0x31,sf);
|
||||
channel_count = read_8bit(0x32,sf);
|
||||
start_offset = 0x100;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
switch (read_8bit(0x33,sf) & 6) {
|
||||
vgmstream->meta_type = meta_SADL;
|
||||
|
||||
switch (flags & 6) { /* possibly > 1? (0/1/2) */
|
||||
case 4:
|
||||
vgmstream->sample_rate = 32728;
|
||||
break;
|
||||
case 2:
|
||||
case 2: /* Layton */
|
||||
case 0: /* Luminous Arc (DS) */
|
||||
vgmstream->sample_rate = 16364;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->meta_type = meta_SADL;
|
||||
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x10;
|
||||
|
||||
switch(read_8bit(0x33,sf) & 0xf0) {
|
||||
switch(flags & 0xf0) { /* possibly >> 6? (0/1/2) */
|
||||
case 0x00: /* Luminous Arc (DS) (non-int IMA? all files are mono though) */
|
||||
case 0x70: /* Ni no Kuni (DS), Professor Layton and the Curious Village (DS), Soma Bringer (DS) */
|
||||
vgmstream->coding_type = coding_IMA_int;
|
||||
|
||||
vgmstream->num_samples = (read_32bitLE(0x40,sf)-start_offset)/channel_count*2;
|
||||
vgmstream->loop_start_sample = (read_32bitLE(0x54,sf)-start_offset)/channel_count*2;
|
||||
vgmstream->num_samples = ima_bytes_to_samples(data_size, channels);
|
||||
vgmstream->loop_start_sample = ima_bytes_to_samples(loop_start, channels);
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < channels; i++) {
|
||||
vgmstream->ch[i].adpcm_history1_32 = read_s16le(0x80 + i*0x04 + 0x00, sf);
|
||||
vgmstream->ch[i].adpcm_step_index = read_s16le(0x80 + i*0x04 + 0x02, sf);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
//TODO: Luminous Arc 2 uses a variation of this, but value 0x70
|
||||
case 0xb0: /* Soma Bringer (DS), Rekishi Taisen Gettenka (DS) */
|
||||
vgmstream->coding_type = coding_NDS_PROCYON;
|
||||
|
||||
vgmstream->num_samples = (read_32bitLE(0x40,sf)-start_offset)/channel_count/16*30;
|
||||
vgmstream->loop_start_sample = (read_32bitLE(0x54,sf)-start_offset)/channel_count/16*30;
|
||||
vgmstream->num_samples = data_size / channels / 16 * 30;
|
||||
vgmstream->loop_start_sample = loop_start / channels / 16 *30;
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
break;
|
||||
|
||||
|
@ -570,7 +570,7 @@ static VGMSTREAM* init_vgmstream_ubi_bao_sequence(ubi_bao_header* bao, STREAMFIL
|
||||
|
||||
|
||||
/* build the base VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(data->segments[0]->channels, !bao->sequence_single);
|
||||
vgmstream = allocate_vgmstream(data->output_channels, !bao->sequence_single);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_UBI_BAO;
|
||||
|
@ -1477,7 +1477,7 @@ static VGMSTREAM* init_vgmstream_ubi_sb_sequence(ubi_sb_header* sb, STREAMFILE*
|
||||
goto fail;
|
||||
|
||||
/* build the base VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(data->segments[0]->channels, !sb->sequence_single);
|
||||
vgmstream = allocate_vgmstream(data->output_channels, !sb->sequence_single);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_UBI_SB;
|
||||
|
@ -40,6 +40,7 @@ typedef struct {
|
||||
int block_align;
|
||||
int average_bps;
|
||||
int bits_per_sample;
|
||||
uint8_t channel_type;
|
||||
uint32_t channel_layout;
|
||||
size_t extra_size;
|
||||
|
||||
@ -462,7 +463,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
break;
|
||||
}
|
||||
|
||||
case OPUS: { /* alt to Vorbis [Girl Cafe Gun (Mobile)] */
|
||||
case OPUS: { /* fully standard Ogg Opus [Girl Cafe Gun (Mobile)] */
|
||||
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
|
||||
|
||||
/* extra: size 0x12 */
|
||||
@ -484,19 +485,26 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
break;
|
||||
}
|
||||
|
||||
#if 0 // disabled until more files/tests
|
||||
case OPUSWW: { /* updated Opus [Assassin's Creed Valhalla (PC)] */
|
||||
int skip, table_count;
|
||||
int mapping;
|
||||
opus_config cfg = {0};
|
||||
|
||||
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
|
||||
if (!ww.seek_offset)) goto fail;
|
||||
if (!ww.seek_offset) goto fail;
|
||||
|
||||
/* extra: size 0x10 */
|
||||
cfg.channels = ww.channels;
|
||||
cfg.table_offset = ww.seek_offset;
|
||||
|
||||
/* extra: size 0x10 (though last 2 fields are beyond, AK plz) */
|
||||
/* 0x12: samples per frame */
|
||||
vgmstream->num_samples = read_s32(ww.fmt_offset + 0x18, sf);
|
||||
table_count = read_u32(ww.fmt_offset + 0x1c, sf); /* same as seek size / 2 */
|
||||
skip = read_u16(ww.fmt_offset + 0x20, sf);
|
||||
/* 0x22: 1? (though extra size is declared as 0x10 so this is outsize, AK plz */
|
||||
cfg.table_count = read_u32(ww.fmt_offset + 0x1c, sf); /* same as seek size / 2 */
|
||||
cfg.skip = read_u16(ww.fmt_offset + 0x20, sf);
|
||||
/* 0x22: codec version */
|
||||
mapping = read_u8(ww.fmt_offset + 0x23, sf);
|
||||
|
||||
if (read_u8(ww.fmt_offset + 0x22, sf) != 1)
|
||||
goto fail;
|
||||
|
||||
/* OPUS is VBR so this is very approximate percent, meh */
|
||||
if (ww.truncated) {
|
||||
@ -505,14 +513,47 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
ww.data_size = ww.file_size - start_offset;
|
||||
}
|
||||
|
||||
/* AK does some wonky implicit config for multichannel */
|
||||
if (mapping == 1 && ww.channel_type == 1) { /* only allowed values ATM, set when >2ch */
|
||||
static const int8_t mapping_matrix[8][8] = {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, },
|
||||
{ 0, 1, 0, 0, 0, 0, 0, 0, },
|
||||
{ 0, 2, 1, 0, 0, 0, 0, 0, },
|
||||
{ 0, 1, 2, 3, 0, 0, 0, 0, },
|
||||
{ 0, 4, 1, 2, 3, 0, 0, 0, },
|
||||
{ 0, 4, 1, 2, 3, 5, 0, 0, },
|
||||
{ 0, 6, 1, 2, 3, 4, 5, 0, },
|
||||
{ 0, 6, 1, 2, 3, 4, 5, 7, },
|
||||
};
|
||||
int i;
|
||||
|
||||
/* find coupled OPUS streams (internal streams using 2ch) */
|
||||
switch(ww.channel_layout) {
|
||||
case mapping_7POINT1_surround: cfg.coupled_count = 3; break; /* 2ch+2ch+2ch+1ch+1ch, 5 streams */
|
||||
case mapping_5POINT1_surround: /* 2ch+2ch+1ch+1ch, 4 streams */
|
||||
case mapping_QUAD_side: cfg.coupled_count = 2; break; /* 2ch+2ch, 2 streams */
|
||||
case mapping_2POINT1_xiph: /* 2ch+1ch, 2 streams */
|
||||
case mapping_STEREO: cfg.coupled_count = 1; break; /* 2ch, 1 stream */
|
||||
default: cfg.coupled_count = 0; break; /* 1ch, 1 stream */
|
||||
//TODO: AK OPUS doesn't seem to handles others mappings, though AK's .h imply they exist (uses 0 coupleds?)
|
||||
}
|
||||
|
||||
/* total number internal OPUS streams (should be >0) */
|
||||
cfg.stream_count = ww.channels - cfg.coupled_count;
|
||||
|
||||
/* channel assignments */
|
||||
for (i = 0; i < ww.channels; i++) {
|
||||
cfg.channel_mapping[i] = mapping_matrix[ww.channels - 1][i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Wwise Opus saves all frame sizes in the seek table */
|
||||
vgmstream->codec_data = init_ffmpeg_wwise_opus(sf, ww.seek_offset, table_count, ww.data_offset, ww.data_size, ww.channels, skip);
|
||||
vgmstream->codec_data = init_ffmpeg_wwise_opus(sf, ww.data_offset, ww.data_size, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
case HEVAG: /* PSV */
|
||||
@ -650,6 +691,7 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) {
|
||||
* - HEVAG: very off
|
||||
* - XMA2: exact file size
|
||||
* - some RIFX have LE size
|
||||
* Value is ignored by AK's parser (set to -1).
|
||||
* (later we'll validate "data" which fortunately is correct)
|
||||
*/
|
||||
if (read_u32(0x04,sf) + 0x04 + 0x04 != ww->file_size) {
|
||||
@ -745,6 +787,7 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) {
|
||||
* - 4b: eConfigType (0=none, 1=standard, 2=ambisonic)
|
||||
* - 19b: uChannelMask */
|
||||
if ((ww->channel_layout & 0xFF) == ww->channels) {
|
||||
ww->channel_type = (ww->channel_layout >> 8) & 0x0F;
|
||||
ww->channel_layout = (ww->channel_layout >> 12);
|
||||
}
|
||||
}
|
||||
@ -781,13 +824,13 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) {
|
||||
case 0x0166: ww->codec = XMA2; break; /* fmt-chunk XMA */
|
||||
case 0xAAC0: ww->codec = AAC; break;
|
||||
case 0xFFF0: ww->codec = DSP; break;
|
||||
case 0xFFFB: ww->codec = HEVAG; break;
|
||||
case 0xFFFB: ww->codec = HEVAG; break; /* "VAG" */
|
||||
case 0xFFFC: ww->codec = ATRAC9; break;
|
||||
case 0xFFFE: ww->codec = PCM; break; /* "PCM for Wwise Authoring" */
|
||||
case 0xFFFF: ww->codec = VORBIS; break;
|
||||
case 0x3039: ww->codec = OPUSNX; break; /* renamed from "OPUS" on Wwise 2018.1 */
|
||||
case 0x3040: ww->codec = OPUS; break;
|
||||
case 0x3041: ww->codec = OPUSWW; break; /* added on Wwise 2019.2.3, presumably replaces OPUS */
|
||||
case 0x3041: ww->codec = OPUSWW; break; /* "OPUS_WEM", added on Wwise 2019.2.3, replaces OPUS */
|
||||
case 0x8311: ww->codec = PTADPCM; break; /* added on Wwise 2019.1, replaces IMA */
|
||||
default:
|
||||
goto fail;
|
||||
|
@ -9,7 +9,7 @@
|
||||
#define WAVEBANKENTRY_FLAGS_IGNORELOOP 0x00000008 // Used internally when the loop region can't be used (no idea...)
|
||||
|
||||
/* the x.x version is just to make it clearer, MS only classifies XACT as 1/2/3 */
|
||||
#define XACT1_0_MAX 1 /* Project Gotham Racing 2 (v1), Silent Hill 4 (v1) */
|
||||
#define XACT1_0_MAX 1 /* Project Gotham Racing 2 (v1), Silent Hill 4 (v1), Shin Megami Tensei NINE (v1) */
|
||||
#define XACT1_1_MAX 3 /* Unreal Championship (v2), The King of Fighters 2003 (v3) */
|
||||
#define XACT2_0_MAX 34 /* Dead or Alive 4 (v17), Kameo (v23), Table Tennis (v34) */ // v35/36/37 too?
|
||||
#define XACT2_1_MAX 38 /* Prey (v38) */ // v39 too?
|
||||
@ -471,12 +471,12 @@ VGMSTREAM* init_vgmstream_xwb(STREAMFILE* sf) {
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case XMA1: { /* Kameo (X360) */
|
||||
case XMA1: { /* Kameo (X360), Table Tennis (X360) */
|
||||
uint8_t buf[0x100];
|
||||
int bytes;
|
||||
|
||||
bytes = ffmpeg_make_riff_xma1(buf,0x100, vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, 0);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, xwb.stream_offset,xwb.stream_size);
|
||||
bytes = ffmpeg_make_riff_xma1(buf, sizeof(buf), vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, 0);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf, bytes, xwb.stream_offset,xwb.stream_size);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
@ -499,8 +499,8 @@ VGMSTREAM* init_vgmstream_xwb(STREAMFILE* sf) {
|
||||
block_size = 0x10000; /* XACT default */
|
||||
block_count = xwb.stream_size / block_size + (xwb.stream_size % block_size ? 1 : 0);
|
||||
|
||||
bytes = ffmpeg_make_riff_xma2(buf,0x100, vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, xwb.stream_offset,xwb.stream_size);
|
||||
bytes = ffmpeg_make_riff_xma2(buf, sizeof(buf), vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf, bytes, xwb.stream_offset,xwb.stream_size);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
@ -537,8 +537,8 @@ VGMSTREAM* init_vgmstream_xwb(STREAMFILE* sf) {
|
||||
block_align = wma_block_align_index[block_index];
|
||||
wma_codec = xwb.bits_per_sample ? 0x162 : 0x161; /* 0=WMAudio2, 1=WMAudio3 */
|
||||
|
||||
bytes = ffmpeg_make_riff_xwma(buf,0x100, wma_codec, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, avg_bps, block_align);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, xwb.stream_offset,xwb.stream_size);
|
||||
bytes = ffmpeg_make_riff_xwma(buf, sizeof(buf), wma_codec, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, avg_bps, block_align);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf, bytes, xwb.stream_offset,xwb.stream_size);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
@ -646,7 +646,7 @@ static int get_xsb_name(char* buf, size_t maxsize, int target_subsong, xwb_heade
|
||||
goto fail;
|
||||
|
||||
if ((xwb->version <= XACT1_1_MAX && xsb.version > XSB_XACT1_2_MAX) ||
|
||||
(xwb->version <= XACT2_2_MAX && xsb.version > XSB_XACT2_MAX)) {
|
||||
(xwb->version <= XACT2_2_MAX && xsb.version > XSB_XACT2_2_MAX)) {
|
||||
VGM_LOG("XSB: mismatched XACT versions: xsb v%i vs xwb v%i\n", xsb.version, xwb->version);
|
||||
goto fail;
|
||||
}
|
||||
|
@ -5,7 +5,9 @@
|
||||
#define XSB_XACT1_0_MAX 5 /* Unreal Championship (Xbox) */
|
||||
#define XSB_XACT1_1_MAX 8 /* Die Hard: Vendetta (Xbox) */
|
||||
#define XSB_XACT1_2_MAX 11 /* other Xbox games */
|
||||
#define XSB_XACT2_MAX 41 /* other PC/X360 games */
|
||||
#define XSB_XACT2_0_MAX 34 /* Table Tennis (v34) */
|
||||
//#define XSB_XACT2_1_MAX 38 /* Prey (v38) */ // v39 too?
|
||||
#define XSB_XACT2_2_MAX 41 /* other PC/X360 games */
|
||||
|
||||
|
||||
typedef struct {
|
||||
@ -44,10 +46,10 @@ typedef struct {
|
||||
static void xsb_check_stream(xsb_header *xsb, int stream_index, int wavebank_index, off_t name_offset, STREAMFILE *sf) {
|
||||
if (xsb->parse_done)
|
||||
return;
|
||||
//;VGM_LOG("XSB old: found stream=%i vs %i, wavebank=%i vs %i, name_offset=%lx\n", stream_index, xsb->selected_stream, wavebank_index, xsb->selected_wavebank, name_offset);
|
||||
//;VGM_LOG("XSB: found stream=%i vs %i, wavebank=%i vs %i, name_offset=%lx\n", stream_index, xsb->selected_stream, wavebank_index, xsb->selected_wavebank, name_offset);
|
||||
|
||||
if (stream_index < 0 || stream_index > 0xFFF || wavebank_index < 0 || wavebank_index > xsb->wavebanks_count) {
|
||||
VGM_LOG("XSB old: bad stream=%i, wavebank=%i\n", stream_index, wavebank_index);
|
||||
VGM_LOG("XSB: bad stream=%i, wavebank=%i\n", stream_index, wavebank_index);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -277,7 +279,7 @@ static int parse_xsb_old_cues(xsb_header *xsb, STREAMFILE *sf) {
|
||||
/* 0x0c: some low value or flag? */
|
||||
/* 0x0e: some index? */
|
||||
/* 0x10: 4 fields? (-1 or 7) */
|
||||
//;VGM_LOG("XSB old index %i at %lx: flags=%x, entry=%i, name_offset=%lx\n", i, offset, flags, cue_entry, name_offset);
|
||||
//;VGM_LOG("XSB old index %i at %lx: entry=%i, name_offset=%lx\n", i, offset, cue_entry, name_offset);
|
||||
|
||||
if (cue_entry < 0) {
|
||||
jump_offset = read_s32(offset + 0x08, sf);
|
||||
@ -315,12 +317,12 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE
|
||||
|
||||
uint32_t flags;
|
||||
int stream_index, wavebank_index;
|
||||
int i, t, track_count, event_count;
|
||||
int i, t, track_count, event_count, size;
|
||||
|
||||
|
||||
event_count = read_s8(offset + 0x00, sf);
|
||||
|
||||
//;VGM_LOG("XSB clip at %lx\n", offset);
|
||||
//;VGM_LOG("XSB clip at %lx, events=%i\n", offset, event_count);
|
||||
offset += 0x01;
|
||||
|
||||
for (i = 0; i < event_count; i++) {
|
||||
@ -333,6 +335,14 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE
|
||||
switch (flags & 0x1F) { /* event ID */
|
||||
|
||||
case 0x01: /* playwave event */
|
||||
if (xsb->version <= XSB_XACT2_0_MAX) { /* v34 (Table Tennis) */
|
||||
/* 00(1): unknown */
|
||||
stream_index = read_s16(offset + 0x01, sf);
|
||||
wavebank_index = read_s8 (offset + 0x03, sf);
|
||||
/* 04(1): loop count? */
|
||||
size = 0x05;
|
||||
}
|
||||
else { /* v40 (Blue Dragon) */
|
||||
/* 00(1): unknown */
|
||||
/* 01(1): flags */
|
||||
stream_index = read_s16(offset + 0x02, sf);
|
||||
@ -340,9 +350,11 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE
|
||||
/* 05(1): loop count */
|
||||
/* 06(2): pan angle */
|
||||
/* 08(2): pan arc */
|
||||
size = 0x0a;
|
||||
}
|
||||
|
||||
//;VGM_LOG("XSB clip event 1 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
|
||||
offset += 0x0a;
|
||||
offset += size;
|
||||
|
||||
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
|
||||
if (xsb->parse_done) return 1;
|
||||
@ -354,11 +366,11 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE
|
||||
/* 02(1): loop count */
|
||||
/* 03(2): pan angle */
|
||||
/* 05(2): pan arc */
|
||||
track_count = read_s16(offset + 0x07, sf);
|
||||
/* 09(1): flags? */
|
||||
/* 0a(5): unknown */
|
||||
/* 07(2): flags? */
|
||||
track_count = read_s16(offset + 0x09, sf); /* MonoGame reads at 0x07, but this looks correct [LocoCycle (X360)-v46] */
|
||||
/* 0b(4): unknown */
|
||||
|
||||
//;VGM_LOG("XSB clip event 3 at %lx\n", offset);
|
||||
//;VGM_LOG("XSB clip event 3 at %lx, tracks=%i\n", offset, track_count);
|
||||
offset += 0x0F;
|
||||
|
||||
for (t = 0; t < track_count; t++) {
|
||||
@ -389,13 +401,13 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE
|
||||
/* 0f(1): max volume */
|
||||
/* 10(4): min frequency */
|
||||
/* 14(4): max frequency */
|
||||
/* 18(1): min Q */
|
||||
/* 19(1): max Q */
|
||||
/* 1a(1): unknown */
|
||||
/* 1b(1): variation flags */
|
||||
/* 18(4): min Q */
|
||||
/* 1c(4): max Q */
|
||||
/* 20(1): unknown */
|
||||
/* 21(1): variation flags */
|
||||
|
||||
//;VGM_LOG("XSB clip event 4 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
|
||||
offset += 0x1c;
|
||||
offset += 0x22;
|
||||
|
||||
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
|
||||
if (xsb->parse_done) return 1;
|
||||
@ -413,16 +425,17 @@ static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STRE
|
||||
/* 0c(1): max volume */
|
||||
/* 0d(4): min frequency */
|
||||
/* 11(4): max frequency */
|
||||
/* 15(1): min Q */
|
||||
/* 16(1): max Q */
|
||||
/* 17(1): unknown */
|
||||
/* 18(1): variation flags */
|
||||
track_count = read_s16(offset + 0x19, sf);
|
||||
/* 1a(1): flags 2 */
|
||||
/* 1b(5): unknown 2 */
|
||||
/* 15(4): min Q */
|
||||
/* 19(4): max Q */
|
||||
/* 1d(1): unknown */
|
||||
/* 1e(1): variation flags? */
|
||||
/* 1f(1): unknown 2 */
|
||||
/* 20(1): variation flags? */
|
||||
track_count = read_s16(offset + 0x21, sf); /* MonoGame reads at 0x1f, but this looks correct [LocoCycle (X360)-v46] */
|
||||
/* 23(4): unknown 3 (-1?) */
|
||||
|
||||
//;VGM_LOG("XSB clip event 6 at %lx\n", offset);
|
||||
offset += 0x20;
|
||||
//;VGM_LOG("XSB clip event 6 at %lx, tracks=%i\n", offset, track_count);
|
||||
offset += 0x27;
|
||||
|
||||
for (t = 0; t < track_count; t++) {
|
||||
stream_index = read_s16(offset + 0x00, sf);
|
||||
@ -529,6 +542,7 @@ static int parse_xsb_sound(xsb_header *xsb, off_t offset, off_t name_offset, STR
|
||||
}
|
||||
}
|
||||
|
||||
//;VGM_LOG("XSB sound end\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -542,11 +556,15 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset,
|
||||
int i, variation_count;
|
||||
|
||||
|
||||
variation_count = read_s16(offset + 0x00, sf);
|
||||
flags = read_u16(offset + 0x02, sf);
|
||||
/* MonoGame reads count first, but this looks correct [LocoCycle (X360)-v46] */
|
||||
flags = read_u16(offset + 0x00, sf);
|
||||
variation_count = read_s16(offset + 0x02, sf);
|
||||
/* 0x04(1): unknown */
|
||||
/* 0x05(2): unknown */
|
||||
/* 0x07(1): unknown */
|
||||
|
||||
//;VGM_LOG("XSB variation at %lx\n", offset);
|
||||
offset += 0x04;
|
||||
//;VGM_LOG("XSB variation at %lx, count=%i\n", offset, variation_count);
|
||||
offset += 0x08;
|
||||
|
||||
for (i = 0; i < variation_count; i++) {
|
||||
off_t sound_offset;
|
||||
@ -558,7 +576,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset,
|
||||
/* 03(1): weight min */
|
||||
/* 04(1): weight max */
|
||||
|
||||
//;VGM_LOG("XSB variation: type 0 with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
|
||||
//;VGM_LOG("XSB variation: type 0 at %lx with stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
|
||||
offset += 0x05;
|
||||
|
||||
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
|
||||
@ -570,7 +588,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset,
|
||||
/* 04(1): weight min */
|
||||
/* 05(1): weight max */
|
||||
|
||||
//;VGM_LOG("XSB variation: type 1\n");
|
||||
//;VGM_LOG("XSB variation: type 1 at %lx\n", offset);
|
||||
offset += 0x06;
|
||||
|
||||
parse_xsb_sound(xsb, sound_offset, name_offset, sf);
|
||||
@ -583,7 +601,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset,
|
||||
/* 08(4): weight max */
|
||||
/* 0c(4): flags */
|
||||
|
||||
//;VGM_LOG("XSB variation: type 3\n");
|
||||
//;VGM_LOG("XSB variation: type 3 at %lx\n", offset);
|
||||
offset += 0x10;
|
||||
|
||||
parse_xsb_sound(xsb, sound_offset, name_offset, sf);
|
||||
@ -594,7 +612,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset,
|
||||
stream_index = read_s16(offset + 0x00, sf);
|
||||
wavebank_index = read_s8(offset + 0x02, sf);
|
||||
|
||||
//;VGM_LOG("XSB variation: type 4 with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
|
||||
//;VGM_LOG("XSB variation: type 4 at %lx with stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
|
||||
offset += 0x03;
|
||||
|
||||
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
|
||||
@ -612,7 +630,7 @@ static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset,
|
||||
/* 03(1): unknown */
|
||||
offset += 0x04;
|
||||
|
||||
|
||||
//;VGM_LOG("XSB variation end\n");
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
@ -767,7 +785,7 @@ static int parse_xsb(xsb_header *xsb, STREAMFILE *sf, char *xwb_wavebank_name) {
|
||||
xsb->index_size = 0x14;
|
||||
xsb->entry_size = 0x14;
|
||||
}
|
||||
else if (xsb->version <= XSB_XACT2_MAX) {
|
||||
else if (xsb->version <= XSB_XACT2_2_MAX) {
|
||||
/* 06(2): crc */
|
||||
/* 08(1): platform? (3=X360) */
|
||||
xsb->simple_cues_count = read_s16(0x09, sf);
|
||||
@ -821,12 +839,9 @@ static int parse_xsb(xsb_header *xsb, STREAMFILE *sf, char *xwb_wavebank_name) {
|
||||
}
|
||||
|
||||
//;VGM_LOG("XSB header: version=%i\n", xsb->version);
|
||||
//;VGM_LOG("XSB header: count: simple=%i, complex=%i, wavebanks=%i, sounds=%i\n",
|
||||
// xsb->simple_cues_count, xsb->complex_cues_count, xsb->wavebanks_count, xsb->sounds_count);
|
||||
//;VGM_LOG("XSB header: offset: simple=%lx, complex=%lx, wavebanks=%lx, sounds=%lx\n",
|
||||
// xsb->simple_cues_offset, xsb->complex_cues_offset, xsb->wavebanks_offset, xsb->sounds_offset);
|
||||
//;VGM_LOG("XSB header: names: cues=%lx, size=%x, hash=%lx\n",
|
||||
// xsb->cue_names_offset, xsb->cue_names_size, xsb->nameoffsets_offset);
|
||||
//;VGM_LOG("XSB header: count: simple=%i, complex=%i, wavebanks=%i, sounds=%i\n", xsb->simple_cues_count, xsb->complex_cues_count, xsb->wavebanks_count, xsb->sounds_count);
|
||||
//;VGM_LOG("XSB header: offset: simple=%lx, complex=%lx, wavebanks=%lx, sounds=%lx\n", xsb->simple_cues_offset, xsb->complex_cues_offset, xsb->wavebanks_offset, xsb->sounds_offset);
|
||||
//;VGM_LOG("XSB header: names: cues=%lx, size=%x, hash=%lx\n", xsb->cue_names_offset, xsb->cue_names_size, xsb->nameoffsets_offset);
|
||||
|
||||
if (xsb->version > XSB_XACT1_2_MAX && xsb->cue_names_size <= 0) {
|
||||
VGM_LOG("XSB: no names found\n");
|
||||
@ -845,7 +860,7 @@ static int parse_xsb(xsb_header *xsb, STREAMFILE *sf, char *xwb_wavebank_name) {
|
||||
offset = xsb->wavebanks_offset;
|
||||
for (i = 0; i < xsb->wavebanks_count; i++) {
|
||||
read_string(xsb_wavebank_name,xsb->wavebanks_name_size, offset, sf);
|
||||
//;VGM_LOG("XSB wavebanks: bank %i=%s\n", i, wavebank_name);
|
||||
//;VGM_LOG("XSB wavebanks: bank %i\n", i); //, wavebank_name
|
||||
if (strcasecmp(xsb_wavebank_name, xwb_wavebank_name)==0) {
|
||||
//;VGM_LOG("XSB banks: current xwb is wavebank %i=%s\n", i, xsb_wavebank_name);
|
||||
xsb->selected_wavebank = i;
|
||||
|
232
src/mixing.c
232
src/mixing.c
@ -56,6 +56,7 @@
|
||||
typedef enum {
|
||||
MIX_SWAP,
|
||||
MIX_ADD,
|
||||
MIX_ADD_COPY,
|
||||
MIX_VOLUME,
|
||||
MIX_LIMIT,
|
||||
MIX_UPMIX,
|
||||
@ -89,29 +90,42 @@ typedef struct {
|
||||
size_t mixing_size; /* mixing max */
|
||||
mix_command_data mixing_chain[VGMSTREAM_MAX_MIXING]; /* effects to apply (could be alloc'ed but to simplify...) */
|
||||
float* mixbuf; /* internal mixing buffer */
|
||||
|
||||
/* fades only apply at some points, other mixes are active */
|
||||
int has_non_fade;
|
||||
int has_fade;
|
||||
} mixing_data;
|
||||
|
||||
|
||||
/* ******************************************************************* */
|
||||
|
||||
static int is_active(mixing_data *data, int32_t current_start, int32_t current_end) {
|
||||
static int is_fade_active(mixing_data *data, int32_t current_start, int32_t current_end) {
|
||||
int i;
|
||||
int32_t fade_start, fade_end;
|
||||
|
||||
for (i = 0; i < data->mixing_count; i++) {
|
||||
mix_command_data *mix = &data->mixing_chain[i];
|
||||
int32_t fade_start, fade_end;
|
||||
float vol_start = mix->vol_start;
|
||||
|
||||
if (mix->command != MIX_FADE)
|
||||
return 1; /* has non-fades = active */
|
||||
continue;
|
||||
|
||||
/* check is current range falls within a fade
|
||||
* (assuming fades were already optimized on add) */
|
||||
if (mix->time_pre < 0 && vol_start == 1.0) {
|
||||
fade_start = mix->time_start; /* ignore unused */
|
||||
}
|
||||
else {
|
||||
fade_start = mix->time_pre < 0 ? 0 : mix->time_pre;
|
||||
}
|
||||
fade_end = mix->time_post < 0 ? INT_MAX : mix->time_post;
|
||||
|
||||
if (current_start < fade_end && current_end > fade_start)
|
||||
//;VGM_LOG("MIX: fade test, tp=%i, te=%i, cs=%i, ce=%i\n", mix->time_pre, mix->time_post, current_start, current_end);
|
||||
if (current_start < fade_end && current_end > fade_start) {
|
||||
//;VGM_LOG("MIX: fade active, cs=%i < fe=%i and ce=%i > fs=%i\n", current_start, fade_end, current_end, fade_start);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -249,7 +263,7 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
int ch, s, m, ok;
|
||||
|
||||
int32_t current_pos, current_subpos;
|
||||
int32_t current_subpos = 0;
|
||||
float temp_f, temp_min, temp_max, cur_vol = 0.0f;
|
||||
float *temp_mixbuf;
|
||||
sample_t *temp_outbuf;
|
||||
@ -261,18 +275,21 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
if (!data || !data->mixing_on || data->mixing_count == 0)
|
||||
return;
|
||||
|
||||
/* try to skip if no ops apply (for example if fade set but does nothing yet) */
|
||||
current_pos = get_current_pos(vgmstream, sample_count);
|
||||
if (!is_active(data, current_pos, current_pos + sample_count))
|
||||
/* try to skip if no fades apply (set but does nothing yet) + only has fades */
|
||||
if (data->has_fade) {
|
||||
int32_t current_pos = get_current_pos(vgmstream, sample_count);
|
||||
//;VGM_LOG("MIX: fade test %i, %i\n", data->has_non_fade, is_fade_active(data, current_pos, current_pos + sample_count));
|
||||
if (!data->has_non_fade && !is_fade_active(data, current_pos, current_pos + sample_count))
|
||||
return;
|
||||
//;VGM_LOG("MIX: fade pos=%i\n", current_pos);
|
||||
current_subpos = current_pos;
|
||||
}
|
||||
|
||||
|
||||
/* use advancing buffer pointers to simplify logic */
|
||||
temp_mixbuf = data->mixbuf;
|
||||
temp_outbuf = outbuf;
|
||||
|
||||
current_subpos = current_pos;
|
||||
|
||||
/* apply mixes in order per channel */
|
||||
for (s = 0; s < sample_count; s++) {
|
||||
/* reset after new sample 'step'*/
|
||||
@ -307,6 +324,10 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src] * mix->vol;
|
||||
break;
|
||||
|
||||
case MIX_ADD_COPY:
|
||||
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src];
|
||||
break;
|
||||
|
||||
case MIX_VOLUME:
|
||||
if (mix->ch_dst < 0) {
|
||||
for (ch = 0; ch < step_channels; ch++) {
|
||||
@ -384,7 +405,8 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
temp_outbuf += vgmstream->channels;
|
||||
}
|
||||
|
||||
/* copy resulting mix to output */
|
||||
/* copy resulting mix to output
|
||||
* (you'd think using a int32 temp buf would be faster but somehow it's slower?) */
|
||||
for (s = 0; s < sample_count * data->output_channels; s++) {
|
||||
/* when casting float to int, value is simply truncated:
|
||||
* - (int)1.7 = 1, (int)-1.7 = -1
|
||||
@ -457,6 +479,14 @@ static int add_mixing(VGMSTREAM* vgmstream, mix_command_data *mix) {
|
||||
data->mixing_chain[data->mixing_count] = *mix; /* memcpy */
|
||||
data->mixing_count++;
|
||||
|
||||
|
||||
if (mix->command == MIX_FADE) {
|
||||
data->has_fade = 1;
|
||||
}
|
||||
else {
|
||||
data->has_non_fade = 1;
|
||||
}
|
||||
|
||||
//;VGM_LOG("MIX: total %i\n", data->mixing_count);
|
||||
return 1;
|
||||
}
|
||||
@ -485,7 +515,7 @@ void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume
|
||||
if (ch_dst < 0 || ch_src < 0) return;
|
||||
if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return;
|
||||
|
||||
mix.command = MIX_ADD; //if (volume == 1.0) MIX_ADD_COPY /* could simplify */
|
||||
mix.command = (volume == 1.0) ? MIX_ADD_COPY : MIX_ADD;
|
||||
mix.ch_dst = ch_dst;
|
||||
mix.ch_src = ch_src;
|
||||
mix.vol = volume;
|
||||
@ -679,6 +709,10 @@ void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double
|
||||
|
||||
/* ******************************************************************* */
|
||||
|
||||
#define MIX_MACRO_VOCALS 'v'
|
||||
#define MIX_MACRO_EQUAL 'e'
|
||||
#define MIX_MACRO_BGM 'b'
|
||||
|
||||
void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
int ch;
|
||||
@ -741,6 +775,153 @@ static int get_layered_max_channels(VGMSTREAM* vgmstream) {
|
||||
return max;
|
||||
}
|
||||
|
||||
static int is_layered_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
int i;
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
layered_layout_data* l_data;
|
||||
|
||||
|
||||
if (vgmstream->layout_type != layout_layered)
|
||||
return 0;
|
||||
|
||||
/* no channels set and only vocals for now */
|
||||
if (max > 0 || mode != MIX_MACRO_VOCALS)
|
||||
return 0;
|
||||
|
||||
/* no channel down/upmixing (cannot guess output) */
|
||||
for (i = 0; i < data->mixing_count; i++) {
|
||||
mix_command_t mix = data->mixing_chain[i].command;
|
||||
if (mix == MIX_UPMIX || mix == MIX_DOWNMIX || mix == MIX_KILLMIX) /*mix == MIX_SWAP || ??? */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* only previsible cases */
|
||||
l_data = vgmstream->layout_data;
|
||||
for (i = 0; i < l_data->layer_count; i++) {
|
||||
int output_channels = 0;
|
||||
|
||||
mixing_info(l_data->layers[i], NULL, &output_channels);
|
||||
|
||||
if (output_channels > 8)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* special layering, where channels are respected (so Ls only go to Ls), also more optimized */
|
||||
static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
layered_layout_data* ldata = vgmstream->layout_data;
|
||||
int i, ch;
|
||||
int target_layer = 0, target_chs = 0, ch_max, target_ch = 0, target_silence = 0;
|
||||
int ch_num;
|
||||
|
||||
/* With N layers like: (ch1 ch2) (ch1 ch2 ch3 ch4) (ch1 ch2), output is normally 2+4+2=8ch.
|
||||
* We want to find highest layer (ch1..4) = 4ch, add other channels to it and drop them */
|
||||
|
||||
/* find target "main" channels (will be first most of the time) */
|
||||
ch_num = 0;
|
||||
ch_max = 0;
|
||||
for (i = 0; i < ldata->layer_count; i++) {
|
||||
int layer_chs = 0;
|
||||
|
||||
mixing_info(ldata->layers[i], NULL, &layer_chs);
|
||||
|
||||
if (ch_max < layer_chs || (ch_max == layer_chs && target_silence)) {
|
||||
target_ch = ch_num;
|
||||
target_chs = layer_chs;
|
||||
target_layer = i;
|
||||
ch_max = layer_chs;
|
||||
/* avoid using silence as main if possible for minor optimization */
|
||||
target_silence = (ldata->layers[i]->coding_type == coding_SILENCE);
|
||||
}
|
||||
|
||||
ch_num += layer_chs;
|
||||
}
|
||||
|
||||
/* all silences? */
|
||||
if (!target_chs) {
|
||||
target_ch = 0;
|
||||
target_chs = 0;
|
||||
target_layer = 0;
|
||||
mixing_info(ldata->layers[0], NULL, &target_chs);
|
||||
}
|
||||
|
||||
/* add other channels to target (assumes standard channel mapping to simplify)
|
||||
* most of the time all layers will have same number of channels though */
|
||||
ch_num = 0;
|
||||
for (i = 0; i < ldata->layer_count; i++) {
|
||||
int layer_chs = 0;
|
||||
|
||||
if (target_layer == i) {
|
||||
ch_num += target_chs;
|
||||
continue;
|
||||
}
|
||||
|
||||
mixing_info(ldata->layers[i], NULL, &layer_chs);
|
||||
|
||||
if (ldata->layers[i]->coding_type == coding_SILENCE) {
|
||||
ch_num += layer_chs;
|
||||
continue; /* unlikely but sometimes in Wwise */
|
||||
}
|
||||
|
||||
if (layer_chs == target_chs) {
|
||||
/* 1:1 mapping */
|
||||
for (ch = 0; ch < layer_chs; ch++) {
|
||||
mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const double vol_sqrt = 1 / sqrt(2);
|
||||
|
||||
/* extra mixing for better sound in some cases (assumes layer_chs is lower than target_chs) */
|
||||
switch(layer_chs) {
|
||||
case 1:
|
||||
mixing_push_add(vgmstream, target_ch + 0, ch_num + 0, vol_sqrt);
|
||||
mixing_push_add(vgmstream, target_ch + 1, ch_num + 0, vol_sqrt);
|
||||
break;
|
||||
case 2:
|
||||
mixing_push_add(vgmstream, target_ch + 0, ch_num + 0, 1.0);
|
||||
mixing_push_add(vgmstream, target_ch + 1, ch_num + 1, 1.0);
|
||||
break;
|
||||
default: /* less common */
|
||||
//TODO add other mixes, depends on target_chs + mapping (ex. 4.0 to 5.0 != 5.1, 2.1 xiph to 5.1 != 5.1 xiph)
|
||||
for (ch = 0; ch < layer_chs; ch++) {
|
||||
mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ch_num += layer_chs;
|
||||
}
|
||||
|
||||
/* drop non-target channels */
|
||||
ch_num = 0;
|
||||
for (i = 0; i < ldata->layer_count; i++) {
|
||||
|
||||
if (i < target_layer) { /* least common, hopefully (slower to drop chs 1 by 1) */
|
||||
int layer_chs = 0;
|
||||
mixing_info(ldata->layers[i], NULL, &layer_chs);
|
||||
|
||||
for (ch = 0; ch < layer_chs; ch++) {
|
||||
mixing_push_downmix(vgmstream, ch_num); //+ ch
|
||||
}
|
||||
|
||||
//ch_num += layer_chs; /* dropped channels change this */
|
||||
}
|
||||
else if (i == target_layer) {
|
||||
ch_num += target_chs;
|
||||
}
|
||||
else { /* most common, hopefully (faster) */
|
||||
mixing_push_killmix(vgmstream, ch_num);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
int current, ch, output_channels, selected_channels;
|
||||
@ -748,6 +929,13 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode)
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
if (is_layered_auto(vgmstream, max, mode)) {
|
||||
//;VGM_LOG("MIX: auto layer mode\n");
|
||||
mixing_macro_layer_auto(vgmstream, max, mode);
|
||||
return;
|
||||
}
|
||||
//;VGM_LOG("MIX: regular layer mode\n");
|
||||
|
||||
if (max == 0) /* auto calculate */
|
||||
max = get_layered_max_channels(vgmstream);
|
||||
|
||||
@ -781,10 +969,10 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode)
|
||||
if (!((mask >> ch) & 1))
|
||||
continue;
|
||||
|
||||
/* mode 'v': same volume for all layers (for layered vocals) */
|
||||
/* mode 'b': volume adjusted depending on layers (for layered bgm) */
|
||||
/* mode 'e': volume adjusted equally for all layers (for generic downmixing) */
|
||||
if (mode == 'b' && ch < max) {
|
||||
/* MIX_MACRO_VOCALS: same volume for all layers (for layered vocals) */
|
||||
/* MIX_MACRO_EQUAL: volume adjusted equally for all layers (for generic downmixing) */
|
||||
/* MIX_MACRO_BGM: volume adjusted depending on layers (for layered bgm) */
|
||||
if (mode == MIX_MACRO_BGM && ch < max) {
|
||||
/* reduce a bit main channels (see below) */
|
||||
int channel_mixes = selected_channels / max;
|
||||
if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */
|
||||
@ -795,7 +983,7 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode)
|
||||
|
||||
volume = 1 / sqrt(channel_mixes);
|
||||
}
|
||||
if ((mode == 'b' && ch >= max) || (mode == 'e')) {
|
||||
if ((mode == MIX_MACRO_BGM && ch >= max) || (mode == MIX_MACRO_EQUAL)) {
|
||||
/* find how many will be mixed in current channel (earlier channels receive more
|
||||
* mixes than later ones, ex: selected 8ch + max 3ch: ch0=0+3+6, ch1=1+4+7, ch2=2+5) */
|
||||
int channel_mixes = selected_channels / max;
|
||||
@ -909,13 +1097,13 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
vgmstream->config.config_set = 1;
|
||||
}
|
||||
|
||||
/* mode 'v': constant volume
|
||||
* mode 'e': sets fades to successively lower/equalize volume per loop for each layer
|
||||
/* MIX_MACRO_VOCALS: constant volume
|
||||
* MIX_MACRO_EQUAL: sets fades to successively lower/equalize volume per loop for each layer
|
||||
* (to keep final volume constant-ish), ex. 3 layers/loops, 2 max:
|
||||
* - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]--
|
||||
* - layer1 (ch2+3): loop0 --[0.0]--, loop1 (=0.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]--
|
||||
* - layer2 (ch4+5): loop0 --[0.0]--, loop1 ---[0.0]--, loop2 (=0.0..0.5, loop3 --[0.5/end]--
|
||||
* mode 'b': similar but 1st layer (main) has higher/delayed volume:
|
||||
* MIX_MACRO_BGM: similar but 1st layer (main) has higher/delayed volume:
|
||||
* - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..1.0, loop2 )=1.0..0.7, loop3 --[0.7/end]--
|
||||
*/
|
||||
for (loop = 1; loop < layer_num; loop++) {
|
||||
@ -927,7 +1115,7 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
change_pos = loop_pre + loop_samples * loop;
|
||||
change_time = 10.0 * vgmstream->sample_rate; /* in secs */
|
||||
|
||||
if (mode == 'e') {
|
||||
if (mode == MIX_MACRO_EQUAL) {
|
||||
volume1 = 1 / sqrt(loop + 0);
|
||||
volume2 = 1 / sqrt(loop + 1);
|
||||
}
|
||||
@ -936,7 +1124,7 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
for (layer = 0; layer < layer_num; layer++) {
|
||||
char type;
|
||||
|
||||
if (mode == 'b') {
|
||||
if (mode == MIX_MACRO_BGM) {
|
||||
if (layer == 0) {
|
||||
volume1 = 1 / sqrt(loop - 1 <= 0 ? 1 : loop - 1);
|
||||
volume2 = 1 / sqrt(loop + 0);
|
||||
|
73
src/render.c
73
src/render.c
@ -361,7 +361,7 @@ static int render_pad_begin(VGMSTREAM* vgmstream, sample_t* buf, int samples_to_
|
||||
return to_do;
|
||||
}
|
||||
|
||||
static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
||||
static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_left) {
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
//play_config_t* pc = &vgmstream->config;
|
||||
|
||||
@ -376,7 +376,7 @@ static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
||||
int32_t to_do = ps->fade_left;
|
||||
|
||||
if (ps->play_position < ps->fade_start) {
|
||||
start = samples_done - (ps->play_position + samples_done - ps->fade_start);
|
||||
start = samples_left - (ps->play_position + samples_left - ps->fade_start);
|
||||
fade_pos = 0;
|
||||
}
|
||||
else {
|
||||
@ -384,8 +384,8 @@ static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
||||
fade_pos = ps->play_position - ps->fade_start;
|
||||
}
|
||||
|
||||
if (to_do > samples_done - start)
|
||||
to_do = samples_done - start;
|
||||
if (to_do > samples_left - start)
|
||||
to_do = samples_left - start;
|
||||
|
||||
//TODO: use delta fadedness to improve performance?
|
||||
for (s = start; s < start + to_do; s++, fade_pos++) {
|
||||
@ -398,27 +398,33 @@ static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
||||
ps->fade_left -= to_do;
|
||||
|
||||
/* next samples after fade end would be pad end/silence, so we can just memset */
|
||||
memset(buf + (start + to_do) * channels, 0, (samples_done - to_do - start) * sizeof(sample_t) * channels);
|
||||
|
||||
return samples_done;
|
||||
memset(buf + (start + to_do) * channels, 0, (samples_left - to_do - start) * sizeof(sample_t) * channels);
|
||||
return start + to_do;
|
||||
}
|
||||
}
|
||||
|
||||
static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
||||
static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_left) {
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
int channels = vgmstream->pstate.output_channels;
|
||||
int start = 0;
|
||||
int skip = 0;
|
||||
int32_t to_do;
|
||||
|
||||
/* since anything beyond pad end is silence no need to check end */
|
||||
/* pad end works like fades, where part of buf samples and part padding (silent),
|
||||
* calc exact totals (beyond pad end normally is silence, except with segmented layout) */
|
||||
if (ps->play_position < ps->pad_end_start) {
|
||||
start = samples_done - (ps->play_position + samples_done - ps->pad_end_start);
|
||||
skip = ps->pad_end_start - ps->play_position;
|
||||
to_do = ps->pad_end_duration;
|
||||
}
|
||||
else {
|
||||
start = 0;
|
||||
skip = 0;
|
||||
to_do = (ps->pad_end_start + ps->pad_end_duration) - ps->play_position;
|
||||
}
|
||||
|
||||
memset(buf + (start * channels), 0, (samples_done - start) * channels * sizeof(sample_t));
|
||||
return samples_done;
|
||||
if (to_do > samples_left - skip)
|
||||
to_do = samples_left - skip;
|
||||
|
||||
memset(buf + (skip * channels), 0, to_do * sizeof(sample_t) * channels);
|
||||
return skip + to_do;
|
||||
}
|
||||
|
||||
|
||||
@ -453,9 +459,9 @@ int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
tmpbuf += done * vgmstream->pstate.output_channels; /* as if mixed */
|
||||
}
|
||||
|
||||
/* end padding (done before to avoid decoding if possible, samples_to_do becomes 0) */
|
||||
if (!vgmstream->config.play_forever /* && ps->pad_end_left */
|
||||
&& ps->play_position + samples_done >= ps->pad_end_start
|
||||
/* end padding (before to avoid decoding if possible, but must be inside pad region) */
|
||||
if (!vgmstream->config.play_forever
|
||||
&& ps->play_position /*+ samples_to_do*/ >= ps->pad_end_start
|
||||
&& samples_to_do) {
|
||||
done = render_pad_end(vgmstream, tmpbuf, samples_to_do);
|
||||
samples_done += done;
|
||||
@ -542,6 +548,33 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
|
||||
int is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */
|
||||
|
||||
|
||||
/* cleanup */
|
||||
if (seek_sample < 0)
|
||||
seek_sample = 0;
|
||||
/* play forever can seek past max */
|
||||
if (vgmstream->config_enabled && seek_sample > ps->play_duration && !play_forever)
|
||||
seek_sample = ps->play_duration;
|
||||
|
||||
|
||||
/* optimize as layouts can seek faster internally */
|
||||
if (vgmstream->layout_type == layout_segmented) {
|
||||
seek_layout_segmented(vgmstream, seek_sample);
|
||||
|
||||
if (vgmstream->config_enabled) {
|
||||
vgmstream->pstate.play_position = seek_sample;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (vgmstream->layout_type == layout_layered) {
|
||||
seek_layout_layered(vgmstream, seek_sample);
|
||||
|
||||
if (vgmstream->config_enabled) {
|
||||
vgmstream->pstate.play_position = seek_sample;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* will decode and loop until seek sample, but slower */
|
||||
//todo apply same loop logic as below, or pretend we have play_forever + settings?
|
||||
if (!vgmstream->config_enabled) {
|
||||
@ -574,12 +607,6 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
|
||||
* | pad-begin | body-begin | body-loop0 | body-loop1 | body-loop2 | fade | pad-end + beyond)
|
||||
* 0 5s (-3s) 25s 95s 165s 235s 245s Ns
|
||||
*/
|
||||
|
||||
if (seek_sample < 0)
|
||||
seek_sample = 0;
|
||||
if (seek_sample > ps->play_duration && !play_forever) /* play forever can seek to any loop */
|
||||
seek_sample = ps->play_duration;
|
||||
|
||||
//;VGM_LOG("SEEK: seek sample=%i, is_looped=%i\n", seek_sample, is_looped);
|
||||
|
||||
/* start/pad-begin: consume pad samples */
|
||||
|
@ -767,16 +767,19 @@ typedef enum {
|
||||
} speaker_t;
|
||||
|
||||
/* typical mappings that metas may use to set channel_layout (but plugin must actually use it)
|
||||
* (in order, so 3ch file could be mapped to FL FR FC or FL FR LFE but not LFE FL FR) */
|
||||
* (in order, so 3ch file could be mapped to FL FR FC or FL FR LFE but not LFE FL FR)
|
||||
* not too sure about names but no clear standards */
|
||||
typedef enum {
|
||||
mapping_MONO = speaker_FC,
|
||||
mapping_STEREO = speaker_FL | speaker_FR,
|
||||
mapping_2POINT1 = speaker_FL | speaker_FR | speaker_LFE,
|
||||
mapping_2POINT1_xiph = speaker_FL | speaker_FR | speaker_FC,
|
||||
mapping_2POINT1_xiph = speaker_FL | speaker_FR | speaker_FC, /* aka 3STEREO? */
|
||||
mapping_QUAD = speaker_FL | speaker_FR | speaker_BL | speaker_BR,
|
||||
mapping_QUAD_surround = speaker_FL | speaker_FR | speaker_FC | speaker_BC,
|
||||
mapping_QUAD_side = speaker_FL | speaker_FR | speaker_SL | speaker_SR,
|
||||
mapping_5POINT0 = speaker_FL | speaker_FR | speaker_LFE | speaker_BL | speaker_BR,
|
||||
mapping_5POINT0_xiph = speaker_FL | speaker_FR | speaker_FC | speaker_BL | speaker_BR,
|
||||
mapping_5POINT0_surround = speaker_FL | speaker_FR | speaker_FC | speaker_SL | speaker_SR,
|
||||
mapping_5POINT1 = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BL | speaker_BR,
|
||||
mapping_5POINT1_surround = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_SL | speaker_SR,
|
||||
mapping_7POINT0 = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BC | speaker_FLC | speaker_FRC,
|
||||
@ -998,6 +1001,7 @@ typedef struct {
|
||||
sample_t* buffer;
|
||||
int input_channels; /* internal buffer channels */
|
||||
int output_channels; /* resulting channels (after mixing, if applied) */
|
||||
int mixed_channels; /* segments have different number of channels */
|
||||
} segmented_layout_data;
|
||||
|
||||
/* for files made of "parallel" layers, one per group of channels (using a complete sub-VGMSTREAM) */
|
||||
|
Loading…
Reference in New Issue
Block a user