mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 16:30:54 +01:00
Add TXTP commands for looping/time (#i #E #lN #fN #dN #F)
- #i (ignore loop) - #E (force end-to-end loop) - #lN (force N loops) - #fN (fade time) - #dN (fade delay) - #F (don't fade after N loops and play song end)
This commit is contained in:
parent
decc160632
commit
1810d3ced6
45
doc/TXTP.md
45
doc/TXTP.md
@ -27,6 +27,8 @@ loop_end_segment = 2 # optional, default is last
|
|||||||
# select subsong 12
|
# select subsong 12
|
||||||
bigfiles/bgm.sxd2#12 #relative paths are ok too for TXTP
|
bigfiles/bgm.sxd2#12 #relative paths are ok too for TXTP
|
||||||
|
|
||||||
|
#bigfiles/bgm.sxd2#s12 # "sN" is al alt for subsong
|
||||||
|
|
||||||
# single files loop normally by default
|
# single files loop normally by default
|
||||||
# if loop segment is defined it forces a full loop (0..num_samples)
|
# if loop segment is defined it forces a full loop (0..num_samples)
|
||||||
#loop_start_segment = 1
|
#loop_start_segment = 1
|
||||||
@ -44,6 +46,7 @@ amb_fx.sb0#121 #notice "#" works as config or comment
|
|||||||
loop_start_segment = 3
|
loop_start_segment = 3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Channel mask for channel subsongs/layers
|
### Channel mask for channel subsongs/layers
|
||||||
- __Final Fantasy XIII-2__: _music_Home_01.ps3.txtp_
|
- __Final Fantasy XIII-2__: _music_Home_01.ps3.txtp_
|
||||||
```
|
```
|
||||||
@ -59,6 +62,7 @@ music_Home.ps3.scd#c3,4
|
|||||||
# song still has 4 channels, just mutes some
|
# song still has 4 channels, just mutes some
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Multilayered songs
|
### Multilayered songs
|
||||||
|
|
||||||
TXTP "layers" play songs with channels/parts divided into files as one
|
TXTP "layers" play songs with channels/parts divided into files as one
|
||||||
@ -93,22 +97,61 @@ file1.ext#m2-3 # "FL BL FR BR" to "FL FR BL BR"
|
|||||||
file2.ext#m2-3,4-5,4-6 # ogg "FL CN FR BL BR SB" to wav "FL FR CN SB BL BR"
|
file2.ext#m2-3,4-5,4-6 # ogg "FL CN FR BL BR SB" to wav "FL FR CN SB BL BR"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Custom play settings
|
||||||
|
Those setting should override player's defaults if set (except "loop forever"). They are equivalent to some test.exe options.
|
||||||
|
|
||||||
|
- __God Hand (PS2)__: _boss2_3ningumi_ver6.txtp_ (each line is a separate TXTP)
|
||||||
|
´´´
|
||||||
|
# set number of loops
|
||||||
|
boss2_3ningumi_ver6.adx#l3
|
||||||
|
|
||||||
|
# set fade time (in seconds)
|
||||||
|
boss2_3ningumi_ver6.adx#f10.5
|
||||||
|
|
||||||
|
# set fade delay (in seconds)
|
||||||
|
boss2_3ningumi_ver6.adx#d0.5
|
||||||
|
|
||||||
|
# ignore and disable loops
|
||||||
|
boss2_3ningumi_ver6.adx#i
|
||||||
|
|
||||||
|
# don't fade out and instead play the song ending after
|
||||||
|
boss2_3ningumi_ver6.adx#F # this song has a nice stop
|
||||||
|
|
||||||
|
# force full loops from end-to-end
|
||||||
|
boss2_3ningumi_ver6.adx#E
|
||||||
|
|
||||||
|
# settings can be combined
|
||||||
|
boss2_3ningumi_ver6.adx#l2#F # 2 loops + ending
|
||||||
|
|
||||||
|
# settings can be combined
|
||||||
|
boss2_3ningumi_ver6.adx#l1.5#d1#f5
|
||||||
|
|
||||||
|
# boss2_3ningumi_ver6.adx#l1.0#F # this is equivalent to #i
|
||||||
|
´´´´
|
||||||
|
|
||||||
|
For segments and layers the first file defines looping options.
|
||||||
|
|
||||||
|
|
||||||
### Force plugin extensions
|
### Force plugin extensions
|
||||||
vgmstream supports a few common extensions that confuse plugins, like .wav/ogg/aac/opus/etc, so for them those extensions are disabled and are expected to be renamed to .lwav/logg/laac/lopus/etc. TXTP can make plugins play those disabled extensions, since it calls files directly by filename.
|
vgmstream supports a few common extensions that confuse plugins, like .wav/ogg/aac/opus/etc, so for them those extensions are disabled and are expected to be renamed to .lwav/logg/laac/lopus/etc. TXTP can make plugins play those disabled extensions, since it calls files directly by filename.
|
||||||
|
|
||||||
Combined with TXTH, this can also be used for extensions that aren't normally accepted by vgmstream.
|
Combined with TXTH, this can also be used for extensions that aren't normally accepted by vgmstream.
|
||||||
|
|
||||||
|
|
||||||
### TXTP combos
|
### TXTP combos
|
||||||
TXTP may even reference other TXTP, or files that require TXTH, for extra complex cases. Each file defined in TXTP is internally parsed like it was a completely separate file, so there is a bunch of valid ways to mix them.
|
TXTP may even reference other TXTP, or files that require TXTH, for extra complex cases. Each file defined in TXTP is internally parsed like it was a completely separate file, so there is a bunch of valid ways to mix them.
|
||||||
|
|
||||||
|
|
||||||
## Mini TXTP
|
## Mini TXTP
|
||||||
|
|
||||||
To simplify TXTP creation, if the .txtp is empty (0 bytes) its filename is used directly as a command.
|
To simplify TXTP creation, if the .txtp is empty (0 bytes) its filename is used directly as a command. Note that extension is also included (since vgmstream needs a full filename).
|
||||||
- _bgm.sxd2#12.txtp_: plays subsong 12
|
- _bgm.sxd2#12.txtp_: plays subsong 12
|
||||||
- _Ryoshima Coast 1 & 2.aix#c1,2.txtp_: channel mask
|
- _Ryoshima Coast 1 & 2.aix#c1,2.txtp_: channel mask
|
||||||
|
- _boss2_3ningumi_ver6.adx#l2#F.txtp_: loop twice then play song end file normally
|
||||||
- etc
|
- etc
|
||||||
|
|
||||||
|
|
||||||
## Other examples
|
## Other examples
|
||||||
|
|
||||||
_Join "segments" (intro+body):_
|
_Join "segments" (intro+body):_
|
||||||
|
132
src/meta/txtp.c
132
src/meta/txtp.c
@ -11,6 +11,13 @@ typedef struct {
|
|||||||
uint32_t channel_mask;
|
uint32_t channel_mask;
|
||||||
int channel_mappings_on;
|
int channel_mappings_on;
|
||||||
int channel_mappings[32];
|
int channel_mappings[32];
|
||||||
|
|
||||||
|
double config_loop_count;
|
||||||
|
double config_fade_time;
|
||||||
|
double config_fade_delay;
|
||||||
|
int config_ignore_loop;
|
||||||
|
int config_force_loop;
|
||||||
|
int config_ignore_fade;
|
||||||
} txtp_entry;
|
} txtp_entry;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -26,9 +33,10 @@ typedef struct {
|
|||||||
|
|
||||||
static txtp_header* parse_txtp(STREAMFILE* streamFile);
|
static txtp_header* parse_txtp(STREAMFILE* streamFile);
|
||||||
static void clean_txtp(txtp_header* txtp);
|
static void clean_txtp(txtp_header* txtp);
|
||||||
|
static void set_config(VGMSTREAM *vgmstream, txtp_entry *current);
|
||||||
|
|
||||||
|
|
||||||
/* TXTP - an artificial playlist-like format to play segmented files with config */
|
/* TXTP - an artificial playlist-like format to play files with segments/layers/config */
|
||||||
VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) {
|
VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) {
|
||||||
VGMSTREAM * vgmstream = NULL;
|
VGMSTREAM * vgmstream = NULL;
|
||||||
txtp_header* txtp = NULL;
|
txtp_header* txtp = NULL;
|
||||||
@ -184,6 +192,9 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* loop settings apply to the resulting vgmstream, so config based on first entry */
|
||||||
|
set_config(vgmstream, &txtp->entry[0]);
|
||||||
|
|
||||||
clean_txtp(txtp);
|
clean_txtp(txtp);
|
||||||
return vgmstream;
|
return vgmstream;
|
||||||
|
|
||||||
@ -195,13 +206,30 @@ fail:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void set_config(VGMSTREAM *vgmstream, txtp_entry *current) {
|
||||||
|
vgmstream->config_loop_count = current->config_loop_count;
|
||||||
|
vgmstream->config_fade_time = current->config_fade_time;
|
||||||
|
vgmstream->config_fade_delay = current->config_fade_delay;
|
||||||
|
vgmstream->config_ignore_loop = current->config_ignore_loop;
|
||||||
|
vgmstream->config_force_loop = current->config_force_loop;
|
||||||
|
vgmstream->config_ignore_fade = current->config_ignore_fade;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ********************************** */
|
||||||
|
|
||||||
|
static void get_double(const char * config, double *value) {
|
||||||
|
int n;
|
||||||
|
if (sscanf(config, "%lf%n", value,&n) != 1) {
|
||||||
|
*value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int add_filename(txtp_header * txtp, char *filename) {
|
static int add_filename(txtp_header * txtp, char *filename) {
|
||||||
int i;
|
int i, n;
|
||||||
uint32_t channel_mask = 0;
|
txtp_entry cfg = {0};
|
||||||
int channel_mappings_on = 0;
|
|
||||||
int channel_mappings[32] = {0};
|
|
||||||
size_t range_start, range_end;
|
size_t range_start, range_end;
|
||||||
|
const char separator = '#';
|
||||||
|
|
||||||
|
|
||||||
//;VGM_LOG("TXTP: filename=%s\n", filename);
|
//;VGM_LOG("TXTP: filename=%s\n", filename);
|
||||||
|
|
||||||
@ -209,33 +237,37 @@ static int add_filename(txtp_header * txtp, char *filename) {
|
|||||||
{
|
{
|
||||||
char *config;
|
char *config;
|
||||||
|
|
||||||
/* position in base extension */
|
/* find config start (filenames and config can contain multiple dots and #,
|
||||||
config = strrchr(filename,'.');
|
* so this may be fooled by certain patterns of . and #) */
|
||||||
if (!config) /* needed...? */
|
config = strchr(filename, '.'); /* first dot (may be a false positive) */
|
||||||
|
if (!config) /* extensionless */
|
||||||
|
config = filename;
|
||||||
|
config = strchr(config,separator); /* next should be config (hopefully right after extension) */
|
||||||
|
if (!config) /* no config */
|
||||||
config = filename;
|
config = filename;
|
||||||
|
|
||||||
range_start = 0;
|
range_start = 0;
|
||||||
range_end = 1;
|
range_end = 1;
|
||||||
do {
|
do {
|
||||||
/* get config pointer but remove config from filename */
|
/* get config pointer but remove config from filename */
|
||||||
config = strchr(config, '#');
|
config = strchr(config, separator);
|
||||||
if (!config)
|
if (!config)
|
||||||
continue;
|
continue;
|
||||||
|
//;VGM_LOG("TXTP: config=%s\n", config);
|
||||||
|
|
||||||
config[0] = '\0';
|
config[0] = '\0';
|
||||||
config++;
|
config++;
|
||||||
|
|
||||||
|
|
||||||
if (config[0] == 'c') {
|
if (config[0] == 'c') {
|
||||||
/* channel mask */
|
/* channel mask: file.ext#c1,2 = play channels 1,2 and mutes rest */
|
||||||
/* - file.ext#c1,2 = play channels 1,2 */
|
int ch;
|
||||||
int n, ch;
|
|
||||||
|
|
||||||
config++;
|
config++;
|
||||||
channel_mask = 0;
|
cfg.channel_mask = 0;
|
||||||
while (sscanf(config, "%d%n", &ch,&n) == 1) {
|
while (sscanf(config, "%d%n", &ch,&n) == 1) {
|
||||||
if (ch > 0 && ch <= 32)
|
if (ch > 0 && ch <= 32)
|
||||||
channel_mask |= (1 << (ch-1));
|
cfg.channel_mask |= (1 << (ch-1));
|
||||||
|
|
||||||
config += n;
|
config += n;
|
||||||
if (config[0]== ',' || config[0]== '-') /* "-" for PowerShell, may have problems with "," */
|
if (config[0]== ',' || config[0]== '-') /* "-" for PowerShell, may have problems with "," */
|
||||||
@ -245,12 +277,11 @@ static int add_filename(txtp_header * txtp, char *filename) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (config[0] == 'm') {
|
else if (config[0] == 'm') {
|
||||||
/* channel mappings */
|
/* channel mappings: file.ext#m1-2,3-4 = swaps channels 1<>2 and 3<>4 */
|
||||||
/* - file.ext#m1-2,3-4 = swaps channels 1<>2 and 3<>4 */
|
int ch_from = 0, ch_to = 0;
|
||||||
int n, ch_from = 0, ch_to = 0;
|
|
||||||
|
|
||||||
config++;
|
config++;
|
||||||
channel_mappings_on = 1;
|
cfg.channel_mappings_on = 1;
|
||||||
|
|
||||||
while (config[0] != '\0') {
|
while (config[0] != '\0') {
|
||||||
if (sscanf(config, "%d%n", &ch_from, &n) != 1)
|
if (sscanf(config, "%d%n", &ch_from, &n) != 1)
|
||||||
@ -270,21 +301,16 @@ static int add_filename(txtp_header * txtp, char *filename) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
if (ch_from > 0 && ch_from <= 32 && ch_to > 0 && ch_to <= 32) {
|
if (ch_from > 0 && ch_from <= 32 && ch_to > 0 && ch_to <= 32) {
|
||||||
channel_mappings[ch_from-1] = ch_to-1;
|
cfg.channel_mappings[ch_from-1] = ch_to-1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (config[0] == 's' || (config[0] >= '0' && config[0] <= '9')) {
|
else if (config[0] == 's' || (config[0] >= '0' && config[0] <= '9')) {
|
||||||
/* subsongs */
|
/* subsongs: file.ext#s2 = play subsong 2, file.ext#2~10 = play subsong range */
|
||||||
/* - file.ext#2 = play subsong 2 */
|
|
||||||
/* - file.ext#s3 = play subsong 3 */
|
|
||||||
/* - file.ext#2~10 = play subsong range */
|
|
||||||
|
|
||||||
int subsong_start = 0, subsong_end = 0;
|
int subsong_start = 0, subsong_end = 0;
|
||||||
|
|
||||||
if (config[0]== 's')
|
if (config[0]== 's')
|
||||||
config++;
|
config++;
|
||||||
|
|
||||||
if (sscanf(config, "%d~%d", &subsong_start, &subsong_end) == 2) {
|
if (sscanf(config, "%d~%d", &subsong_start, &subsong_end) == 2) {
|
||||||
if (subsong_start > 0 && subsong_end > 0) {
|
if (subsong_start > 0 && subsong_end > 0) {
|
||||||
range_start = subsong_start-1;
|
range_start = subsong_start-1;
|
||||||
@ -301,9 +327,36 @@ static int add_filename(txtp_header * txtp, char *filename) {
|
|||||||
config = NULL; /* wrong config, ignore */
|
config = NULL; /* wrong config, ignore */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (config[0] == 'i') {
|
||||||
|
config++;
|
||||||
|
cfg.config_ignore_loop = 1;
|
||||||
|
}
|
||||||
|
else if (config[0] == 'E') {
|
||||||
|
config++;
|
||||||
|
cfg.config_force_loop = 1;
|
||||||
|
}
|
||||||
|
else if (config[0] == 'F') {
|
||||||
|
config++;
|
||||||
|
cfg.config_ignore_fade = 1;
|
||||||
|
}
|
||||||
|
else if (config[0] == 'l') {
|
||||||
|
config++;
|
||||||
|
get_double(config, &cfg.config_loop_count);
|
||||||
|
}
|
||||||
|
else if (config[0] == 'f') {
|
||||||
|
config++;
|
||||||
|
get_double(config, &cfg.config_fade_time);
|
||||||
|
}
|
||||||
|
else if (config[0] == 'd') {
|
||||||
|
config++;
|
||||||
|
get_double(config, &cfg.config_fade_delay);
|
||||||
|
}
|
||||||
|
else if (config[0] == ' ') {
|
||||||
|
continue; /* likely a comment, find next # */
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
VGM_LOG("TXTP: unknown command\n");
|
//;VGM_LOG("TXTP: unknown command '%c'\n", config[0]);
|
||||||
goto fail;
|
break; /* also possibly a comment too */
|
||||||
}
|
}
|
||||||
|
|
||||||
} while (config != NULL);
|
} while (config != NULL);
|
||||||
@ -327,6 +380,8 @@ static int add_filename(txtp_header * txtp, char *filename) {
|
|||||||
|
|
||||||
/* add filesnames */
|
/* add filesnames */
|
||||||
for (i = range_start; i < range_end; i++){
|
for (i = range_start; i < range_end; i++){
|
||||||
|
txtp_entry *current;
|
||||||
|
|
||||||
/* resize in steps if not enough */
|
/* resize in steps if not enough */
|
||||||
if (txtp->entry_count+1 > txtp->entry_max) {
|
if (txtp->entry_count+1 > txtp->entry_max) {
|
||||||
txtp_entry *temp_entry;
|
txtp_entry *temp_entry;
|
||||||
@ -338,20 +393,29 @@ static int add_filename(txtp_header * txtp, char *filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* new entry */
|
/* new entry */
|
||||||
memset(&txtp->entry[txtp->entry_count],0, sizeof(txtp_entry));
|
current = &txtp->entry[txtp->entry_count];
|
||||||
|
memset(current,0, sizeof(txtp_entry));
|
||||||
|
strcpy(current->filename, filename);
|
||||||
|
|
||||||
strcpy(txtp->entry[txtp->entry_count].filename, filename);
|
current->subsong = (i+1);
|
||||||
|
|
||||||
txtp->entry[txtp->entry_count].channel_mask = channel_mask;
|
current->channel_mask = cfg.channel_mask;
|
||||||
|
|
||||||
if (channel_mappings_on) {
|
if (cfg.channel_mappings_on) {
|
||||||
int ch;
|
int ch;
|
||||||
txtp->entry[txtp->entry_count].channel_mappings_on = channel_mappings_on;
|
current->channel_mappings_on = cfg.channel_mappings_on;
|
||||||
for (ch = 0; ch < 32; ch++) {
|
for (ch = 0; ch < 32; ch++) {
|
||||||
txtp->entry[txtp->entry_count].channel_mappings[ch] = channel_mappings[ch];
|
current->channel_mappings[ch] = cfg.channel_mappings[ch];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
txtp->entry[txtp->entry_count].subsong = (i+1);
|
|
||||||
|
current->config_loop_count = cfg.config_loop_count;
|
||||||
|
current->config_fade_time = cfg.config_fade_time;
|
||||||
|
current->config_fade_delay = cfg.config_fade_delay;
|
||||||
|
current->config_ignore_loop = cfg.config_ignore_loop;
|
||||||
|
current->config_force_loop = cfg.config_force_loop;
|
||||||
|
current->config_ignore_fade = cfg.config_ignore_fade;
|
||||||
|
|
||||||
txtp->entry_count++;
|
txtp->entry_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -897,20 +897,19 @@ void close_vgmstream(VGMSTREAM * vgmstream) {
|
|||||||
/* calculate samples based on player's config */
|
/* calculate samples based on player's config */
|
||||||
int32_t get_vgmstream_play_samples(double looptimes, double fadeseconds, double fadedelayseconds, VGMSTREAM * vgmstream) {
|
int32_t get_vgmstream_play_samples(double looptimes, double fadeseconds, double fadedelayseconds, VGMSTREAM * vgmstream) {
|
||||||
if (vgmstream->loop_flag) {
|
if (vgmstream->loop_flag) {
|
||||||
if (fadeseconds < 0) { /* a bit hack-y to avoid signature change */
|
if (vgmstream->loop_target == (int)looptimes) { /* set externally, as this function is info-only */
|
||||||
/* Continue playing the file normally after looping, instead of fading.
|
/* Continue playing the file normally after looping, instead of fading.
|
||||||
* Most files cut abruply after the loop, but some do have proper endings.
|
* Most files cut abruply after the loop, but some do have proper endings.
|
||||||
* With looptimes = 1 this option should give the same output vs loop disabled */
|
* With looptimes = 1 this option should give the same output vs loop disabled */
|
||||||
int loop_count = (int)looptimes; /* no half loops allowed */
|
int loop_count = (int)looptimes; /* no half loops allowed */
|
||||||
//vgmstream->loop_target = loop_count; /* handled externally, as this is into-only */
|
|
||||||
return vgmstream->loop_start_sample
|
return vgmstream->loop_start_sample
|
||||||
+ (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count
|
+ (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count
|
||||||
+ (vgmstream->num_samples - vgmstream->loop_end_sample);
|
+ (vgmstream->num_samples - vgmstream->loop_end_sample);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return (int32_t)(vgmstream->loop_start_sample
|
return vgmstream->loop_start_sample
|
||||||
+ (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * looptimes
|
+ (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * looptimes
|
||||||
+ (fadedelayseconds + fadeseconds) * vgmstream->sample_rate);
|
+ (fadedelayseconds + fadeseconds) * vgmstream->sample_rate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -952,6 +951,22 @@ void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sa
|
|||||||
/* segmented layout only works (ATM) with exact/header loop, full loop or no loop */
|
/* segmented layout only works (ATM) with exact/header loop, full loop or no loop */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target) {
|
||||||
|
if (!vgmstream) return;
|
||||||
|
|
||||||
|
vgmstream->loop_target = loop_target; /* loop count must be rounded (int) as otherwise target is meaningless */
|
||||||
|
|
||||||
|
/* propagate changes to layouts that need them */
|
||||||
|
if (vgmstream->layout_type == layout_layered) {
|
||||||
|
int i;
|
||||||
|
layered_layout_data *data = vgmstream->layout_data;
|
||||||
|
for (i = 0; i < data->layer_count; i++) {
|
||||||
|
vgmstream_set_loop_target(data->layers[i], loop_target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Decode data into sample buffer */
|
/* Decode data into sample buffer */
|
||||||
void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
|
void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
|
||||||
switch (vgmstream->layout_type) {
|
switch (vgmstream->layout_type) {
|
||||||
|
@ -762,25 +762,12 @@ typedef struct {
|
|||||||
/* main vgmstream info */
|
/* main vgmstream info */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
/* basics */
|
/* basics */
|
||||||
int32_t num_samples; /* the actual number of samples in this stream */
|
int32_t num_samples; /* the actual max number of samples */
|
||||||
int32_t sample_rate; /* sample rate in Hz */
|
int32_t sample_rate; /* sample rate in Hz */
|
||||||
int channels; /* number of channels */
|
int channels; /* number of channels */
|
||||||
coding_t coding_type; /* type of encoding */
|
coding_t coding_type; /* type of encoding */
|
||||||
layout_t layout_type; /* type of layout for data */
|
layout_t layout_type; /* type of layout */
|
||||||
meta_t meta_type; /* how we know the metadata */
|
meta_t meta_type; /* type of metadata */
|
||||||
|
|
||||||
/* subsongs */
|
|
||||||
int num_streams; /* for multi-stream formats (0=not set/one stream, 1=one stream) */
|
|
||||||
int stream_index; /* selected stream (also 1-based) */
|
|
||||||
char stream_name[STREAM_NAME_SIZE]; /* name of the current stream (info), if the file stores it and it's filled */
|
|
||||||
size_t stream_size; /* info to properly calculate bitrate in case of subsongs */
|
|
||||||
/* config */
|
|
||||||
int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */
|
|
||||||
uint32_t channel_mask; /* to silence crossfading subsongs/layers */
|
|
||||||
int channel_mappings_on; /* channel mappings are active */
|
|
||||||
int channel_mappings[32]; /* swap channel "i" with "[i]" */
|
|
||||||
double config_loops; /* appropriate number of loops (config request for players) */
|
|
||||||
int config_nofade; /* continue normally after target loop count (config request for players) */
|
|
||||||
|
|
||||||
/* looping */
|
/* looping */
|
||||||
int loop_flag; /* is this stream looped? */
|
int loop_flag; /* is this stream looped? */
|
||||||
@ -791,6 +778,27 @@ typedef struct {
|
|||||||
size_t interleave_block_size; /* interleave, or block/frame size (depending on the codec) */
|
size_t interleave_block_size; /* interleave, or block/frame size (depending on the codec) */
|
||||||
size_t interleave_last_block_size; /* smaller interleave for last block */
|
size_t interleave_last_block_size; /* smaller interleave for last block */
|
||||||
|
|
||||||
|
/* subsongs */
|
||||||
|
int num_streams; /* for multi-stream formats (0=not set/one stream, 1=one stream) */
|
||||||
|
int stream_index; /* selected subsong (also 1-based) */
|
||||||
|
size_t stream_size; /* info to properly calculate bitrate in case of subsongs */
|
||||||
|
char stream_name[STREAM_NAME_SIZE]; /* name of the current stream (info), if the file stores it and it's filled */
|
||||||
|
|
||||||
|
/* config */
|
||||||
|
int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */
|
||||||
|
uint32_t channel_mask; /* to silence crossfading subsongs/layers */
|
||||||
|
int channel_mappings_on; /* channel mappings are active */
|
||||||
|
int channel_mappings[32]; /* swap channel "i" with "[i]" */
|
||||||
|
/* config requests, players must read and honor these values */
|
||||||
|
/* (ideally internally would work as a player, but for now player must do it manually) */
|
||||||
|
double config_loop_count;
|
||||||
|
double config_fade_time;
|
||||||
|
double config_fade_delay;
|
||||||
|
int config_ignore_loop;
|
||||||
|
int config_force_loop;
|
||||||
|
int config_ignore_fade;
|
||||||
|
|
||||||
|
|
||||||
/* channel state */
|
/* channel state */
|
||||||
VGMSTREAMCHANNEL * ch; /* pointer to array of channels */
|
VGMSTREAMCHANNEL * ch; /* pointer to array of channels */
|
||||||
VGMSTREAMCHANNEL * start_ch; /* copies of channel status as they were at the beginning of the stream */
|
VGMSTREAMCHANNEL * start_ch; /* copies of channel status as they were at the beginning of the stream */
|
||||||
@ -814,13 +822,12 @@ typedef struct {
|
|||||||
|
|
||||||
/* loop state */
|
/* loop state */
|
||||||
int hit_loop; /* have we seen the loop yet? */
|
int hit_loop; /* have we seen the loop yet? */
|
||||||
/* counters for "loop + play end of the stream instead of fading" (not used/needed otherwise) */
|
int loop_count; /* counter of complete loops (1=looped once) */
|
||||||
int loop_count; /* number of complete loops (1=looped once) */
|
int loop_target; /* max loops before continuing with the stream end (loops forever if not set) */
|
||||||
int loop_target; /* max loops before continuing with the stream end */
|
|
||||||
|
|
||||||
/* decoder specific */
|
/* decoder specific */
|
||||||
int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */
|
int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */
|
||||||
int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to the user */
|
int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to the codec */
|
||||||
|
|
||||||
int32_t ws_output_size; /* WS ADPCM: output bytes for this block */
|
int32_t ws_output_size; /* WS ADPCM: output bytes for this block */
|
||||||
|
|
||||||
@ -1286,13 +1293,17 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length);
|
|||||||
/* Return the average bitrate in bps of all unique files contained within this stream. */
|
/* Return the average bitrate in bps of all unique files contained within this stream. */
|
||||||
int get_vgmstream_average_bitrate(VGMSTREAM * vgmstream);
|
int get_vgmstream_average_bitrate(VGMSTREAM * vgmstream);
|
||||||
|
|
||||||
/* List supported formats and return elements in the list, for plugins that need to know. */
|
/* List supported formats and return elements in the list, for plugins that need to know.
|
||||||
|
* The list disables some common formats that may conflict (.wav, .ogg, etc). */
|
||||||
const char ** vgmstream_get_formats(size_t * size);
|
const char ** vgmstream_get_formats(size_t * size);
|
||||||
|
|
||||||
/* Force enable/disable internal looping. Should be done before playing anything,
|
/* Force enable/disable internal looping. Should be done before playing anything,
|
||||||
* and not all codecs support arbitrary loop values ATM. */
|
* and not all codecs support arbitrary loop values ATM. */
|
||||||
void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sample, int loop_end_sample);
|
void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sample, int loop_end_sample);
|
||||||
|
|
||||||
|
/* Set number of max loops to do, then play up to stream end (for songs with proper endings) */
|
||||||
|
void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target);
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------*/
|
/* -------------------------------------------------------------------------*/
|
||||||
/* vgmstream "private" API */
|
/* vgmstream "private" API */
|
||||||
/* -------------------------------------------------------------------------*/
|
/* -------------------------------------------------------------------------*/
|
||||||
|
Loading…
Reference in New Issue
Block a user