Add subsongs and !tags.m3u support for Audacious

This commit is contained in:
bnnm 2020-09-12 15:03:22 +02:00
parent a76abbcf17
commit bc80041925
7 changed files with 276 additions and 77 deletions

View File

@ -54,7 +54,7 @@ Converts playable files to wav. Typical usage would be:
- `test.exe -o happy.wav happy.adx` to decode `happy.adx` to `happy.wav`.
If command-line isn't your thing you can also drag and drop files to the
executable to decode them as (filename).wav
executable to decode them as `(filename).wav`.
There are multiple options that alter how the file is converted, for example:
- `test.exe -m -o file.wav file.adx`: print info but don't decode
@ -83,27 +83,30 @@ For example `test.exe -s 2 -o ?04s_?n.wav file.fsb` could generate `0002_song1.w
### in_vgmstream
*Installation*: drop the ```in_vgmstream.dll``` in your Winamp plugins directory,
*Installation*: drop the `in_vgmstream.dll` in your Winamp plugins directory,
and follow the above instructions for installing the other files needed.
Once installed supported files should be playable.
### xmp-vgmstream
*Installation*: drop the ```xmp-vgmstream.dll``` in your XMPlay plugins directory,
*Installation*: drop the `xmp-vgmstream.dll` in your XMPlay plugins directory,
and follow the above instructions for installing the other files needed.
Note that this has less features compared to in_vgmstream and has no configuration.
Since XMPlay supports Winamp plugins you may also use ```in_vgmstream.dll``` instead.
Since XMPlay supports Winamp plugins you may also use `in_vgmstream.dll` instead.
Because the XMPlay MP3 decoder incorrectly tries to play some vgmstream exts,
you need to manually fix it by going to **options > plugins > input > vgmstream**
and in the "priority filetypes" put: `ahx,asf,awc,ckd,fsb,genh,msf,p3d,rak,scd,txth,xvag`
XMPlay cannot support subsongs due to player limitations, try using *TXTP* instead
(explained below).
### foo_input_vgmstream
*Installation*: every file should be installed automatically by the `.fb2k-component`
bundle.
A known quirk is that when loop options or tags change, playlist won't refresh
A known quirk is that when loop options or tags change, playlist info won't refresh
automatically. You need to manually refresh it by selecting songs and doing
**shift + right click > Tagging > Reload info from file(s)**.
@ -149,6 +152,11 @@ not meant to be extracted (no simple separation). Some plugins are able to "unpa
those files automatically into the playlist. For others without support, you can create
multiple .txtp (explained below) to select one of the subsongs (like `bgm.sxd#10.txtp`).
You can use this python script to autogenerate one `.txtp` per subsong:
https://github.com/losnoco/vgmstream/tree/master/cli/txtp_maker.py
Put in the same dir as test.exe/vgmstream_cli, then to drag-and-drop files with subsongs
to `txtp_maker.py`.
### Renamed files
A few extensions that vgmstream supports clash with common ones. Since players
like foobar or Winamp don't react well to that, they may be renamed to make
@ -180,7 +188,7 @@ internal loop info, or apply subtle fixes, but is also limited in some ways
may work as a last resort to make a file playable.
Some plugins have options that allow any extension (common or unknown) to be
played, making renaming is unnecessary (may need to adjust plugin priority in
played, making renaming unnecessary (may need to adjust plugin priority in
player's options).
Also be aware that some plugins can tell the player they handle some extension,
@ -269,8 +277,8 @@ complex and non-standard ways, like playing multiple small songs as a single
one, or using some channels as a section of the song. For those cases we
can use create a *TXTP* file.
**TXTP**: a text player configurator named `(name).txtp`. Text inside can
contain a list of filenames to play as one (ex. `intro.vag(line)loop.vag`),
**TXTP**: text files with player configuration, named `(name).txtp`. Text inside
can contain a list of filenames to play as one (ex. `intro.vag(line)loop.vag`),
list of separate channel files to join as a single multichannel file,
subsong index (ex. `bgm.sxd#10`), per-file configurations like number of
loops, remove unneeded channels, and many other features.
@ -294,7 +302,7 @@ However other plugins may set themselves higher, stealing formats instead.
If current Audacious version doesn't let to change plugin priority you may
need to disable some plugins (requires restart) or set priority on compile
time. Particularly, mpg123 plugin may steal formats that aren't even MP3,
making impossible for vgmstream to play it properly.
making impossible for vgmstream to play them properly.
### Channel issues
Some games layer a huge number of channels, that are disabled or downmixed
@ -303,7 +311,8 @@ foobar can only play up to 8 channels, and Winamp depends on your sound
card). For those files you can set the "downmix" option in vgmstream, that
can reduce the number of channels to a playable amount. Note that this type
of downmixing is very generic, not meant to be used when converting to other
formats.
formats (channels are re-assigned and volumes modified in simplistic ways,
since it can't guess how the file should be properly adjusted).
You can also choose which channels to play using *TXTP*. For example, create
a file named `song.adx#C1,2.txtp` to play only channels 1 and 2 from `song.adx`.
@ -327,10 +336,10 @@ filename1
# %LOCAL_TAG text (applies to next track only)
filename2
```
Accepted tags depend on the player (foobar: any; winamp: see ATF config),
typically *ALBUM/ARTIST/TITLE/DISC/TRACK/COMPOSER/etc*, lower or uppercase,
separated by one or multiple spaces. Repeated tags overwrite previous (ex.-
may define *@COMPOSER* multiple times for "sections"). It only reads up to
Accepted tags depend on the player (foobar: any; winamp: see ATF config, Audacious:
few standard ones), typically *ALBUM/ARTIST/TITLE/DISC/TRACK/COMPOSER/etc*, lower
or uppercase, separated by one or multiple spaces. Repeated tags overwrite previous
(ex.- may define *@COMPOSER* multiple times for "sections"). It only reads up to
current *filename* though, so any *@TAG* below would be ignored.
Playlist title formatting should follow player's config. ASCII or UTF-8 tags work.
@ -359,6 +368,8 @@ filename1
```
As a side effect if text has @/% inside you also need them: `# @ALBUMARTIST@ Tom-H@ck`
For interoperability with other plugins, consider using only common tags without spaces.
### ReplayGain
foobar2000/Winamp can apply the following replaygain tags (if ReplayGain is
enabled in preferences):
@ -414,18 +425,22 @@ If your player isn't picking tags make sure vgmstream is detecting the song
(as other plugins can steal its extensions, see above), .m3u is properly
named and that filenames inside match the song filename. For Winamp you need
to make sure *options > titles > advanced title formatting* checkbox is set and
the format defined. For foobar2000 don't forget you need to force refresh when
tags change (for reasons outside vgmstream's control):
**select songs > shift + right click > Tagging > Reload info from file(s)**.
the format defined.
Currently there is no tool to aid in the creation of there m3u, but you can create
a base m3u and edit as a text file.
When tags change behavior varies depending on player:
- *Winamp*: should refresh tags when file is played again.
- *foobar2000*: needs to force refresh (for reasons outside vgmstream's control)
- **select songs > shift + right click > Tagging > Reload info from file(s)**.
- *Audacious*: files need to be readded to the playlist
Currently there is no tool to aid in the creation of these tags, but you can create
a base .m3u and edit as a text file.
vgmstream's "m3u tagging" is meant to be simple to make and share (just a text
file), easier to support in multiple players (rather than needing a custom plugin),
having OST-like ordering in the M3U, and be flexible enough to have commands.
If you are not satisfied with vgmstream's tagging format, foobar2000 has other
plugins (with write support) that may be of use:
allow OST-like ordering but also combinable with other .m3u, and be flexible enough
to have commands. If you are not satisfied with vgmstream's tagging format,
foobar2000 has other plugins (with write support) that may be of use:
- m-TAGS: http://www.m-tags.org/
- foo_external_tags: https://foobar.hyv.fi/?view=foo_external_tags
@ -484,7 +499,7 @@ are used in few games.
- Nintendo AFC ADPCM
- ITU-T G.721
- CD-ROM XA ADPCM
- Sony PSX ADPCM a.k.a VAG (standard, badflags, configurable, Pivotal)
- Sony PSX ADPCM a.k.a VAG (standard, badflags, configurable, extended)
- Sony HEVAG
- Electronic Arts EA-XA (stereo, mono, Maxis)
- Electronic Arts EA-XAS (v0, v1)
@ -537,10 +552,17 @@ are used in few games.
- FLAC
- Others
Sometimes standard codecs come in non-standard layouts that aren't normally
supported by other players (like multiple `.ogg` or `.mp3` files chunked and
interleaved together in custom ways).
Some codecs are not fully correct compared to the games due to minor bugs, but
in most cases it isn't audible, and general accuracy is high, with emphasis in
proper support of encoder delay, accurate sample counts and seeking that other
plugins may lack.
Note that vgmstream doesn't (can't) reproduce in-game music 1:1, as internal
resampling, filters, volume, etc, are not replicated. Some codecs are not
fully accurate compared to the games due to minor bugs, but in most cases
it isn't audible.
resampling, filters, volume, etc, are not replicated.
## Supported file types

View File

@ -36,6 +36,8 @@ extern "C" {
/*EXPORT*/ VgmstreamPlugin aud_plugin_instance;
audacious_settings_t settings;
static const char* tagfile_name = "!tags.m3u";
/* Audacious will first send the file to a plugin based on this static extension list. If none
* accepts it'll try again all plugins, ordered by priority, until one accepts the file. Problem is,
* mpg123 plugin has higher priority and tendency to accept files that aren't even MP3. To fix this
@ -56,6 +58,7 @@ const char *const VgmstreamPlugin::defaults[] = {
"downmix_channels", "2",
"exts_unknown_on", "FALSE",
"exts_common_on", "FALSE",
"tagfile_disable", "FALSE",
NULL
};
@ -81,10 +84,11 @@ const PreferencesWidget VgmstreamPlugin::widgets[] = {
WidgetSpin(N_("Fade length:"), WidgetFloat(settings.fade_time), {0, 60, 0.1}),
WidgetSpin(N_("Fade delay:"), WidgetFloat(settings.fade_delay), {0, 60, 0.1}),
WidgetSpin(N_("Downmix:"), WidgetInt(settings.downmix_channels), {0, 8, 1}),
WidgetCheck(N_("Enable unknown exts"), WidgetBool(settings.exts_unknown_on)),
WidgetCheck(N_("Enable unknown exts:"), WidgetBool(settings.exts_unknown_on)),
// Audacious 3.6 will only match one plugin so this option has no actual use
// (ex. a fake .flac only gets to the FLAC plugin and never to vgmstream, even on error)
//WidgetCheck(N_("Enable common exts"), WidgetBool(settings.exts_common_on)),
WidgetCheck(N_("Disable tagfile:"), WidgetBool(settings.tagfile_disable)),
};
void vgmstream_settings_load() {
@ -143,18 +147,25 @@ void VgmstreamPlugin::cleanup() {
vgmstream_settings_save();
}
#if 0
static int get_filename_subtune(const char* filename) {
static int get_basename_subtune(const char* filename, char* buf, int buf_len, int* p_subtune) {
int subtune;
int pos = strstr("?"))
if (pos <= 0)
return -1;
if (sscanf(filename + pos, "%i", &subtune) != 1)
return -1;
return subtune;
const char* pos = strrchr(filename, '?');
if (!pos)
return 0;
if (sscanf(pos, "?%i", &subtune) != 1)
return 0;
if (p_subtune)
*p_subtune = subtune;
strncpy(buf, filename, buf_len);
char* pos2 = strrchr(buf, '?');
if (pos2) //removes '?'
pos2[0] = '\0';
return 1;
}
#endif
static void apply_config(VGMSTREAM* vgmstream, audacious_settings_t* settings) {
vgmstream_cfg_t vcfg = {0};
@ -173,41 +184,41 @@ static void apply_config(VGMSTREAM* vgmstream, audacious_settings_t* settings) {
static bool read_info(const char* filename, Tuple & tuple) {
AUDINFO("read file=%s\n", filename);
#if 0
//todo subsongs:
// in theory just set FlagSubtunes in plugin.h, and set_subtunes below
// Audacious will call "play" and this(?) with filename?N where N=subtune
// and you just load subtune N, but I can't get it working
int subtune;
string basename;
int subtune = get_filename_subtune(basename, &subtune);
//must use basename to open streamfile
#endif
// Audacious first calls this as a regular file (use_subtune is 0). If file has subsongs,
// you need to detect and call set_subtunes below and return. Then Audacious will call again
// this and "play" with "filename?N" (where N=subtune, 1=first), that must be detected and handled
// (see plugin.h)
char basename[PATH_LIMIT]; //filename without '?'
int subtune = 0;
int use_subtune = get_basename_subtune(filename, basename, sizeof(basename), &subtune);
STREAMFILE* sf = open_vfs(filename);
STREAMFILE* sf = open_vfs(use_subtune ? basename : filename);
if (!sf) return false;
if (use_subtune)
sf->stream_index = subtune;
VGMSTREAM* infostream = init_vgmstream_from_STREAMFILE(sf);
if (!infostream) {
close_streamfile(sf);
return false;
}
#if 0
int total_subtunes = infostream->num_streams;
//somehow max was changed to short in recent versions, though
// some formats can exceed this
// int was changed to short in some version, though vgmstream formats can exceed it
if (total_subtunes > 32767)
total_subtunes = 32767;
if (infostream->num_streams > 1 && subtune <= 0) {
// format has subsongs but Audacious didn't ask for subsong yet
if (total_subtunes >= 1 && !use_subtune) {
//set nullptr to leave subsong index linear (must add +1 to subtune)
tuple.set_subtunes(total_subtunes, nullptr);
return true; //must return?
}
sf->stream_index = (subtune + 1);
#endif
close_streamfile(sf);
close_vgmstream(infostream);
return true;
}
apply_config(infostream, &settings);
@ -229,13 +240,78 @@ static bool read_info(const char* filename, Tuple & tuple) {
//todo here we could call describe_vgmstream() and get substring to add tags and stuff
tuple.set_str(Tuple::Codec, "vgmstream codec");
//tuple.set_int(Tuple::Subtune, subtune);
//tuple.set_int(Tuple::NumSubtunes, subtune); //done un set_subtunes
if (use_subtune) {
tuple.set_int(Tuple::Subtune, subtune);
tuple.set_int(Tuple::NumSubtunes, infostream->num_streams);
char title[1024];
vgmstream_get_title(title, sizeof(title), basename, infostream, NULL);
tuple.set_str(Tuple::Title, title); //may be overwritten by tags
}
// this function is only called when files are added to playlist,
// so to reload tags files need to readded
if (!settings.tagfile_disable) {
//todo improve string functions
char tagfile_path[PATH_LIMIT];
strcpy(tagfile_path, filename);
char *path = strrchr(tagfile_path,'/');
if (path != NULL) {
path[1] = '\0'; /* includes "/", remove after that from tagfile_path */
strcat(tagfile_path,tagfile_name);
}
else { /* ??? */
strcpy(tagfile_path,tagfile_name);
}
STREAMFILE* sf_tags = open_vfs(tagfile_path);
if (sf_tags != NULL) {
VGMSTREAM_TAGS* tags;
const char *tag_key, *tag_val;
tags = vgmstream_tags_init(&tag_key, &tag_val);
vgmstream_tags_reset(tags, filename);
while (vgmstream_tags_next_tag(tags, sf_tags)) {
// see tuple.h (ugly but other plugins do it like this)
if (strcasecmp(tag_key, "ARTIST") == 0)
tuple.set_str(Tuple::Artist, tag_val);
else if (strcasecmp(tag_key, "ALBUMARTIST") == 0)
tuple.set_str(Tuple::AlbumArtist, tag_val);
else if (strcasecmp(tag_key, "TITLE") == 0)
tuple.set_str(Tuple::Title, tag_val);
else if (strcasecmp(tag_key, "ALBUM") == 0)
tuple.set_str(Tuple::Album, tag_val);
else if (strcasecmp(tag_key, "PERFORMER") == 0)
tuple.set_str(Tuple::Performer, tag_val);
else if (strcasecmp(tag_key, "COMPOSER") == 0)
tuple.set_str(Tuple::Composer, tag_val);
else if (strcasecmp(tag_key, "COMMENT") == 0)
tuple.set_str(Tuple::Comment, tag_val);
else if (strcasecmp(tag_key, "GENRE") == 0)
tuple.set_str(Tuple::Genre, tag_val);
else if (strcasecmp(tag_key, "TRACK") == 0)
tuple.set_int(Tuple::Track, atoi(tag_val));
else if (strcasecmp(tag_key, "YEAR") == 0)
tuple.set_int(Tuple::Year, atoi (tag_val));
#if defined(_AUD_PLUGIN_VERSION) && _AUD_PLUGIN_VERSION >= 48 // Audacious 3.8+
else if (strcasecmp(tag_key, "REPLAYGAIN_TRACK_GAIN") == 0)
tuple.set_gain(Tuple::TrackGain, Tuple::GainDivisor, tag_val);
else if (strcasecmp(tag_key, "REPLAYGAIN_TRACK_PEAK") == 0)
tuple.set_gain(Tuple::TrackPeak, Tuple::PeakDivisor, tag_val);
else if (strcasecmp(tag_key, "REPLAYGAIN_ALBUM_GAIN") == 0)
tuple.set_gain(Tuple::AlbumGain, Tuple::GainDivisor, tag_val);
else if (strcasecmp(tag_key, "REPLAYGAIN_ALBUM_PEAK") == 0)
tuple.set_gain(Tuple::AlbumPeak, Tuple::PeakDivisor, tag_val);
#endif
}
vgmstream_tags_close(tags);
close_streamfile(sf_tags);
}
}
//todo tags (see tuple.h)
//tuple.set_int (Tuple::Track, ...);
//tuple.set_str (Tuple::Artist, ...);
//tuple.set_str (Tuple::Album, ...);
close_streamfile(sf);
close_vgmstream(infostream);
@ -271,12 +347,20 @@ static void do_seek(VGMSTREAM* vgmstream, int seek_ms, int& current_sample_pos)
bool VgmstreamPlugin::play(const char * filename, VFSFile & file) {
AUDINFO("play file=%s\n", filename);
STREAMFILE* sf = open_vfs(filename);
//handle subsongs (see read_info)
char basename[PATH_LIMIT]; //filename without '?'
int subtune = 0;
int use_subtune = get_basename_subtune(filename, basename, sizeof(basename), &subtune);
STREAMFILE* sf = open_vfs(use_subtune ? basename : filename);
if (!sf) {
AUDERR("failed opening file %s\n", filename);
return false;
}
if (use_subtune)
sf->stream_index = subtune;
VGMSTREAM* vgmstream = init_vgmstream_from_STREAMFILE(sf);
close_streamfile(sf);

View File

@ -29,7 +29,7 @@ public:
};
constexpr VgmstreamPlugin() : InputPlugin (info,
InputInfo() //InputInfo(FlagSubtunes) // allow subsongs
InputInfo(FlagSubtunes) // allow subsongs
.with_priority(AUDACIOUS_VGMSTREAM_PRIORITY) // where 0=highest, 10=lowest (older) or 5 (newer)
.with_exts(exts)) {} // priority exts (accepted exts are still validated at runtime)
@ -52,6 +52,7 @@ typedef struct {
int downmix_channels;
bool exts_unknown_on;
bool exts_common_on;
bool tagfile_disable;
} audacious_settings_t;
extern audacious_settings_t settings;

View File

@ -3,7 +3,9 @@
#include <libaudcore/plugin.h>
extern "C" {
#include "../src/vgmstream.h"
}
#include "plugin.h"
#include "vfs.h"
@ -19,7 +21,7 @@ static STREAMFILE *open_vfs_by_VFSFILE(VFSFile *file, const char *path);
static size_t read_vfs(VFS_STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length) {
size_t bytes_read;
if (!dest || length <= 0 || offset < 0)
if (/*!streamfile->vfsFile ||*/ !dest || length <= 0 || offset < 0)
return 0;
// if the offsets don't match, then we need to perform a seek
@ -36,11 +38,14 @@ static size_t read_vfs(VFS_STREAMFILE *streamfile, uint8_t *dest, off_t offset,
}
static void close_vfs(VFS_STREAMFILE *streamfile) {
//if (streamfile->vfsFile)
delete streamfile->vfsFile; //fcloses the internal file too
free(streamfile);
}
static size_t get_size_vfs(VFS_STREAMFILE *streamfile) {
//if (!streamfile->vfsFile)
// return 0;
return streamfile->vfsFile->fsize();
}
@ -100,5 +105,16 @@ STREAMFILE *open_vfs(const char *path) {
return NULL;
}
#if 0 // files that don't exist seem blocked by probe.cc before reaching here
bool infile_exists = vfsFile && *vfsFile;
if (!infile_exists) {
/* allow non-existing files in some cases */
if (!vgmstream_is_virtual_filename(path)) {
delete vfsFile;
return NULL;
}
vfsFile = NULL;
}
#endif
return open_vfs_by_VFSFILE(vfsFile, path);
}

View File

@ -65,6 +65,7 @@ static void usage(const char* name, int is_full) {
" -k N: seeks to N samples before decoding (for seek testing)\n"
" -K N: seeks again to N samples before decoding (for seek testing)\n"
" -t file: print tags found in file (for tag testing)\n"
" -T: print title (for title testing)\n"
" -D <max channels>: downmix to <max channels> (for plugin downmix testing)\n"
" -O: decode but don't write to file (for performance testing)\n"
);
@ -100,6 +101,7 @@ typedef struct {
int seek_samples1;
int seek_samples2;
int decode_only;
int show_title;
int downmix_channels;
/* not quite config but eh */
@ -122,7 +124,7 @@ static int parse_config(cli_config* cfg, int argc, char** argv) {
opterr = 0;
/* read config */
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:k:K:hOvD:")) != -1) {
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:Tk:K:hOvD:")) != -1) {
switch (opt) {
case 'o':
cfg->outfilename = optarg;
@ -185,6 +187,9 @@ static int parse_config(cli_config* cfg, int argc, char** argv) {
case 't':
cfg->tag_filename= optarg;
break;
case 'T':
cfg->show_title = 1;
break;
case 'k':
cfg->seek_samples1 = atoi(optarg);
break;
@ -351,6 +356,17 @@ static void print_tags(cli_config* cfg) {
close_streamfile(sf_tags);
}
static void print_title(VGMSTREAM* vgmstream, cli_config* cfg) {
char title[1024];
if (!cfg->show_title)
return;
vgmstream_get_title(title, sizeof(title), cfg->infilename, vgmstream, NULL);
printf("title: %s\n", title);
}
static void clean_filename(char* dst, int clean_paths) {
int i;
for (i = 0; i < strlen(dst); i++) {
@ -565,11 +581,10 @@ int main(int argc, char** argv) {
}
/* print file info (or batch commands, depending on config) */
/* prints */
print_info(vgmstream, &cfg);
/* print tags info */
print_tags(&cfg);
print_title(vgmstream, &cfg);
/* prints done */
if (cfg.print_metaonly) {

View File

@ -63,6 +63,58 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
return 0;
}
void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM* vgmstream, vgmstream_title_t* cfg) {
const char *pos;
char* pos2;
char temp[1024];
/* name without path */
pos = strrchr(filename, '\\');
if (!pos)
pos = strrchr(filename, '/');
if (!pos)
pos = filename;
else
pos++;
strncpy(buf, pos, buf_len);
/* name without extension */
pos2 = strrchr(buf, '.');
if (pos2)
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;
if (target_subsong == 0)
target_subsong = 1;
/* show number if file has more than 1 subsong */
if (total_subsongs > 1) {
if (cfg && cfg->subsong_range)
snprintf(temp, sizeof(temp), "%s#1~%i", buf, total_subsongs);
else
snprintf(temp, sizeof(temp), "%s#%i", buf, target_subsong);
strncpy(buf, temp, buf_len);
}
/* show name for some cases */
show_name = (total_subsongs > 0 && (!cfg || !cfg->subsong_range)) ||
(cfg && cfg->force_title);
if (stream_name[0] != '\0' && show_name) {
snprintf(temp, sizeof(temp), "%s (%s)", buf, stream_name);
strncpy(buf, temp, buf_len);
}
}
}
static void copy_time(int* dst_flag, int32_t* dst_time, double* dst_time_s, int* src_flag, int32_t* src_time, double* src_time_s) {
if (!*src_flag)
return;

View File

@ -68,6 +68,15 @@ int vgmstream_get_play_forever(VGMSTREAM* vgmstream);
void vgmstream_set_play_forever(VGMSTREAM* vgmstream, int enabled);
typedef struct {
int force_title;
int subsong_range;
} vgmstream_title_t;
/* get a simple title for plugins */
void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM* vgmstream, vgmstream_title_t* cfg);
#if 0
//possible future public/opaque API