2017-05-01 14:18:33 +02:00
|
|
|
/**
|
|
|
|
* vgmstream for foobar2000
|
|
|
|
*/
|
2008-05-26 15:29:21 +02:00
|
|
|
|
|
|
|
#ifdef _MSC_VER
|
|
|
|
#define _CRT_SECURE_NO_DEPRECATE
|
|
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <io.h>
|
2023-08-19 23:35:28 +02:00
|
|
|
#include <locale.h>
|
2008-05-26 15:29:21 +02:00
|
|
|
|
2022-05-01 18:07:53 +02:00
|
|
|
#include <foobar2000/SDK/foobar2000.h>
|
2008-05-26 15:29:21 +02:00
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
#include "../src/vgmstream.h"
|
2023-05-14 20:17:51 +02:00
|
|
|
#include "../src/api.h"
|
2008-05-26 15:29:21 +02:00
|
|
|
}
|
|
|
|
#include "foo_vgmstream.h"
|
2017-05-18 23:03:55 +02:00
|
|
|
#include "foo_filetypes.h"
|
2008-05-26 15:29:21 +02:00
|
|
|
|
2021-08-15 19:24:48 +02:00
|
|
|
|
2021-08-07 20:41:50 +02:00
|
|
|
#include "../version.h"
|
|
|
|
#ifndef VGMSTREAM_VERSION
|
2021-08-15 19:24:48 +02:00
|
|
|
#define VGMSTREAM_VERSION "unknown version " __DATE__
|
2008-05-26 15:29:21 +02:00
|
|
|
#endif
|
|
|
|
|
2021-08-15 19:24:48 +02:00
|
|
|
#define PLUGIN_NAME "vgmstream plugin"
|
|
|
|
#define PLUGIN_VERSION VGMSTREAM_VERSION
|
|
|
|
#define PLUGIN_INFO PLUGIN_NAME " " PLUGIN_VERSION " (" __DATE__ ")"
|
|
|
|
#define PLUGIN_DESCRIPTION PLUGIN_INFO "\n" \
|
|
|
|
"by hcs, FastElbja, manakoAT, bxaimc, snakemeat, soneek, kode54, bnnm, Nicknine, Thealexbarney, CyberBotX, and many others\n" \
|
2017-05-01 14:18:33 +02:00
|
|
|
"\n" \
|
2021-08-15 19:24:48 +02:00
|
|
|
"foobar2000 plugin by Josh W, kode54, others\n" \
|
2017-05-01 14:18:33 +02:00
|
|
|
"\n" \
|
2021-08-15 19:24:48 +02:00
|
|
|
"https://github.com/vgmstream/vgmstream/\n" \
|
2017-08-16 05:06:03 +02:00
|
|
|
"https://sourceforge.net/projects/vgmstream/ (original)"
|
2010-10-27 14:28:32 +02:00
|
|
|
|
2020-07-29 19:41:17 +02:00
|
|
|
#define PLUGIN_FILENAME "foo_input_vgmstream.dll"
|
|
|
|
|
|
|
|
|
2021-08-26 18:43:17 +02:00
|
|
|
static void log_callback(int level, const char* str) {
|
|
|
|
console::formatter() /*<< "vgmstream: "*/ << str;
|
|
|
|
}
|
|
|
|
|
2019-09-21 13:29:12 +02:00
|
|
|
// called every time a file is added to the playlist (to get info) or when playing
|
2017-08-12 21:17:44 +02:00
|
|
|
input_vgmstream::input_vgmstream() {
|
|
|
|
vgmstream = NULL;
|
2018-04-15 00:54:46 +02:00
|
|
|
subsong = 0; // 0 = not set, will be properly changed on first setup_vgmstream
|
|
|
|
direct_subsong = false;
|
2019-03-23 22:50:15 +01:00
|
|
|
output_channels = 0;
|
2017-08-12 21:17:44 +02:00
|
|
|
|
|
|
|
decoding = false;
|
|
|
|
paused = 0;
|
|
|
|
decode_pos_ms = 0;
|
|
|
|
decode_pos_samples = 0;
|
2020-07-29 19:41:17 +02:00
|
|
|
length_samples = 0;
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2020-07-29 19:41:17 +02:00
|
|
|
fade_seconds = 10.0;
|
|
|
|
fade_delay_seconds = 0.0;
|
|
|
|
loop_count = 2.0;
|
2017-08-12 21:17:44 +02:00
|
|
|
loop_forever = false;
|
2024-08-10 23:26:53 +02:00
|
|
|
ignore_loop = false;
|
2017-08-19 12:35:38 +02:00
|
|
|
disable_subsongs = false;
|
2018-04-29 23:31:52 +02:00
|
|
|
downmix_channels = 0;
|
2018-11-03 22:58:59 +01:00
|
|
|
tagfile_disable = false;
|
2019-03-23 22:50:15 +01:00
|
|
|
tagfile_name = "!tags.m3u";
|
2018-11-04 00:36:28 +01:00
|
|
|
override_title = false;
|
2017-08-12 21:17:44 +02:00
|
|
|
|
|
|
|
load_settings();
|
2021-08-26 18:43:17 +02:00
|
|
|
|
|
|
|
vgmstream_set_log_callback(VGM_LOG_LEVEL_ALL, &log_callback);
|
2008-05-26 15:29:21 +02:00
|
|
|
}
|
|
|
|
|
2019-09-21 13:29:12 +02:00
|
|
|
// called on stop or when playlist info has been read
|
2017-08-12 21:17:44 +02:00
|
|
|
input_vgmstream::~input_vgmstream() {
|
|
|
|
close_vgmstream(vgmstream);
|
|
|
|
vgmstream = NULL;
|
|
|
|
}
|
2008-05-26 15:29:21 +02:00
|
|
|
|
2019-09-21 13:29:12 +02:00
|
|
|
// called first when a new file is accepted, before playing it
|
2020-07-29 19:41:17 +02:00
|
|
|
void input_vgmstream::open(service_ptr_t<file> p_filehint, const char * p_path, t_input_open_reason p_reason, abort_callback & p_abort) {
|
2008-05-26 15:29:21 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
if (!p_path) // shouldn't be possible
|
2017-08-12 21:17:44 +02:00
|
|
|
throw exception_io_data();
|
|
|
|
|
|
|
|
filename = p_path;
|
|
|
|
|
2019-09-15 15:47:41 +02:00
|
|
|
// allow non-existing files in some cases
|
2024-08-10 23:26:53 +02:00
|
|
|
bool infile_virtual = !filesystem::g_exists(p_path, p_abort) && vgmstream_is_virtual_filename(filename);
|
2019-09-15 15:47:41 +02:00
|
|
|
|
|
|
|
// don't try to open virtual files as it'll fail
|
|
|
|
// (doesn't seem to have any adverse effect, except maybe no stats)
|
|
|
|
// setup_vgmstream also makes further checks before file is finally opened
|
|
|
|
if (!infile_virtual) {
|
|
|
|
// keep file stats around (timestamp, filesize)
|
2024-08-10 23:26:53 +02:00
|
|
|
if ( p_filehint.is_empty() ) {
|
2019-09-15 15:47:41 +02:00
|
|
|
input_open_file_helper( p_filehint, filename, p_reason, p_abort );
|
2024-08-10 23:26:53 +02:00
|
|
|
}
|
2019-09-15 15:47:41 +02:00
|
|
|
stats = p_filehint->get_stats( p_abort );
|
2023-02-12 00:10:49 +01:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
uint32_t flags = stats2_legacy; //foobar2000_io.stats2_xxx, not sure about implications
|
2023-02-12 00:10:49 +01:00
|
|
|
stats2 = p_filehint->get_stats2_(flags, p_abort); // ???
|
2019-09-15 15:47:41 +02:00
|
|
|
}
|
2017-08-13 22:26:40 +02:00
|
|
|
|
|
|
|
switch(p_reason) {
|
2024-08-10 23:26:53 +02:00
|
|
|
case input_open_decode: // prepare to retrieve info and decode
|
|
|
|
case input_open_info_read: // prepare to retrieve info
|
|
|
|
// init vgmstream to get subsongs
|
|
|
|
setup_vgmstream(p_abort);
|
2017-08-12 21:17:44 +02:00
|
|
|
break;
|
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
case input_open_info_write: // prepare to retrieve info and tag
|
|
|
|
default:
|
2017-08-13 22:26:40 +02:00
|
|
|
throw exception_io_data();
|
2017-08-12 21:17:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-15 00:54:46 +02:00
|
|
|
// called after opening file (possibly per subsong too)
|
2017-08-12 21:17:44 +02:00
|
|
|
unsigned input_vgmstream::get_subsong_count() {
|
2017-08-13 22:26:40 +02:00
|
|
|
if (disable_subsongs)
|
|
|
|
return 1;
|
2024-08-10 23:26:53 +02:00
|
|
|
// If the plugin uses input_factory_t template and returns > 1 here when adding a song to the playlist,
|
|
|
|
// foobar will automagically "unpack" it by calling decode_initialize/get_info with all subsong indexes.
|
|
|
|
// There is no need to add any playlist code, only properly handle the subsong index.
|
2017-08-13 22:26:40 +02:00
|
|
|
|
|
|
|
// vgmstream ready as method is valid after open() with any reason
|
|
|
|
int subsong_count = vgmstream->num_streams;
|
|
|
|
if (subsong_count == 0)
|
|
|
|
subsong_count = 1; // most formats don't have subsongs
|
2018-04-15 00:54:46 +02:00
|
|
|
|
|
|
|
// pretend we don't have subsongs as we don't want foobar to unpack the rest
|
|
|
|
if (direct_subsong)
|
|
|
|
subsong_count = 1;
|
|
|
|
|
2017-08-13 22:26:40 +02:00
|
|
|
return subsong_count;
|
2017-08-12 21:17:44 +02:00
|
|
|
}
|
|
|
|
|
2019-09-21 13:29:12 +02:00
|
|
|
// called after get_subsong_count to play subsong N (even when count is 1)
|
2017-08-12 21:17:44 +02:00
|
|
|
t_uint32 input_vgmstream::get_subsong(unsigned p_index) {
|
2024-08-10 23:26:53 +02:00
|
|
|
// translates index (0..N < subsong_count) for vgmstream: 1=first
|
|
|
|
return p_index + 1;
|
2017-08-12 21:17:44 +02:00
|
|
|
}
|
|
|
|
|
2019-09-21 13:29:12 +02:00
|
|
|
// called before playing to get info
|
2024-08-10 23:26:53 +02:00
|
|
|
void input_vgmstream::get_info(t_uint32 p_subsong, file_info& p_info, abort_callback& p_abort) {
|
|
|
|
vgmstream_info_t v_info = {}; // init default (not {0} since it has classes)
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
query_subsong_info(p_subsong, v_info, p_abort);
|
2017-08-19 12:35:38 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
// export tags ('metadata' tab in file properties)
|
|
|
|
put_info_tags(p_info, v_info);
|
|
|
|
put_into_tagfile(p_info, p_abort);
|
2018-11-03 22:58:59 +01:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
// set technical info ('details' tab in file properties)
|
|
|
|
put_info_details(p_info, v_info);
|
|
|
|
}
|
2018-11-03 22:58:59 +01:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
void input_vgmstream::put_info_tags(file_info& p_info, vgmstream_info_t& v_info) {
|
2019-02-10 02:54:16 +01:00
|
|
|
if (!override_title) {
|
2024-08-10 23:26:53 +02:00
|
|
|
/* Shows a default (sub)song title with stream name.
|
|
|
|
* This can be overriden and extended using the exported STREAM_x below and foobar's formatting.
|
|
|
|
* foobar defaults to filename minus extension if there is no meta "title" value. */
|
|
|
|
p_info.meta_set("TITLE", v_info.title);
|
2017-08-19 12:35:38 +02:00
|
|
|
}
|
2024-08-10 23:26:53 +02:00
|
|
|
|
|
|
|
if (!v_info.stream_name.is_empty()) {
|
|
|
|
p_info.meta_set("stream_name", v_info.stream_name);
|
2024-03-31 16:21:55 +02:00
|
|
|
}
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
if (v_info.subsong_count > 1) {
|
|
|
|
p_info.meta_set("stream_count", pfc::format_int(v_info.subsong_count));
|
|
|
|
p_info.meta_set("stream_index", pfc::format_int(v_info.subsong_index == 0 ? 1 : v_info.subsong_index));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (v_info.loop_end > 0) {
|
|
|
|
p_info.meta_set("loop_start", pfc::format_int(v_info.loop_start));
|
|
|
|
p_info.meta_set("loop_end", pfc::format_int(v_info.loop_end));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void input_vgmstream::put_into_tagfile(file_info& p_info, abort_callback& p_abort) {
|
|
|
|
if (tagfile_disable)
|
|
|
|
return;
|
|
|
|
//TODO: optimize and don't parse tags again for this session (not sure how).
|
|
|
|
// Seems foobar calls get_info on every play even if the file hasn't changed,
|
|
|
|
// and won't refresh "meta" unless forced or closing playlist + exe.
|
|
|
|
|
|
|
|
//TODO: use foobar's fancy-but-arcane string functions
|
|
|
|
char tagfile_path[FOO_PATH_LIMIT];
|
|
|
|
strcpy(tagfile_path, filename);
|
|
|
|
|
|
|
|
char* path = strrchr(tagfile_path, '\\');
|
|
|
|
if (path != NULL) {
|
|
|
|
path[1] = '\0'; // includes "\", remove after that from tagfile_path
|
|
|
|
strcat(tagfile_path, tagfile_name);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// possible?
|
|
|
|
strcpy(tagfile_path, tagfile_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
STREAMFILE* sf_tags = open_foo_streamfile(tagfile_path, &p_abort, NULL);
|
|
|
|
if (sf_tags == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
VGMSTREAM_TAGS* tags;
|
|
|
|
const char *tag_key, *tag_val;
|
|
|
|
|
|
|
|
tags = vgmstream_tags_init(&tag_key, &tag_val);
|
|
|
|
|
|
|
|
vgmstream_tags_reset(tags, filename);
|
|
|
|
while (vgmstream_tags_next_tag(tags, sf_tags)) {
|
|
|
|
if (replaygain_info::g_is_meta_replaygain(tag_key)) {
|
|
|
|
p_info.info_set_replaygain(tag_key, tag_val);
|
|
|
|
// there is set_replaygain_auto/set_replaygain_ex too but no doc
|
2018-11-03 22:58:59 +01:00
|
|
|
}
|
2024-08-10 23:26:53 +02:00
|
|
|
else if (stricmp_utf8("ALBUMARTIST", tag_key) == 0) {
|
|
|
|
// normalize tag for foobar as otherwise can't understand it (though it's recognized in .ogg)
|
|
|
|
p_info.meta_set("ALBUM ARTIST", tag_val);
|
2018-11-03 22:58:59 +01:00
|
|
|
}
|
2024-08-10 23:26:53 +02:00
|
|
|
else {
|
|
|
|
p_info.meta_set(tag_key, tag_val);
|
2018-11-03 22:58:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
vgmstream_tags_close(tags);
|
|
|
|
close_streamfile(sf_tags);
|
|
|
|
}
|
|
|
|
|
|
|
|
// include main info, note that order doesn't matter (foobar sorts by fixed order + name)
|
|
|
|
void input_vgmstream::put_info_details(file_info& p_info, vgmstream_info_t& v_info) {
|
|
|
|
p_info.info_set("vgmstream_version", PLUGIN_VERSION); //to make clearer vgmsrteam is actually opening the file
|
|
|
|
|
|
|
|
// not quite accurate but some people are confused by "lossless"
|
|
|
|
// (could set lossless if PCM, but then again in vgm may be converted/"lossy" vs original source)
|
|
|
|
p_info.info_set("encoding", "lossy/lossless");
|
|
|
|
|
|
|
|
p_info.info_set_int("channels", v_info.channels);
|
|
|
|
p_info.info_set_int("samplerate", v_info.sample_rate);
|
|
|
|
p_info.info_set_int("bitspersample", v_info.bits_per_sample);
|
|
|
|
p_info.info_set_bitrate(v_info.bitrate / 1000);
|
|
|
|
|
|
|
|
if (v_info.input_channels > 0 && v_info.channels != v_info.input_channels) {
|
|
|
|
p_info.info_set_int("input_channels", v_info.input_channels);
|
|
|
|
//p_info.info_set_int("output_channels", v_info.output_channels);
|
|
|
|
}
|
|
|
|
|
|
|
|
p_info.set_length(v_info.play_length_s);
|
2018-11-03 22:58:59 +01:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
if (v_info.stream_samples > 0) { // ?
|
|
|
|
p_info.info_set_int("samples", v_info.stream_samples);
|
2017-05-19 17:44:15 +02:00
|
|
|
}
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
if (v_info.loop_end > 0) {
|
|
|
|
p_info.info_set_int("loop_start", v_info.loop_start);
|
|
|
|
p_info.info_set_int("loop_end", v_info.loop_end);
|
|
|
|
if (!v_info.loop_flag)
|
|
|
|
p_info.info_set("looping", "disabled");
|
|
|
|
}
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
p_info.info_set("codec", v_info.codec_name);
|
|
|
|
p_info.info_set("layout", v_info.layout_name);
|
|
|
|
p_info.info_set("metadata", v_info.meta_name);
|
2019-03-02 15:57:41 +01:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
if (!v_info.stream_name.is_empty()) {
|
|
|
|
p_info.info_set("stream_name", v_info.stream_name);
|
|
|
|
}
|
2019-03-23 22:50:15 +01:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
if (v_info.subsong_count > 1) {
|
|
|
|
p_info.info_set_int("stream_count", v_info.subsong_count);
|
|
|
|
p_info.info_set_int("stream_index", v_info.subsong_index);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!v_info.channel_mask.is_empty()) {
|
|
|
|
p_info.info_set("channel_mask", v_info.channel_mask);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
// for >2ch foobar writes info like "Channels - 8: FL FR FC LFE BL BR FCL FCR", which may be undesirable?
|
|
|
|
if (v_info.channel_layout > 0) {
|
|
|
|
// there is info_set_channels_ex in newer SDKs too
|
|
|
|
p_info.info_set_wfx_chanMask(v_info.channel_layout);
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
//if (v_info.interleave > 0) p_info.info_set("interleave", v_info.interleave);
|
|
|
|
//if (v_info.interleave_last > 0) p_info.info_set("interleave_last_block", v_info.interleave_last);
|
|
|
|
//if (v_info.block_size > 0) p_info.info_set("block_size", v_info.block_size);
|
2017-08-12 21:17:44 +02:00
|
|
|
}
|
2017-05-19 17:44:15 +02:00
|
|
|
|
2017-08-12 21:17:44 +02:00
|
|
|
t_filestats input_vgmstream::get_file_stats(abort_callback & p_abort) {
|
|
|
|
return stats;
|
2010-02-26 08:45:06 +01:00
|
|
|
}
|
2008-05-26 15:29:21 +02:00
|
|
|
|
2023-02-12 00:10:49 +01:00
|
|
|
t_filestats2 input_vgmstream::get_stats2(uint32_t f, abort_callback & p_abort) {
|
|
|
|
return stats2;
|
|
|
|
}
|
|
|
|
|
2018-04-15 00:54:46 +02:00
|
|
|
// called right before actually playing (decoding) a song/subsong
|
2017-08-12 21:17:44 +02:00
|
|
|
void input_vgmstream::decode_initialize(t_uint32 p_subsong, unsigned p_flags, abort_callback & p_abort) {
|
|
|
|
|
2018-04-15 00:54:46 +02:00
|
|
|
// if subsong changes recreate vgmstream
|
|
|
|
if (subsong != p_subsong && !direct_subsong) {
|
2017-08-13 22:26:40 +02:00
|
|
|
subsong = p_subsong;
|
|
|
|
setup_vgmstream(p_abort);
|
|
|
|
}
|
|
|
|
|
2020-08-05 21:17:34 +02:00
|
|
|
// "don't loop forever" flag (set when converting to file, scanning for replaygain, etc)
|
|
|
|
// flag is set *after* loading vgmstream + applying config so manually disable
|
|
|
|
bool force_ignore_loop = !!(p_flags & input_flag_no_looping);
|
|
|
|
if (force_ignore_loop) // could always set but vgmstream is re-created on play start
|
|
|
|
vgmstream_set_play_forever(vgmstream, 0);
|
|
|
|
|
|
|
|
decode_seek(0, p_abort);
|
2010-02-26 08:45:06 +01:00
|
|
|
};
|
2008-05-26 15:29:21 +02:00
|
|
|
|
2019-09-21 13:29:12 +02:00
|
|
|
// called when audio buffer needs to be filled
|
2020-07-29 19:41:17 +02:00
|
|
|
bool input_vgmstream::decode_run(audio_chunk & p_chunk, abort_callback & p_abort) {
|
2024-08-10 23:26:53 +02:00
|
|
|
if (!decoding)
|
|
|
|
return false;
|
|
|
|
if (!vgmstream)
|
|
|
|
return false;
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2019-03-23 22:50:15 +01:00
|
|
|
int max_buffer_samples = SAMPLE_BUFFER_SIZE;
|
2017-11-10 20:26:44 +01:00
|
|
|
int samples_to_do = max_buffer_samples;
|
|
|
|
t_size bytes;
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2017-11-10 20:26:44 +01:00
|
|
|
{
|
2020-07-29 19:41:17 +02:00
|
|
|
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;
|
2017-08-12 21:17:44 +02:00
|
|
|
else
|
2019-03-23 22:50:15 +01:00
|
|
|
samples_to_do = max_buffer_samples;
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2020-07-29 19:41:17 +02:00
|
|
|
if (samples_to_do == 0) { /*< DECODE_SIZE*/
|
2017-08-12 21:17:44 +02:00
|
|
|
decoding = false;
|
2018-07-15 01:15:08 +02:00
|
|
|
return false; /* EOF, didn't decode samples in this call */
|
2017-08-12 21:17:44 +02:00
|
|
|
}
|
|
|
|
|
2020-07-29 19:41:17 +02:00
|
|
|
render_vgmstream(sample_buffer, samples_to_do, vgmstream);
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2019-03-23 22:50:15 +01:00
|
|
|
unsigned channel_config = vgmstream->channel_layout;
|
|
|
|
if (!channel_config)
|
|
|
|
channel_config = audio_chunk::g_guess_channel_config(output_channels);
|
2020-07-29 19:41:17 +02:00
|
|
|
|
|
|
|
bytes = (samples_to_do * output_channels * sizeof(sample_buffer[0]));
|
2019-03-23 22:50:15 +01:00
|
|
|
p_chunk.set_data_fixedpoint((char*)sample_buffer, bytes, vgmstream->sample_rate, output_channels, 16, channel_config);
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2020-07-29 19:41:17 +02:00
|
|
|
decode_pos_samples += samples_to_do;
|
|
|
|
decode_pos_ms = decode_pos_samples * 1000LL / vgmstream->sample_rate;
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2018-07-15 01:15:08 +02:00
|
|
|
return true; /* decoded in this call (sample_buffer or less) */
|
2017-08-12 21:17:44 +02:00
|
|
|
}
|
2010-02-26 08:45:06 +01:00
|
|
|
}
|
2008-05-26 15:29:21 +02:00
|
|
|
|
2019-09-21 13:29:12 +02:00
|
|
|
// called when seeking
|
2024-08-10 23:26:53 +02:00
|
|
|
void input_vgmstream::decode_seek(double p_seconds, abort_callback& p_abort) {
|
|
|
|
int64_t seek_sample = (int64_t)audio_math::time_to_samples(p_seconds, vgmstream->sample_rate);
|
2020-07-29 19:41:17 +02:00
|
|
|
bool play_forever = vgmstream_get_play_forever(vgmstream);
|
2008-05-26 15:29:21 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
// TODO: check play position after seek_sample and let seek clamp
|
2019-02-10 17:57:34 +01:00
|
|
|
// possible when disabling looping without refreshing foobar's cached song length
|
2020-07-29 19:41:17 +02:00
|
|
|
// (p_seconds can't go over seek bar with infinite looping on, though)
|
|
|
|
if (seek_sample > length_samples)
|
|
|
|
seek_sample = length_samples;
|
2010-02-26 08:45:06 +01:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
seek_vgmstream(vgmstream, (int32_t)seek_sample);
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2020-07-29 19:41:17 +02:00
|
|
|
decode_pos_samples = seek_sample;
|
2019-03-23 22:50:15 +01:00
|
|
|
decode_pos_ms = decode_pos_samples * 1000LL / vgmstream->sample_rate;
|
2020-07-29 19:41:17 +02:00
|
|
|
decoding = play_forever || decode_pos_samples < length_samples;
|
2010-02-26 08:45:06 +01:00
|
|
|
}
|
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
bool input_vgmstream::decode_can_seek() { return true; }
|
|
|
|
bool input_vgmstream::decode_get_dynamic_info(file_info& p_out, double& p_timestamp_delta) { return false; }
|
|
|
|
bool input_vgmstream::decode_get_dynamic_info_track(file_info& p_out, double& p_timestamp_delta) { return false; }
|
|
|
|
void input_vgmstream::decode_on_idle(abort_callback& p_abort) { /*m_file->on_idle(p_abort);*/ }
|
2010-02-26 08:45:06 +01:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
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();*/ }
|
|
|
|
void input_vgmstream::remove_tags(abort_callback& p_abort) { /*throw exception_io_data();*/ }
|
2010-02-26 08:45:06 +01:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
bool input_vgmstream::g_is_our_content_type(const char* p_content_type) { return false; }
|
2019-09-21 13:29:12 +02:00
|
|
|
|
|
|
|
// called to check if file can be processed by the plugin
|
2024-08-10 23:26:53 +02:00
|
|
|
bool input_vgmstream::g_is_our_path(const char* p_path, const char* p_extension) {
|
2019-09-30 00:56:29 +02:00
|
|
|
vgmstream_ctx_valid_cfg cfg = {0};
|
2017-01-03 13:34:20 +01:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
cfg.is_extension = true;
|
2019-09-30 00:56:29 +02:00
|
|
|
input_vgmstream::g_load_cfg(&cfg.accept_unknown, &cfg.accept_common);
|
|
|
|
return vgmstream_ctx_is_valid(p_extension, &cfg) > 0 ? true : false;
|
2010-02-26 08:45:06 +01:00
|
|
|
}
|
2008-05-26 15:29:21 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
|
2019-09-21 13:29:12 +02:00
|
|
|
// internal util to create a VGMSTREAM
|
2024-08-10 23:26:53 +02:00
|
|
|
VGMSTREAM* input_vgmstream::init_vgmstream_foo(t_uint32 p_subsong, const char* const filename, abort_callback& p_abort) {
|
2020-07-29 19:41:17 +02:00
|
|
|
VGMSTREAM* vgmstream = NULL;
|
|
|
|
|
2023-08-19 23:35:28 +02:00
|
|
|
/* Workaround for a foobar bug (mainly for complex TXTP):
|
|
|
|
* When converting to .ogg foobar calls oggenc, that calls setlocale(LC_ALL, "") to use system's locale.
|
|
|
|
* After that, text parsing using sscanf that expects US locale for "N.N" decimals fails in some locales,
|
|
|
|
* so reset it here just in case
|
|
|
|
* (maybe should be done on lib and/or restore original locale but it's not common to change it in C) */
|
|
|
|
//const char* original_locale = setlocale(LC_ALL, NULL);
|
|
|
|
setlocale(LC_ALL, "C");
|
|
|
|
|
2020-07-29 19:41:17 +02:00
|
|
|
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);
|
2017-08-12 21:17:44 +02:00
|
|
|
}
|
2023-08-19 23:35:28 +02:00
|
|
|
|
|
|
|
//setlocale(LC_ALL, original_locale);
|
2017-08-12 21:17:44 +02:00
|
|
|
return vgmstream;
|
|
|
|
}
|
2008-05-26 15:29:21 +02:00
|
|
|
|
2019-09-21 13:29:12 +02:00
|
|
|
// internal util to initialize vgmstream
|
2017-08-12 21:17:44 +02:00
|
|
|
void input_vgmstream::setup_vgmstream(abort_callback & p_abort) {
|
|
|
|
// close first in case of changing subsongs
|
|
|
|
if (vgmstream) {
|
|
|
|
close_vgmstream(vgmstream);
|
|
|
|
}
|
|
|
|
|
|
|
|
// subsong and filename are always defined before this
|
2017-08-13 22:26:40 +02:00
|
|
|
vgmstream = init_vgmstream_foo(subsong, filename, p_abort);
|
2024-08-10 23:26:53 +02:00
|
|
|
if (!vgmstream)
|
2017-08-12 21:17:44 +02:00
|
|
|
throw exception_io_data();
|
|
|
|
|
2018-04-15 00:54:46 +02:00
|
|
|
// default subsong is 0, meaning first init (vgmstream should open first stream, but not set stream_index).
|
|
|
|
// if the stream_index is already set, then the subsong was opened directly by some means (txtp, playlist, etc).
|
|
|
|
// add flag as in that case we only want to play the subsong but not unpack subsongs (which foobar does by default).
|
|
|
|
if (subsong == 0 && vgmstream->stream_index > 0) {
|
|
|
|
subsong = vgmstream->stream_index;
|
|
|
|
direct_subsong = true;
|
|
|
|
}
|
|
|
|
if (subsong == 0)
|
|
|
|
subsong = 1;
|
|
|
|
|
2020-07-29 19:41:17 +02:00
|
|
|
apply_config(vgmstream);
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2019-03-23 22:50:15 +01:00
|
|
|
/* 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);
|
|
|
|
vgmstream_mixing_enable(vgmstream, SAMPLE_BUFFER_SIZE, NULL /*&input_channels*/, &output_channels);
|
2019-03-24 01:21:09 +01:00
|
|
|
|
2017-08-12 21:17:44 +02:00
|
|
|
decode_pos_ms = 0;
|
|
|
|
decode_pos_samples = 0;
|
|
|
|
paused = 0;
|
2020-07-29 19:41:17 +02:00
|
|
|
length_samples = vgmstream_get_samples(vgmstream);
|
2017-08-12 21:17:44 +02:00
|
|
|
}
|
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
void input_vgmstream::apply_config(VGMSTREAM* vgmstream) {
|
|
|
|
vgmstream_cfg_t vcfg = {0};
|
|
|
|
|
|
|
|
vcfg.allow_play_forever = true;
|
|
|
|
vcfg.play_forever = loop_forever;
|
|
|
|
vcfg.loop_count = loop_count;
|
|
|
|
vcfg.fade_time = fade_seconds;
|
|
|
|
vcfg.fade_delay = fade_delay_seconds;
|
|
|
|
vcfg.ignore_loop = ignore_loop;
|
|
|
|
|
|
|
|
vgmstream_apply_config(vgmstream, &vcfg);
|
|
|
|
}
|
|
|
|
|
2019-09-21 13:29:12 +02:00
|
|
|
// internal util to get info
|
2024-08-10 23:26:53 +02:00
|
|
|
void input_vgmstream::query_subsong_info(t_uint32 p_subsong, vgmstream_info_t& v_info, abort_callback& p_abort) {
|
2020-07-29 19:41:17 +02:00
|
|
|
VGMSTREAM* infostream = NULL;
|
2018-09-08 01:22:49 +02:00
|
|
|
bool is_infostream = false;
|
2019-03-23 22:50:15 +01:00
|
|
|
int info_channels;
|
2024-08-10 23:26:53 +02:00
|
|
|
char temp[1024];
|
2017-08-13 22:26:40 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
// Reuse current vgmstream if not querying a new subsong.
|
|
|
|
// If it's a direct subsong then subsong may be N while p_subsong = 1
|
|
|
|
// so there is no need to recreate the infostream, only one subsong is used.
|
2018-04-15 00:54:46 +02:00
|
|
|
if (subsong != p_subsong && !direct_subsong) {
|
2017-08-13 22:26:40 +02:00
|
|
|
infostream = init_vgmstream_foo(p_subsong, filename, p_abort);
|
2024-08-10 23:26:53 +02:00
|
|
|
if (!infostream)
|
2018-12-17 11:00:01 +01:00
|
|
|
throw exception_io_data();
|
2020-07-29 19:41:17 +02:00
|
|
|
|
2018-09-08 01:22:49 +02:00
|
|
|
is_infostream = true;
|
2019-03-24 01:21:09 +01:00
|
|
|
|
2020-07-29 19:41:17 +02:00
|
|
|
apply_config(infostream);
|
|
|
|
|
2019-03-23 22:50:15 +01:00
|
|
|
vgmstream_mixing_autodownmix(infostream, downmix_channels);
|
|
|
|
vgmstream_mixing_enable(infostream, 0, NULL /*&input_channels*/, &info_channels);
|
2024-08-10 23:26:53 +02:00
|
|
|
}
|
|
|
|
else {
|
2017-08-13 22:26:40 +02:00
|
|
|
// vgmstream ready as get_info is valid after open() with any reason
|
|
|
|
infostream = vgmstream;
|
2019-03-23 22:50:15 +01:00
|
|
|
info_channels = output_channels;
|
2017-08-13 22:26:40 +02:00
|
|
|
}
|
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
if (!infostream)
|
|
|
|
throw exception_io_data();
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
/* basic info */
|
|
|
|
{
|
|
|
|
v_info.channels = info_channels;
|
|
|
|
v_info.input_channels = infostream->channels;
|
|
|
|
v_info.sample_rate = infostream->sample_rate;
|
|
|
|
v_info.stream_samples = infostream->num_samples;
|
|
|
|
v_info.bitrate = get_vgmstream_average_bitrate(infostream);
|
|
|
|
v_info.loop_flag = infostream->loop_flag;
|
|
|
|
v_info.loop_start = infostream->loop_start_sample;
|
|
|
|
v_info.loop_end = infostream->loop_end_sample;
|
|
|
|
|
|
|
|
v_info.subsong_count = infostream->num_streams;
|
|
|
|
v_info.subsong_index = infostream->stream_index;
|
|
|
|
if (v_info.subsong_index == 0)
|
|
|
|
v_info.subsong_index = 1;
|
|
|
|
|
|
|
|
v_info.channel_layout = infostream->channel_layout;
|
|
|
|
|
|
|
|
int64_t play_duration = vgmstream_get_samples(infostream);
|
|
|
|
v_info.play_length_s = (double)play_duration / (double)infostream->sample_rate;
|
|
|
|
|
|
|
|
v_info.bits_per_sample = sizeof(short) * 8;
|
|
|
|
}
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
// formatted info
|
|
|
|
{
|
|
|
|
pfc::string8 description;
|
2019-03-23 22:50:15 +01:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
describe_vgmstream(infostream, temp, sizeof(temp));
|
|
|
|
description = temp;
|
|
|
|
|
|
|
|
query_description_tag(v_info.codec_name, description, "encoding: ");
|
|
|
|
query_description_tag(v_info.layout_name, description, "layout: ");
|
|
|
|
query_description_tag(v_info.meta_name, description, "metadata from: ");
|
|
|
|
query_description_tag(v_info.stream_name, description, "stream name: ");
|
|
|
|
query_description_tag(v_info.channel_mask, description, "channel mask: ");
|
2017-08-12 21:17:44 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
//query_description_tag(temp, description,"interleave: ",' ');
|
|
|
|
//query_description_tag(temp, description,"interleave last block:",' ');
|
|
|
|
//query_description_tag(temp, description,"block size: ");
|
|
|
|
}
|
2019-02-10 02:54:16 +01:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
// infostream gets added with index 0 (other) or 1 (current)
|
|
|
|
{
|
2020-11-02 01:07:39 +01:00
|
|
|
vgmstream_title_t tcfg = {0};
|
2024-08-10 23:26:53 +02:00
|
|
|
tcfg.remove_extension = true;
|
|
|
|
tcfg.remove_archive = true;
|
2017-08-19 12:35:38 +02:00
|
|
|
|
2020-11-02 01:07:39 +01:00
|
|
|
const char* filename_str = filename;
|
|
|
|
vgmstream_get_title(temp, sizeof(temp), filename_str, infostream, &tcfg);
|
2024-08-10 23:26:53 +02:00
|
|
|
v_info.title = temp;
|
2017-08-12 21:17:44 +02:00
|
|
|
}
|
|
|
|
|
2017-08-13 22:26:40 +02:00
|
|
|
// and only close if was querying a new subsong
|
2018-09-08 01:22:49 +02:00
|
|
|
if (is_infostream) {
|
2017-08-13 22:26:40 +02:00
|
|
|
close_vgmstream(infostream);
|
|
|
|
infostream = NULL;
|
|
|
|
}
|
2017-08-12 21:17:44 +02:00
|
|
|
}
|
2008-05-26 15:29:21 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
// extract a "tag" from the description string
|
|
|
|
bool input_vgmstream::query_description_tag(pfc::string_base& tag_value, pfc::string_base const& description, const char* tag_key, char delimiter) {
|
2008-05-26 15:29:21 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
t_size pos = description.find_first(tag_key);
|
|
|
|
if (pos == pfc::infinite_size)
|
|
|
|
return false;
|
|
|
|
pos += strlen(tag_key);
|
2018-09-08 01:22:49 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
t_size eos = description.find_first(delimiter, pos);
|
|
|
|
if (eos == pfc::infinite_size)
|
|
|
|
eos = description.length();
|
2020-06-21 00:33:21 +02:00
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
tag_value.set_string(description + pos, eos - pos);
|
|
|
|
return true;
|
2018-09-08 01:22:49 +02:00
|
|
|
}
|
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
|
|
|
|
// checks priority (foobar 1.4+)
|
|
|
|
bool input_vgmstream::g_is_low_merit() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// foobar recognizes plugin with this (meaning, different GUID = different plugin)
|
2019-09-21 13:29:12 +02:00
|
|
|
GUID input_vgmstream::g_get_guid() {
|
2017-12-31 04:15:34 +01:00
|
|
|
static const GUID guid = { 0x9e7263c7, 0x4cdd, 0x482c,{ 0x9a, 0xec, 0x5e, 0x71, 0x28, 0xcb, 0xc3, 0x4 } };
|
|
|
|
return guid;
|
|
|
|
}
|
|
|
|
|
2019-09-21 13:29:12 +02:00
|
|
|
GUID input_vgmstream::g_get_preferences_guid() {
|
2020-07-29 19:41:17 +02:00
|
|
|
static const GUID guid = { 0x2b5d0302, 0x165b, 0x409c,{ 0x94, 0x74, 0x2c, 0x8c, 0x2c, 0xd7, 0x6a, 0x25 } };
|
2017-12-31 04:15:34 +01:00
|
|
|
return guid;
|
|
|
|
}
|
|
|
|
|
2024-08-10 23:26:53 +02:00
|
|
|
const char* input_vgmstream::g_get_name() {
|
|
|
|
return "vgmstream";
|
2017-12-31 04:15:34 +01:00
|
|
|
}
|
2008-05-26 15:29:21 +02:00
|
|
|
|
2019-09-21 13:29:12 +02:00
|
|
|
// foobar plugin defs
|
2017-08-12 21:17:44 +02:00
|
|
|
static input_factory_t<input_vgmstream> g_input_vgmstream_factory;
|
2008-05-26 15:29:21 +02:00
|
|
|
|
2021-08-15 19:24:48 +02:00
|
|
|
DECLARE_COMPONENT_VERSION(PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_DESCRIPTION);
|
2020-07-29 19:41:17 +02:00
|
|
|
VALIDATE_COMPONENT_FILENAME(PLUGIN_FILENAME);
|