mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-15 02:57:38 +01:00
commit
6d16132964
@ -386,11 +386,16 @@ static void print_tags(cli_config* cfg) {
|
||||
|
||||
static void print_title(VGMSTREAM* vgmstream, cli_config* cfg) {
|
||||
char title[1024];
|
||||
vgmstream_title_t tcfg = {0};
|
||||
|
||||
if (!cfg->show_title)
|
||||
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);
|
||||
}
|
||||
|
92
doc/TXTP.md
92
doc/TXTP.md
@ -37,7 +37,7 @@ Some games clumsily loop audio by using multiple full file "segments", so you ca
|
||||
BGM01_BEGIN.VAG
|
||||
BGM01_LOOPED.VAG
|
||||
|
||||
# segments must define loops
|
||||
# segments may define loops
|
||||
loop_start_segment = 2 # 2nd file start
|
||||
loop_end_segment = 2 # optional, default is last
|
||||
mode = segments # optional, default is segments
|
||||
@ -51,6 +51,7 @@ BGM01_LOOPED.VAG
|
||||
# (only for multiple segments, to repeat a single file use #E)
|
||||
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:
|
||||
@ -158,6 +159,7 @@ mode = layers
|
||||
# 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)`:
|
||||
- `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
|
||||
@ -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
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
### 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.
|
||||
```
|
||||
mainA_2ch.at3
|
||||
@ -233,30 +260,7 @@ mode = segments
|
||||
loop_start_segment = 3 #refers to final group at position 2
|
||||
loop_mode = keep
|
||||
```
|
||||
|
||||
|
||||
### 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".
|
||||
Also see loop anchors to handle looping in some cases.
|
||||
|
||||
|
||||
## 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).
|
||||
|
||||
|
||||
### 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
|
||||
**`#@(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.
|
||||
- `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
|
||||
- `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)
|
||||
|
@ -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);
|
||||
*length_in_ms = num_samples*1000LL / infostream->sample_rate;
|
||||
|
||||
char temp[1024];
|
||||
describe_vgmstream(infostream, temp, 1024);
|
||||
describe_vgmstream(infostream, temp, sizeof(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) */
|
||||
if (infostream && title) {
|
||||
const char *p = filename + strlen(filename);
|
||||
while (*p != '\\' && p >= filename) p--;
|
||||
p++;
|
||||
const char *e = filename + strlen(filename);
|
||||
while (*e != '.' && e >= filename) e--;
|
||||
title.set_string(p, e - p); /* name without ext */
|
||||
vgmstream_title_t tcfg = {0};
|
||||
tcfg.remove_extension = 1;
|
||||
|
||||
const char* info_name = infostream->stream_name;
|
||||
int info_streams = infostream->num_streams;
|
||||
int info_subsong = infostream->stream_index;
|
||||
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;
|
||||
}
|
||||
const char* filename_str = filename;
|
||||
vgmstream_get_title(temp, sizeof(temp), filename_str, infostream, &tcfg);
|
||||
title = temp;
|
||||
}
|
||||
|
||||
// and only close if was querying a new subsong
|
||||
|
@ -119,7 +119,7 @@ void decode_vadpcm(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacin
|
||||
}
|
||||
|
||||
/* 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_history1_16 = hist[7];
|
||||
}
|
||||
|
@ -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.
|
||||
* 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).
|
||||
* 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);
|
||||
if (version == 0 || version == 1) { /* early games */
|
||||
version = read_u32(base_offset + 0x10, sf);
|
||||
}
|
||||
|
||||
/* first chunk also follows standard chunk sizes unlike RIFF */
|
||||
if (version <= 26) {
|
||||
off_t data_offset, data_start, offset;
|
||||
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
|
||||
* 0c: padding size after entries
|
||||
* 10: data size
|
||||
* 14: size?
|
||||
* 14: size? or null
|
||||
* 18: data start
|
||||
* 1c: data size
|
||||
* per entry:
|
||||
@ -136,12 +137,22 @@ VGMSTREAM* init_vgmstream_bkhd(STREAMFILE* sf) {
|
||||
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
|
||||
if (is_dummy)
|
||||
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%u/dummy", subfile_id);
|
||||
else if (is_wmid)
|
||||
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%u/wmid", subfile_id);
|
||||
else if (subfile_id != 0xFFFFFFFF)
|
||||
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%u", subfile_id);
|
||||
{
|
||||
const char* info = NULL;
|
||||
if (is_dummy)
|
||||
info = "dummy";
|
||||
else if (is_wmid)
|
||||
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);
|
||||
return vgmstream;
|
||||
|
@ -121,6 +121,8 @@ 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");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
@ -129,12 +131,12 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
|
||||
vgmstream = (read_u32be(0x00, temp_sf) == 0x46534235) ? /* "FSB5" (better flag?)*/
|
||||
init_vgmstream_fsb5(temp_sf) :
|
||||
init_vgmstream_fsb_encrypted(temp_sf);
|
||||
close_streamfile(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->stream_index = sf->stream_index; //target_subsong; /* 0-index matters */
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
|
145
src/meta/txtp.c
145
src/meta/txtp.c
@ -10,6 +10,7 @@
|
||||
#define TXTP_GROUP_MODE_SEGMENTED 'S'
|
||||
#define TXTP_GROUP_MODE_LAYERED 'L'
|
||||
#define TXTP_GROUP_MODE_RANDOM 'R'
|
||||
#define TXTP_GROUP_RANDOM_ALL '-'
|
||||
#define TXTP_GROUP_REPEAT 'R'
|
||||
#define TXTP_POSITION_LOOPS 'L'
|
||||
|
||||
@ -88,6 +89,9 @@ typedef struct {
|
||||
int32_t loop_start_sample;
|
||||
double loop_end_second;
|
||||
int32_t loop_end_sample;
|
||||
/* flags */
|
||||
int loop_anchor_start;
|
||||
int loop_anchor_end;
|
||||
|
||||
int trim_set;
|
||||
double trim_second;
|
||||
@ -174,6 +178,10 @@ VGMSTREAM* init_vgmstream_txtp(STREAMFILE* sf) {
|
||||
/* should result in a final, single vgmstream possibly containing multiple vgmstreams */
|
||||
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);
|
||||
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++) {
|
||||
//;VGM_LOG("TXTP: copy %i to %i\n", i, i + 1 - count);
|
||||
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 */
|
||||
@ -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);
|
||||
}
|
||||
|
||||
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) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
segmented_layout_data *data_s = NULL;
|
||||
int i, loop_flag = 0;
|
||||
int loop_start = 0, loop_end = 0;
|
||||
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* loop settings only make sense if this group becomes final vgmstream */
|
||||
if (position == 0 && txtp->vgmstream_count == count) {
|
||||
if (txtp->loop_start_segment && !txtp->loop_end_segment) {
|
||||
txtp->loop_end_segment = count;
|
||||
|
||||
/* set loops with "anchors" (this allows loop config inside groups, not just in the final group,
|
||||
* which is sometimes useful when paired with random/selectable groups or loop times) */
|
||||
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 */
|
||||
txtp->loop_start_segment = count;
|
||||
txtp->loop_end_segment = count;
|
||||
loop_start = 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;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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) {
|
||||
int32_t current_samples = 0;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -479,9 +525,13 @@ static int make_group_random(txtp_header* txtp, int is_group, int position, int
|
||||
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
|
||||
* (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) {
|
||||
static int random_seed = 0;
|
||||
srand((unsigned)txtp + random_seed++); /* whatevs */
|
||||
@ -563,6 +613,7 @@ static int parse_groups(txtp_header* txtp) {
|
||||
|
||||
/* group may also have settings (like downmixing) */
|
||||
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?) */
|
||||
@ -840,6 +891,45 @@ static int get_position(const char* params, double* value_f, char* value_type) {
|
||||
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) {
|
||||
int n,m;
|
||||
@ -1127,6 +1217,14 @@ static void add_settings(txtp_entry* current, txtp_entry* entry, const char* fil
|
||||
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) {
|
||||
@ -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);
|
||||
}
|
||||
|
||||
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
|
||||
/* macros */
|
||||
else if (strcmp(command,"@volume") == 0) {
|
||||
txtp_mix_data mix = {0};
|
||||
|
||||
nm = get_double(params, &mix.vol, NULL);
|
||||
nm = get_volume(params, &mix.vol, NULL);
|
||||
params += nm;
|
||||
|
||||
if (nm == 0) continue;
|
||||
@ -1479,11 +1586,18 @@ static int add_group(txtp_header* txtp, char* line) {
|
||||
line += n;
|
||||
}
|
||||
|
||||
m = sscanf(line, " >%d%n", &cfg.selected, &n);
|
||||
if (m == 1) {
|
||||
cfg.selected--; /* externally 1=first but internally 0=first */
|
||||
m = sscanf(line, " >%c%n", &c, &n);
|
||||
if (m == 1 && c == TXTP_GROUP_RANDOM_ALL) {
|
||||
cfg.selected = cfg.count; /* special meaning */
|
||||
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);
|
||||
|
||||
@ -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) {
|
||||
int i;
|
||||
txtp_entry entry = {0};
|
||||
|
@ -7,6 +7,7 @@
|
||||
* There is some repetition from other metas, but not enough to bother me.
|
||||
*
|
||||
* 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 struct {
|
||||
@ -115,7 +116,7 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
else {
|
||||
if (!find_chunk(sf, 0x666d7420,first_offset,0, &ww.fmt_offset,&ww.fmt_size, ww.big_endian, 0)) /* "fmt " */
|
||||
goto fail;
|
||||
if (ww.fmt_size < 0x12)
|
||||
if (ww.fmt_size < 0x10)
|
||||
goto fail;
|
||||
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 */
|
||||
//}
|
||||
|
||||
/* other Wwise specific chunks:
|
||||
* "JUNK": optional padding for aligment (0-size JUNK exists too)
|
||||
/* other chunks:
|
||||
* "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?)
|
||||
*/
|
||||
|
||||
@ -249,6 +250,7 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
|
||||
switch(ww.codec) {
|
||||
case PCM: /* common */
|
||||
VGM_LOG("1\n");
|
||||
/* 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.bits_per_sample != 16) goto fail;
|
||||
@ -268,7 +270,7 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
/* slightly modified XBOX-IMA */
|
||||
/* 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.block_align != 0x24 * ww.channels) goto fail;
|
||||
|
||||
@ -488,7 +490,10 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
|
||||
/* 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.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 */
|
||||
bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,0x100, xma2_offset, xma2_size, ww.data_size, sf);
|
||||
|
@ -68,6 +68,7 @@ void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM
|
||||
char* pos2;
|
||||
char temp[1024];
|
||||
|
||||
buf[0] = '\0';
|
||||
|
||||
/* name without path */
|
||||
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);
|
||||
|
||||
/* name without extension */
|
||||
pos2 = strrchr(buf, '.');
|
||||
if (pos2)
|
||||
pos2[0] = '\0';
|
||||
if (cfg->remove_extension) {
|
||||
pos2 = strrchr(buf, '.');
|
||||
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;
|
||||
int total_subsongs = vgmstream->num_streams;
|
||||
int target_subsong = vgmstream->stream_index;
|
||||
//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;
|
||||
|
||||
/* 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)
|
||||
target_subsong = 1;
|
||||
|
||||
/* 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)
|
||||
snprintf(temp, sizeof(temp), "%s#1~%i", buf, total_subsongs);
|
||||
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 = (total_subsongs > 0 && (!cfg || !cfg->subsong_range)) ||
|
||||
(cfg && cfg->force_title);
|
||||
show_name = (total_subsongs > 0) && (!cfg || !cfg->subsong_range);
|
||||
if (full_txtp)
|
||||
show_name = 0;
|
||||
if (cfg && cfg->force_title)
|
||||
show_name = 1;
|
||||
|
||||
if (stream_name[0] != '\0' && show_name) {
|
||||
snprintf(temp, sizeof(temp), "%s (%s)", buf, stream_name);
|
||||
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_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);
|
||||
|
||||
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) {
|
||||
|
@ -71,6 +71,7 @@ void vgmstream_set_play_forever(VGMSTREAM* vgmstream, int enabled);
|
||||
typedef struct {
|
||||
int force_title;
|
||||
int subsong_range;
|
||||
int remove_extension;
|
||||
} vgmstream_title_t;
|
||||
|
||||
/* get a simple title for plugins */
|
||||
|
@ -821,6 +821,9 @@ typedef struct {
|
||||
int fade_time_set;
|
||||
int pad_end_set;
|
||||
|
||||
/* for lack of a better place... */
|
||||
int is_txtp;
|
||||
int is_mini_txtp;
|
||||
|
||||
} 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_position; /* absolute sample where stream is */
|
||||
|
||||
} play_state_t;
|
||||
|
||||
|
||||
|
@ -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);
|
||||
WideCharToMultiByte(CP_UTF8,0, wsrc,-1, dst,dstsize, NULL, NULL);
|
||||
#else
|
||||
strcpy(dst,wsrc);
|
||||
strncpy(dst, wsrc, dstsize);
|
||||
dst[dstsize - 1] = '\0';
|
||||
#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);
|
||||
MultiByteToWideChar(CP_UTF8,0, src,-1, wdst,wdstsize);
|
||||
#else
|
||||
strcpy(wdst,src);
|
||||
strncpy(wdst, src, wdstsize);
|
||||
wdst[wdstsize - 1] = '\0';
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -891,43 +893,27 @@ static void build_extension_list(char *winamp_list, int winamp_list_size) {
|
||||
|
||||
/* unicode utils */
|
||||
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];
|
||||
//int stream_index = 0;
|
||||
char buffer[PATH_LIMIT];
|
||||
char filename_utf8[PATH_LIMIT];
|
||||
|
||||
parse_fn_string(fn, NULL, filename,PATH_LIMIT);
|
||||
//parse_fn_int(fn, wa_L("$s"), &stream_index);
|
||||
|
||||
basename = (in_char*)filename + wa_strlen(filename); /* find end */
|
||||
while (*basename != '\\' && basename >= filename) /* and find last "\" */
|
||||
basename--;
|
||||
basename++;
|
||||
wa_strcpy(dst,basename);
|
||||
wa_ichar_to_char(filename_utf8, PATH_LIMIT, filename);
|
||||
|
||||
/* infostream gets added at first with index 0, then once played it re-adds proper numbers */
|
||||
if (infostream) {
|
||||
const char* info_name = infostream->stream_name;
|
||||
int info_streams = infostream->num_streams;
|
||||
int info_subsong = infostream->stream_index;
|
||||
vgmstream_title_t tcfg = {0};
|
||||
int is_first = infostream->stream_index == 0;
|
||||
|
||||
/* show number if file has more than 1 subsong */
|
||||
if (info_streams > 1) {
|
||||
if (is_first)
|
||||
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);
|
||||
}
|
||||
tcfg.force_title = settings.force_title;
|
||||
tcfg.subsong_range = is_first;
|
||||
tcfg.remove_extension = 1;
|
||||
|
||||
/* show name if file has subsongs (implicitly shows also for TXTP) */
|
||||
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(stream_name, PATH_LIMIT, info_name);
|
||||
wa_snprintf(buffer,PATH_LIMIT, wa_L(" (%s)"), stream_name);
|
||||
wa_strcat(dst,buffer);
|
||||
}
|
||||
vgmstream_get_title(buffer, sizeof(buffer), filename_utf8, infostream, &tcfg);
|
||||
|
||||
wa_char_to_ichar(dst, dst_size, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user