mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-12 01:30:49 +01:00
Add mixing/priority/fixes to Audacious
This commit is contained in:
parent
421cefbdd7
commit
028ff2f3d0
65
README.md
65
README.md
@ -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
|
||||
|
@ -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 ¤t_sample_pos) {
|
||||
debugMessage("seeking");
|
||||
// internal util to seek during play
|
||||
static void seek_helper(int seek_value, int ¤t_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 ¤t_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 ¤t_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;
|
||||
}
|
||||
|
@ -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 ¤t_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
|
||||
|
133
audacious/vfs.cc
133
audacious/vfs.cc
@ -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);
|
||||
}
|
||||
|
33
doc/BUILD.md
33
doc/BUILD.md
@ -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.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user