Add mixing/priority/fixes to Audacious

This commit is contained in:
bnnm 2019-10-06 23:31:32 +02:00
parent 421cefbdd7
commit 028ff2f3d0
5 changed files with 371 additions and 273 deletions

View File

@ -93,11 +93,11 @@ automatically. You need to manually refresh it by selecting songs and doing
### Audacious plugin
*Installation*: needs to be manually built. Instructions can be found in the BUILD
document.
document in vgmstream's source code.
### vgmstream123
*Installation*: needs to be manually built. Instructions can be found in the
BUILD document in vgmstream's source code.
*Installation*: needs to be manually built. Instructions can be found in the BUILD
document in vgmstream's source code.
Usage: `vgmstream123 [options] INFILE ...`
@ -148,15 +148,18 @@ them playable through vgmstream.
- .wav to .lwav (standard WAV)
- .wma to .lwma (standard WMA)
- .(any) to .vgmstream (FFmpeg formats or TXTH)
Command line tools don't have this restriction and will accept the original
filename.
The main advantage to rename them is that vgmstream may use the file's
internal loop info, or apply subtle fixes, but is also limited in some ways
(like standard/player's tagging).
(like standard/player's tagging). .vgmstream is a catch-all extension that
may work as a last resort to make a file playable.
.vgmstream is a catch-all extension that 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
player's options).
When extracting from a bigfile, sometimes internal files don't have an actual
name+extension. Those should be renamed to its proper/common extension, as the
@ -214,7 +217,7 @@ a companion file:
The key file can be ".(ext)key" (for the whole folder), or "(name).(ext)key"
(for a single file). The format is made up to suit vgmstream.
### Artificial/generic headers
### Artificial files
In some cases a file only has raw data, while important header info (codec type,
sample rate, channels, etc) is stored in the .exe or other hard to locate places.
@ -225,18 +228,29 @@ The resulting file must be (name).genh. Contains static header data.
Programs like VGMToolbox can help to create GENH.
**TXTH**: a text header placed in an external file. The TXTH must be named
".txth" or ".(ext).txth" (for the whole folder), or "(name.ext).txth" (for a
`.txth` or `.(ext).txth` (for the whole folder), or `(name.ext).txth` (for a
single file). Contains dynamic text commands to read data from the original
file, or static values.
file, or static values.
**TXTP**: a text playing configurator. Can contain a list of filenames to
play as one (ex. "intro.vag" "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, and many other features.
*TXTH* is recomended over *GENH* as it's far easier to create and has many
more functions.
For files that already play, sometimes they are used by the game in various
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`),
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.
Creation of those files is meant for advanced users, docs can be found in
vgmstream source.
### Plugin conflicts
Since vgmstream supports a huge amount of formats it's possibly that some of
them are also supported in other plugins, and this sometimes causes conflicts.
@ -244,9 +258,18 @@ If a file that should isn't playing or looping, first make sure vgmstream is
really opening it (should show "VGMSTREAM" somewhere in the file info), and
try to remove a few other plugins.
foobar's ffmpeg plugin and foo_adpcm are known to cause issues, but in
foobar's FFmpeg plugin and foo_adpcm are known to cause issues, but in
recent versions (1.4.x) you can configure plugin priority.
In Audacious, vgmstream is set with slightly higher priority than FFmpeg,
since it steals many formats that you normally want to loop (like .adx).
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.
### Channel issues
Some games layer a huge number of channels, that are disabled or downmixed
during gameplay. The player may be unable to play those files (for example
@ -275,17 +298,17 @@ filename1
filename2
```
Accepted tags depend on the player (foobar: any; winamp: see ATF config),
typically ALBUM/ARTIST/TITLE/DISC/TRACK/COMPOSER/etc, lower or uppercase,
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 for multiple tracks). It only reads up to current
_filename_ though, so any @TAG below would be ignored.
(ex.- may define *@COMPOSER* for multiple tracks). It only reads up to current
*filename* though, so any *@TAG* below would be ignored.
Playlist formatting should follow player's config. ASCII or UTF-8 tags work.
GLOBAL_COMMANDs currently can be:
- AUTOTRACK: sets %TRACK% tag automatically (1..N as files are encountered
*GLOBAL_COMMAND*s currently can be:
- *AUTOTRACK*: sets *%TRACK* tag automatically (1..N as files are encountered
in the tag file).
- AUTOALBUM: sets %ALBUM% tag automatically using the containing dir as album.
- *AUTOALBUM*: sets *%ALBUM* tag automatically using the containing dir as album.
Note that since you can use global tags don't need to put all files inside.
This would be a perfectly valid *!tags.m3u*:
@ -306,7 +329,7 @@ enabled in preferences):
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
to make sure *options > titles > advanced title formatting* checkbox is set and
the format defined.
## Virtual TXTP files

View File

@ -16,6 +16,7 @@
extern "C" {
#include "../src/vgmstream.h"
#include "../src/plugins.h"
}
#include "plugin.h"
#include "vfs.h"
@ -31,20 +32,24 @@ extern "C" {
#define CFG_ID "vgmstream" // ID for storing in audacious
#define MIN_BUFFER_SIZE 576
/* internal state */
VgmstreamPlugin aud_plugin_instance;
Settings vgmstream_cfg;
VGMSTREAM *vgmstream = NULL;
/* global state */
/*EXPORT*/ VgmstreamPlugin aud_plugin_instance;
audacious_settings settings;
VGMSTREAM *vgmstream = NULL; //todo make local?
const char *const VgmstreamPlugin::defaults[] = {
"loop_forever", "1",
"loop_count", "2",
"fade_length", "3",
"fade_delay", "3",
"loop_forever", "FALSE",
"loop_count", "2", //maybe double?
"fade_length", "10.0",
"fade_delay", "0.0",
"downmix_channels", "8",
"exts_unknown_on", "FALSE",
"exts_common_on", "FALSE",
NULL
};
// N_(...) for i18n but not much point here
const char VgmstreamPlugin::about[] =
"vgmstream plugin " VERSION " " __DATE__ "\n"
"by hcs, FastElbja, manakoAT, bxaimc, snakemeat, soneek, kode54, bnnm and many others\n"
@ -58,70 +63,99 @@ const char VgmstreamPlugin::about[] =
"https://sourceforge.net/projects/vgmstream/ (original)";
const PreferencesWidget VgmstreamPlugin::widgets[] = {
WidgetLabel(N_("<b>vgmstream Config</b>")),
WidgetCheck(N_("Loop Forever:"), WidgetBool(vgmstream_cfg.loop_forever)),
WidgetSpin(N_("Loop Count:"), WidgetInt(vgmstream_cfg.loop_count), {1, 20, 1}),
WidgetSpin(N_("Fade Length:"), WidgetFloat(vgmstream_cfg.fade_length), {0, 60, 0.1}),
WidgetSpin(N_("Fade Delay:"), WidgetFloat(vgmstream_cfg.fade_delay), {0, 60, 0.1}),
WidgetLabel(N_("<b>vgmstream config</b>")),
WidgetCheck(N_("Loop forever:"), WidgetBool(settings.loop_forever)),
WidgetSpin(N_("Loop count:"), WidgetInt(settings.loop_count), {1, 20, 1}),
WidgetSpin(N_("Fade length:"), WidgetFloat(settings.fade_length), {0, 60, 0.1}),
WidgetSpin(N_("Fade delay:"), WidgetFloat(settings.fade_delay), {0, 60, 0.1}),
WidgetSpin(N_("Downmix:"), WidgetInt(settings.downmix_channels), {1, 20, 1}),
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)),
};
void vgmstream_cfg_load() {
debugMessage("cfg_load called");
void vgmstream_settings_load() {
AUDINFO("load settings\n");
aud_config_set_defaults(CFG_ID, VgmstreamPlugin::defaults);
vgmstream_cfg.loop_forever = aud_get_bool(CFG_ID, "loop_forever");
vgmstream_cfg.loop_count = aud_get_int(CFG_ID, "loop_count");
vgmstream_cfg.fade_length = aud_get_double(CFG_ID, "fade_length");
vgmstream_cfg.fade_delay = aud_get_double(CFG_ID, "fade_delay");
settings.loop_forever = aud_get_bool(CFG_ID, "loop_forever");
settings.loop_count = aud_get_int(CFG_ID, "loop_count");
settings.fade_length = aud_get_double(CFG_ID, "fade_length");
settings.fade_delay = aud_get_double(CFG_ID, "fade_delay");
settings.downmix_channels = aud_get_int(CFG_ID, "downmix_channels");
settings.exts_unknown_on = aud_get_bool(CFG_ID, "exts_unknown_on");
settings.exts_common_on = aud_get_bool(CFG_ID, "exts_common_on");
}
void vgmstream_cfg_save() {
debugMessage("cfg_save called");
aud_set_bool(CFG_ID, "loop_forever", vgmstream_cfg.loop_forever);
aud_set_int(CFG_ID, "loop_count", vgmstream_cfg.loop_count);
aud_set_double(CFG_ID, "fade_length", vgmstream_cfg.fade_length);
aud_set_double(CFG_ID, "fade_delay", vgmstream_cfg.fade_delay);
void vgmstream_settings_save() {
AUDINFO("save settings\n");
aud_set_bool(CFG_ID, "loop_forever", settings.loop_forever);
aud_set_int(CFG_ID, "loop_count", settings.loop_count);
aud_set_double(CFG_ID, "fade_length", settings.fade_length);
aud_set_double(CFG_ID, "fade_delay", settings.fade_delay);
aud_set_int(CFG_ID, "downmix_channels", settings.downmix_channels);
aud_set_bool(CFG_ID, "exts_unknown_on", settings.exts_unknown_on);
aud_set_bool(CFG_ID, "exts_common_on", settings.exts_common_on);
}
const PluginPreferences VgmstreamPlugin::prefs = {
{widgets}, vgmstream_cfg_load, vgmstream_cfg_save
{widgets}, vgmstream_settings_load, vgmstream_settings_save
};
// validate extension
// validate extension (thread safe)
bool VgmstreamPlugin::is_our_file(const char *filename, VFSFile &file) {
const char * ext = strrchr(filename,'.');
if (ext==NULL)
ext = filename+strlen(filename); /* point to null, i.e. an empty string for the extension */
else
ext = ext+1; /* skip the dot */
AUDDBG("test file=%s\n", filename);
size_t ext_list_len = 0;
const char ** ext_list = vgmstream_get_formats(&ext_list_len);
vgmstream_ctx_valid_cfg cfg = {0};
for (int i=0; i < ext_list_len; i++) {
if (!strcasecmp(ext, ext_list[i]))
return true;
}
return false;
cfg.accept_unknown = settings.exts_unknown_on;
cfg.accept_common = settings.exts_common_on;
return vgmstream_ctx_is_valid(filename, &cfg) > 0 ? true : false;
}
// called on startup (main thread)
bool VgmstreamPlugin::init() {
debugMessage("init");
AUDINFO("plugin start\n");
vgmstream_cfg_load();
vgmstream_settings_load();
debugMessage("after load cfg");
return true;
}
// called on stop (main thread)
void VgmstreamPlugin::cleanup() {
debugMessage("cleanup");
AUDINFO("plugin end\n");
vgmstream_cfg_save();
vgmstream_settings_save();
}
// called every time the user adds a new file to playlist
bool read_data(const char * filename, Tuple & tuple) {
#if 0
static int get_filename_subtune(const char * filename) {
int subtune;
int pos = strstr("?"))
if (pos <= 0)
return -1;
if (sscanf(filename + pos, "%i", &subtune) != 1)
return -1;
return subtune;
}
#endif
// internal helper, called every time user adds a new file to playlist
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
STREAMFILE *streamfile = open_vfs(filename);
if (!streamfile) return false;
@ -132,16 +166,47 @@ bool read_data(const char * filename, Tuple & tuple) {
return false;
}
tuple.set_filename(filename); //may leak string???
int bitrate = get_vgmstream_average_bitrate(infostream);
tuple.set_int(Tuple::Bitrate, bitrate);
#if 0
int total_subtunes = infostream->num_streams;
int ms = get_vgmstream_play_samples(vgmstream_cfg.loop_count, vgmstream_cfg.fade_length, vgmstream_cfg.fade_delay, infostream);
//somehow max was changed to short in recent versions, though
// some formats can exceed this
if (total_subtunes > 32767)
total_subtunes = 32767;
if (infostream->num_streams > 1 && subtune <= 0) {
//set nullptr to leave subsong index linear (must add +1 to subtune)
tuple.set_subtunes(total_subtunes, nullptr);
return true; //must return?
}
streamfile->stream_index = (subtune + 1);
#endif
//todo apply_config
int output_channels = infostream->channels;
vgmstream_mixing_autodownmix(infostream, settings.downmix_channels);
vgmstream_mixing_enable(infostream, 0, NULL, &output_channels);
int bitrate = get_vgmstream_average_bitrate(infostream);
int ms = get_vgmstream_play_samples(settings.loop_count, settings.fade_length, settings.fade_delay, infostream);
ms = ms* 1000LL / infostream->sample_rate;
// short form, not sure if better way
tuple.set_format("vgmstream codec", output_channels, infostream->sample_rate, bitrate);
tuple.set_filename(filename); //used?
tuple.set_int(Tuple::Bitrate, bitrate); //in kb/s
tuple.set_int(Tuple::Length, ms);
tuple.set_str(Tuple::Codec, "vgmstream codec");//doesn't show?
// here we could call describe_vgmstream() and get substring to add tags and stuff
//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
//todo tags (see tuple.h)
//tuple.set_int (Tuple::Track, ...);
//tuple.set_str (Tuple::Artist, ...);
//tuple.set_str (Tuple::Album, ...);
close_streamfile(streamfile);
close_vgmstream(infostream);
@ -149,118 +214,31 @@ bool read_data(const char * filename, Tuple & tuple) {
return true;
}
//for Audacious <= 3.7 (unused otherwise)
// thread safe (for Audacious <= 3.7, unused otherwise)
Tuple VgmstreamPlugin::read_tuple(const char *filename, VFSFile &file) {
debugMessage("read_tuple");
Tuple tuple;
read_data(filename, tuple);
read_info(filename, tuple);
return tuple;
}
//for Audacious >= 3.8 (unused otherwise)
// thread safe (for Audacious >= 3.8, unused otherwise)
bool VgmstreamPlugin::read_tag(const char * filename, VFSFile & file, Tuple & tuple, Index<char> * image) {
debugMessage("read_tag");
return read_data(filename, tuple);
return read_info(filename, tuple);
}
bool VgmstreamPlugin::play(const char *filename, VFSFile &file) {
debugMessage("start play");
int current_sample_pos = 0;
int bitrate;
// just in case
if (vgmstream)
close_vgmstream(vgmstream);
STREAMFILE *streamfile = open_vfs(filename);
if (!streamfile) {
printf("failed opening %s\n", filename);
return false;
}
vgmstream = init_vgmstream_from_STREAMFILE(streamfile);
close_streamfile(streamfile);
if (!vgmstream || vgmstream->channels <= 0) {
printf("Error::Channels are zero or couldn't init plugin\n");
if (vgmstream)
close_vgmstream(vgmstream);
vgmstream = NULL;
return false;
}
short buffer[MIN_BUFFER_SIZE * vgmstream->channels];
int max_buffer_samples = sizeof(buffer) / sizeof(buffer[0]) / vgmstream->channels;
int stream_samples_amount = get_vgmstream_play_samples(
vgmstream_cfg.loop_count, vgmstream_cfg.fade_length,
vgmstream_cfg.fade_delay, vgmstream);
bitrate = get_vgmstream_average_bitrate(vgmstream);
set_stream_bitrate(bitrate);
open_audio(FMT_S16_LE, vgmstream->sample_rate, vgmstream->channels);
int fade_samples = vgmstream_cfg.fade_length * vgmstream->sample_rate;
while (!check_stop()) {
int toget = max_buffer_samples;
int seek_value = check_seek();
if (seek_value > 0)
seek(seek_value, current_sample_pos);
// If we haven't configured the plugin to play forever
// or the current song is not loopable.
if (!vgmstream_cfg.loop_forever || !vgmstream->loop_flag) {
if (current_sample_pos >= stream_samples_amount)
break;
if (current_sample_pos + toget > stream_samples_amount)
toget = stream_samples_amount - current_sample_pos;
}
render_vgmstream(buffer, toget, vgmstream);
if (vgmstream->loop_flag && fade_samples > 0 &&
!vgmstream_cfg.loop_forever) {
int samples_into_fade =
current_sample_pos - (stream_samples_amount - fade_samples);
if (samples_into_fade + toget > 0) {
for (int j = 0; j < toget; j++, samples_into_fade++) {
if (samples_into_fade > 0) {
double fadedness =
(double)(fade_samples - samples_into_fade) / fade_samples;
for (int k = 0; k < vgmstream->channels; k++)
buffer[j * vgmstream->channels + k] *= fadedness;
}
}
}
}
write_audio(buffer, toget * sizeof(short) * vgmstream->channels);
current_sample_pos += toget;
}
debugMessage("finished");
if (vgmstream)
close_vgmstream(vgmstream);
vgmstream = NULL;
return true;
}
void VgmstreamPlugin::seek(int seek_value, int &current_sample_pos) {
debugMessage("seeking");
// internal util to seek during play
static void seek_helper(int seek_value, int &current_sample_pos, int input_channels) {
AUDINFO("seeking\n");
// compute from ms to samples
int seek_needed_samples = (long long)seek_value * vgmstream->sample_rate / 1000L;
short buffer[MIN_BUFFER_SIZE * vgmstream->channels];
int max_buffer_samples = sizeof(buffer) / sizeof(buffer[0]) / vgmstream->channels;
short buffer[MIN_BUFFER_SIZE * input_channels];
int max_buffer_samples = MIN_BUFFER_SIZE;
int samples_to_do = 0;
if (seek_needed_samples < current_sample_pos) {
// go back in time, reopen file
debugMessage("reopen file to seek backward");
AUDINFO("resetting file to seek backwards\n");
reset_vgmstream(vgmstream);
current_sample_pos = 0;
samples_to_do = seek_needed_samples;
@ -271,7 +249,7 @@ void VgmstreamPlugin::seek(int seek_value, int &current_sample_pos) {
// do the actual seeking
if (samples_to_do >= 0) {
debugMessage("render forward");
AUDINFO("rendering forward\n");
// render till seeked sample
while (samples_to_do > 0) {
@ -280,21 +258,97 @@ void VgmstreamPlugin::seek(int seek_value, int &current_sample_pos) {
samples_to_do -= seek_samples;
render_vgmstream(buffer, seek_samples, vgmstream);
}
debugMessage("after render vgmstream");
}
}
void debugMessage(const char *str) {
#ifdef DEBUG
timeval curTime;
gettimeofday(&curTime, NULL);
int milli = curTime.tv_usec / 1000;
// called on play (play thread)
bool VgmstreamPlugin::play(const char *filename, VFSFile &file) {
AUDINFO("play file=%s\n", filename);
char buffer[80];
strftime(buffer, 80, "%H:%M:%S", localtime(&curTime.tv_sec));
// just in case
if (vgmstream)
close_vgmstream(vgmstream);
char currentTime[84] = "";
sprintf(currentTime, "%s:%d", buffer, milli);
printf("[%s] Debug: %s\n", currentTime, str);
#endif
STREAMFILE *streamfile = open_vfs(filename);
if (!streamfile) {
AUDERR("failed opening file %s\n", filename);
return false;
}
vgmstream = init_vgmstream_from_STREAMFILE(streamfile);
close_streamfile(streamfile);
if (!vgmstream) {
AUDINFO("filename %s is not a valid format\n", filename);
close_vgmstream(vgmstream);
vgmstream = NULL;
return false;
}
int bitrate = get_vgmstream_average_bitrate(vgmstream);
set_stream_bitrate(bitrate);
//todo apply config
int input_channels = vgmstream->channels;
int output_channels = vgmstream->channels;
/* enable after all config but before outbuf */
vgmstream_mixing_autodownmix(vgmstream, settings.downmix_channels);
vgmstream_mixing_enable(vgmstream, MIN_BUFFER_SIZE, &input_channels, &output_channels);
//FMT_S8 / FMT_S16_NE / FMT_S24_NE / FMT_S32_NE / FMT_FLOAT
open_audio(FMT_S16_LE, vgmstream->sample_rate, output_channels);
// play
short buffer[MIN_BUFFER_SIZE * input_channels];
int max_buffer_samples = MIN_BUFFER_SIZE;
int stream_samples_amount = get_vgmstream_play_samples(
settings.loop_count, settings.fade_length,
settings.fade_delay, vgmstream);
int fade_samples = settings.fade_length * vgmstream->sample_rate;
int current_sample_pos = 0;
while (!check_stop()) {
int toget = max_buffer_samples;
// handle seek request
int seek_value = check_seek();
if (seek_value >= 0)
seek_helper(seek_value, current_sample_pos, input_channels);
// check stream finished
if (!settings.loop_forever || !vgmstream->loop_flag) {
if (current_sample_pos >= stream_samples_amount)
break;
if (current_sample_pos + toget > stream_samples_amount)
toget = stream_samples_amount - current_sample_pos;
}
render_vgmstream(buffer, toget, vgmstream);
if (vgmstream->loop_flag && fade_samples > 0 &&
!settings.loop_forever) {
int samples_into_fade =
current_sample_pos - (stream_samples_amount - fade_samples);
if (samples_into_fade + toget > 0) {
for (int j = 0; j < toget; j++, samples_into_fade++) {
if (samples_into_fade > 0) {
double fadedness =
(double)(fade_samples - samples_into_fade) / fade_samples;
for (int k = 0; k < output_channels; k++)
buffer[j * output_channels + k] *= fadedness;
}
}
}
}
write_audio(buffer, toget * sizeof(short) * output_channels);
current_sample_pos += toget;
}
AUDINFO("play finished\n");
close_vgmstream(vgmstream);
vgmstream = NULL;
return true;
}

View File

@ -7,6 +7,11 @@
#include <libaudcore/preferences.h>
#include <libaudcore/runtime.h>
#ifndef AUDACIOUS_VGMSTREAM_PRIORITY
/* set higher than FFmpeg's 10 */
#define AUDACIOUS_VGMSTREAM_PRIORITY 9
#endif
class VgmstreamPlugin : public InputPlugin {
public:
//static const char *const exts[];
@ -19,12 +24,11 @@ public:
N_("vgmstream Decoder"), N_("vgmstream"), about, &prefs,
};
// accepted exts are validated at runtime in is_our_file now, this is to set a static list
//static constexpr auto iinfo = InputInfo().with_exts(exts);
//constexpr VgmstreamPlugin() : InputPlugin(info, iinfo) {}
//constexpr VgmstreamPlugin() : InputPlugin (info, InputInfo().with_exts(exts)) {}
constexpr VgmstreamPlugin() : InputPlugin (info, InputInfo()) {}
constexpr VgmstreamPlugin() : InputPlugin (info,
InputInfo() //InputInfo(FlagSubtunes) // allow subsongs
.with_priority(AUDACIOUS_VGMSTREAM_PRIORITY) // where 0=default(?), 10=lowest
//.with_exts(exts)) {} //accepted exts are validated at runtime now
) {}
bool init();
void cleanup();
@ -33,12 +37,9 @@ public:
bool read_tag(const char * filename, VFSFile & file, Tuple & tuple, Index<char> * image);
bool play(const char *filename, VFSFile &file);
private:
void seek(int seek_value, int &current_sample_pos);
};
// reminder of usage, probably no more need
// static extension list, not sure of advantages (uses is_our_file)
//const char *const VgmstreamPlugin::exts[] = { "ext1", "ext2", ..., NULL }
@ -47,12 +48,14 @@ typedef struct {
int loop_count;
double fade_length;
double fade_delay;
} Settings;
int downmix_channels;
bool exts_unknown_on;
bool exts_common_on;
} audacious_settings;
extern Settings vgmstream_cfg;
extern audacious_settings settings;
void debugMessage(const char *str);
void vgmstream_cfg_load();
void vgmstream_cfg_save();
void vgmstream_settings_load();
void vgmstream_settings_save();
#endif

View File

@ -7,99 +7,98 @@
#include "plugin.h"
#include "vfs.h"
typedef struct _VFSSTREAMFILE {
STREAMFILE sf;
VFSFile *vfsFile;
off_t offset;
char name[32768];
} VFSSTREAMFILE;
typedef struct {
STREAMFILE sf;
VFSFile *vfsFile;
off_t offset;
char name[32768];
} VFS_STREAMFILE;
static STREAMFILE *open_vfs_by_VFSFILE(VFSFile *file, const char *path);
static size_t read_vfs(VFSSTREAMFILE *streamfile, uint8_t *dest, off_t offset,
size_t length) {
size_t sz;
// if the offsets don't match, then we need to perform a seek
if (streamfile->offset != offset) {
streamfile->vfsFile->fseek(offset, VFS_SEEK_SET);
streamfile->offset = offset;
}
static size_t read_vfs(VFS_STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length) {
size_t bytes_read;
sz = streamfile->vfsFile->fread(dest, 1, length);
// increment our current offset
streamfile->offset += sz;
if (!dest || length <= 0 || offset < 0)
return 0;
return sz;
// if the offsets don't match, then we need to perform a seek
if (streamfile->offset != offset) {
int ok = streamfile->vfsFile->fseek(offset, VFS_SEEK_SET);
if (ok != 0) return 0;
streamfile->offset = offset;
}
bytes_read = streamfile->vfsFile->fread(dest, 1, length);
streamfile->offset += bytes_read;
return bytes_read;
}
static void close_vfs(VFSSTREAMFILE *streamfile) {
debugMessage("close_vfs");
delete streamfile->vfsFile; //fcloses the internal file too
free(streamfile);
static void close_vfs(VFS_STREAMFILE *streamfile) {
delete streamfile->vfsFile; //fcloses the internal file too
free(streamfile);
}
static size_t get_size_vfs(VFSSTREAMFILE *streamfile) {
return streamfile->vfsFile->fsize();
static size_t get_size_vfs(VFS_STREAMFILE *streamfile) {
return streamfile->vfsFile->fsize();
}
static size_t get_offset_vfs(VFSSTREAMFILE *streamfile) {
return streamfile->offset;
static size_t get_offset_vfs(VFS_STREAMFILE *streamfile) {
return streamfile->offset;
}
static void get_name_vfs(VFSSTREAMFILE *streamfile, char *buffer,
size_t length) {
strncpy(buffer, streamfile->name, length);
buffer[length - 1] = '\0';
static void get_name_vfs(VFS_STREAMFILE *streamfile, char *buffer, size_t length) {
strncpy(buffer, streamfile->name, length);
buffer[length - 1] = '\0';
}
static STREAMFILE *open_vfs_impl(VFSSTREAMFILE *streamfile,
const char *const filename,
size_t buffersize) {
if (!filename)
return NULL;
static STREAMFILE *open_vfs_impl(VFS_STREAMFILE *streamfile, const char *const filename, size_t buffersize) {
if (!filename)
return NULL;
return open_vfs(filename);
return open_vfs(filename);
}
STREAMFILE *open_vfs_by_VFSFILE(VFSFile *file, const char *path) {
VFSSTREAMFILE *streamfile = (VFSSTREAMFILE *)malloc(sizeof(VFSSTREAMFILE));
if (!streamfile)
return NULL;
VFS_STREAMFILE *streamfile = (VFS_STREAMFILE *)malloc(sizeof(VFS_STREAMFILE));
if (!streamfile)
return NULL;
// success, set our pointers
memset(streamfile, 0, sizeof(VFSSTREAMFILE));
// success, set our pointers
memset(streamfile, 0, sizeof(VFS_STREAMFILE));
streamfile->sf.read = (size_t (*)(STREAMFILE *, uint8_t *, off_t, size_t))read_vfs;
streamfile->sf.get_size = (size_t (*)(STREAMFILE *))get_size_vfs;
streamfile->sf.get_offset = (off_t (*)(STREAMFILE *))get_offset_vfs;
streamfile->sf.get_name = (void (*)(STREAMFILE *, char *, size_t))get_name_vfs;
streamfile->sf.open = (STREAMFILE *(*)(STREAMFILE *, const char *, size_t))open_vfs_impl;
streamfile->sf.close = (void (*)(STREAMFILE *))close_vfs;
streamfile->sf.read = (size_t (*)(STREAMFILE *, uint8_t *, off_t, size_t))read_vfs;
streamfile->sf.get_size = (size_t (*)(STREAMFILE *))get_size_vfs;
streamfile->sf.get_offset = (off_t (*)(STREAMFILE *))get_offset_vfs;
streamfile->sf.get_name = (void (*)(STREAMFILE *, char *, size_t))get_name_vfs;
streamfile->sf.open = (STREAMFILE *(*)(STREAMFILE *, const char *, size_t))open_vfs_impl;
streamfile->sf.close = (void (*)(STREAMFILE *))close_vfs;
streamfile->vfsFile = file;
streamfile->offset = 0;
strncpy(streamfile->name, path, sizeof(streamfile->name));
streamfile->name[sizeof(streamfile->name) - 1] = '\0';
streamfile->vfsFile = file;
streamfile->offset = 0;
strncpy(streamfile->name, path, sizeof(streamfile->name));
streamfile->name[sizeof(streamfile->name) - 1] = '\0';
// for reference, actual file path ("name" has protocol path, file://...).
// name should work for all situations but in case it's needed again maybe
// get_name should always return realname, as it's used to open companion VFSFiles
//{
// gchar *realname = g_filename_from_uri(path, NULL, NULL);
// strncpy(streamfile->realname, realname, sizeof(streamfile->realname));
// streamfile->realname[sizeof(streamfile->realname) - 1] = '\0';
// g_free(realname);
//}
// for reference, actual file path ("name" has protocol path, file://...).
// name should work for all situations but in case it's needed again maybe
// get_name should always return realname, as it's used to open companion VFSFiles
//{
// gchar *realname = g_filename_from_uri(path, NULL, NULL);
// strncpy(streamfile->realname, realname, sizeof(streamfile->realname));
// streamfile->realname[sizeof(streamfile->realname) - 1] = '\0';
// g_free(realname);
//}
return &streamfile->sf;
return &streamfile->sf;
}
STREAMFILE *open_vfs(const char *path) {
VFSFile *vfsFile = new VFSFile(path, "rb");
if (!vfsFile || !*vfsFile) {
delete vfsFile;
return NULL;
}
VFSFile *vfsFile = new VFSFile(path, "rb");
if (!vfsFile || !*vfsFile) {
delete vfsFile;
return NULL;
}
return open_vfs_by_VFSFILE(vfsFile, path);
return open_vfs_by_VFSFILE(vfsFile, path);
}

View File

@ -32,7 +32,7 @@ If you wish to use CMake, see [CMAKE.md](CMAKE.md).
**With GCC**: use the *./Makefile* in the root folder, see inside for options. For compilation flags check the *Makefile* in each folder.
You may need to manually rebuild if you change a *.h* file (use *make clean*).
In Linux, Makefiles can be used to cross-compile with the MingW headers, but may not be updated to generate native code at the moment. It should be fixable with some effort. Autotools should build it as vgmstream-cli instead (see the Audacious section). Some Linux distributions like Arch Linux include pre-patched vgmstream with most libraries, you may want that instead (see: https://aur.archlinux.org/packages/vgmstream-kode54-git/).
In Linux, Makefiles can be used to cross-compile with the MingW headers, but may not be updated to generate native code at the moment. It should be fixable with some effort. Autotools should build and install it as `vgmstream-cli` instead (see the Audacious section). Some Linux distributions like Arch Linux include pre-patched vgmstream with most libraries, you may want that instead (see: https://aur.archlinux.org/packages/vgmstream-kode54-git/).
Windows CMD example:
```
@ -99,10 +99,14 @@ libvorbis and libmpg123 will be used if found, while FFmpeg and other external l
Windows builds aren't supported at the moment (should be possible but there are complex dependency chains).
Take note of other plugins stealing extensions (see README). To change Audacious's default priority for vgmstream you can make with CFLAG `AUDACIOUS_VGMSTREAM_PRIORITY n` (where `N` is number with 10=lowest)
Terminal example, assuming a Ubuntu-based Linux distribution:
```
# build requirements
# build setup
# default requirements
sudo apt-get update
sudo apt-get install gcc g++ make
sudo apt-get install autoconf automake libtool
@ -110,13 +114,18 @@ sudo apt-get install git
# vgmstream dependencies
sudo apt-get install libmpg123-dev libvorbis-dev
# Audacious player and dependencies
sudo apt-get install audacious
sudo apt-get install audacious
sudo apt-get install audacious-dev libglib2.0-dev libgtk2.0-dev libpango1.0-dev
# if you want vgmstream123 do this too
sudo apt-get install libao-dev
# check Audacious version >= 3.5
pkg-config --modversion audacious
```
```
# vgmstream build
# build
git clone https://github.com/kode54/vgmstream
cd vgmstream
@ -124,13 +133,21 @@ cd vgmstream
./configure
make -f Makefile.autotools
# copy to audacious plugins and update global libvgmstream.so.0 refs
# copy to audacious plugins (note that this will also install "libvgmstream",
# vgmstream-cli and vgmstream123, so they can be invoked from the terminal)
sudo make -f Makefile.autotools install
# update global libvgmstream.so.0 refs
sudo ldconfig
# start audacious in verbose mode to check if it was installed correctly
audacious -V
# if all goes well no "ERROR (something) referencing libvgmstream should show
# in the terminal log, then go to menu services > plugins > input tab and check
# vgmstream is there (you can start audacious normally next time)
```
```
# uninstall if needed
sudo make -f Makefile.autotools uninstall
@ -141,11 +158,13 @@ find . -name ".deps" -type d -exec rm -r "{}" \;
## WARNING, removes *all* untracked files not in .gitignore
git clean -fd
```
To update vgmstream it's probably easiest to remove the `vgmstream` folder and start again from "vgmstream build" step, since updates often require a full rebuild anyway.
### vgmstream123 player
Should be buildable with Autotools, much like the Audacious plugin, though requires libao (libao-dev).
Should be buildable with Autotools by following the same steps as listen in the Audacious section (requires libao-dev).
Windows builds are possible with libao.dll and includes, but some features are disabled.
Windows builds are possible with `libao.dll` and `libao` includes (found elsewhere) through the `Makefile`, but some features are disabled.
libao is licensed under the GPL v2 or later.