Use internal plugin settings and seeking in foobar

This commit is contained in:
bnnm 2020-07-29 19:41:17 +02:00
parent 37056e22d3
commit df0755104b
2 changed files with 72 additions and 217 deletions

View File

@ -38,6 +38,9 @@ extern "C" {
"https://github.com/kode54/vgmstream/\n" \
"https://sourceforge.net/projects/vgmstream/ (original)"
#define PLUGIN_FILENAME "foo_input_vgmstream.dll"
// called every time a file is added to the playlist (to get info) or when playing
input_vgmstream::input_vgmstream() {
vgmstream = NULL;
@ -49,13 +52,11 @@ input_vgmstream::input_vgmstream() {
paused = 0;
decode_pos_ms = 0;
decode_pos_samples = 0;
stream_length_samples = 0;
fade_samples = 0;
seek_pos_samples = 0;
length_samples = 0;
fade_seconds = 10.0f;
fade_delay_seconds = 0.0f;
loop_count = 2.0f;
fade_seconds = 10.0;
fade_delay_seconds = 0.0;
loop_count = 2.0;
loop_forever = false;
ignore_loop = 0;
disable_subsongs = false;
@ -74,7 +75,7 @@ input_vgmstream::~input_vgmstream() {
}
// called first when a new file is accepted, before playing it
void input_vgmstream::open(service_ptr_t<file> p_filehint, const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) {
void input_vgmstream::open(service_ptr_t<file> p_filehint, const char * p_path, t_input_open_reason p_reason, abort_callback & p_abort) {
if (!p_path) { // shouldn't be possible
throw exception_io_data();
@ -180,14 +181,14 @@ void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_cal
strcpy(tagfile_path,tagfile_name);
}
STREAMFILE *tagFile = open_foo_streamfile(tagfile_path, &p_abort, NULL);
if (tagFile != NULL) {
VGMSTREAM_TAGS *tags;
STREAMFILE* sf_tags = open_foo_streamfile(tagfile_path, &p_abort, NULL);
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, tagFile)) {
while (vgmstream_tags_next_tag(tags, sf_tags)) {
if (replaygain_info::g_is_meta_replaygain(tag_key)) {
p_info.info_set_replaygain(tag_key, tag_val);
/* there is info_set_replaygain_auto too but no doc */
@ -200,17 +201,17 @@ void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_cal
}
}
vgmstream_tags_close(tags);
close_streamfile(tagFile);
close_streamfile(sf_tags);
}
}
/* set technical info (details tab in file properties) */
p_info.info_set("vgmstream_version",PLUGIN_VERSION);
p_info.info_set("vgmstream_version", PLUGIN_VERSION);
p_info.info_set_int("samplerate", samplerate);
p_info.info_set_int("channels", channels);
p_info.info_set_int("bitspersample",16);
p_info.info_set_int("bitspersample", 16);
/* not quite accurate but some people are confused by "lossless"
* (could set lossless if PCM, but then again PCMFloat or PCM8 are converted/"lossy" in vgmstream) */
p_info.info_set("encoding","lossy/lossless");
@ -259,7 +260,7 @@ void input_vgmstream::decode_initialize(t_uint32 p_subsong, unsigned p_flags, ab
};
// called when audio buffer needs to be filled
bool input_vgmstream::decode_run(audio_chunk & p_chunk,abort_callback & p_abort) {
bool input_vgmstream::decode_run(audio_chunk & p_chunk, abort_callback & p_abort) {
if (!decoding) return false;
if (!vgmstream) return false;
@ -268,111 +269,48 @@ bool input_vgmstream::decode_run(audio_chunk & p_chunk,abort_callback & p_abort)
t_size bytes;
{
bool loop_okay = config.song_play_forever && vgmstream->loop_flag && !config.song_ignore_loop && !force_ignore_loop;
if (decode_pos_samples + max_buffer_samples > stream_length_samples && !loop_okay)
samples_to_do = stream_length_samples - decode_pos_samples;
bool play_forever = vgmstream_get_play_forever(vgmstream);
if (decode_pos_samples + max_buffer_samples > length_samples && !play_forever)
samples_to_do = length_samples - decode_pos_samples;
else
samples_to_do = max_buffer_samples;
if (samples_to_do /*< DECODE_SIZE*/ == 0) {
if (samples_to_do == 0) { /*< DECODE_SIZE*/
decoding = false;
return false; /* EOF, didn't decode samples in this call */
}
render_vgmstream(sample_buffer,samples_to_do,vgmstream);
/* fade! */
if (vgmstream->loop_flag && fade_samples > 0 && !loop_okay) {
int fade_channels = output_channels;
int samples_into_fade = decode_pos_samples - (stream_length_samples - fade_samples);
if (samples_into_fade + samples_to_do > 0) {
int j,k;
for (j=0;j<samples_to_do;j++,samples_into_fade++) {
if (samples_into_fade > 0) {
double fadedness = (double)(fade_samples-samples_into_fade)/fade_samples;
for (k = 0; k < fade_channels; k++) {
sample_buffer[j*fade_channels+k] =
(short)(sample_buffer[j*fade_channels+k]*fadedness);
}
}
}
}
}
render_vgmstream(sample_buffer, samples_to_do, vgmstream);
unsigned channel_config = vgmstream->channel_layout;
if (!channel_config)
channel_config = audio_chunk::g_guess_channel_config(output_channels);
bytes = (samples_to_do*output_channels * sizeof(sample_buffer[0]));
bytes = (samples_to_do * output_channels * sizeof(sample_buffer[0]));
p_chunk.set_data_fixedpoint((char*)sample_buffer, bytes, vgmstream->sample_rate, output_channels, 16, channel_config);
decode_pos_samples+=samples_to_do;
decode_pos_ms=decode_pos_samples*1000LL/vgmstream->sample_rate;
decode_pos_samples += samples_to_do;
decode_pos_ms = decode_pos_samples * 1000LL / vgmstream->sample_rate;
return true; /* decoded in this call (sample_buffer or less) */
}
}
// called when seeking
void input_vgmstream::decode_seek(double p_seconds,abort_callback & p_abort) {
seek_pos_samples = (int) audio_math::time_to_samples(p_seconds, vgmstream->sample_rate);
int max_buffer_samples = SAMPLE_BUFFER_SIZE;
bool loop_okay = config.song_play_forever && vgmstream->loop_flag && !config.song_ignore_loop && !force_ignore_loop;
void input_vgmstream::decode_seek(double p_seconds, abort_callback & p_abort) {
int32_t seek_sample = (int)audio_math::time_to_samples(p_seconds, vgmstream->sample_rate);
bool play_forever = vgmstream_get_play_forever(vgmstream);
// possible when disabling looping without refreshing foobar's cached song length
// (with infinite looping on p_seconds can't go over seek bar though)
if (seek_pos_samples > stream_length_samples)
seek_pos_samples = stream_length_samples;
// (p_seconds can't go over seek bar with infinite looping on, though)
if (seek_sample > length_samples)
seek_sample = length_samples;
int corrected_pos_samples = seek_pos_samples;
int loop_length = (vgmstream->loop_end_sample - vgmstream->loop_start_sample);
int loop_count = 0;
// optimize seeks withing loops
if (vgmstream->loop_flag && loop_length > 0 && seek_pos_samples >= vgmstream->loop_end_sample) {
corrected_pos_samples -= vgmstream->loop_start_sample;
loop_count = corrected_pos_samples / loop_length;
corrected_pos_samples %= loop_length;
corrected_pos_samples += vgmstream->loop_start_sample;
}
// Allow for delta seeks forward, by up to the total length of the stream, if the delta is less than the corrected offset
if (decode_pos_samples > corrected_pos_samples && decode_pos_samples <= seek_pos_samples &&
(seek_pos_samples - decode_pos_samples) < stream_length_samples) {
if (corrected_pos_samples > (seek_pos_samples - decode_pos_samples))
corrected_pos_samples = seek_pos_samples;
}
// Reset of backwards seek
else if(corrected_pos_samples < decode_pos_samples) {
reset_vgmstream(vgmstream);
apply_config(vgmstream, &config); /* config is undone by reset */
decode_pos_samples = 0;
}
// seeking overrun = bad
if (corrected_pos_samples > stream_length_samples)
corrected_pos_samples = stream_length_samples;
while(decode_pos_samples < corrected_pos_samples) {
int seek_samples = max_buffer_samples;
if ((decode_pos_samples + max_buffer_samples >= stream_length_samples) && !loop_okay)
seek_samples = stream_length_samples - seek_pos_samples;
if (decode_pos_samples + max_buffer_samples > seek_pos_samples)
seek_samples = seek_pos_samples - decode_pos_samples;
decode_pos_samples += seek_samples;
render_vgmstream(sample_buffer, seek_samples, vgmstream);
}
// seek may have been clamped to skip unneeded loops, adjust as some internals need this value
vgmstream->loop_count = loop_count; //todo make seek_vgmstream, not ok if seeking in fade section with ignore_fade
// remove seek loop correction from counter so file ends correctly
decode_pos_samples = seek_pos_samples;
seek_vgmstream(vgmstream, seek_sample);
decode_pos_samples = seek_sample;
decode_pos_ms = decode_pos_samples * 1000LL / vgmstream->sample_rate;
decoding = loop_okay || decode_pos_samples < stream_length_samples;
decoding = play_forever || decode_pos_samples < length_samples;
}
bool input_vgmstream::decode_can_seek() {return true;}
@ -383,7 +321,7 @@ void input_vgmstream::decode_on_idle(abort_callback & p_abort) {/*m_file->on_idl
void input_vgmstream::retag_set_info(t_uint32 p_subsong, const file_info & p_info, abort_callback & p_abort) { /*throw exception_io_data();*/ }
void input_vgmstream::retag_commit(abort_callback & p_abort) { /*throw exception_io_data();*/ }
bool input_vgmstream::g_is_our_content_type(const char * p_content_type) {return false;}
bool input_vgmstream::g_is_our_content_type(const char * p_content_type) { return false; }
// called to check if file can be processed by the plugin
bool input_vgmstream::g_is_our_path(const char * p_path, const char * p_extension) {
@ -395,14 +333,14 @@ bool input_vgmstream::g_is_our_path(const char * p_path, const char * p_extensio
}
// internal util to create a VGMSTREAM
VGMSTREAM * input_vgmstream::init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort) {
VGMSTREAM *vgmstream = NULL;
VGMSTREAM* input_vgmstream::init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE *streamFile = open_foo_streamfile(filename, &p_abort, &stats);
if (streamFile) {
streamFile->stream_index = p_subsong;
vgmstream = init_vgmstream_from_STREAMFILE(streamFile);
close_streamfile(streamFile);
STREAMFILE* sf = open_foo_streamfile(filename, &p_abort, &stats);
if (sf) {
sf->stream_index = p_subsong;
vgmstream = init_vgmstream_from_STREAMFILE(sf);
close_streamfile(sf);
}
return vgmstream;
}
@ -431,8 +369,7 @@ void input_vgmstream::setup_vgmstream(abort_callback & p_abort) {
if (subsong == 0)
subsong = 1;
set_config_defaults(&config);
apply_config(vgmstream, &config);
apply_config(vgmstream);
/* enable after all config but before outbuf (though ATM outbuf is not dynamic so no need to read input_channels) */
vgmstream_mixing_autodownmix(vgmstream, downmix_channels);
@ -441,15 +378,13 @@ void input_vgmstream::setup_vgmstream(abort_callback & p_abort) {
decode_pos_ms = 0;
decode_pos_samples = 0;
paused = 0;
stream_length_samples = get_vgmstream_play_samples(config.song_loop_count,config.song_fade_time,config.song_fade_delay,vgmstream);
fade_samples = (int)(config.song_fade_time * vgmstream->sample_rate);
length_samples = vgmstream_get_samples(vgmstream);
}
// internal util to get info
void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & title, int *length_in_ms, int *total_samples, int *loop_flag, int *loop_start, int *loop_end, int *sample_rate, int *channels, int *bitrate, pfc::string_base & description, abort_callback & p_abort) {
VGMSTREAM * infostream = NULL;
VGMSTREAM* infostream = NULL;
bool is_infostream = false;
foobar_song_config infoconfig;
char temp[1024];
int info_channels;
@ -461,16 +396,16 @@ void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & ti
if (!infostream) {
throw exception_io_data();
}
set_config_defaults(&infoconfig);
apply_config(infostream,&infoconfig);
is_infostream = true;
apply_config(infostream);
vgmstream_mixing_autodownmix(infostream, downmix_channels);
vgmstream_mixing_enable(infostream, 0, NULL /*&input_channels*/, &info_channels);
} else {
// vgmstream ready as get_info is valid after open() with any reason
infostream = vgmstream;
infoconfig = config;
info_channels = output_channels;
}
@ -486,7 +421,7 @@ void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & ti
*loop_start = infostream->loop_start_sample;
*loop_end = infostream->loop_end_sample;
int num_samples = get_vgmstream_play_samples(infoconfig.song_loop_count,infoconfig.song_fade_time,infoconfig.song_fade_delay,infostream);
int num_samples = vgmstream_get_samples(infostream);
*length_in_ms = num_samples*1000LL / infostream->sample_rate;
char temp[1024];
@ -546,80 +481,17 @@ bool input_vgmstream::get_description_tag(pfc::string_base & temp, pfc::string_b
return false;
}
void input_vgmstream::set_config_defaults(foobar_song_config *current) {
current->song_play_forever = loop_forever;
current->song_loop_count = loop_count;
current->song_fade_time = fade_seconds;
current->song_fade_delay = fade_delay_seconds;
current->song_ignore_fade = 0;
current->song_force_loop = 0;
current->song_really_force_loop = 0;
current->song_ignore_loop = ignore_loop;
}
void input_vgmstream::apply_config(VGMSTREAM* vgmstream) {
vgmstream_cfg_t vcfg = {0};
void input_vgmstream::apply_config(VGMSTREAM* vgmstream, foobar_song_config* cfg) {
vcfg.allow_play_forever = 1;
vcfg.play_forever = loop_forever;
vcfg.loop_times = loop_count;
vcfg.fade_period = fade_seconds;
vcfg.fade_delay = fade_delay_seconds;
vcfg.ignore_loop = ignore_loop;
/* honor suggested config (order matters, and config mixes with/overwrites player defaults) */
if (vgmstream->config.play_forever) {
cfg->song_play_forever = 1;
cfg->song_ignore_loop = 0;
}
if (vgmstream->config.loop_count_set) {
cfg->song_loop_count = vgmstream->config.loop_count;
cfg->song_play_forever = 0;
cfg->song_ignore_loop = 0;
}
if (vgmstream->config.fade_delay_set) {
cfg->song_fade_delay = vgmstream->config.fade_delay;
}
if (vgmstream->config.fade_time_set) {
cfg->song_fade_time = vgmstream->config.fade_time;
}
if (vgmstream->config.ignore_fade) {
cfg->song_ignore_fade = 1;
}
if (vgmstream->config.force_loop) {
cfg->song_ignore_loop = 0;
cfg->song_force_loop = 1;
cfg->song_really_force_loop = 0;
}
if (vgmstream->config.really_force_loop) {
cfg->song_ignore_loop = 0;
cfg->song_force_loop = 0;
cfg->song_really_force_loop = 1;
}
if (vgmstream->config.ignore_loop) {
cfg->song_ignore_loop = 1;
cfg->song_force_loop = 0;
cfg->song_really_force_loop = 0;
}
/* apply config */
if (cfg->song_force_loop && !vgmstream->loop_flag) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
}
if (cfg->song_really_force_loop) {
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
}
if (cfg->song_ignore_loop) {
vgmstream_force_loop(vgmstream, 0, 0,0);
}
/* remove non-compatible options */
if (!vgmstream->loop_flag) {
cfg->song_play_forever = 0;
}
if (cfg->song_play_forever) {
cfg->song_ignore_fade = 0;
}
/* loop N times, but also play stream end instead of fading out */
if (cfg->song_loop_count > 0 && cfg->song_ignore_fade) {
vgmstream_set_loop_target(vgmstream, (int)cfg->song_loop_count);
cfg->song_fade_time = 0;
}
vgmstream_apply_config(vgmstream, &vcfg);
}
GUID input_vgmstream::g_get_guid() {
@ -632,7 +504,7 @@ const char * input_vgmstream::g_get_name() {
}
GUID input_vgmstream::g_get_preferences_guid() {
static const GUID guid = { 0x2b5d0302, 0x165b, 0x409c,{ 0x94, 0x74, 0x2c, 0x8c, 0x2c, 0xd7, 0x6a, 0x25 } };;
static const GUID guid = { 0x2b5d0302, 0x165b, 0x409c,{ 0x94, 0x74, 0x2c, 0x8c, 0x2c, 0xd7, 0x6a, 0x25 } };
return guid;
}
@ -644,5 +516,5 @@ bool input_vgmstream::g_is_low_merit() {
// foobar plugin defs
static input_factory_t<input_vgmstream> g_input_vgmstream_factory;
DECLARE_COMPONENT_VERSION(APP_NAME,PLUGIN_VERSION,PLUGIN_DESCRIPTION);
VALIDATE_COMPONENT_FILENAME("foo_input_vgmstream.dll");
DECLARE_COMPONENT_VERSION(APP_NAME, PLUGIN_VERSION, PLUGIN_DESCRIPTION);
VALIDATE_COMPONENT_FILENAME(PLUGIN_FILENAME);

View File

@ -3,44 +3,32 @@
#define SAMPLE_BUFFER_SIZE 1024
/* current song settings */
typedef struct {
int song_play_forever;
double song_loop_count;
double song_fade_time;
double song_fade_delay;
int song_ignore_loop;
int song_force_loop;
int song_really_force_loop;
int song_ignore_fade;
} foobar_song_config;
class input_vgmstream : public input_stubs {
public:
input_vgmstream();
~input_vgmstream();
void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort);
void open(service_ptr_t<file> p_filehint, const char * p_path, t_input_open_reason p_reason, abort_callback & p_abort);
unsigned get_subsong_count();
t_uint32 get_subsong(unsigned p_index);
void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort);
void get_info(t_uint32 p_subsong, file_info & p_info, abort_callback & p_abort);
t_filestats get_file_stats(abort_callback & p_abort);
void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort);
bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort);
void decode_seek(double p_seconds,abort_callback & p_abort);
void decode_initialize(t_uint32 p_subsong, unsigned p_flags, abort_callback & p_abort);
bool decode_run(audio_chunk & p_chunk, abort_callback & p_abort);
void decode_seek(double p_seconds, abort_callback & p_abort);
bool decode_can_seek();
bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta);
bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta);
void decode_on_idle(abort_callback & p_abort);
void retag_set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort);
void retag_set_info(t_uint32 p_subsong, const file_info & p_info, abort_callback & p_abort);
void retag_commit(abort_callback & p_abort);
static bool g_is_our_content_type(const char * p_content_type);
static bool g_is_our_path(const char * p_path,const char * p_extension);
static bool g_is_our_path(const char * p_path, const char * p_extension);
static GUID g_get_guid();
static const char * g_get_name();
@ -53,7 +41,7 @@ class input_vgmstream : public input_stubs {
t_filestats stats;
/* state */
VGMSTREAM * vgmstream;
VGMSTREAM* vgmstream;
t_uint32 subsong;
bool direct_subsong;
int output_channels;
@ -62,9 +50,7 @@ class input_vgmstream : public input_stubs {
int paused;
int decode_pos_ms;
int decode_pos_samples;
int stream_length_samples;
int fade_samples;
int seek_pos_samples;
int length_samples;
short sample_buffer[SAMPLE_BUFFER_SIZE * VGMSTREAM_MAX_CHANNELS];
/* settings */
@ -75,6 +61,7 @@ class input_vgmstream : public input_stubs {
bool force_ignore_loop;
int ignore_loop;
bool disable_subsongs;
int downmix_channels;
bool tagfile_disable;
pfc::string8 tagfile_name;
@ -82,17 +69,13 @@ class input_vgmstream : public input_stubs {
//bool exts_common_on;
//bool exts_unknown_on;
/* song config */
foobar_song_config config;
/* helpers */
VGMSTREAM * init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort);
void setup_vgmstream(abort_callback & p_abort);
void load_settings();
void get_subsong_info(t_uint32 p_subsong, pfc::string_base & title, int *length_in_ms, int *total_samples, int *loop_flag, int *loop_start, int *loop_end, int *sample_rate, int *channels, int *bitrate, pfc::string_base & description, abort_callback & p_abort);
bool get_description_tag(pfc::string_base & temp, pfc::string_base const& description, const char *tag, char delimiter = '\n');
void set_config_defaults(foobar_song_config *current);
void apply_config(VGMSTREAM * vgmstream, foobar_song_config *current);
void apply_config(VGMSTREAM * vgmstream);
static void g_load_cfg(int *accept_unknown, int *accept_common);
};