Merge pull request #769 from bnnm/txtp-3do

txtp 3do
This commit is contained in:
bnnm 2020-11-29 20:22:34 +01:00 committed by GitHub
commit 77cc431be7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 286 additions and 223 deletions

View File

@ -332,6 +332,7 @@ music_Home.ps3.scd#C1~3
- **`#b(time)`**: set target time (even without loops) for the body part (modified by other values) - **`#b(time)`**: set target time (even without loops) for the body part (modified by other values)
- **`#f(fade period)`**: set (in seconds) how long the fade out lasts after target number of loops (if file loops) - **`#f(fade period)`**: set (in seconds) how long the fade out lasts after target number of loops (if file loops)
- **`#d(fade delay)`**: set (in seconds) delay before fade out kicks in (if file loops) - **`#d(fade delay)`**: set (in seconds) delay before fade out kicks in (if file loops)
- **`#B(time)`**: same as `#b`, but implies no `#f`/`#d` (for easier exact times).
- **`#p(time-begin)`**: pad song beginning (not between loops) - **`#p(time-begin)`**: pad song beginning (not between loops)
- **`#P(time-end)`**: pad song song end (not between loops) - **`#P(time-end)`**: pad song song end (not between loops)
- **`#r(time-begin)`**: remove/trim song beginning (not between loops) - **`#r(time-begin)`**: remove/trim song beginning (not between loops)
@ -505,9 +506,25 @@ bgm03.adx
bgm04.adx #A ##defines loop end bgm04.adx #A ##defines loop end
bgm05.adx bgm05.adx
``` ```
You can also use `#@loop` to set loop start. You can also use `#@loop` and `#@loop-end` aliases.
This setting also works in groups, which allows loops when using multiple segmented groups (not possible with `loop_start/end_segment`). Anchors can be applied to groups too.
```
bgm01a.adx
bgm01b.adx
group -L2
bgm02a.adx
bgm02b.adx
group -L2 #a ##loops here
group -S2
```
```
bgm01.adx
bgm02.adx
group = -L2 #a ##similar to loop_start_segment=1 or #E
```
This setting also works inside groups, which allows internal loops when using multiple segmented layouts (not possible with `loop_start/end_segment`).
``` ```
bgm01.adx bgm01.adx
bgm02.adx #a bgm02.adx #a
@ -517,10 +534,9 @@ This setting also works in groups, which allows loops when using multiple segmen
bgm03.adx bgm03.adx
group -S2 #l 3.0 group -S2 #l 3.0
group -S2 group -S2
#could use R groups to select one sub-groups that loops # could even use R group to select one sub-groups that loops
# (loop_start_segment doesn't make sense for both segments) # (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 ### Force sample rate

View File

@ -25,7 +25,8 @@ static const char* extension_list[] = {
"208", "208",
"2dx9", "2dx9",
"2pfs", "2pfs",
"4", // for Game.com audio "3do",
"4", //for Game.com audio
"8", //txth/reserved [Gungage (PS1)] "8", //txth/reserved [Gungage (PS1)]
"800", "800",
"9tav", "9tav",
@ -943,7 +944,7 @@ static const meta_info meta_info_list[] = {
{meta_DSP_WSI, "Alone in the Dark .WSI header"}, {meta_DSP_WSI, "Alone in the Dark .WSI header"},
{meta_AIFC, "Apple AIFF-C (Audio Interchange File Format) header"}, {meta_AIFC, "Apple AIFF-C (Audio Interchange File Format) header"},
{meta_AIFF, "Apple AIFF (Audio Interchange File Format) header"}, {meta_AIFF, "Apple AIFF (Audio Interchange File Format) header"},
{meta_STR_SNDS, "3DO .str header"}, {meta_STR_SNDS, "3DO SNDS header"},
{meta_WS_AUD, "Westwood Studios .aud header"}, {meta_WS_AUD, "Westwood Studios .aud header"},
{meta_WS_AUD_old, "Westwood Studios .aud (old) header"}, {meta_WS_AUD_old, "Westwood Studios .aud (old) header"},
{meta_PS2_IVB, "IVB/BVII header"}, {meta_PS2_IVB, "IVB/BVII header"},

View File

@ -376,6 +376,12 @@ static const hcakey_info hcakey_list[] = {
/* D4DJ Groovy Mix (Android) [base files] */ /* D4DJ Groovy Mix (Android) [base files] */
{393410674916959300}, // 0575ACECA945A444 {393410674916959300}, // 0575ACECA945A444
/* Toji no Miko: Kizamishi Issen no Tomoshibi (Android) */
{62057514034227932}, // 00DC78FAEFA76ADC
/* Readyyy! (Android) */
{1234567890987654321}, // 112210F4B16C1CB1
/* Dragalia Lost (iOS/Android) */ /* Dragalia Lost (iOS/Android) */
{2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD {2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD

View File

@ -1,114 +1,119 @@
#include "meta.h" #include "meta.h"
#include "../coding/coding.h" #include "../coding/coding.h"
#include "../layout/layout.h" #include "../layout/layout.h"
/* .str - 3DO format with CTRL/SNDS/SHDR blocks [Icebreaker (3DO), Battle Pinball (3DO)] */ /* .str - 3DO format with CTRL/SNDS/SHDR blocks [Icebreaker (3DO), Battle Pinball (3DO)] */
VGMSTREAM * init_vgmstream_str_snds(STREAMFILE *streamFile) { VGMSTREAM* init_vgmstream_str_snds(STREAMFILE* sf) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
off_t start_offset, shdr_offset = -1; off_t start_offset, shdr_offset = -1;
int loop_flag, channel_count, found_shdr = 0; int loop_flag, channels, found_shdr = 0;
size_t file_size, ctrl_size = -1; size_t file_size, ctrl_size = -1;
/* checks */ /* checks */
if (!check_extensions(streamFile, "str")) /* .str: standard
goto fail; * .3do: Aqua World - Umimi Monogatari (3DO) movies */
if (!check_extensions(sf, "str,3do"))
if (read_32bitBE(0x00,streamFile) != 0x4354524c && /* "CTRL" */ goto fail;
read_32bitBE(0x00,streamFile) != 0x534e4453 && /* "SNDS" */
read_32bitBE(0x00,streamFile) != 0x53484452) /* "SHDR" */ if (read_u32be(0x00,sf) != 0x4354524c && /* "CTRL" */
goto fail; read_u32be(0x00,sf) != 0x534e4453 && /* "SNDS" */
read_u32be(0x00,sf) != 0x53484452) /* "SHDR" */
file_size = get_streamfile_size(streamFile); goto fail;
start_offset = 0x00;
file_size = get_streamfile_size(sf);
/* scan chunks until we find a SNDS containing a SHDR */ start_offset = 0x00;
{
off_t current_chunk = 0; /* scan chunks until we find a SNDS containing a SHDR */
{
while (!found_shdr && current_chunk < file_size) { off_t offset = 0;
if (current_chunk < 0) goto fail; uint32_t size;
if (current_chunk+read_32bitBE(current_chunk+0x04,streamFile) >= file_size) while (!found_shdr && offset < file_size) {
goto fail; if (offset < 0) goto fail;
switch (read_32bitBE(current_chunk,streamFile)) { size = read_u32be(offset + 0x04,sf);
case 0x4354524C: /* "CTRL" */ if (offset + size >= file_size)
ctrl_size = read_32bitBE(current_chunk+4,streamFile); goto fail;
break;
switch (read_u32be(offset + 0x00,sf)) {
case 0x534e4453: /* "SNDS" */ case 0x4354524C: /* "CTRL" */
switch (read_32bitBE(current_chunk+16,streamFile)) { ctrl_size = read_u32be(offset + 0x04,sf);
case 0x53484452: /* SHDR */ break;
found_shdr = 1;
shdr_offset = current_chunk+16; case 0x534e4453: /* "SNDS" */
break; switch (read_u32be(offset + 0x10,sf)) {
default: case 0x53484452: /* SHDR */
break; found_shdr = 1;
} shdr_offset = offset + 0x10;
break; break;
default:
case 0x53484452: /* "SHDR" */ break;
switch (read_32bitBE(current_chunk+0x7C, streamFile)) { }
case 0x4354524C: /* "CTRL" */ break;
/* to distinguish between styles */
ctrl_size = read_32bitBE(current_chunk + 0x80, streamFile); case 0x53484452: /* "SHDR" */
break; switch (read_u32be(offset + 0x7C, sf)) {
case 0x4354524C: /* "CTRL" */
default: /* to distinguish between styles */
break; ctrl_size = read_u32be(offset + 0x80, sf);
} break;
break;
default:
default: break;
/* ignore others for now */ }
break; break;
}
default: /* ignore others */
current_chunk += read_32bitBE(current_chunk+0x04,streamFile); break;
} }
}
offset += size;
if (!found_shdr) goto fail; }
}
channel_count = read_32bitBE(shdr_offset+0x20,streamFile);
loop_flag = 0; if (!found_shdr)
goto fail;
/* build the VGMSTREAM */ channels = read_u32be(shdr_offset+0x20,sf);
vgmstream = allocate_vgmstream(channel_count,loop_flag); loop_flag = 0;
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_STR_SNDS; /* build the VGMSTREAM */
vgmstream->sample_rate = read_32bitBE(shdr_offset+0x1c,streamFile); vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
if (ctrl_size == 0x1C || ctrl_size == 0x0B || ctrl_size == -1) {
vgmstream->num_samples = read_32bitBE(shdr_offset+0x2c,streamFile) - 1; /* sample count? */ vgmstream->meta_type = meta_STR_SNDS;
} vgmstream->sample_rate = read_u32be(shdr_offset+0x1c,sf);
else {
vgmstream->num_samples = read_32bitBE(shdr_offset+0x2c,streamFile) * 0x10; /* frame count? */ if (ctrl_size == 0x1C || ctrl_size == 0x0B || ctrl_size == -1) {
} vgmstream->num_samples = read_u32be(shdr_offset+0x2c,sf) - 1; /* sample count? */
vgmstream->num_samples /= vgmstream->channels; }
else {
switch (read_32bitBE(shdr_offset+0x24,streamFile)) { vgmstream->num_samples = read_u32be(shdr_offset+0x2c,sf) * 0x10; /* frame count? */
case 0x53445832: /* "SDX2" */ }
if (channel_count > 1) { vgmstream->num_samples /= vgmstream->channels;
vgmstream->coding_type = coding_SDX2_int;
vgmstream->interleave_block_size = 1; switch (read_u32be(shdr_offset + 0x24,sf)) {
} else case 0x53445832: /* "SDX2" */
vgmstream->coding_type = coding_SDX2; if (channels > 1) {
break; vgmstream->coding_type = coding_SDX2_int;
default: vgmstream->interleave_block_size = 1;
goto fail; } else {
} vgmstream->coding_type = coding_SDX2;
vgmstream->layout_type = layout_blocked_str_snds; }
break;
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) default:
goto fail; goto fail;
return vgmstream; }
vgmstream->layout_type = layout_blocked_str_snds;
fail:
close_vgmstream(vgmstream); if (!vgmstream_open_stream(vgmstream, sf, start_offset))
return NULL; goto fail;
} return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -107,7 +107,7 @@ typedef struct {
char repeat; char repeat;
int selected; int selected;
txtp_entry group_settings; txtp_entry entry;
} txtp_group; } txtp_group;
@ -334,10 +334,11 @@ static int find_loop_anchors(txtp_header* txtp, int position, int count, int* p_
//;VGM_LOG("TXTP: find loop anchors from %i to %i\n", position, count); //;VGM_LOG("TXTP: find loop anchors from %i to %i\n", position, count);
for (i = position, j = 0; i < position + count; i++, j++) { for (i = position, j = 0; i < position + count; i++, j++) {
if (txtp->entry[i].loop_anchor_start) { /* catch first time anchors appear only, also logic elsewhere also uses +1 */
loop_start = j + 1; /* logic elsewhere also uses +1 */ if (txtp->entry[i].loop_anchor_start && !loop_start) {
loop_start = j + 1;
} }
if (txtp->entry[i].loop_anchor_end) { if (txtp->entry[i].loop_anchor_end && !loop_end) {
loop_end = j + 1; loop_end = j + 1;
} }
} }
@ -354,7 +355,8 @@ static int find_loop_anchors(txtp_header* txtp, int position, int count, int* p_
return 0; return 0;
} }
static int make_group_segment(txtp_header* txtp, int is_group, int position, int count) {
static int make_group_segment(txtp_header* txtp, txtp_group* grp, int position, int count) {
VGMSTREAM* vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
segmented_layout_data *data_s = NULL; segmented_layout_data *data_s = NULL;
int i, loop_flag = 0; int i, loop_flag = 0;
@ -362,7 +364,7 @@ static int make_group_segment(txtp_header* txtp, int is_group, int position, int
/* allowed for actual groups (not final "mode"), otherwise skip to optimize */ /* allowed for actual groups (not final "mode"), otherwise skip to optimize */
if (!is_group && count == 1) { if (!grp && count == 1) {
//;VGM_LOG("TXTP: ignored single group\n"); //;VGM_LOG("TXTP: ignored single group\n");
return 1; return 1;
} }
@ -440,6 +442,13 @@ static int make_group_segment(txtp_header* txtp, int is_group, int position, int
/* set new vgmstream and reorder positions */ /* set new vgmstream and reorder positions */
update_vgmstream_list(vgmstream, txtp, position, count); update_vgmstream_list(vgmstream, txtp, position, count);
/* special "whole loop" settings */
if (grp->entry.loop_anchor_start == 1) {
grp->entry.config.config_set = 1;
grp->entry.config.really_force_loop = 1;
}
return 1; return 1;
fail: fail:
close_vgmstream(vgmstream); close_vgmstream(vgmstream);
@ -448,14 +457,14 @@ fail:
return 0; return 0;
} }
static int make_group_layer(txtp_header* txtp, int is_group, int position, int count) { static int make_group_layer(txtp_header* txtp, txtp_group* grp, int position, int count) {
VGMSTREAM* vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
layered_layout_data* data_l = NULL; layered_layout_data* data_l = NULL;
int i; int i;
/* allowed for actual groups (not final mode), otherwise skip to optimize */ /* allowed for actual groups (not final mode), otherwise skip to optimize */
if (!is_group && count == 1) { if (!grp && count == 1) {
//;VGM_LOG("TXTP: ignored single group\n"); //;VGM_LOG("TXTP: ignored single group\n");
return 1; return 1;
} }
@ -492,16 +501,17 @@ static int make_group_layer(txtp_header* txtp, int is_group, int position, int c
} }
} }
/* loop settings only make sense if this group becomes final vgmstream */
if (position == 0 && txtp->vgmstream_count == count) {
if (txtp->is_loop_auto && !vgmstream->loop_flag) {
vgmstream_force_loop(vgmstream, 1, 0, vgmstream->num_samples);
}
}
/* set new vgmstream and reorder positions */ /* set new vgmstream and reorder positions */
update_vgmstream_list(vgmstream, txtp, position, count); update_vgmstream_list(vgmstream, txtp, position, count);
/* special "whole loop" settings (also loop if this group becomes final vgmstream) */
if (grp->entry.loop_anchor_start == 1
|| (position == 0 && txtp->vgmstream_count == count && txtp->is_loop_auto)) {
grp->entry.config.config_set = 1;
grp->entry.config.really_force_loop = 1;
}
return 1; return 1;
fail: fail:
close_vgmstream(vgmstream); close_vgmstream(vgmstream);
@ -510,12 +520,12 @@ fail:
return 0; return 0;
} }
static int make_group_random(txtp_header* txtp, int is_group, int position, int count, int selected) { static int make_group_random(txtp_header* txtp, txtp_group* grp, int position, int count, int selected) {
VGMSTREAM* vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
int i; int i;
/* allowed for actual groups (not final mode), otherwise skip to optimize */ /* allowed for actual groups (not final mode), otherwise skip to optimize */
if (!is_group && count == 1) { if (!grp && count == 1) {
//;VGM_LOG("TXTP: ignored single group\n"); //;VGM_LOG("TXTP: ignored single group\n");
return 1; return 1;
} }
@ -525,11 +535,6 @@ static int make_group_random(txtp_header* txtp, int is_group, int position, int
return 1; return 1;
} }
/* special case meaning "play all", basically for quick testing */
if (selected == count) {
return make_group_segment(txtp, is_group, position, count);
}
/* 0=actually random for fun and testing, but undocumented since random music is kinda weird, may change anytime /* 0=actually random for fun and testing, but undocumented since random music is kinda weird, may change anytime
* (plus foobar caches song duration unless .txtp is modifies, so it can get strange if randoms are too different) */ * (plus foobar caches song duration unless .txtp is modifies, so it can get strange if randoms are too different) */
if (selected < 0) { if (selected < 0) {
@ -539,19 +544,43 @@ static int make_group_random(txtp_header* txtp, int is_group, int position, int
//;VGM_LOG("TXTP: autoselected random %i\n", selected); //;VGM_LOG("TXTP: autoselected random %i\n", selected);
} }
if (selected < 0 || selected >= count) { if (selected < 0 || selected > count) {
goto fail; goto fail;
} }
/* get selected and remove non-selected */ if (selected == count) {
vgmstream = txtp->vgmstream[position + selected]; /* special case meaning "select all", basically for quick testing and clearer Wwise */
txtp->vgmstream[position + selected] = NULL; if (!make_group_segment(txtp, grp, position, count))
for (i = 0; i < count; i++) { goto fail;
close_vgmstream(txtp->vgmstream[i + position]); vgmstream = txtp->vgmstream[position];
}
else {
/* get selected and remove non-selected */
vgmstream = txtp->vgmstream[position + selected];
txtp->vgmstream[position + selected] = NULL;
for (i = 0; i < count; i++) {
close_vgmstream(txtp->vgmstream[i + position]);
}
/* set new vgmstream and reorder positions */
update_vgmstream_list(vgmstream, txtp, position, count);
} }
/* set new vgmstream and reorder positions */
update_vgmstream_list(vgmstream, txtp, position, count); /* special "whole loop" settings */
if (grp->entry.loop_anchor_start == 1) {
grp->entry.config.config_set = 1;
grp->entry.config.really_force_loop = 1;
}
/* force selected vgmstream to be a segment when not a group already, and
* group + vgmstream has config (AKA must loop/modify over the result) */
//todo could optimize to not generate segment in some cases?
if (!(vgmstream->layout_type == layout_layered || vgmstream->layout_type == layout_segmented) &&
(grp->entry.config.config_set && vgmstream->config.config_set) ) {
if (!make_group_segment(txtp, grp, position, 1))
goto fail;
}
return 1; return 1;
fail: fail:
@ -595,15 +624,15 @@ static int parse_groups(txtp_header* txtp) {
//;VGM_LOG("TXTP: group=%i, count=%i, groups=%i\n", pos, grp->count, groups); //;VGM_LOG("TXTP: group=%i, count=%i, groups=%i\n", pos, grp->count, groups);
switch(grp->type) { switch(grp->type) {
case TXTP_GROUP_MODE_LAYERED: case TXTP_GROUP_MODE_LAYERED:
if (!make_group_layer(txtp, 1, pos, grp->count)) if (!make_group_layer(txtp, grp, pos, grp->count))
goto fail; goto fail;
break; break;
case TXTP_GROUP_MODE_SEGMENTED: case TXTP_GROUP_MODE_SEGMENTED:
if (!make_group_segment(txtp, 1, pos, grp->count)) if (!make_group_segment(txtp, grp, pos, grp->count))
goto fail; goto fail;
break; break;
case TXTP_GROUP_MODE_RANDOM: case TXTP_GROUP_MODE_RANDOM:
if (!make_group_random(txtp, 1, pos, grp->count, grp->selected)) if (!make_group_random(txtp, grp, pos, grp->count, grp->selected))
goto fail; goto fail;
break; break;
default: default:
@ -611,24 +640,28 @@ static int parse_groups(txtp_header* txtp) {
} }
} }
/* group may also have settings (like downmixing) */ /* group may also have settings (like downmixing) */
apply_settings(txtp->vgmstream[grp->position], &grp->group_settings); apply_settings(txtp->vgmstream[grp->position], &grp->entry);
txtp->entry[grp->position] = grp->group_settings; /* memcpy old settings for subgroups */ txtp->entry[grp->position] = grp->entry; /* memcpy old settings for subgroups */
} }
/* final tweaks (should be integrated with the above?) */ /* final tweaks (should be integrated with the above?) */
if (txtp->is_layered) { if (txtp->is_layered) {
if (!make_group_layer(txtp, 0, 0, txtp->vgmstream_count)) if (!make_group_layer(txtp, NULL, 0, txtp->vgmstream_count))
goto fail; goto fail;
} }
if (txtp->is_segmented) { if (txtp->is_segmented) {
if (!make_group_segment(txtp, 0, 0, txtp->vgmstream_count)) if (!make_group_segment(txtp, NULL, 0, txtp->vgmstream_count))
goto fail; goto fail;
} }
if (txtp->is_single) { if (txtp->is_single) {
/* special case of setting start_segment to force/overwrite looping /* special case of setting start_segment to force/overwrite looping
* (better to use #E but left for compatibility with older TXTPs) */ * (better to use #E but left for compatibility with older TXTPs) */
if (txtp->loop_start_segment == 1 && !txtp->loop_end_segment) { if (txtp->loop_start_segment == 1 && !txtp->loop_end_segment) {
//todo try look settings
//txtp->default_entry.config.config_set = 1;
//txtp->default_entry.config.really_force_loop = 1;
vgmstream_force_loop(txtp->vgmstream[0], 1, txtp->vgmstream[0]->loop_start_sample, txtp->vgmstream[0]->num_samples); vgmstream_force_loop(txtp->vgmstream[0], 1, txtp->vgmstream[0]->loop_start_sample, txtp->vgmstream[0]->num_samples);
} }
} }
@ -1426,6 +1459,15 @@ static void parse_params(txtp_entry* entry, char* params) {
params += get_time_f(params, &tcfg->body_time_s, &tcfg->body_time, &tcfg->body_time_set); params += get_time_f(params, &tcfg->body_time_s, &tcfg->body_time, &tcfg->body_time_set);
tcfg->config_set = 1; tcfg->config_set = 1;
} }
else if (strcmp(command,"B") == 0) {
params += get_time_f(params, &tcfg->body_time_s, &tcfg->body_time, &tcfg->body_time_set);
tcfg->config_set = 1;
/* similar to 'b' but implies no fades */
tcfg->fade_time_set = 1;
tcfg->fade_time = 0;
tcfg->fade_delay_set = 1;
tcfg->fade_delay = 0;
}
/* other settings */ /* other settings */
else if (strcmp(command,"h") == 0) { else if (strcmp(command,"h") == 0) {
@ -1458,7 +1500,7 @@ static void parse_params(txtp_entry* entry, char* params) {
entry->loop_anchor_start = 1; entry->loop_anchor_start = 1;
//;VGM_LOG("TXTP: anchor start set\n"); //;VGM_LOG("TXTP: anchor start set\n");
} }
else if (is_match(command,"A") || is_match(command,"@LOOP")) { else if (is_match(command,"A") || is_match(command,"@loop-end")) {
entry->loop_anchor_end = 1; entry->loop_anchor_end = 1;
//;VGM_LOG("TXTP: anchor end set\n"); //;VGM_LOG("TXTP: anchor end set\n");
} }
@ -1598,7 +1640,7 @@ static int add_group(txtp_header* txtp, char* line) {
} }
} }
parse_params(&cfg.group_settings, line); parse_params(&cfg.entry, line);
/* Groups can use "auto" position of last N files, so we need a counter that changes like this: /* Groups can use "auto" position of last N files, so we need a counter that changes like this:
* #layer of 2 (pos = 0) * #layer of 2 (pos = 0)

View File

@ -67,7 +67,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
/* checks */ /* checks */
/* .wem: newer "Wwise Encoded Media" used after the 2011.2 SDK (~july 2011) /* .wem: newer "Wwise Encoded Media" used after the 2011.2 SDK (~july 2011)
* .wav: older ADPCM files [Punch Out!! (Wii)] * .wav: older PCM/ADPCM files [Spider-Man: Web of Shadows (PC), Punch Out!! (Wii)]
* .xma: older XMA files [Too Human (X360), Tron Evolution (X360)] * .xma: older XMA files [Too Human (X360), Tron Evolution (X360)]
* .ogg: older Vorbis files [The King of Fighters XII (X360)] * .ogg: older Vorbis files [The King of Fighters XII (X360)]
* .bnk: Wwise banks for memory .wem detection */ * .bnk: Wwise banks for memory .wem detection */
@ -498,6 +498,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
if (!ww.seek_offset) goto fail; if (!ww.seek_offset) goto fail;
if (ww.channels > 8) goto fail; /* mapping not defined */
cfg.channels = ww.channels; cfg.channels = ww.channels;
cfg.table_offset = ww.seek_offset; cfg.table_offset = ww.seek_offset;
@ -827,7 +828,7 @@ static int parse_wwise(STREAMFILE* sf, wwise_header* ww) {
/* format to codec */ /* format to codec */
switch(ww->format) { switch(ww->format) {
case 0x0001: ww->codec = PCM; break; /* older Wwise */ case 0x0001: ww->codec = PCM; break; /* older Wwise */
case 0x0002: ww->codec = IMA; break; /* newer Wwise (conflicts with MSADPCM, probably means "platform's ADPCM") */ case 0x0002: ww->codec = IMA; break; /* newer Wwise (variable, probably means "platform's ADPCM") */
case 0x0069: ww->codec = IMA; break; /* older Wwise [Spiderman Web of Shadows (X360), LotR Conquest (PC)] */ case 0x0069: ww->codec = IMA; break; /* older Wwise [Spiderman Web of Shadows (X360), LotR Conquest (PC)] */
case 0x0161: ww->codec = XWMA; break; /* WMAv2 */ case 0x0161: ww->codec = XWMA; break; /* WMAv2 */
case 0x0162: ww->codec = XWMA; break; /* WMAPro */ case 0x0162: ww->codec = XWMA; break; /* WMAPro */
@ -893,61 +894,54 @@ fail:
/* /*
- old format - old format
"fmt" size 0x28, extra size 0x16 / size 0x18, extra size 0x06 "fmt" size 0x28, extra size 0x16 / size 0x18, extra size 0x06
0x12 (2): flag? (00,10,18): not related to seek table, codebook type, chunk count, looping
0x14 (4): channel config
0x18-24 (16): ? (fixed: 0x01000000 00001000 800000AA 00389B71) [removed when extra size is 0x06] 0x18-24 (16): ? (fixed: 0x01000000 00001000 800000AA 00389B71) [removed when extra size is 0x06]
"vorb" size 0x34 "vorb" size 0x34
0x00 (4): num_samples 0x00 (4): dwTotalPCMFrames
0x04 (4): skip samples? 0x04 (4): skip samples?
0x08 (4): ? (small if loop, 0 otherwise) 0x08 (4): LoopInfo.uLoopBeginExtra? (present if loop)
0x0c (4): data start offset after seek table+setup, or loop start when "smpl" is present 0x0c (4): LoopInfo.dwLoopStartPacketOffset (data start, or loop start when "smpl" is present)
0x10 (4): ? (small, 0..~0x400) 0x10 (4): LoopInfo.uLoopEndExtra? (0..~0x400)
0x14 (4): approximate data size without seek table? (almost setup+packets) 0x14 (4): LoopInfo.dwLoopEndPacketOffset?
0x18 (4): setup_offset within data (0 = no seek table) 0x18 (4): dwSeekTableSize (0 = no seek table)
0x1c (4): audio_offset within data 0x1c (4): dwVorbisDataOffset (offset within data)
0x20 (2): biggest packet size (not including header)? 0x20 (2): uMaxPacketSize (not including header)
0x22 (2): ? (small, N..~0x100) uLastGranuleExtra? 0x22 (2): uLastGranuleExtra (0..~0x100)
0x24 (4): ? (mid, 0~0x5000) dwDecodeAllocSize? 0x24 (4): dwDecodeAllocSize (0~0x5000)
0x28 (4): ? (mid, 0~0x5000) dwDecodeX64AllocSize? 0x28 (4): dwDecodeX64AllocSize (mid, 0~0x5000)
0x2c (4): parent bank/event id? uHashCodebook? (shared by several .wem a game, but not all need to share it) 0x2c (4): uHashCodebook? (shared by several .wem a game, but not all need to share it)
0x30 (1): blocksize_1_exp (small) 0x30 (1): uBlockSizes[0] (blocksize_1_exp, small)
0x31 (1): blocksize_0_exp (large) 0x31 (1): uBlockSizes[1] (blocksize_0_exp, large)
0x32 (2): empty 0x32 (2): empty
"vorb" size 0x28 / 0x2c / 0x2a "vorb" size 0x28 / 0x2c / 0x2a
0x00 (4): num_samples 0x00 (4): dwTotalPCMFrames
0x04 (4): data start offset after seek table+setup, or loop start when "smpl" is present 0x04 (4): LoopInfo.dwLoopStartPacketOffset (data start, or loop start when "smpl" is present)
0x08 (4): data end offset after seek table (setup+packets), or loop end when "smpl" is present 0x08 (4): LoopInfo.dwLoopEndPacketOffset (data end, or loop end when "smpl" is present)
0x0c (2): ? (small, 0..~0x400) [(4) when size is 0x2C] 0x0c (2): ? (small, 0..~0x400) [(4) when size is 0x2C]
0x10 (4): setup_offset within data (0 = no seek table) 0x10 (4): dwSeekTableSize (0 = no seek table)
0x14 (4): audio_offset within data 0x14 (4): dwVorbisDataOffset (offset within data)
0x18 (2): biggest packet size (not including header)? 0x18 (2): uMaxPacketSize (not including header)
0x1a (2): ? (small, N..~0x100) uLastGranuleExtra? [(4) when size is 0x2C] 0x1a (2): uLastGranuleExtra (0..~0x100) [(4) when size is 0x2C]
0x1c (4): ? (mid, 0~0x5000) dwDecodeAllocSize? 0x1c (4): dwDecodeAllocSize (0~0x5000)
0x20 (4): ? (mid, 0~0x5000) dwDecodeX64AllocSize? 0x20 (4): dwDecodeX64AllocSize (0~0x5000)
0x24 (4): parent bank/event id? uHashCodebook? (shared by several .wem a game, but not all need to share it) 0x24 (4): uHashCodebook? (shared by several .wem a game, but not all need to share it)
0x28 (1): blocksize_1_exp (small) [removed when size is 0x28] 0x28 (1): uBlockSizes[0] (blocksize_1_exp, small) [removed when size is 0x28]
0x29 (1): blocksize_0_exp (large) [removed when size is 0x28] 0x29 (1): uBlockSizes[1] (blocksize_0_exp, large) [removed when size is 0x28]
- new format: - new format:
"fmt" size 0x42, extra size 0x30 "fmt" size 0x42, extra size 0x30
0x12 (2): flag? (00,10,18): not related to seek table, codebook type, chunk count, looping, etc 0x18 (4): dwTotalPCMFrames
0x14 (4): channel config 0x1c (4): LoopInfo.dwLoopStartPacketOffset (data start, or loop start when "smpl" is present)
0x18 (4): num_samples 0x20 (4): LoopInfo.dwLoopEndPacketOffset (data end, or loop end when "smpl" is present)
0x1c (4): data start offset after seek table+setup, or loop start when "smpl" is present 0x24 (2): LoopInfo.uLoopBeginExtra (small, 0..~0x400)
0x20 (4): data end offset after seek table (setup+packets), or loop end when "smpl" is present 0x26 (2): LoopInfo.uLoopEndExtra (extra samples after seek?)
0x24 (2): ?1 (small, 0..~0x400) 0x28 (4): dwSeekTableSize (0 = no seek table)
0x26 (2): ?2 (small, N..~0x100): not related to seek table, codebook type, chunk count, looping, packet size, samples, etc 0x2c (4): dwVorbisDataOffset (offset within data)
0x28 (4): setup offset within data (0 = no seek table) 0x30 (2): uMaxPacketSize (not including header)
0x2c (4): audio offset within data 0x32 (2): uLastGranuleExtra (small, 0..~0x100)
0x30 (2): biggest packet size (not including header) 0x34 (4): dwDecodeAllocSize (mid, 0~0x5000)
0x32 (2): (small, 0..~0x100) uLastGranuleExtra? 0x38 (4): dwDecodeX64AllocSize (mid, 0~0x5000)
0x34 (4): ? (mid, 0~0x5000) dwDecodeAllocSize? 0x40 (1): uBlockSizes[0] (blocksize_1_exp, small)
0x38 (4): ? (mid, 0~0x5000) dwDecodeX64AllocSize? 0x41 (1): uBlockSizes[1] (blocksize_0_exp, large)
0x40 (1): blocksize_1_exp (small)
0x41 (1): blocksize_0_exp (large)
Wwise encoder options, unknown fields above may be reflect these:
https://www.audiokinetic.com/library/edge/?source=Help&id=vorbis_encoder_parameters
*/ */

View File

@ -877,12 +877,20 @@ void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target) {
/* MISC */ /* MISC */
/*******************************************************************************/ /*******************************************************************************/
static void describe_get_time(int32_t samples, int sample_rate, double* p_time_mm, double* p_time_ss) {
double seconds = (double)samples / sample_rate;
*p_time_mm = (int)(seconds / 60.0);
*p_time_ss = seconds - *p_time_mm * 60.0f;
if (*p_time_ss >= 59.999) /* avoid round up to 60.0 when printing to %06.3f */
*p_time_ss = 59.999;
}
/* Write a description of the stream into array pointed by desc, which must be length bytes long. /* Write a description of the stream into array pointed by desc, which must be length bytes long.
* Will always be null-terminated if length > 0 */ * Will always be null-terminated if length > 0 */
void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) { void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
#define TEMPSIZE (256+32) #define TEMPSIZE (256+32)
char temp[TEMPSIZE]; char temp[TEMPSIZE];
double time_mm, time_ss, seconds; double time_mm, time_ss;
if (!vgmstream) { if (!vgmstream) {
snprintf(temp,TEMPSIZE, "NULL VGMSTREAM"); snprintf(temp,TEMPSIZE, "NULL VGMSTREAM");
@ -935,27 +943,22 @@ void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
concatn(length,desc,"\n"); concatn(length,desc,"\n");
} }
/* times mod sounds avoid round up to 60.0 */
if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) { if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) {
if (!vgmstream->loop_flag) { if (!vgmstream->loop_flag) {
concatn(length,desc,"looping: disabled\n"); concatn(length,desc,"looping: disabled\n");
} }
seconds = (double)vgmstream->loop_start_sample / vgmstream->sample_rate; describe_get_time(vgmstream->loop_start_sample, vgmstream->sample_rate, &time_mm, &time_ss);
time_mm = (int)(seconds / 60.0);
time_ss = seconds - time_mm * 60.0f;
snprintf(temp,TEMPSIZE, "loop start: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_start_sample, time_mm, time_ss); snprintf(temp,TEMPSIZE, "loop start: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_start_sample, time_mm, time_ss);
concatn(length,desc,temp); concatn(length,desc,temp);
seconds = (double)vgmstream->loop_end_sample / vgmstream->sample_rate; describe_get_time(vgmstream->loop_end_sample, vgmstream->sample_rate, &time_mm, &time_ss);
time_mm = (int)(seconds / 60.0);
time_ss = seconds - time_mm * 60.0f;
snprintf(temp,TEMPSIZE, "loop end: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_end_sample, time_mm, time_ss); snprintf(temp,TEMPSIZE, "loop end: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_end_sample, time_mm, time_ss);
concatn(length,desc,temp); concatn(length,desc,temp);
} }
seconds = (double)vgmstream->num_samples / vgmstream->sample_rate; describe_get_time(vgmstream->num_samples, vgmstream->sample_rate, &time_mm, &time_ss);
time_mm = (int)(seconds / 60.0);
time_ss = seconds - time_mm * 60.0;
snprintf(temp,TEMPSIZE, "stream total samples: %d (%1.0f:%06.3f seconds)\n", vgmstream->num_samples, time_mm, time_ss); snprintf(temp,TEMPSIZE, "stream total samples: %d (%1.0f:%06.3f seconds)\n", vgmstream->num_samples, time_mm, time_ss);
concatn(length,desc,temp); concatn(length,desc,temp);
@ -1012,7 +1015,7 @@ void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
concatn(length,desc,temp); concatn(length,desc,temp);
concatn(length,desc,"\n"); concatn(length,desc,"\n");
snprintf(temp,TEMPSIZE, "bitrate: %d kbps\n", get_vgmstream_average_bitrate(vgmstream) / 1000); //todo \n? snprintf(temp,TEMPSIZE, "bitrate: %d kbps\n", get_vgmstream_average_bitrate(vgmstream) / 1000);
concatn(length,desc,temp); concatn(length,desc,temp);
/* only interesting if more than one */ /* only interesting if more than one */
@ -1032,13 +1035,9 @@ void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
} }
if (vgmstream->config_enabled) { if (vgmstream->config_enabled) {
double time_mm, time_ss, seconds;
int32_t samples = vgmstream->pstate.play_duration; int32_t samples = vgmstream->pstate.play_duration;
seconds = (double)samples / vgmstream->sample_rate; describe_get_time(samples, vgmstream->sample_rate, &time_mm, &time_ss);
time_mm = (int)(seconds / 60.0);
time_ss = seconds - time_mm * 60.0f;
snprintf(temp,TEMPSIZE, "play duration: %d samples (%1.0f:%06.3f seconds)\n", samples, time_mm, time_ss); snprintf(temp,TEMPSIZE, "play duration: %d samples (%1.0f:%06.3f seconds)\n", samples, time_mm, time_ss);
concatn(length,desc,temp); concatn(length,desc,temp);
} }