Merge pull request #744 from bnnm/txtp-misc-title

txtp misc title
This commit is contained in:
bnnm 2020-11-02 01:23:31 +01:00 committed by GitHub
commit 6d16132964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 286 additions and 120 deletions

View File

@ -386,11 +386,16 @@ static void print_tags(cli_config* cfg) {
static void print_title(VGMSTREAM* vgmstream, cli_config* cfg) { static void print_title(VGMSTREAM* vgmstream, cli_config* cfg) {
char title[1024]; char title[1024];
vgmstream_title_t tcfg = {0};
if (!cfg->show_title) if (!cfg->show_title)
return; return;
vgmstream_get_title(title, sizeof(title), cfg->infilename, vgmstream, NULL); tcfg.force_title = 0;
tcfg.subsong_range = 0;
tcfg.remove_extension = 0;
vgmstream_get_title(title, sizeof(title), cfg->infilename, vgmstream, &tcfg);
printf("title: %s\n", title); printf("title: %s\n", title);
} }

View File

@ -37,7 +37,7 @@ Some games clumsily loop audio by using multiple full file "segments", so you ca
BGM01_BEGIN.VAG BGM01_BEGIN.VAG
BGM01_LOOPED.VAG BGM01_LOOPED.VAG
# segments must define loops # segments may define loops
loop_start_segment = 2 # 2nd file start loop_start_segment = 2 # 2nd file start
loop_end_segment = 2 # optional, default is last loop_end_segment = 2 # optional, default is last
mode = segments # optional, default is segments mode = segments # optional, default is segments
@ -51,6 +51,7 @@ BGM01_LOOPED.VAG
# (only for multiple segments, to repeat a single file use #E) # (only for multiple segments, to repeat a single file use #E)
loop_mode = auto loop_mode = auto
``` ```
Another way to set looping is using "loop anchors", that are meant to simplify more complex .txtp (explained later).
If your loop segment has proper loops you want to keep, you can use: If your loop segment has proper loops you want to keep, you can use:
@ -158,6 +159,7 @@ mode = layers
# you could also set: group = L and mode = mixed, same thing # you could also set: group = L and mode = mixed, same thing
``` ```
### Group definition
`group` can go anywhere in the .txtp, as many times as needed (groups are read and kept in an list that is applied in order at the end). Format is `(position)(type)(count)(repeat)`: `group` can go anywhere in the .txtp, as many times as needed (groups are read and kept in an list that is applied in order at the end). Format is `(position)(type)(count)(repeat)`:
- `position`: file start (optional, default is 1 = first, or set `-` for auto from prev N files) - `position`: file start (optional, default is 1 = first, or set `-` for auto from prev N files)
- `type`: group as `S`=segments, `L`=layers, or `R`=pseudo-random - `type`: group as `S`=segments, `L`=layers, or `R`=pseudo-random
@ -190,7 +192,8 @@ group = -S2 #segment prev 2 (will start from pos.1 = bgm1+2, makes group of bgm
# may mix groups of auto and manual positions too, but results are harder to predict # may mix groups of auto and manual positions too, but results are harder to predict
``` ```
Group `R` is meant to help with games that randomly select a file in a group. You can set with `>N` which file will be selected. This way you can quickly edit the TXTP and change the file (you could/should just comment files too, this is just for convenience in complex cases and testing). Files do need to exist and are parsed before being selected, and it can select groups too. ### Pseudo-random groups
Group `R` is meant to help with games that randomly select a file in a group. You can set with `>N` which file will be selected. This way you can quickly edit the TXTP and change the file (you could just comment files too, this is just for convenience in complex cases and testing). You can also set `>-`, meaning "play all", basically turning `R` into `S`. Files do need to exist and are parsed before being selected, and it can select groups too.
``` ```
bgm1.adx bgm1.adx
bgm2.adx bgm2.adx
@ -207,6 +210,30 @@ group = -R3>1 #first file, change to >2 for second
group = -R2>2 #select either group >1 or >2 group = -R2>2 #select either group >1 or >2
``` ```
### Silent files
You can put `?.` in an entry to make a silent (non-existing) file. By default takes channels and sample rate of nearby files, can be combined with regular commands to configure.
```
intro.adx
?.silence #b 3.0 # 3 seconds of silence
loop.adx
```
It also doubles as a quick "silence this file" while keeping the same structure, for complex cases. The `.` can actually be anywhere after `?`, but must appear before commands to function correctly.
```
layer1a.adx
?layer1b.adx
group = -L2
?layer2a.adx
layer2b.adx
group = -L2
group = -S2
```
Most of the time you can do the same with `#p`/`#P` padding commands or `#@volume 0.0`. This is mainly for complex engines that combine silent entries in twisted ways. You can't silence `group` with `?group` though since they aren't considered "entries".
### Other considerations
Internally, `mode = segment/layers` are treated basically as a (default, at the end) group. You can apply commands to the resulting group (rather than the individual files) too. `commands` would be applied to this final group. Internally, `mode = segment/layers` are treated basically as a (default, at the end) group. You can apply commands to the resulting group (rather than the individual files) too. `commands` would be applied to this final group.
``` ```
mainA_2ch.at3 mainA_2ch.at3
@ -233,30 +260,7 @@ mode = segments
loop_start_segment = 3 #refers to final group at position 2 loop_start_segment = 3 #refers to final group at position 2
loop_mode = keep loop_mode = keep
``` ```
Also see loop anchors to handle looping in some cases.
### Silent files
You can put `?.` in an entry to make a silent (non-existing) file. By default takes channels and sample rate of nearby files, can be combined with regular commands to configure.
```
intro.adx
?.silence #b 3.0 # 3 seconds of silence
loop.adx
```
It also doubles as a quick "silence this file" while keeping the same structure, for complex cases. The `.` can actually be anywhere after `?`, but must appear before commands to function correctly.
```
layer1a.adx
?layer1b.adx
group = -L2
?layer2a.adx
layer2b.adx
group = -L2
group = -S2
```
Most of the time you can do the same with `#p`/`#P` padding commands or `#@volume 0.0`. This is mainly for complex engines that combine silent entries in twisted ways. You can't silence `group` with `?group` though since they aren't considered "entries".
## TXTP COMMANDS ## TXTP COMMANDS
@ -569,11 +573,45 @@ This can be applied to individual layers and segments, but normally you want to
Mixing must be supported by the plugin, otherwise it's ignored (there is a negligible performance penalty per mix operation though). 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.
### Macros ### Macros
**`#@(macro name and parameters)`**: adds a new macro **`#@(macro name and parameters)`**: adds a new macro
Manually setting values gets old, so TXTP supports a bunch of simple macros. They automate some of the above commands (analyzing the file), and may be combined, so order still matters. Manually setting values gets old, so TXTP supports a bunch of simple macros. They automate some of the above commands (analyzing the file), and may be combined, so order still matters.
- `volume N (channels)`: sets volume V to selected channels - `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
- `track (channels)`: makes a file of selected channels - `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-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-b N (channels)`: same, but adjusts volume depending on layers (for layered bgm)

View File

@ -429,8 +429,7 @@ void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & ti
int num_samples = vgmstream_get_samples(infostream); int num_samples = vgmstream_get_samples(infostream);
*length_in_ms = num_samples*1000LL / infostream->sample_rate; *length_in_ms = num_samples*1000LL / infostream->sample_rate;
char temp[1024]; describe_vgmstream(infostream, temp, sizeof(temp));
describe_vgmstream(infostream, temp, 1024);
description = temp; description = temp;
} }
} }
@ -438,30 +437,12 @@ void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & ti
/* infostream gets added with index 0 (other) or 1 (current) */ /* infostream gets added with index 0 (other) or 1 (current) */
if (infostream && title) { if (infostream && title) {
const char *p = filename + strlen(filename); vgmstream_title_t tcfg = {0};
while (*p != '\\' && p >= filename) p--; tcfg.remove_extension = 1;
p++;
const char *e = filename + strlen(filename);
while (*e != '.' && e >= filename) e--;
title.set_string(p, e - p); /* name without ext */
const char* info_name = infostream->stream_name; const char* filename_str = filename;
int info_streams = infostream->num_streams; vgmstream_get_title(temp, sizeof(temp), filename_str, infostream, &tcfg);
int info_subsong = infostream->stream_index; title = temp;
if (info_subsong == 0)
info_subsong = 1;
/* show number if file has more than 1 subsong */
if (info_streams > 1) {
sprintf(temp,"#%d",info_subsong);
title += temp;
}
/* show name if file has subsongs (implicitly shows also for TXTP) */
if (info_name[0] != '\0' && info_streams > 0) {
sprintf(temp," (%s)",info_name);
title += temp;
}
} }
// and only close if was querying a new subsong // and only close if was querying a new subsong

View File

@ -119,7 +119,7 @@ void decode_vadpcm(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacin
} }
/* update hist once all frame is actually copied */ /* update hist once all frame is actually copied */
if (first_sample + sample_count == samples_per_frame) { if (first_sample + sample_count / channelspacing == samples_per_frame) {
stream->adpcm_history2_16 = hist[6]; stream->adpcm_history2_16 = hist[6];
stream->adpcm_history1_16 = hist[7]; stream->adpcm_history1_16 = hist[7];
} }

View File

@ -32,13 +32,14 @@ VGMSTREAM* init_vgmstream_bkhd(STREAMFILE* sf) {
* as other chunks, and may have a DATA/DIDX index to memory .wem in DATA. * as other chunks, and may have a DATA/DIDX index to memory .wem in DATA.
* We support the internal .wem mainly for quick tests, as the HIRC is * We support the internal .wem mainly for quick tests, as the HIRC is
* complex and better handled with TXTP (some info from Nicknine's script). * complex and better handled with TXTP (some info from Nicknine's script).
* unlike RIFF, first chunk follows chunk rules */ * Use this to explore HIRC and covert to .txtp: https://github.com/bnnm/wwiser */
version = read_u32(base_offset + 0x08, sf); version = read_u32(base_offset + 0x08, sf);
if (version == 0 || version == 1) { /* early games */ if (version == 0 || version == 1) { /* early games */
version = read_u32(base_offset + 0x10, sf); version = read_u32(base_offset + 0x10, sf);
} }
/* first chunk also follows standard chunk sizes unlike RIFF */
if (version <= 26) { if (version <= 26) {
off_t data_offset, data_start, offset; off_t data_offset, data_start, offset;
if (!find_chunk(sf, 0x44415441, base_offset, 0, &data_offset, NULL, big_endian, 0)) /* "DATA" */ if (!find_chunk(sf, 0x44415441, base_offset, 0, &data_offset, NULL, big_endian, 0)) /* "DATA" */
@ -50,7 +51,7 @@ VGMSTREAM* init_vgmstream_bkhd(STREAMFILE* sf) {
* 08: entries size * 08: entries size
* 0c: padding size after entries * 0c: padding size after entries
* 10: data size * 10: data size
* 14: size? * 14: size? or null
* 18: data start * 18: data start
* 1c: data size * 1c: data size
* per entry: * per entry:
@ -135,13 +136,23 @@ VGMSTREAM* init_vgmstream_bkhd(STREAMFILE* sf) {
vgmstream->num_streams = total_subsongs; vgmstream->num_streams = total_subsongs;
if (is_dummy) {
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%u/dummy", subfile_id); const char* info = NULL;
else if (is_wmid) if (is_dummy)
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%u/wmid", subfile_id); info = "dummy";
else if (subfile_id != 0xFFFFFFFF) else if (is_wmid)
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%u", subfile_id); info = "wmid";
/* old Wwise shows index or (more often) -1, unify to index*/
if (subfile_id == 0xFFFFFFFF)
subfile_id = target_subsong - 1;
if (info)
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%u/%s", subfile_id, info);
else
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%u", subfile_id);
}
close_streamfile(temp_sf); close_streamfile(temp_sf);
return vgmstream; return vgmstream;

View File

@ -121,20 +121,22 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
} }
} }
//;VGM_LOG("FSB5 FEV: offset=%lx, size=%x\n", subfile_offset,subfile_size);
temp_sf = setup_subfile_streamfile(sf, subfile_offset,subfile_size, "fsb"); temp_sf = setup_subfile_streamfile(sf, subfile_offset,subfile_size, "fsb");
if (!temp_sf) goto fail; if (!temp_sf) goto fail;
temp_sf->stream_index = fsb5_subsong; /* relative subsong, in case of multiple FSBs */ temp_sf->stream_index = fsb5_subsong; /* relative subsong, in case of multiple FSBs */
vgmstream = (read_u32be(0x00, temp_sf) == 0x46534235) ? /* "FSB5" (better flag?)*/ vgmstream = (read_u32be(0x00, temp_sf) == 0x46534235) ? /* "FSB5" (better flag?)*/
init_vgmstream_fsb5(temp_sf) : init_vgmstream_fsb5(temp_sf) :
init_vgmstream_fsb_encrypted(temp_sf); init_vgmstream_fsb_encrypted(temp_sf);
close_streamfile(temp_sf);
if (!vgmstream) goto fail; if (!vgmstream) goto fail;
vgmstream->stream_index = sf->stream_index; //target_subsong; /* 0-index matters */ vgmstream->stream_index = sf->stream_index; //target_subsong; /* 0-index matters */
vgmstream->num_streams = total_subsongs; vgmstream->num_streams = total_subsongs;
close_streamfile(temp_sf);
return vgmstream; return vgmstream;
fail: fail:

View File

@ -10,6 +10,7 @@
#define TXTP_GROUP_MODE_SEGMENTED 'S' #define TXTP_GROUP_MODE_SEGMENTED 'S'
#define TXTP_GROUP_MODE_LAYERED 'L' #define TXTP_GROUP_MODE_LAYERED 'L'
#define TXTP_GROUP_MODE_RANDOM 'R' #define TXTP_GROUP_MODE_RANDOM 'R'
#define TXTP_GROUP_RANDOM_ALL '-'
#define TXTP_GROUP_REPEAT 'R' #define TXTP_GROUP_REPEAT 'R'
#define TXTP_POSITION_LOOPS 'L' #define TXTP_POSITION_LOOPS 'L'
@ -88,6 +89,9 @@ typedef struct {
int32_t loop_start_sample; int32_t loop_start_sample;
double loop_end_second; double loop_end_second;
int32_t loop_end_sample; int32_t loop_end_sample;
/* flags */
int loop_anchor_start;
int loop_anchor_end;
int trim_set; int trim_set;
double trim_second; double trim_second;
@ -174,6 +178,10 @@ VGMSTREAM* init_vgmstream_txtp(STREAMFILE* sf) {
/* should result in a final, single vgmstream possibly containing multiple vgmstreams */ /* should result in a final, single vgmstream possibly containing multiple vgmstreams */
vgmstream = txtp->vgmstream[0]; vgmstream = txtp->vgmstream[0];
/* flags for title config */
vgmstream->config.is_txtp = 1;
vgmstream->config.is_mini_txtp = (get_streamfile_size(sf) == 0);
clean_txtp(txtp, 0); clean_txtp(txtp, 0);
return vgmstream; return vgmstream;
@ -311,6 +319,7 @@ static void update_vgmstream_list(VGMSTREAM* vgmstream, txtp_header* txtp, int p
for (i = position + count; i < txtp->vgmstream_count; i++) { for (i = position + count; i < txtp->vgmstream_count; i++) {
//;VGM_LOG("TXTP: copy %i to %i\n", i, i + 1 - count); //;VGM_LOG("TXTP: copy %i to %i\n", i, i + 1 - count);
txtp->vgmstream[i + 1 - count] = txtp->vgmstream[i]; txtp->vgmstream[i + 1 - count] = txtp->vgmstream[i];
txtp->entry[i + 1 - count] = txtp->entry[i]; /* memcpy old settings for other groups */
} }
/* list can only become smaller, no need to alloc/free/etc */ /* list can only become smaller, no need to alloc/free/etc */
@ -318,10 +327,38 @@ static void update_vgmstream_list(VGMSTREAM* vgmstream, txtp_header* txtp, int p
//;VGM_LOG("TXTP: compact vgmstreams=%i\n", txtp->vgmstream_count); //;VGM_LOG("TXTP: compact vgmstreams=%i\n", txtp->vgmstream_count);
} }
static int find_loop_anchors(txtp_header* txtp, int position, int count, int* p_loop_start, int* p_loop_end) {
int loop_start = 0, loop_end = 0;
int i, j;
//;VGM_LOG("TXTP: find loop anchors from %i to %i\n", position, count);
for (i = position, j = 0; i < position + count; i++, j++) {
if (txtp->entry[i].loop_anchor_start) {
loop_start = j + 1; /* logic elsewhere also uses +1 */
}
if (txtp->entry[i].loop_anchor_end) {
loop_end = j + 1;
}
}
if (loop_start) {
if (!loop_end)
loop_end = count;
*p_loop_start = loop_start;
*p_loop_end = loop_end;
//;VGM_LOG("TXTP: loop anchors %i, %i\n", loop_start, loop_end);
return 1;
}
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, int is_group, 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;
int loop_start = 0, loop_end = 0;
/* allowed for actual groups (not final "mode"), otherwise skip to optimize */ /* allowed for actual groups (not final "mode"), otherwise skip to optimize */
@ -335,16 +372,25 @@ static int make_group_segment(txtp_header* txtp, int is_group, int position, int
return 1; return 1;
} }
/* loop settings only make sense if this group becomes final vgmstream */
if (position == 0 && txtp->vgmstream_count == count) { /* set loops with "anchors" (this allows loop config inside groups, not just in the final group,
if (txtp->loop_start_segment && !txtp->loop_end_segment) { * which is sometimes useful when paired with random/selectable groups or loop times) */
txtp->loop_end_segment = count; if (find_loop_anchors(txtp, position, count, &loop_start, &loop_end)) {
loop_flag = (loop_start > 0 && loop_start <= count);
}
/* loop segment settings only make sense if this group becomes final vgmstream */
else if (position == 0 && txtp->vgmstream_count == count) {
loop_start = txtp->loop_start_segment;
loop_end = txtp->loop_end_segment;
if (loop_start && !loop_end) {
loop_end = count;
} }
else if (txtp->is_loop_auto) { /* auto set to last segment */ else if (txtp->is_loop_auto) { /* auto set to last segment */
txtp->loop_start_segment = count; loop_start = count;
txtp->loop_end_segment = count; loop_end = count;
} }
loop_flag = (txtp->loop_start_segment > 0 && txtp->loop_start_segment <= count); loop_flag = (loop_start > 0 && loop_start <= count);
} }
@ -363,7 +409,7 @@ static int make_group_segment(txtp_header* txtp, int is_group, int position, int
goto fail; goto fail;
/* build the layout VGMSTREAM */ /* build the layout VGMSTREAM */
vgmstream = allocate_segmented_vgmstream(data_s,loop_flag, txtp->loop_start_segment - 1, txtp->loop_end_segment - 1); vgmstream = allocate_segmented_vgmstream(data_s, loop_flag, loop_start - 1, loop_end - 1);
if (!vgmstream) goto fail; if (!vgmstream) goto fail;
/* custom meta name if all parts don't match */ /* custom meta name if all parts don't match */
@ -378,13 +424,13 @@ static int make_group_segment(txtp_header* txtp, int is_group, int position, int
if (loop_flag && txtp->is_loop_keep) { if (loop_flag && txtp->is_loop_keep) {
int32_t current_samples = 0; int32_t current_samples = 0;
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
if (txtp->loop_start_segment == i+1 /*&& data_s->segments[i]->loop_start_sample*/) { if (loop_start == i+1 /*&& data_s->segments[i]->loop_start_sample*/) {
vgmstream->loop_start_sample = current_samples + data_s->segments[i]->loop_start_sample; vgmstream->loop_start_sample = current_samples + data_s->segments[i]->loop_start_sample;
} }
current_samples += data_s->segments[i]->num_samples; current_samples += data_s->segments[i]->num_samples;
if (txtp->loop_end_segment == i+1 && data_s->segments[i]->loop_end_sample) { if (loop_end == i+1 && data_s->segments[i]->loop_end_sample) {
vgmstream->loop_end_sample = current_samples - data_s->segments[i]->num_samples + data_s->segments[i]->loop_end_sample; vgmstream->loop_end_sample = current_samples - data_s->segments[i]->num_samples + data_s->segments[i]->loop_end_sample;
} }
} }
@ -479,9 +525,13 @@ 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 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) {
static int random_seed = 0; static int random_seed = 0;
srand((unsigned)txtp + random_seed++); /* whatevs */ srand((unsigned)txtp + random_seed++); /* whatevs */
@ -563,6 +613,7 @@ 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->group_settings);
txtp->entry[grp->position] = grp->group_settings; /* memcpy old settings for subgroups */
} }
/* final tweaks (should be integrated with the above?) */ /* final tweaks (should be integrated with the above?) */
@ -840,6 +891,45 @@ static int get_position(const char* params, double* value_f, char* value_type) {
return n; return n;
} }
static int get_volume(const char* params, double *value, int *is_set) {
int n, m;
double temp_f;
int temp_i;
char temp_c1, temp_c2;
if (is_set) *is_set = 0;
/* test if format is NdB (decibels) */
m = sscanf(params, " %i%c%c%n", &temp_i, &temp_c1, &temp_c2, &n);
if (m == 3 && temp_c1 == 'd' && (temp_c2 == 'B' || temp_c2 == 'b')) {
/* dB 101:
* - logaritmic scale
* - dB = 20 * log(percent / 100)
* - percent = pow(10, dB / 20)) * 100
* - for audio: 100% = 0dB (base max volume of current file = reference dB)
* - negative dB decreases volume, positive dB increases
* ex.
* 200% = 20 * log(200 / 100) = +6.02059991328 dB
* 50% = 20 * log( 50 / 100) = -6.02059991328 dB
* 6dB = pow(10, 6 / 20) * 100 = +195.26231497 %
* -6dB = pow(10, -6 / 20) * 100 = +50.50118723362 %
*/
if (is_set) *is_set = 1;
*value = pow(10, temp_i / 20.0); /* dB to % where 1.0 = max */
return n;
}
/* test if format is N.N (percent) */
m = sscanf(params, " %lf%n", &temp_f, &n);
if (m == 1) {
if (is_set) *is_set = 1;
*value = temp_f;
return n;
}
return 0;
}
static int get_time(const char* params, double* value_f, int32_t* value_i) { static int get_time(const char* params, double* value_f, int32_t* value_i) {
int n,m; int n,m;
@ -1127,6 +1217,14 @@ static void add_settings(txtp_entry* current, txtp_entry* entry, const char* fil
current->mixing_count++; current->mixing_count++;
} }
} }
current->loop_anchor_start = entry->loop_anchor_start;
current->loop_anchor_end = entry->loop_anchor_end;
}
//TODO use
static inline int is_match(const char* str1, const char* str2) {
return strcmp(str1, str2) == 0;
} }
static void parse_params(txtp_entry* entry, char* params) { static void parse_params(txtp_entry* entry, char* params) {
@ -1357,12 +1455,21 @@ static void parse_params(txtp_entry* entry, char* params) {
//;VGM_LOG("TXTP: trim %i - %f / %i\n", entry->trim_set, entry->trim_second, entry->trim_sample); //;VGM_LOG("TXTP: trim %i - %f / %i\n", entry->trim_set, entry->trim_second, entry->trim_sample);
} }
else if (is_match(command,"a") || is_match(command,"@loop")) {
entry->loop_anchor_start = 1;
//;VGM_LOG("TXTP: anchor start set\n");
}
else if (is_match(command,"A") || is_match(command,"@LOOP")) {
entry->loop_anchor_end = 1;
//;VGM_LOG("TXTP: anchor end set\n");
}
//todo cleanup //todo cleanup
/* macros */ /* macros */
else if (strcmp(command,"@volume") == 0) { else if (strcmp(command,"@volume") == 0) {
txtp_mix_data mix = {0}; txtp_mix_data mix = {0};
nm = get_double(params, &mix.vol, NULL); nm = get_volume(params, &mix.vol, NULL);
params += nm; params += nm;
if (nm == 0) continue; if (nm == 0) continue;
@ -1479,11 +1586,18 @@ static int add_group(txtp_header* txtp, char* line) {
line += n; line += n;
} }
m = sscanf(line, " >%d%n", &cfg.selected, &n); m = sscanf(line, " >%c%n", &c, &n);
if (m == 1) { if (m == 1 && c == TXTP_GROUP_RANDOM_ALL) {
cfg.selected--; /* externally 1=first but internally 0=first */ cfg.selected = cfg.count; /* special meaning */
line += n; line += n;
} }
else {
m = sscanf(line, " >%d%n", &cfg.selected, &n);
if (m == 1) {
cfg.selected--; /* externally 1=first but internally 0=first */
line += n;
}
}
parse_params(&cfg.group_settings, line); parse_params(&cfg.group_settings, line);
@ -1555,6 +1669,7 @@ static void clean_filename(char* filename) {
} }
//TODO see if entry can be set to &default/&entry[entry_count] to avoid add_settings
static int add_entry(txtp_header* txtp, char* filename, int is_default) { static int add_entry(txtp_header* txtp, char* filename, int is_default) {
int i; int i;
txtp_entry entry = {0}; txtp_entry entry = {0};

View File

@ -7,6 +7,7 @@
* There is some repetition from other metas, but not enough to bother me. * There is some repetition from other metas, but not enough to bother me.
* *
* Some info: https://www.audiokinetic.com/en/library/edge/ * Some info: https://www.audiokinetic.com/en/library/edge/
* .bnk (dynamic music/loop) info: https://github.com/bnnm/wwiser
*/ */
typedef enum { PCM, IMA, VORBIS, DSP, XMA2, XWMA, AAC, HEVAG, ATRAC9, OPUSNX, OPUS, PTADPCM } wwise_codec; typedef enum { PCM, IMA, VORBIS, DSP, XMA2, XWMA, AAC, HEVAG, ATRAC9, OPUSNX, OPUS, PTADPCM } wwise_codec;
typedef struct { typedef struct {
@ -115,7 +116,7 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
else { else {
if (!find_chunk(sf, 0x666d7420,first_offset,0, &ww.fmt_offset,&ww.fmt_size, ww.big_endian, 0)) /* "fmt " */ if (!find_chunk(sf, 0x666d7420,first_offset,0, &ww.fmt_offset,&ww.fmt_size, ww.big_endian, 0)) /* "fmt " */
goto fail; goto fail;
if (ww.fmt_size < 0x12) if (ww.fmt_size < 0x10)
goto fail; goto fail;
ww.format = (uint16_t)read_16bit(ww.fmt_offset+0x00,sf); ww.format = (uint16_t)read_16bit(ww.fmt_offset+0x00,sf);
} }
@ -167,8 +168,8 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
// /* usually contains "cue"s with sample positions for events (ex. Platinum Games) but no real looping info */ // /* usually contains "cue"s with sample positions for events (ex. Platinum Games) but no real looping info */
//} //}
/* other Wwise specific chunks: /* other chunks:
* "JUNK": optional padding for aligment (0-size JUNK exists too) * "JUNK": optional padding for aligment (0-size JUNK exists too), also in regular RIFF
* "akd ": seem to store extra info for Wwise editor (wave peaks/loudness/HDR envelope?) * "akd ": seem to store extra info for Wwise editor (wave peaks/loudness/HDR envelope?)
*/ */
@ -249,6 +250,7 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
switch(ww.codec) { switch(ww.codec) {
case PCM: /* common */ case PCM: /* common */
VGM_LOG("1\n");
/* normally riff.c has priority but it's needed when .wem is used */ /* normally riff.c has priority but it's needed when .wem is used */
if (ww.fmt_size != 0x10 && ww.fmt_size != 0x18 && ww.fmt_size != 0x28) goto fail; /* old, new/Limbo (PC) */ if (ww.fmt_size != 0x10 && ww.fmt_size != 0x18 && ww.fmt_size != 0x28) goto fail; /* old, new/Limbo (PC) */
if (ww.bits_per_sample != 16) goto fail; if (ww.bits_per_sample != 16) goto fail;
@ -268,7 +270,7 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
/* slightly modified XBOX-IMA */ /* slightly modified XBOX-IMA */
/* Wwise reuses common codec ids (ex. 0x0002 MSADPCM) for IMA so this parser should go AFTER riff.c avoid misdetection */ /* Wwise reuses common codec ids (ex. 0x0002 MSADPCM) for IMA so this parser should go AFTER riff.c avoid misdetection */
if (ww.fmt_size != 0x28 && ww.fmt_size != 0x18) goto fail; /* old, new */ if (ww.fmt_size != 0x14 && ww.fmt_size != 0x28 && ww.fmt_size != 0x18) goto fail; /* oldest, old, new */
if (ww.bits_per_sample != 4) goto fail; if (ww.bits_per_sample != 4) goto fail;
if (ww.block_align != 0x24 * ww.channels) goto fail; if (ww.block_align != 0x24 * ww.channels) goto fail;
@ -488,7 +490,10 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
/* endian check should be enough */ /* endian check should be enough */
//if (ww.fmt_size != ...) goto fail; /* XMA1 0x20, XMA2old: 0x34, XMA2new: 0x40, XMA2 Guitar Hero Live/padded: 0x64, etc */ //if (ww.fmt_size != ...) goto fail; /* XMA1 0x20, XMA2old: 0x34, XMA2new: 0x40, XMA2 Guitar Hero Live/padded: 0x64, etc */
if (!ww.big_endian) goto fail; /* must be Wwise (real XMA are LE and parsed elsewhere) */
/* only Wwise XMA: X360=BE, or XBone=LE+wem (real X360 XMA are LE and parsed elsewhere) */
if (!(ww.big_endian || (!ww.big_endian && check_extensions(sf,"wem,bnk"))))
goto fail;
if (find_chunk(sf, 0x584D4132,first_offset,0, &xma2_offset,&xma2_size, ww.big_endian, 0)) { /*"XMA2"*/ /* older Wwise */ if (find_chunk(sf, 0x584D4132,first_offset,0, &xma2_offset,&xma2_size, ww.big_endian, 0)) { /*"XMA2"*/ /* older Wwise */
bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,0x100, xma2_offset, xma2_size, ww.data_size, sf); bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,0x100, xma2_offset, xma2_size, ww.data_size, sf);

View File

@ -68,6 +68,7 @@ void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM
char* pos2; char* pos2;
char temp[1024]; char temp[1024];
buf[0] = '\0';
/* name without path */ /* name without path */
pos = strrchr(filename, '\\'); pos = strrchr(filename, '\\');
@ -80,23 +81,31 @@ void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM
strncpy(buf, pos, buf_len); strncpy(buf, pos, buf_len);
/* name without extension */ /* name without extension */
pos2 = strrchr(buf, '.'); if (cfg->remove_extension) {
if (pos2) pos2 = strrchr(buf, '.');
pos2[0] = '\0'; if (pos2 && strlen(pos2) < 15) /* too big extension = file name probably has a dot in the middle */
pos2[0] = '\0';
}
{ {
const char* stream_name = vgmstream->stream_name; const char* stream_name = vgmstream->stream_name;
int total_subsongs = vgmstream->num_streams; int total_subsongs = vgmstream->num_streams;
int target_subsong = vgmstream->stream_index; int target_subsong = vgmstream->stream_index;
//int is_first = vgmstream->stream_index == 0; //int is_first = vgmstream->stream_index == 0;
//int is_txtp = ; //todo don't show number/name for txtp but show for mini-txtp
int show_name; int show_name;
/* special considerations for TXTP:
* - full txtp: don't show subsong number, nor name (assumes one names .txtp as wanted)
* - mini txtp: don't show subsong number, but show name (assumes one choses song #n in filename, but wants title)
*/
int full_txtp = vgmstream->config.is_txtp && !vgmstream->config.is_mini_txtp;
int mini_txtp = vgmstream->config.is_mini_txtp;
if (target_subsong == 0) if (target_subsong == 0)
target_subsong = 1; target_subsong = 1;
/* show number if file has more than 1 subsong */ /* show number if file has more than 1 subsong */
if (total_subsongs > 1) { if (total_subsongs > 1 && !(full_txtp || mini_txtp)) {
if (cfg && cfg->subsong_range) if (cfg && cfg->subsong_range)
snprintf(temp, sizeof(temp), "%s#1~%i", buf, total_subsongs); snprintf(temp, sizeof(temp), "%s#1~%i", buf, total_subsongs);
else else
@ -105,13 +114,19 @@ void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM
} }
/* show name for some cases */ /* show name for some cases */
show_name = (total_subsongs > 0 && (!cfg || !cfg->subsong_range)) || show_name = (total_subsongs > 0) && (!cfg || !cfg->subsong_range);
(cfg && cfg->force_title); if (full_txtp)
show_name = 0;
if (cfg && cfg->force_title)
show_name = 1;
if (stream_name[0] != '\0' && show_name) { if (stream_name[0] != '\0' && show_name) {
snprintf(temp, sizeof(temp), "%s (%s)", buf, stream_name); snprintf(temp, sizeof(temp), "%s (%s)", buf, stream_name);
strncpy(buf, temp, buf_len); strncpy(buf, temp, buf_len);
} }
} }
buf[buf_len - 1] = '\0';
} }
@ -174,6 +189,9 @@ static void load_default_config(play_config_t* def, play_config_t* tcfg) {
copy_time(&def->trim_begin_set, &def->trim_begin, &def->trim_begin_s, &tcfg->trim_begin_set, &tcfg->trim_begin, &tcfg->trim_begin_s); copy_time(&def->trim_begin_set, &def->trim_begin, &def->trim_begin_s, &tcfg->trim_begin_set, &tcfg->trim_begin, &tcfg->trim_begin_s);
copy_time(&def->trim_end_set, &def->trim_end, &def->trim_end_s, &tcfg->trim_end_set, &tcfg->trim_end, &tcfg->trim_end_s); copy_time(&def->trim_end_set, &def->trim_end, &def->trim_end_s, &tcfg->trim_end_set, &tcfg->trim_end, &tcfg->trim_end_s);
copy_time(&def->body_time_set, &def->body_time, &def->body_time_s, &tcfg->body_time_set, &tcfg->body_time, &tcfg->body_time_s); copy_time(&def->body_time_set, &def->body_time, &def->body_time_s, &tcfg->body_time_set, &tcfg->body_time, &tcfg->body_time_s);
def->is_mini_txtp = tcfg->is_mini_txtp;
def->is_txtp = tcfg->is_txtp;
} }
static void load_player_config(play_config_t* def, vgmstream_cfg_t* vcfg) { static void load_player_config(play_config_t* def, vgmstream_cfg_t* vcfg) {

View File

@ -71,6 +71,7 @@ void vgmstream_set_play_forever(VGMSTREAM* vgmstream, int enabled);
typedef struct { typedef struct {
int force_title; int force_title;
int subsong_range; int subsong_range;
int remove_extension;
} vgmstream_title_t; } vgmstream_title_t;
/* get a simple title for plugins */ /* get a simple title for plugins */

View File

@ -821,6 +821,9 @@ typedef struct {
int fade_time_set; int fade_time_set;
int pad_end_set; int pad_end_set;
/* for lack of a better place... */
int is_txtp;
int is_mini_txtp;
} play_config_t; } play_config_t;
@ -843,6 +846,7 @@ typedef struct {
int32_t play_duration; /* total samples that the stream lasts (after applying all config) */ int32_t play_duration; /* total samples that the stream lasts (after applying all config) */
int32_t play_position; /* absolute sample where stream is */ int32_t play_position; /* absolute sample where stream is */
} play_state_t; } play_state_t;

View File

@ -150,7 +150,8 @@ static void wa_ichar_to_char(char *dst, size_t dstsize, const in_char *wsrc) {
//int size_needed = WideCharToMultiByte(CP_UTF8,0, src,-1, NULL,0, NULL, NULL); //int size_needed = WideCharToMultiByte(CP_UTF8,0, src,-1, NULL,0, NULL, NULL);
WideCharToMultiByte(CP_UTF8,0, wsrc,-1, dst,dstsize, NULL, NULL); WideCharToMultiByte(CP_UTF8,0, wsrc,-1, dst,dstsize, NULL, NULL);
#else #else
strcpy(dst,wsrc); strncpy(dst, wsrc, dstsize);
dst[dstsize - 1] = '\0';
#endif #endif
} }
@ -160,7 +161,8 @@ static void wa_char_to_ichar(in_char *wdst, size_t wdstsize, const char *src) {
//int size_needed = MultiByteToWideChar(CP_UTF8,0, src,-1, NULL,0); //int size_needed = MultiByteToWideChar(CP_UTF8,0, src,-1, NULL,0);
MultiByteToWideChar(CP_UTF8,0, src,-1, wdst,wdstsize); MultiByteToWideChar(CP_UTF8,0, src,-1, wdst,wdstsize);
#else #else
strcpy(wdst,src); strncpy(wdst, src, wdstsize);
wdst[wdstsize - 1] = '\0';
#endif #endif
} }
@ -891,43 +893,27 @@ static void build_extension_list(char *winamp_list, int winamp_list_size) {
/* unicode utils */ /* unicode utils */
static void get_title(in_char* dst, int dst_size, const in_char* fn, VGMSTREAM* infostream) { static void get_title(in_char* dst, int dst_size, const in_char* fn, VGMSTREAM* infostream) {
in_char* basename;
in_char buffer[PATH_LIMIT];
in_char filename[PATH_LIMIT]; in_char filename[PATH_LIMIT];
//int stream_index = 0; char buffer[PATH_LIMIT];
char filename_utf8[PATH_LIMIT];
parse_fn_string(fn, NULL, filename,PATH_LIMIT); parse_fn_string(fn, NULL, filename,PATH_LIMIT);
//parse_fn_int(fn, wa_L("$s"), &stream_index); //parse_fn_int(fn, wa_L("$s"), &stream_index);
basename = (in_char*)filename + wa_strlen(filename); /* find end */ wa_ichar_to_char(filename_utf8, PATH_LIMIT, filename);
while (*basename != '\\' && basename >= filename) /* and find last "\" */
basename--;
basename++;
wa_strcpy(dst,basename);
/* infostream gets added at first with index 0, then once played it re-adds proper numbers */ /* infostream gets added at first with index 0, then once played it re-adds proper numbers */
if (infostream) { if (infostream) {
const char* info_name = infostream->stream_name; vgmstream_title_t tcfg = {0};
int info_streams = infostream->num_streams;
int info_subsong = infostream->stream_index;
int is_first = infostream->stream_index == 0; int is_first = infostream->stream_index == 0;
/* show number if file has more than 1 subsong */ tcfg.force_title = settings.force_title;
if (info_streams > 1) { tcfg.subsong_range = is_first;
if (is_first) tcfg.remove_extension = 1;
wa_snprintf(buffer,PATH_LIMIT, wa_L("#1~%i"), info_streams);
else
wa_snprintf(buffer,PATH_LIMIT, wa_L("#%i"), info_subsong);
wa_strcat(dst,buffer);
}
/* show name if file has subsongs (implicitly shows also for TXTP) */ vgmstream_get_title(buffer, sizeof(buffer), filename_utf8, infostream, &tcfg);
if (info_name[0] != '\0' && ((info_streams > 0 && !is_first) || info_streams == 1 || settings.force_title)) {
in_char stream_name[PATH_LIMIT]; wa_char_to_ichar(dst, dst_size, buffer);
wa_char_to_ichar(stream_name, PATH_LIMIT, info_name);
wa_snprintf(buffer,PATH_LIMIT, wa_L(" (%s)"), stream_name);
wa_strcat(dst,buffer);
}
} }
} }