diff --git a/fb2k/foo_filetypes.h b/fb2k/foo_filetypes.h index c7e2c1ac..aec135e0 100644 --- a/fb2k/foo_filetypes.h +++ b/fb2k/foo_filetypes.h @@ -1,28 +1,32 @@ -#ifndef _FOO_FILETYPES_H_ -#define _FOO_FILETYPES_H_ - -class input_file_type_v2_impl_vgmstream : public input_file_type_v2 { -public: - input_file_type_v2_impl_vgmstream() { - ext_list = vgmstream_get_formats(&ext_list_len); - } - unsigned get_count() { return ext_list_len; } - bool is_associatable(unsigned idx) { return true; } - void get_format_name(unsigned idx, pfc::string_base & out, bool isPlural) { - out.reset(); - pfc::stringToUpperAppend(out, ext_list[idx], pfc::strlen_utf8(ext_list[idx])); - out += " Audio File"; - if (isPlural) out += "s"; - } - void get_extensions(unsigned idx, pfc::string_base & out) { - out = ext_list[idx]; - } - -private: - const char ** ext_list; - size_t ext_list_len; -}; - -namespace { static service_factory_single_t g_filetypes; } - -#endif /*_FOO_FILETYPES_H_ */ +#ifndef _FOO_FILETYPES_H_ +#define _FOO_FILETYPES_H_ + +class input_file_type_v2_impl_vgmstream : public input_file_type_v2 { +public: + input_file_type_v2_impl_vgmstream() { + ext_list = vgmstream_get_formats(&ext_list_len); + } + + unsigned get_count() { return ext_list_len; } + + bool is_associatable(unsigned idx) { return true; } + + void get_format_name(unsigned idx, pfc::string_base & out, bool isPlural) { + out.reset(); + pfc::stringToUpperAppend(out, ext_list[idx], pfc::strlen_utf8(ext_list[idx])); + out += " Audio File"; + if (isPlural) out += "s"; + } + + void get_extensions(unsigned idx, pfc::string_base & out) { + out = ext_list[idx]; + } + +private: + const char ** ext_list; + size_t ext_list_len; +}; + +namespace { static service_factory_single_t g_filetypes; } + +#endif /*_FOO_FILETYPES_H_ */ diff --git a/fb2k/foo_prefs.cpp b/fb2k/foo_prefs.cpp index 97e338f7..1673da67 100755 --- a/fb2k/foo_prefs.cpp +++ b/fb2k/foo_prefs.cpp @@ -5,10 +5,6 @@ #include #include "foo_prefs.h" -extern "C" { -#include "../src/vgmstream.h" -#include "../src/util.h" -} #include "foo_vgmstream.h" @@ -27,13 +23,13 @@ static cfg_bool cfg_ExtsCommonOn ({0x405af423,0x5037,0x4eae,{0xa6,0xe3,0x // Needs to be here in rder to access the static config void input_vgmstream::load_settings() { // no verification needed here, as it is done below - sscanf(cfg_FadeLength.get_ptr(),"%lf",&fade_seconds); - sscanf(cfg_LoopCount.get_ptr(),"%lf",&loop_count); - sscanf(cfg_FadeDelay.get_ptr(),"%lf",&fade_delay_seconds); + sscanf(cfg_FadeLength.get_ptr(), "%lf", &fade_seconds); + sscanf(cfg_LoopCount.get_ptr(), "%lf", &loop_count); + sscanf(cfg_FadeDelay.get_ptr(), "%lf", &fade_delay_seconds); loop_forever = cfg_LoopForever; ignore_loop = cfg_IgnoreLoop; disable_subsongs = cfg_DisableSubsongs; - sscanf(cfg_DownmixChannels.get_ptr(),"%d",&downmix_channels); + sscanf(cfg_DownmixChannels.get_ptr(), "%d", &downmix_channels); tagfile_disable = cfg_TagfileDisable; override_title = cfg_OverrideTitle; //exts_unknown_on = cfg_ExtsUnknownOn; @@ -43,13 +39,13 @@ void input_vgmstream::load_settings() { if (loop_count <= 0) loop_count = 1; } -void input_vgmstream::g_load_cfg(int *accept_unknown, int *accept_common) { - //todo improve - *accept_unknown = cfg_ExtsUnknownOn ? 1 : 0; - *accept_common = cfg_ExtsCommonOn ? 1 : 0; +void input_vgmstream::g_load_cfg(int* accept_unknown, int* accept_common) { + //TODO improve + *accept_unknown = cfg_ExtsUnknownOn; + *accept_common = cfg_ExtsCommonOn; } -const char * vgmstream_prefs::get_name() { +const char* vgmstream_prefs::get_name() { return input_vgmstream::g_get_name(); } @@ -61,50 +57,55 @@ GUID vgmstream_prefs::get_parent_guid() { return guid_input; } +static UINT get_check(bool value) { + return value ? BST_CHECKED : BST_UNCHECKED; +} + BOOL vgmstreamPreferences::OnInitDialog(CWindow, LPARAM) { - CheckDlgButton(IDC_LOOP_FOREVER, cfg_LoopForever?BST_CHECKED:BST_UNCHECKED); - CheckDlgButton(IDC_IGNORE_LOOP, cfg_IgnoreLoop?BST_CHECKED:BST_UNCHECKED); - CheckDlgButton(IDC_LOOP_NORMALLY, (!cfg_IgnoreLoop && !cfg_LoopForever)?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(IDC_LOOP_FOREVER, get_check(cfg_LoopForever)); + CheckDlgButton(IDC_IGNORE_LOOP, get_check(cfg_IgnoreLoop)); + CheckDlgButton(IDC_LOOP_NORMALLY, get_check(!cfg_IgnoreLoop && !cfg_LoopForever)); uSetDlgItemText(m_hWnd, IDC_LOOP_COUNT, cfg_LoopCount); uSetDlgItemText(m_hWnd, IDC_FADE_SECONDS, cfg_FadeLength); uSetDlgItemText(m_hWnd, IDC_FADE_DELAY_SECONDS, cfg_FadeDelay); - CheckDlgButton(IDC_DISABLE_SUBSONGS, cfg_DisableSubsongs?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(IDC_DISABLE_SUBSONGS, get_check(cfg_DisableSubsongs)); uSetDlgItemText(m_hWnd, IDC_DOWNMIX_CHANNELS, cfg_DownmixChannels); - CheckDlgButton(IDC_TAGFILE_DISABLE, cfg_TagfileDisable?BST_CHECKED:BST_UNCHECKED); - CheckDlgButton(IDC_OVERRIDE_TITLE, cfg_OverrideTitle?BST_CHECKED:BST_UNCHECKED); - CheckDlgButton(IDC_EXTS_UNKNOWN_ON, cfg_ExtsUnknownOn?BST_CHECKED:BST_UNCHECKED); - CheckDlgButton(IDC_EXTS_COMMON_ON, cfg_ExtsCommonOn?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(IDC_TAGFILE_DISABLE, get_check(cfg_TagfileDisable)); + CheckDlgButton(IDC_OVERRIDE_TITLE, get_check(cfg_OverrideTitle)); + CheckDlgButton(IDC_EXTS_UNKNOWN_ON, get_check(cfg_ExtsUnknownOn)); + CheckDlgButton(IDC_EXTS_COMMON_ON, get_check(cfg_ExtsCommonOn)); return TRUE; } t_uint32 vgmstreamPreferences::get_state() { t_uint32 state = preferences_state::resettable; - if (HasChanged()) state |= preferences_state::changed; + if (HasChanged()) + state |= preferences_state::changed; return state; } void vgmstreamPreferences::reset() { - CheckDlgButton(IDC_LOOP_FOREVER, DEFAULT_LOOP_FOREVER?BST_CHECKED:BST_UNCHECKED); - CheckDlgButton(IDC_IGNORE_LOOP, DEFAULT_IGNORE_LOOP?BST_CHECKED:BST_UNCHECKED); - CheckDlgButton(IDC_LOOP_NORMALLY, (!DEFAULT_IGNORE_LOOP && !DEFAULT_LOOP_FOREVER)?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(IDC_LOOP_FOREVER, get_check(DEFAULT_LOOP_FOREVER)); + CheckDlgButton(IDC_IGNORE_LOOP, get_check(DEFAULT_IGNORE_LOOP)); + CheckDlgButton(IDC_LOOP_NORMALLY, get_check(!DEFAULT_IGNORE_LOOP && !DEFAULT_LOOP_FOREVER)); uSetDlgItemText(m_hWnd, IDC_LOOP_COUNT, DEFAULT_LOOP_COUNT); uSetDlgItemText(m_hWnd, IDC_FADE_SECONDS, DEFAULT_FADE_SECONDS); uSetDlgItemText(m_hWnd, IDC_FADE_DELAY_SECONDS, DEFAULT_FADE_DELAY_SECONDS); - CheckDlgButton(IDC_DISABLE_SUBSONGS, DEFAULT_DISABLE_SUBSONGS?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(IDC_DISABLE_SUBSONGS, get_check(DEFAULT_DISABLE_SUBSONGS)); uSetDlgItemText(m_hWnd, IDC_DOWNMIX_CHANNELS, DEFAULT_DOWNMIX_CHANNELS); - CheckDlgButton(IDC_TAGFILE_DISABLE, DEFAULT_TAGFILE_DISABLE?BST_CHECKED:BST_UNCHECKED); - CheckDlgButton(IDC_OVERRIDE_TITLE, DEFAULT_OVERRIDE_TITLE?BST_CHECKED:BST_UNCHECKED); - CheckDlgButton(IDC_EXTS_UNKNOWN_ON, DEFAULT_EXTS_UNKNOWN_ON?BST_CHECKED:BST_UNCHECKED); - CheckDlgButton(IDC_EXTS_COMMON_ON, DEFAULT_EXTS_COMMON_ON?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(IDC_TAGFILE_DISABLE, get_check(DEFAULT_TAGFILE_DISABLE)); + CheckDlgButton(IDC_OVERRIDE_TITLE, get_check(DEFAULT_OVERRIDE_TITLE)); + CheckDlgButton(IDC_EXTS_UNKNOWN_ON, get_check(DEFAULT_EXTS_UNKNOWN_ON)); + CheckDlgButton(IDC_EXTS_COMMON_ON, get_check(DEFAULT_EXTS_COMMON_ON)); } @@ -125,9 +126,8 @@ void vgmstreamPreferences::apply() { pfc::string buf; buf = uGetDlgItemText(m_hWnd, IDC_FADE_SECONDS); - if (sscanf(buf.get_ptr(),"%lf%n",&temp_fade_seconds,&consumed)<1 - || consumed!=strlen(buf.get_ptr()) || - temp_fade_seconds<0) { + if (sscanf(buf.get_ptr(), "%lf%n", &temp_fade_seconds, &consumed) < 1 + || consumed != strlen(buf.get_ptr()) || temp_fade_seconds < 0) { uMessageBox(m_hWnd, "Invalid value for Fade Length\n" "Must be a number greater than or equal to zero", @@ -136,9 +136,8 @@ void vgmstreamPreferences::apply() { } else cfg_FadeLength = buf.get_ptr(); buf = uGetDlgItemText(m_hWnd, IDC_LOOP_COUNT); - if (sscanf(buf.get_ptr(),"%lf%n",&temp_loop_count,&consumed)<1 - || consumed!=strlen(buf.get_ptr()) || - temp_loop_count<0) { + if (sscanf(buf.get_ptr(), "%lf%n", &temp_loop_count, &consumed) < 1 + || consumed != strlen(buf.get_ptr()) || temp_loop_count < 0) { uMessageBox(m_hWnd, "Invalid value for Loop Count\n" "Must be a number greater than or equal to zero", @@ -147,24 +146,22 @@ void vgmstreamPreferences::apply() { } else cfg_LoopCount = buf.get_ptr(); buf = uGetDlgItemText(m_hWnd, IDC_FADE_DELAY_SECONDS); - if (sscanf(buf.get_ptr(),"%lf%n",&temp_fade_delay_seconds,&consumed)<1 - || consumed!=strlen(buf.get_ptr()) || - temp_fade_delay_seconds<0) { + if (sscanf(buf.get_ptr(), "%lf%n", &temp_fade_delay_seconds, &consumed) < 1 + || consumed != strlen(buf.get_ptr()) || temp_fade_delay_seconds < 0) { uMessageBox(m_hWnd, "Invalid value for Fade Delay\n" "Must be a number", - "Error",MB_OK|MB_ICONERROR); + "Error", MB_OK|MB_ICONERROR); return; } else cfg_FadeDelay = buf.get_ptr(); buf = uGetDlgItemText(m_hWnd, IDC_DOWNMIX_CHANNELS); - if (sscanf(buf.get_ptr(),"%d%n",&temp_downmix_channels,&consumed)<1 - || consumed!=strlen(buf.get_ptr()) || - temp_downmix_channels<0) { + if (sscanf(buf.get_ptr(), "%d%n", &temp_downmix_channels, &consumed) < 1 + || consumed != strlen(buf.get_ptr()) || temp_downmix_channels < 0) { uMessageBox(m_hWnd, "Invalid value for Downmix Channels\n" "Must be a number greater than or equal to zero", - "Error",MB_OK|MB_ICONERROR); + "Error", MB_OK|MB_ICONERROR); return; } else cfg_DownmixChannels = buf.get_ptr(); diff --git a/fb2k/foo_streamfile.cpp b/fb2k/foo_streamfile.cpp index c4aa0a84..574fba55 100644 --- a/fb2k/foo_streamfile.cpp +++ b/fb2k/foo_streamfile.cpp @@ -115,12 +115,15 @@ static size_t foo_read(FOO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t l sf->offset = offset; /* last fread offset */ return read_total; } + static size_t foo_get_size(FOO_STREAMFILE* sf) { return sf->file_size; } + static offv_t foo_get_offset(FOO_STREAMFILE* sf) { return sf->offset; } + static void foo_get_name(FOO_STREAMFILE* sf, char* name, size_t name_size) { int copy_size = sf->name_len + 1; if (copy_size > name_size) @@ -138,6 +141,7 @@ static void foo_get_name(FOO_STREAMFILE* sf, char* name, size_t name_size) { } */ } + static void foo_close(FOO_STREAMFILE* sf) { sf->m_file.release(); //release alloc'ed ptr free(sf->name); @@ -247,11 +251,11 @@ static STREAMFILE* open_foo_streamfile_buffer_by_file(service_ptr_t m_file if (strncmp(filename, "unpack", 6) == 0) { const char* archfile_ptr = strrchr(this_sf->name, '|'); if (archfile_ptr) - this_sf->archfile_end = (intptr_t)archfile_ptr + 1 - (intptr_t)this_sf->name; // after "|"" + this_sf->archfile_end = (int)((intptr_t)archfile_ptr + 1 - (intptr_t)this_sf->name); // after "|"" const char* archpath_ptr = strrchr(this_sf->name, '\\'); if (archpath_ptr) - this_sf->archpath_end = (intptr_t)archpath_ptr + 1 - (intptr_t)this_sf->name; // after "\\" + this_sf->archpath_end = (int)((intptr_t)archpath_ptr + 1 - (intptr_t)this_sf->name); // after "\\" if (this_sf->archpath_end <= 0 || this_sf->archfile_end <= 0 || this_sf->archpath_end > this_sf->archfile_end || this_sf->archfile_end > this_sf->name_len || this_sf->archfile_end >= PATH_LIMIT) { diff --git a/fb2k/foo_vgmstream.cpp b/fb2k/foo_vgmstream.cpp index 5fc3443b..b30ef60d 100644 --- a/fb2k/foo_vgmstream.cpp +++ b/fb2k/foo_vgmstream.cpp @@ -59,7 +59,7 @@ input_vgmstream::input_vgmstream() { fade_delay_seconds = 0.0; loop_count = 2.0; loop_forever = false; - ignore_loop = 0; + ignore_loop = false; disable_subsongs = false; downmix_channels = 0; tagfile_disable = false; @@ -80,53 +80,48 @@ input_vgmstream::~input_vgmstream() { // called first when a new file is accepted, before playing it void input_vgmstream::open(service_ptr_t p_filehint, const char * p_path, t_input_open_reason p_reason, abort_callback & p_abort) { - if (!p_path) { // shouldn't be possible + if (!p_path) // shouldn't be possible throw exception_io_data(); - return; //??? - } filename = p_path; // allow non-existing files in some cases - bool infile_virtual = !filesystem::g_exists(p_path, p_abort) - && vgmstream_is_virtual_filename(filename) == 1; + bool infile_virtual = !filesystem::g_exists(p_path, p_abort) && vgmstream_is_virtual_filename(filename); // 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) - if ( p_filehint.is_empty() ) + if ( p_filehint.is_empty() ) { input_open_file_helper( p_filehint, filename, p_reason, p_abort ); + } stats = p_filehint->get_stats( p_abort ); - uint32_t flags = stats2_legacy; //foobar2000_io.stats2_xxx, not sure about the implications + uint32_t flags = stats2_legacy; //foobar2000_io.stats2_xxx, not sure about implications stats2 = p_filehint->get_stats2_(flags, p_abort); // ??? } switch(p_reason) { - case input_open_decode: // prepare to retrieve info and decode - case input_open_info_read: // prepare to retrieve info - setup_vgmstream(p_abort); // must init vgmstream to get subsongs + 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); break; - case input_open_info_write: // prepare to retrieve info and tag + case input_open_info_write: // prepare to retrieve info and tag + default: throw exception_io_data(); - break; - - default: // nothing else should be possible - throw exception_io_data(); - break; } } // called after opening file (possibly per subsong too) unsigned input_vgmstream::get_subsong_count() { - // 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. if (disable_subsongs) return 1; + // 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. // vgmstream ready as method is valid after open() with any reason int subsong_count = vgmstream->num_streams; @@ -142,117 +137,155 @@ unsigned input_vgmstream::get_subsong_count() { // called after get_subsong_count to play subsong N (even when count is 1) t_uint32 input_vgmstream::get_subsong(unsigned p_index) { - return p_index + 1; // translates index (0..N < subsong_count) for vgmstream: 1=first + // translates index (0..N < subsong_count) for vgmstream: 1=first + return p_index + 1; } // called before playing to get info -void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_callback & p_abort) { - int length_in_ms=0, channels = 0, samplerate = 0; - int total_samples = -1; - int bitrate = 0; - int loop_flag = -1, loop_start = -1, loop_end = -1; - pfc::string8 description; - pfc::string8_fast temp; +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) - get_subsong_info(p_subsong, temp, &length_in_ms, &total_samples, &loop_flag, &loop_start, &loop_end, &samplerate, &channels, &bitrate, description, p_abort); + query_subsong_info(p_subsong, v_info, p_abort); + // export tags ('metadata' tab in file properties) + put_info_tags(p_info, v_info); + put_into_tagfile(p_info, p_abort); - /* set tag info (metadata tab in file properties) */ + // set technical info ('details' tab in file properties) + put_info_details(p_info, v_info); +} - /* Shows a custom subsong title by default with subsong name, to simplify for average users. - * 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. */ +void input_vgmstream::put_info_tags(file_info& p_info, vgmstream_info_t& v_info) { if (!override_title) { - p_info.meta_set("TITLE",temp); - } - if (get_description_tag(temp,description,"stream count: ")) p_info.meta_set("stream_count",temp); - if (get_description_tag(temp,description,"stream index: ")) p_info.meta_set("stream_index",temp); - if (get_description_tag(temp,description,"stream name: ")) p_info.meta_set("stream_name",temp); - if (loop_end) { - p_info.meta_set("loop_start", pfc::format_int(loop_start)); - p_info.meta_set("loop_end", pfc::format_int(loop_end)); - // has extra text info - //if (get_description_tag(temp,description,"loop start: ")) p_info.meta_set("loop_start",temp); - //if (get_description_tag(temp,description,"loop end: ")) p_info.meta_set("loop_end",temp); + /* 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); } - /* get external file tags */ - //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 changes, and won't refresh "meta" - // unless forced or closing playlist+exe - if (!tagfile_disable) { - //todo use foobar's fancy-but-arcane string functions - char tagfile_path[PATH_LIMIT]; - strcpy(tagfile_path, filename); + if (!v_info.stream_name.is_empty()) { + p_info.meta_set("stream_name", v_info.stream_name); + } - char *path = strrchr(tagfile_path,'\\'); - if (path!=NULL) { - path[1] = '\0'; /* includes "\", remove after that from tagfile_path */ - strcat(tagfile_path,tagfile_name); + 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 } - else { /* ??? */ - strcpy(tagfile_path,tagfile_name); + 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); } - - 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, 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 */ - } - else if (stricmp_utf8("ALBUMARTIST", tag_key) == 0) - /* normalize as foobar won't handle (though it's accepted in .ogg) */ - p_info.meta_set("ALBUM ARTIST", tag_val); - else { - p_info.meta_set(tag_key, tag_val); - } - } - vgmstream_tags_close(tags); - close_streamfile(sf_tags); + else { + p_info.meta_set(tag_key, tag_val); } } + vgmstream_tags_close(tags); + close_streamfile(sf_tags); +} - /* set technical info (details tab in file properties) */ +// 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 - 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); - /* 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"); - p_info.info_set_bitrate(bitrate / 1000); - if (total_samples > 0) - p_info.info_set_int("stream_total_samples", total_samples); - if (loop_start >= 0 && loop_end > loop_start) { - if (!loop_flag) p_info.info_set("looping", "disabled"); - p_info.info_set_int("loop_start", loop_start); - p_info.info_set_int("loop_end", loop_end); + // 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(((double)length_in_ms)/1000); - if (get_description_tag(temp,description,"encoding: ")) p_info.info_set("codec",temp); - if (get_description_tag(temp,description,"layout: ")) p_info.info_set("layout",temp); - if (get_description_tag(temp,description,"interleave: ",' ')) p_info.info_set("interleave",temp); - if (get_description_tag(temp,description,"interleave last block:",' ')) p_info.info_set("interleave_last_block",temp); + p_info.set_length(v_info.play_length_s); - if (get_description_tag(temp,description,"block size: ")) p_info.info_set("block_size",temp); - if (get_description_tag(temp,description,"metadata from: ")) p_info.info_set("metadata_source",temp); - if (get_description_tag(temp,description,"stream count: ")) p_info.info_set("stream_count",temp); - if (get_description_tag(temp,description,"stream index: ")) p_info.info_set("stream_index",temp); - if (get_description_tag(temp,description,"stream name: ")) p_info.info_set("stream_name",temp); + if (v_info.stream_samples > 0) { // ? + p_info.info_set_int("samples", v_info.stream_samples); + } - if (get_description_tag(temp,description,"channel mask: ")) p_info.info_set("channel_mask",temp); - if (get_description_tag(temp,description,"output channels: ")) p_info.info_set("output_channels",temp); - if (get_description_tag(temp,description,"input channels: ")) p_info.info_set("input_channels",temp); + 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"); + } + 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); + + if (!v_info.stream_name.is_empty()) { + p_info.info_set("stream_name", v_info.stream_name); + } + + 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); } t_filestats input_vgmstream::get_file_stats(abort_callback & p_abort) { @@ -283,8 +316,10 @@ 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) { - if (!decoding) return false; - if (!vgmstream) return false; + if (!decoding) + return false; + if (!vgmstream) + return false; int max_buffer_samples = SAMPLE_BUFFER_SIZE; int samples_to_do = max_buffer_samples; @@ -319,44 +354,46 @@ bool input_vgmstream::decode_run(audio_chunk & p_chunk, abort_callback & p_abort } // called when seeking -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); +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); bool play_forever = vgmstream_get_play_forever(vgmstream); + // TODO: check play position after seek_sample and let seek clamp // possible when disabling looping without refreshing foobar's cached song length // (p_seconds can't go over seek bar with infinite looping on, though) if (seek_sample > length_samples) seek_sample = length_samples; - seek_vgmstream(vgmstream, seek_sample); + seek_vgmstream(vgmstream, (int32_t)seek_sample); decode_pos_samples = seek_sample; decode_pos_ms = decode_pos_samples * 1000LL / vgmstream->sample_rate; decoding = play_forever || decode_pos_samples < length_samples; } -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);*/} +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);*/ } -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();*/ } +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();*/ } -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) { +bool input_vgmstream::g_is_our_path(const char* p_path, const char* p_extension) { vgmstream_ctx_valid_cfg cfg = {0}; - cfg.is_extension = 1; + cfg.is_extension = true; input_vgmstream::g_load_cfg(&cfg.accept_unknown, &cfg.accept_common); return vgmstream_ctx_is_valid(p_extension, &cfg) > 0 ? true : false; } + // 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* input_vgmstream::init_vgmstream_foo(t_uint32 p_subsong, const char* const filename, abort_callback& p_abort) { VGMSTREAM* vgmstream = NULL; /* Workaround for a foobar bug (mainly for complex TXTP): @@ -387,10 +424,8 @@ void input_vgmstream::setup_vgmstream(abort_callback & p_abort) { // subsong and filename are always defined before this vgmstream = init_vgmstream_foo(subsong, filename, p_abort); - if (!vgmstream) { + if (!vgmstream) throw exception_io_data(); - return; - } // 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). @@ -414,92 +449,10 @@ void input_vgmstream::setup_vgmstream(abort_callback & p_abort) { 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; - bool is_infostream = false; - char temp[1024]; - int info_channels; - - // reuse current vgmstream if not querying a new subsong - // if it's a direct subsong then subsong may be N while p_subsong 1 - // there is no need to recreate the infostream, there is only one subsong used - if (subsong != p_subsong && !direct_subsong) { - infostream = init_vgmstream_foo(p_subsong, filename, p_abort); - if (!infostream) { - throw exception_io_data(); - } - - 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; - info_channels = output_channels; - } - - - if (length_in_ms) { - *length_in_ms = -1000; - if (infostream) { - *channels = info_channels; - *sample_rate = infostream->sample_rate; - *total_samples = infostream->num_samples; - *bitrate = get_vgmstream_average_bitrate(infostream); - *loop_flag = infostream->loop_flag; - *loop_start = infostream->loop_start_sample; - *loop_end = infostream->loop_end_sample; - - int num_samples = vgmstream_get_samples(infostream); - *length_in_ms = num_samples*1000LL / infostream->sample_rate; - - describe_vgmstream(infostream, temp, sizeof(temp)); - description = temp; - } - } - - - /* infostream gets added with index 0 (other) or 1 (current) */ - if (infostream && title) { - vgmstream_title_t tcfg = {0}; - tcfg.remove_extension = 1; - tcfg.remove_archive = 1; - - const char* filename_str = filename; - vgmstream_get_title(temp, sizeof(temp), filename_str, infostream, &tcfg); - title = temp; - } - - // and only close if was querying a new subsong - if (is_infostream) { - close_vgmstream(infostream); - infostream = NULL; - } -} - -bool input_vgmstream::get_description_tag(pfc::string_base & temp, pfc::string_base const& description, const char *tag, char delimiter) { - // extract a "tag" from the description string - t_size pos = description.find_first(tag); - t_size eos; - if (pos != pfc::infinite_size) { - pos += strlen(tag); - eos = description.find_first(delimiter, pos); - if (eos == pfc::infinite_size) eos = description.length(); - temp.set_string(description + pos, eos - pos); - //console::formatter() << "tag=" << tag << ", delim=" << delimiter << "temp=" << temp << ", pos=" << pos << "" << eos; - return true; - } - return false; -} - void input_vgmstream::apply_config(VGMSTREAM* vgmstream) { vgmstream_cfg_t vcfg = {0}; - vcfg.allow_play_forever = 1; + vcfg.allow_play_forever = true; vcfg.play_forever = loop_forever; vcfg.loop_count = loop_count; vcfg.fade_time = fade_seconds; @@ -509,23 +462,132 @@ void input_vgmstream::apply_config(VGMSTREAM* vgmstream) { vgmstream_apply_config(vgmstream, &vcfg); } +// internal util to get info +void input_vgmstream::query_subsong_info(t_uint32 p_subsong, vgmstream_info_t& v_info, abort_callback& p_abort) { + VGMSTREAM* infostream = NULL; + bool is_infostream = false; + int info_channels; + char temp[1024]; + + // 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. + if (subsong != p_subsong && !direct_subsong) { + infostream = init_vgmstream_foo(p_subsong, filename, p_abort); + if (!infostream) + throw exception_io_data(); + + 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; + info_channels = output_channels; + } + + if (!infostream) + throw exception_io_data(); + + /* 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; + } + + // formatted info + { + pfc::string8 description; + + 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: "); + + //query_description_tag(temp, description,"interleave: ",' '); + //query_description_tag(temp, description,"interleave last block:",' '); + //query_description_tag(temp, description,"block size: "); + } + + // infostream gets added with index 0 (other) or 1 (current) + { + vgmstream_title_t tcfg = {0}; + tcfg.remove_extension = true; + tcfg.remove_archive = true; + + const char* filename_str = filename; + vgmstream_get_title(temp, sizeof(temp), filename_str, infostream, &tcfg); + v_info.title = temp; + } + + // and only close if was querying a new subsong + if (is_infostream) { + close_vgmstream(infostream); + infostream = NULL; + } +} + +// 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) { + + t_size pos = description.find_first(tag_key); + if (pos == pfc::infinite_size) + return false; + pos += strlen(tag_key); + + t_size eos = description.find_first(delimiter, pos); + if (eos == pfc::infinite_size) + eos = description.length(); + + tag_value.set_string(description + pos, eos - pos); + return true; +} + + +// checks priority (foobar 1.4+) +bool input_vgmstream::g_is_low_merit() { + return true; +} + +// foobar recognizes plugin with this (meaning, different GUID = different plugin) GUID input_vgmstream::g_get_guid() { static const GUID guid = { 0x9e7263c7, 0x4cdd, 0x482c,{ 0x9a, 0xec, 0x5e, 0x71, 0x28, 0xcb, 0xc3, 0x4 } }; return guid; } -const char * input_vgmstream::g_get_name() { - return "vgmstream"; -} - GUID input_vgmstream::g_get_preferences_guid() { static const GUID guid = { 0x2b5d0302, 0x165b, 0x409c,{ 0x94, 0x74, 0x2c, 0x8c, 0x2c, 0xd7, 0x6a, 0x25 } }; return guid; } -// checks priority (foobar 1.4+) -bool input_vgmstream::g_is_low_merit() { - return true; +const char* input_vgmstream::g_get_name() { + return "vgmstream"; } // foobar plugin defs diff --git a/fb2k/foo_vgmstream.h b/fb2k/foo_vgmstream.h index e3f0ef30..e59feb51 100644 --- a/fb2k/foo_vgmstream.h +++ b/fb2k/foo_vgmstream.h @@ -1,12 +1,41 @@ #ifndef _FOO_VGMSTREAM_ #define _FOO_VGMSTREAM_ -#define SAMPLE_BUFFER_SIZE 1024 +#define SAMPLE_BUFFER_SIZE 1024 +#define FOO_PATH_LIMIT 4096 /* see vgmstream_limits.h*/ extern "C" { #include "../src/vgmstream.h" } +typedef struct { + pfc::string8_fast title; + pfc::string8_fast stream_name; + pfc::string8_fast layout_name; + pfc::string8_fast codec_name; + pfc::string8_fast meta_name; + pfc::string8_fast channel_mask; + + int channels; + int sample_rate; + + int input_channels; + int subsong_count; + int subsong_index; + + int bits_per_sample; + + int64_t stream_samples; + int64_t loop_start; + int64_t loop_end; + bool loop_flag; + + int bitrate; + uint32_t channel_layout; + + double play_length_s; + +} vgmstream_info_t; class input_vgmstream : public input_stubs { public: @@ -65,7 +94,7 @@ class input_vgmstream : public input_stubs { double fade_delay_seconds; double loop_count; bool loop_forever; - int ignore_loop; + bool ignore_loop; bool disable_subsongs; int downmix_channels; @@ -76,13 +105,19 @@ class input_vgmstream : public input_stubs { //bool exts_unknown_on; /* helpers */ + void load_settings(); + + void put_info_tags(file_info& p_info, vgmstream_info_t& v_info); + void put_into_tagfile(file_info& p_info, abort_callback& p_abort); + void put_info_details(file_info& p_info, vgmstream_info_t& v_info); + 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 apply_config(VGMSTREAM* vgmstream); + void query_subsong_info(t_uint32 p_subsong, vgmstream_info_t& v_info, abort_callback& p_abort); + bool query_description_tag(pfc::string_base& temp, pfc::string_base const& description, const char* tag, char delimiter = '\n'); + static void g_load_cfg(int* accept_unknown, int* accept_common); };