123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893 |
- /*
- * xmp-openmpt.cpp
- * ---------------
- * Purpose: libopenmpt xmplay input plugin implementation
- * Notes : (currently none)
- * Authors: OpenMPT Devs
- * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
- */
- #ifndef NO_XMPLAY
- #ifndef _CRT_SECURE_NO_WARNINGS
- #define _CRT_SECURE_NO_WARNINGS
- #endif
- #if !defined(WINVER) && !defined(_WIN32_WINDOWS)
- #ifndef _WIN32_WINNT
- #define _WIN32_WINNT 0x0501 // _WIN32_WINNT_WINXP
- #endif
- #endif
- #if !defined(MPT_BUILD_RETRO)
- #if defined(_MSC_VER)
- #define MPT_WITH_MFC
- #endif
- #else
- #if defined(_WIN32_WINNT)
- #if (_WIN32_WINNT >= 0x0501)
- #if defined(_MSC_VER)
- #define MPT_WITH_MFC
- #endif
- #endif
- #endif
- #endif
- #if defined(MPT_WITH_MFC)
- #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS // Avoid binary bloat from linking unused MFC controls
- #endif // MPT_WITH_MFC
- #ifndef NOMINMAX
- #define NOMINMAX
- #endif
- #if defined(MPT_WITH_MFC)
- #include <afxwin.h>
- #include <afxcmn.h>
- #endif // MPT_WITH_MFC
- #include <windows.h>
- #include <WindowsX.h>
- #ifdef LIBOPENMPT_BUILD_DLL
- #undef LIBOPENMPT_BUILD_DLL
- #endif
- #ifdef _MSC_VER
- #ifndef _CRT_SECURE_NO_WARNINGS
- #define _CRT_SECURE_NO_WARNINGS
- #endif
- #ifndef _SCL_SECURE_NO_WARNINGS
- #define _SCL_SECURE_NO_WARNINGS
- #endif
- #endif // _MSC_VER
- #include <cctype>
- #include <cstring>
- #include <tchar.h>
- #include "libopenmpt.hpp"
- #include "libopenmpt_ext.hpp"
- #include "libopenmpt_plugin_settings.hpp"
- #include "libopenmpt_plugin_gui.hpp"
- #include "svn_version.h"
- #if defined(OPENMPT_VERSION_REVISION)
- static const char * xmp_openmpt_string = "OpenMPT (" OPENMPT_API_VERSION_STRING "." OPENMPT_API_VERSION_STRINGIZE(OPENMPT_VERSION_REVISION) ")";
- #else
- static const char * xmp_openmpt_string = "OpenMPT (" OPENMPT_API_VERSION_STRING ")";
- #endif
- #define USE_XMPLAY_FILE_IO
- #define USE_XMPLAY_ISTREAM
- #include "xmplay/xmpin.h"
- // Shortcut block assigned to the OpenMPT plugin by un4seen.
- enum {
- openmpt_shortcut_first = 0x21000,
- openmpt_shortcut_tempo_decrease = openmpt_shortcut_first,
- openmpt_shortcut_tempo_increase,
- openmpt_shortcut_pitch_decrease,
- openmpt_shortcut_pitch_increase,
- openmpt_shortcut_switch_interpolation,
- openmpt_shortcut_last = 0x21fff,
- openmpt_shortcut_ex = 0x80000000, // Use extended version of the shortcut callback
- };
- #include <algorithm>
- #include <fstream>
- #include <iomanip>
- #include <iostream>
- #include <iterator>
- #include <map>
- #include <queue>
- #include <sstream>
- #include <string>
- #include <cmath>
- #include <pugixml.hpp>
- #define SHORT_TITLE "xmp-openmpt"
- #define SHORTER_TITLE "openmpt"
- static CRITICAL_SECTION xmpopenmpt_mutex;
- class xmpopenmpt_lock {
- public:
- xmpopenmpt_lock() {
- EnterCriticalSection( &xmpopenmpt_mutex );
- }
- ~xmpopenmpt_lock() {
- LeaveCriticalSection( &xmpopenmpt_mutex );
- }
- };
- static XMPFUNC_IN * xmpfin = nullptr;
- static XMPFUNC_MISC * xmpfmisc = nullptr;
- static XMPFUNC_REGISTRY * xmpfregistry = nullptr;
- static XMPFUNC_FILE * xmpffile = nullptr;
- static XMPFUNC_TEXT * xmpftext = nullptr;
- static XMPFUNC_STATUS * xmpfstatus = nullptr;
- struct self_xmplay_t;
- static self_xmplay_t * self = 0;
- static void save_options();
- static void apply_and_save_options();
- static std::string convert_to_native( const std::string & str );
- static std::string StringEncode( const std::wstring &src, UINT codepage );
- static std::wstring StringDecode( const std::string & src, UINT codepage );
- #if defined(UNICODE)
- static std::wstring StringToWINAPI( const std::wstring & src );
- #else
- static std::string StringToWINAPI( const std::wstring & src );
- #endif
- class xmp_openmpt_settings
- : public libopenmpt::plugin::settings
- {
- protected:
- void read_setting( const std::string & key, const std::basic_string<TCHAR> & keyW, int & val ) override {
- libopenmpt::plugin::settings::read_setting( key, keyW, val );
- int storedVal = 0;
- if ( xmpfregistry->GetInt( "OpenMPT", key.c_str(), &storedVal ) ) {
- val = storedVal;
- }
- }
- void write_setting( const std::string & key, const std::basic_string<TCHAR> & /* keyW */ , int val ) override {
- if ( !xmpfregistry->SetInt( "OpenMPT", key.c_str(), &val ) ) {
- // error
- }
- // ok
- }
- public:
- xmp_openmpt_settings()
- : libopenmpt::plugin::settings(TEXT(SHORT_TITLE), false)
- {
- return;
- }
- virtual ~xmp_openmpt_settings()
- {
- return;
- }
- };
- struct self_xmplay_t {
- std::vector<float> subsong_lengths;
- std::vector<std::string> subsong_names;
- std::size_t samplerate = 48000;
- std::size_t num_channels = 2;
- xmp_openmpt_settings settings;
- openmpt::module_ext * mod = nullptr;
- bool set_format_called = false;
- openmpt::ext::pattern_vis * pattern_vis = nullptr;
- std::int32_t tempo_factor = 0, pitch_factor = 0;
- bool single_subsong_mode = false;
- self_xmplay_t() {
- settings.changed = apply_and_save_options;
- }
- void on_new_mod() {
- set_format_called = false;
- self->pattern_vis = static_cast<openmpt::ext::pattern_vis *>( self->mod->get_interface( openmpt::ext::pattern_vis_id ) );
- }
- void delete_mod() {
- if ( mod ) {
- pattern_vis = 0;
- set_format_called = false;
- delete mod;
- mod = 0;
- }
- }
- ~self_xmplay_t() {
- return;
- }
- };
- static std::string convert_to_native( const std::string & str ) {
- char * native_string = xmpftext->Utf8( str.c_str(), -1 );
- std::string result = native_string ? native_string : "";
- if ( native_string ) {
- xmpfmisc->Free( native_string );
- native_string = 0;
- }
- return result;
- }
- static std::string StringEncode( const std::wstring &src, UINT codepage )
- {
- int required_size = WideCharToMultiByte( codepage, 0, src.c_str(), -1, nullptr, 0, nullptr, nullptr);
- if(required_size <= 0)
- {
- return std::string();
- }
- std::vector<CHAR> encoded_string( required_size );
- WideCharToMultiByte( codepage, 0, src.c_str(), -1, &encoded_string[0], encoded_string.size(), nullptr, nullptr);
- return &encoded_string[0];
- }
- static std::wstring StringDecode( const std::string & src, UINT codepage )
- {
- int required_size = MultiByteToWideChar( codepage, 0, src.c_str(), -1, nullptr, 0 );
- if(required_size <= 0)
- {
- return std::wstring();
- }
- std::vector<WCHAR> decoded_string( required_size );
- MultiByteToWideChar( codepage, 0, src.c_str(), -1, &decoded_string[0], decoded_string.size() );
- return &decoded_string[0];
- }
- #if defined(UNICODE)
- static std::wstring StringToWINAPI( const std::wstring & src )
- {
- return src;
- }
- #else
- static std::string StringToWINAPI( const std::wstring & src )
- {
- return StringEncode( src, CP_ACP );
- }
- #endif
- template <typename Tstring, typename Tstring2, typename Tstring3>
- static inline Tstring StringReplace( Tstring str, const Tstring2 & oldStr_, const Tstring3 & newStr_ ) {
- std::size_t pos = 0;
- const Tstring oldStr = oldStr_;
- const Tstring newStr = newStr_;
- while ( ( pos = str.find( oldStr, pos ) ) != Tstring::npos ) {
- str.replace( pos, oldStr.length(), newStr );
- pos += newStr.length();
- }
- return str;
- }
- static std::string StringUpperCase( std::string str ) {
- std::transform( str.begin(), str.end(), str.begin(), []( char c ) { return static_cast<char>( std::toupper( c ) ); } );
- return str;
- }
- static std::string seconds_to_string( float time ) {
- std::int64_t time_ms = static_cast<std::int64_t>( time * 1000 );
- std::int64_t seconds = ( time_ms / 1000 ) % 60;
- std::int64_t minutes = ( time_ms / ( 1000 * 60 ) ) % 60;
- std::int64_t hours = ( time_ms / ( 1000 * 60 * 60 ) );
- std::ostringstream str;
- if ( hours > 0 ) {
- str << hours << ":";
- }
- str << std::setfill('0') << std::setw(2) << minutes;
- str << ":";
- str << std::setfill('0') << std::setw(2) << seconds;
- return str.str();
- }
- static void save_settings_to_map( std::map<std::string,int> & result, const libopenmpt::plugin::settings & s ) {
- result.clear();
- result[ "Samplerate_Hz" ] = s.samplerate;
- result[ "Channels" ] = s.channels;
- result[ "MasterGain_milliBel" ] = s.mastergain_millibel;
- result[ "StereoSeparation_Percent" ] = s.stereoseparation;
- result[ "RepeatCount" ] = s.repeatcount;
- result[ "InterpolationFilterLength" ] = s.interpolationfilterlength;
- result[ "UseAmigaResampler" ] = s.use_amiga_resampler;
- result[ "AmigaFilterType" ] = s.amiga_filter_type;
- result[ "VolumeRampingStrength" ] = s.ramping;
- }
- static inline void load_map_setting( const std::map<std::string,int> & map, const std::string & key, int & val ) {
- auto it = map.find( key );
- if ( it != map.end() ) {
- val = it->second;
- }
- }
- static void load_settings_from_map( libopenmpt::plugin::settings & s, const std::map<std::string,int> & map ) {
- load_map_setting( map, "Samplerate_Hz", s.samplerate );
- load_map_setting( map, "Channels", s.channels );
- load_map_setting( map, "MasterGain_milliBel", s.mastergain_millibel );
- load_map_setting( map, "StereoSeparation_Percent", s.stereoseparation );
- load_map_setting( map, "RepeatCount", s.repeatcount );
- load_map_setting( map, "InterpolationFilterLength", s.interpolationfilterlength );
- load_map_setting( map, "UseAmigaResampler", s.use_amiga_resampler );
- load_map_setting( map, "AmigaFilterType", s.amiga_filter_type );
- load_map_setting( map, "VolumeRampingStrength", s.ramping );
- }
- static void load_settings_from_xml( libopenmpt::plugin::settings & s, const std::string & xml ) {
- pugi::xml_document doc;
- doc.load_string( xml.c_str() );
- pugi::xml_node settings_node = doc.child( "settings" );
- std::map<std::string,int> map;
- for ( const auto & attr : settings_node.attributes() ) {
- map[ attr.name() ] = attr.as_int();
- }
- load_settings_from_map( s, map );
- }
- static void save_settings_to_xml( std::string & xml, const libopenmpt::plugin::settings & s ) {
- std::map<std::string,int> map;
- save_settings_to_map( map, s );
- pugi::xml_document doc;
- pugi::xml_node settings_node = doc.append_child( "settings" );
- for ( const auto & setting : map ) {
- settings_node.append_attribute( setting.first.c_str() ).set_value( setting.second );
- }
- std::ostringstream buf;
- doc.save( buf );
- xml = buf.str();
- }
- static void apply_options() {
- if ( self->mod ) {
- if ( !self->set_format_called ) {
- // SetFormat will only be called once after loading a file.
- // We cannot apply samplerate or numchannels changes afterwards during playback.
- self->samplerate = self->settings.samplerate;
- self->num_channels = self->settings.channels;
- }
- self->mod->set_repeat_count( self->settings.repeatcount );
- self->mod->set_render_param( openmpt::module::RENDER_MASTERGAIN_MILLIBEL, self->settings.mastergain_millibel );
- self->mod->set_render_param( openmpt::module::RENDER_STEREOSEPARATION_PERCENT, self->settings.stereoseparation );
- self->mod->set_render_param( openmpt::module::RENDER_INTERPOLATIONFILTER_LENGTH, self->settings.interpolationfilterlength );
- self->mod->set_render_param( openmpt::module::RENDER_VOLUMERAMPING_STRENGTH, self->settings.ramping );
- self->mod->ctl_set_boolean( "render.resampler.emulate_amiga", self->settings.use_amiga_resampler ? true : false );
- switch ( self->settings.amiga_filter_type ) {
- case 0:
- self->mod->ctl_set_text( "render.resampler.emulate_amiga_type", "auto" );
- break;
- case 1:
- self->mod->ctl_set_text( "render.resampler.emulate_amiga_type", "unfiltered" );
- break;
- case 0xA500:
- self->mod->ctl_set_text( "render.resampler.emulate_amiga_type", "a500" );
- break;
- case 0xA1200:
- self->mod->ctl_set_text( "render.resampler.emulate_amiga_type", "a1200" );
- break;
- }
- }
- }
- static void save_options() {
- self->settings.save();
- }
- static void apply_and_save_options() {
- apply_options();
- save_options();
- }
- static void reset_options() {
- self->settings = xmp_openmpt_settings();
- self->settings.changed = apply_and_save_options;
- self->settings.load();
- }
- // get config (return size of config data) (OPTIONAL)
- static DWORD WINAPI openmpt_GetConfig( void * config ) {
- std::string xml;
- save_settings_to_xml( xml, self->settings );
- if ( config ) {
- std::memcpy( config, xml.c_str(), xml.length() + 1 );
- }
- return xml.length() + 1;
- }
- // apply config (OPTIONAL)
- static void WINAPI openmpt_SetConfig( void * config, DWORD size ) {
- reset_options();
- if ( config ) {
- load_settings_from_xml( self->settings, std::string( (char*)config, (char*)config + size ) );
- apply_options();
- }
- }
- static void WINAPI ShortcutHandler( DWORD id ) {
- if ( !self->mod ) {
- return;
- }
- bool tempo_changed = false, pitch_changed = false;
- switch ( id ) {
- case openmpt_shortcut_tempo_decrease: self->tempo_factor--; tempo_changed = true; break;
- case openmpt_shortcut_tempo_increase: self->tempo_factor++; tempo_changed = true; break;
- case openmpt_shortcut_pitch_decrease: self->pitch_factor--; pitch_changed = true; break;
- case openmpt_shortcut_pitch_increase: self->pitch_factor++; pitch_changed = true; break;
- case openmpt_shortcut_switch_interpolation:
- self->settings.interpolationfilterlength *= 2;
- if ( self->settings.interpolationfilterlength > 8 ) {
- self->settings.interpolationfilterlength = 1;
- }
- apply_and_save_options();
- const char *s = nullptr;
- switch ( self->settings.interpolationfilterlength )
- {
- case 1: s = "Interpolation: Off"; break;
- case 2: s = "Interpolation: Linear"; break;
- case 4: s = "Interpolation: Cubic"; break;
- case 8: s = "Interpolation: Polyphase"; break;
- }
- if ( s ) {
- xmpfmisc->ShowBubble( s, 0 );
- }
- break;
- }
-
- self->tempo_factor = std::min( 48, std::max( -48, self->tempo_factor ) );
- self->pitch_factor = std::min( 48, std::max( -48, self->pitch_factor ) );
- const double tempo_factor = std::pow( 2.0, self->tempo_factor / 24.0 );
- const double pitch_factor = std::pow( 2.0, self->pitch_factor / 24.0 );
- if ( tempo_changed ) {
- std::ostringstream s;
- s << "Tempo: " << static_cast<std::int32_t>( 100.0 * tempo_factor ) << "%";
- xmpfmisc->ShowBubble( s.str().c_str(), 0 );
- } else if ( pitch_changed) {
- std::ostringstream s;
- s << "Pitch: ";
- if ( self->pitch_factor > 0 )
- s << "+";
- else if ( self->pitch_factor == 0 )
- s << "+/-";
- s << (self->pitch_factor * 0.5) << " semitones";
- xmpfmisc->ShowBubble( s.str().c_str(), 0 );
- }
- openmpt::ext::interactive *interactive = static_cast<openmpt::ext::interactive *>( self->mod->get_interface( openmpt::ext::interactive_id ) );
- interactive->set_tempo_factor( tempo_factor );
- interactive->set_pitch_factor( pitch_factor );
- xmpfin->SetLength( static_cast<float>( self->mod->get_duration_seconds() / tempo_factor ), TRUE );
- }
- static double timeinfo_position = 0.0;
- struct timeinfo {
- bool valid;
- double seconds;
- std::int32_t pattern;
- std::int32_t row;
- };
- static std::queue<timeinfo> timeinfos;
- static void reset_timeinfos( double position = 0.0 ) {
- while ( !timeinfos.empty() ) {
- timeinfos.pop();
- }
- timeinfo_position = position;
- }
- static void update_timeinfos( std::int32_t samplerate, std::int32_t count ) {
- timeinfo_position += (double)count / (double)samplerate;
- timeinfo info;
- info.valid = true;
- info.seconds = timeinfo_position;
- info.pattern = self->mod->get_current_pattern();
- info.row = self->mod->get_current_row();
- timeinfos.push( info );
- }
- static timeinfo current_timeinfo;
- static timeinfo lookup_timeinfo( double seconds ) {
- timeinfo info = current_timeinfo;
- #if 0
- info.seconds = timeinfo_position;
- info.pattern = self->mod->get_current_pattern();
- info.row = self->mod->get_current_row();
- #endif
- while ( timeinfos.size() > 0 && timeinfos.front().seconds <= seconds ) {
- info = timeinfos.front();
- timeinfos.pop();
- }
- current_timeinfo = info;
- return current_timeinfo;
- }
- static void clear_current_timeinfo() {
- current_timeinfo = timeinfo();
- }
- static void WINAPI openmpt_About( HWND win ) {
- std::ostringstream about;
- about << SHORT_TITLE << " version " << openmpt::string::get( "library_version" ) << " " << "(built " << openmpt::string::get( "build" ) << ")" << std::endl;
- about << " Copyright (c) 2013-2022 OpenMPT Project Developers and Contributors (https://lib.openmpt.org/)" << std::endl;
- about << " OpenMPT version " << openmpt::string::get( "core_version" ) << std::endl;
- about << std::endl;
- about << openmpt::string::get( "contact" ) << std::endl;
- about << std::endl;
- about << "Show full credits?" << std::endl;
- if ( MessageBox( win, StringToWINAPI( StringDecode( about.str(), CP_UTF8 ) ).c_str(), TEXT(SHORT_TITLE), MB_ICONINFORMATION | MB_YESNOCANCEL | MB_DEFBUTTON1 ) != IDYES ) {
- return;
- }
- std::ostringstream credits;
- credits << openmpt::string::get( "credits" );
- credits << "Additional thanks to:" << std::endl;
- credits << std::endl;
- credits << "Arseny Kapoulkine for pugixml" << std::endl;
- credits << "https://pugixml.org/" << std::endl;
- #if 1
- libopenmpt::plugin::gui_show_file_info( win, TEXT(SHORT_TITLE), StringToWINAPI( StringReplace( StringDecode( credits.str(), CP_UTF8 ), L"\n", L"\r\n" ) ) );
- #else
- MessageBox( win, StringToWINAPI( StringReplace( StringDecode( credits.str(), CP_UTF8 ), L"\n", L"\r\n" ) ).c_str(), TEXT(SHORT_TITLE), MB_OK );
- #endif
- }
- static void WINAPI openmpt_Config( HWND win ) {
- #if 1
- libopenmpt::plugin::gui_edit_settings( &self->settings, win, TEXT(SHORT_TITLE) );
- #else
- static_cast<void>(win);
- #endif
- apply_and_save_options();
- }
- #ifdef USE_XMPLAY_FILE_IO
- #ifdef USE_XMPLAY_ISTREAM
- class xmplay_streambuf : public std::streambuf {
- public:
- explicit xmplay_streambuf( XMPFILE & file );
- private:
- int_type underflow() override;
- xmplay_streambuf( const xmplay_streambuf & );
- xmplay_streambuf & operator = ( const xmplay_streambuf & );
- private:
- XMPFILE & file;
- static const std::size_t put_back = 4096;
- static const std::size_t buf_size = 65536;
- std::vector<char> buffer;
- }; // class xmplay_streambuf
- xmplay_streambuf::xmplay_streambuf( XMPFILE & file_ ) : file(file_), buffer(buf_size) {
- char * end = &buffer.front() + buffer.size();
- setg( end, end, end );
- }
- std::streambuf::int_type xmplay_streambuf::underflow() {
- if ( gptr() < egptr() ) {
- return traits_type::to_int_type( *gptr() );
- }
- char * base = &buffer.front();
- char * start = base;
- if ( eback() == base ) {
- std::size_t put_back_count = std::min( put_back, static_cast<std::size_t>( egptr() - base ) );
- std::memmove( base, egptr() - put_back_count, put_back_count );
- start += put_back_count;
- }
- std::size_t n = xmpffile->Read( file, start, buffer.size() - ( start - base ) );
- if ( n == 0 ) {
- return traits_type::eof();
- }
- setg( base, start, start + n );
- return traits_type::to_int_type( *gptr() );
- }
- class xmplay_istream : public std::istream {
- private:
- xmplay_streambuf buf;
- private:
- xmplay_istream( const xmplay_istream & );
- xmplay_istream & operator = ( const xmplay_istream & );
- public:
- xmplay_istream( XMPFILE & file ) : std::istream(&buf), buf(file) {
- return;
- }
- ~xmplay_istream() {
- return;
- }
- }; // class xmplay_istream
- // Stream for memory-based files (required for could_open_probability)
- struct xmplay_membuf : std::streambuf {
- xmplay_membuf( const char * base, size_t size ) {
- char* p( const_cast<char *>( base ) );
- setg(p, p, p + size);
- }
- };
- struct xmplay_imemstream : virtual xmplay_membuf, std::istream {
- xmplay_imemstream( const char * base, size_t size )
- : xmplay_membuf( base, size )
- , std::istream( static_cast<std::streambuf *>(this)) {
- return;
- }
- };
- #else // !USE_XMPLAY_ISTREAM
- static std::vector<char> read_XMPFILE_vector( XMPFILE & file ) {
- std::vector<char> data( xmpffile->GetSize( file ) );
- if ( data.size() != xmpffile->Read( file, data.data(), data.size() ) ) {
- return std::vector<char>();
- }
- return data;
- }
- static std::string read_XMPFILE_string( XMPFILE & file ) {
- std::vector<char> data = read_XMPFILE_vector( file );
- return std::string( data.begin(), data.end() );
- }
- #endif // USE_XMPLAY_ISTREAM
- #endif // USE_XMPLAY_FILE_IO
- static std::string string_replace( std::string str, const std::string & oldStr, const std::string & newStr ) {
- std::size_t pos = 0;
- while((pos = str.find(oldStr, pos)) != std::string::npos)
- {
- str.replace(pos, oldStr.length(), newStr);
- pos += newStr.length();
- }
- return str;
- }
- static void write_xmplay_string( char * dst, std::string src ) {
- // xmplay buffers are ~40kB, be conservative and truncate at 32kB-2
- if ( !dst ) {
- return;
- }
- src = src.substr( 0, (1<<15) - 2 );
- std::strcpy( dst, src.c_str() );
- }
- static std::string extract_date( const openmpt::module & mod ) {
- std::string result = mod.get_metadata("date");
- if ( result.empty() ) {
- // Search the sample, instrument and message texts for possible release years.
- // We'll look for things that may vaguely resemble a release year, such as 4-digit numbers
- // or 2-digit numbers with a leading apostrophe. Generally, only years between
- // 1988 (release of Ultimate SoundTracker) and the current year + 1 (safety margin) will
- // be considered.
- std::string s = " " + mod.get_metadata("message");
- auto names = mod.get_sample_names();
- for ( const auto & name : names ) {
- s += " " + name;
- }
- names = mod.get_instrument_names();
- for ( const auto & name : names ) {
- s += " " + name;
- }
- s += " ";
- int32_t best_year = 0;
- SYSTEMTIME time;
- GetSystemTime( &time );
- const int32_t current_year = time.wYear + 1;
- #define MPT_NUMERIC( x ) ( ( x >= '0' ) && ( x <= '9' ) )
- for ( auto i = s.cbegin(); i != s.cend(); ++i ) {
- std::size_t len = s.length();
- std::size_t idx = i - s.begin();
- std::size_t remaining = len - idx;
- if ( ( remaining >= 6 ) && !MPT_NUMERIC( i[0] ) && MPT_NUMERIC( i[1] ) && MPT_NUMERIC( i[2] ) && MPT_NUMERIC( i[3] ) && MPT_NUMERIC( i[4] ) && !MPT_NUMERIC( i[5] ) ) {
- // Four-digit year
- const int32_t year = ( i[1] - '0' ) * 1000 + ( i[2] - '0' ) * 100 + ( i[3] - '0' ) * 10 + ( i[4] - '0' );
- if ( year >= 1988 && year <= current_year ) {
- best_year = std::max( year, best_year );
- }
- } else if ( ( remaining >= 4 ) && ( i[0] == '\'' ) && MPT_NUMERIC( i[1] ) && MPT_NUMERIC( i[2] ) && !MPT_NUMERIC( i[3] ) ) {
- // Apostrophe + two-digit year
- const int32_t year = ( i[1] - '0' ) * 10 + ( i[2] - '0' );
- if ( year >= 88 && year <= 99 ) {
- best_year = std::max( 1900 + year, best_year );
- } else if ( year >= 00 && ( 2000 + year ) <= current_year ) {
- best_year = std::max( 2000 + year, best_year );
- }
- }
- }
- #undef MPT_NUMERIC
- if ( best_year != 0 ) {
- std::ostringstream os;
- os << best_year;
- result = os.str();
- }
- }
- return result;
- }
- static void append_xmplay_tag( std::string & tags, const std::string & tag, const std::string & val ) {
- if ( tag.empty() ) {
- return;
- }
- if ( val.empty() ) {
- return;
- }
- tags.append( tag );
- tags.append( 1, '\0' );
- tags.append( val );
- tags.append( 1, '\0' );
- }
- static char * build_xmplay_tags( const openmpt::module & mod, int32_t subsong = -1 ) {
- std::string tags;
- const std::string title = mod.get_metadata("title");
- const auto subsong_names = mod.get_subsong_names();
- auto first_subsong = subsong_names.cbegin(), last_subsong = subsong_names.cend();
- if ( subsong >= 0 && static_cast<size_t>( subsong ) < subsong_names.size() ) {
- first_subsong += subsong;
- last_subsong = first_subsong + 1;
- } else
- {
- last_subsong = first_subsong + 1;
- }
- for ( auto subsong_name = first_subsong; subsong_name != last_subsong; subsong_name++ ) {
- append_xmplay_tag( tags, "filetype", convert_to_native( StringUpperCase( mod.get_metadata( "type" ) ) ) );
- append_xmplay_tag( tags, "title", convert_to_native( ( subsong_name->empty() || subsong == -1 ) ? title : *subsong_name ) );
- append_xmplay_tag( tags, "artist", convert_to_native( mod.get_metadata( "artist" ) ) );
- append_xmplay_tag( tags, "album", convert_to_native( mod.get_metadata( "xmplay-album" ) ) ); // todo, libopenmpt does not support that
- append_xmplay_tag( tags, "date", convert_to_native( extract_date( mod ) ) );
- append_xmplay_tag( tags, "track", convert_to_native( mod.get_metadata( "xmplay-tracknumber" ) ) ); // todo, libopenmpt does not support that
- append_xmplay_tag( tags, "genre", convert_to_native( mod.get_metadata( "xmplay-genre" ) ) ); // todo, libopenmpt does not support that
- append_xmplay_tag( tags, "comment", convert_to_native( mod.get_metadata( "message" ) ) );
- tags.append( 1, '\0' );
- }
- char * result = static_cast<char*>( xmpfmisc->Alloc( tags.size() ) );
- if ( !result ) {
- return nullptr;
- }
- std::copy( tags.data(), tags.data() + tags.size(), result );
- return result;
- }
- static float * build_xmplay_length( const openmpt::module & /* mod */ ) {
- float * result = static_cast<float*>( xmpfmisc->Alloc( sizeof( float ) * self->subsong_lengths.size() ) );
- if ( !result ) {
- return nullptr;
- }
- for ( std::size_t i = 0; i < self->subsong_lengths.size(); ++i ) {
- result[i] = self->subsong_lengths[i];
- }
- return result;
- }
- static void clear_xmplay_string( char * str ) {
- if ( !str ) {
- return;
- }
- str[0] = '\0';
- }
- static std::string sanitize_xmplay_info_string( const std::string & str ) {
- std::string result;
- result.reserve(str.size());
- for ( auto c : str ) {
- switch ( c ) {
- case '\0':
- case '\t':
- case '\r':
- case '\n':
- break;
- default:
- result.push_back( c );
- break;
- }
- }
- return result;
- }
- static std::string sanitize_xmplay_multiline_info_string( const std::string & str ) {
- std::string result;
- result.reserve(str.size());
- for ( auto c : str ) {
- switch ( c ) {
- case '\0':
- case '\t':
- case '\r':
- break;
- case '\n':
- result.push_back( '\r' );
- result.push_back( '\t' );
- break;
- default:
- result.push_back( c );
- break;
- }
- }
- return result;
- }
- static std::string sanitize_xmplay_multiline_string( const std::string & str ) {
- std::string result;
- result.reserve(str.size());
- for ( auto c : str ) {
- switch ( c ) {
- case '\0':
- case '\t':
- break;
- default:
- result.push_back( c );
- break;
- }
- }
- return result;
- }
- // check if a file is playable by this plugin
- // more thorough checks can be saved for the GetFileInfo and Open functions
- static BOOL WINAPI openmpt_CheckFile( const char * filename, XMPFILE file ) {
- static_cast<void>( filename );
- try {
- #ifdef USE_XMPLAY_FILE_IO
- #ifdef USE_XMPLAY_ISTREAM
- switch ( xmpffile->GetType( file ) ) {
- case XMPFILE_TYPE_MEMORY:
- {
- xmplay_imemstream s( reinterpret_cast<const char *>( xmpffile->GetMemory( file ) ), xmpffile->GetSize( file ) );
- return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE;
- }
- break;
- case XMPFILE_TYPE_FILE:
- case XMPFILE_TYPE_NETFILE:
- case XMPFILE_TYPE_NETSTREAM:
- default:
- {
- xmplay_istream s( file );
- return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE;
- }
- break;
- }
- #else
- if ( xmpffile->GetType( file ) == XMPFILE_TYPE_MEMORY ) {
- std::string data( reinterpret_cast<const char*>( xmpffile->GetMemory( file ) ), xmpffile->GetSize( file ) );
- std::istringstream s( data );
- return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE;
- } else {
- std::string data = read_XMPFILE_string( file );
- std::istringstream s(data);
- return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE;
- }
- #endif
- #else
- std::ifstream s( filename, std::ios_base::binary );
- return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE;
- #endif
- } catch ( ... ) {
- return FALSE;
- }
- return FALSE;
- }
- static DWORD WINAPI openmpt_GetFileInfo( const char * filename, XMPFILE file, float * * length, char * * tags ) {
- static_cast<void>( filename );
- try {
- std::map< std::string, std::string > ctls
- {
- { "load.skip_plugins", "1" },
- { "load.skip_samples", "1" },
- };
- #ifdef USE_XMPLAY_FILE_IO
- #ifdef USE_XMPLAY_ISTREAM
- switch ( xmpffile->GetType( file ) ) {
- case XMPFILE_TYPE_MEMORY:
- {
- openmpt::module mod( xmpffile->GetMemory( file ), xmpffile->GetSize( file ), std::clog, ctls );
- if ( length ) {
- *length = build_xmplay_length( mod );
- }
- if ( tags ) {
- *tags = build_xmplay_tags( mod );
- }
- }
- break;
- case XMPFILE_TYPE_FILE:
- case XMPFILE_TYPE_NETFILE:
- case XMPFILE_TYPE_NETSTREAM:
- default:
- {
- xmplay_istream s( file );
- openmpt::module mod( s, std::clog, ctls );
- if ( length ) {
- *length = build_xmplay_length( mod );
- }
- if ( tags ) {
- *tags = build_xmplay_tags( mod );
- }
- }
- break;
- }
- #else
- if ( xmpffile->GetType( file ) == XMPFILE_TYPE_MEMORY ) {
- openmpt::module mod( xmpffile->GetMemory( file ), xmpffile->GetSize( file ), std::clog, ctls );
- if ( length ) {
- *length = build_xmplay_length( mod );
- }
- if ( tags ) {
- *tags = build_xmplay_tags( mod );
- }
- } else {
- openmpt::module mod( read_XMPFILE_vector( file ), std::clog, ctls );
- if ( length ) {
- *length = build_xmplay_length( mod );
- }
- if ( tags ) {
- *tags = build_xmplay_tags( mod );
- }
- }
- #endif
- #else
- std::ifstream s( filename, std::ios_base::binary );
- openmpt::module mod( s, std::clog, ctls );
- if ( length ) {
- *length = build_xmplay_length( mod );
- }
- if ( tags ) {
- *tags = build_xmplay_tags( mod );
- }
- #endif
- } catch ( ... ) {
- if ( length ) *length = nullptr;
- if ( tags ) *tags = nullptr;
- return 0;
- }
- return self->subsong_lengths.size() + XMPIN_INFO_NOSUBTAGS;
- }
- // open a file for playback
- // return: 0=failed, 1=success, 2=success and XMPlay can close the file
- static DWORD WINAPI openmpt_Open( const char * filename, XMPFILE file ) {
- static_cast<void>( filename );
- xmpopenmpt_lock guard;
- reset_options();
- try {
- std::map< std::string, std::string > ctls
- {
- { "seek.sync_samples", "1" },
- { "play.at_end", "continue" },
- };
- self->delete_mod();
- #ifdef USE_XMPLAY_FILE_IO
- #ifdef USE_XMPLAY_ISTREAM
- switch ( xmpffile->GetType( file ) ) {
- case XMPFILE_TYPE_MEMORY:
- self->mod = new openmpt::module_ext( xmpffile->GetMemory( file ), xmpffile->GetSize( file ), std::clog, ctls );
- break;
- case XMPFILE_TYPE_FILE:
- case XMPFILE_TYPE_NETFILE:
- case XMPFILE_TYPE_NETSTREAM:
- default:
- {
- xmplay_istream s( file );
- self->mod = new openmpt::module_ext( s, std::clog, ctls );
- }
- break;
- }
- #else
- if ( xmpffile->GetType( file ) == XMPFILE_TYPE_MEMORY ) {
- self->mod = new openmpt::module_ext( xmpffile->GetMemory( file ), xmpffile->GetSize( file ), std::clog, ctls );
- } else {
- self->mod = new openmpt::module_ext( read_XMPFILE_vector( file ), std::clog, ctls );
- }
- #endif
- #else
- self->mod = new openmpt::module_ext( std::ifstream( filename, std::ios_base::binary ), std::clog, ctls );
- #endif
- self->on_new_mod();
- clear_current_timeinfo();
- reset_timeinfos();
- apply_options();
- std::int32_t num_subsongs = self->mod->get_num_subsongs();
- self->subsong_lengths.resize( num_subsongs );
- for ( std::int32_t i = 0; i < num_subsongs; ++i ) {
- self->mod->select_subsong( i );
- self->subsong_lengths[i] = static_cast<float>( self->mod->get_duration_seconds() );
- }
- self->subsong_names = self->mod->get_subsong_names();
- self->mod->select_subsong( 0 );
- self->tempo_factor = 0;
- self->pitch_factor = 0;
- xmpfin->SetLength( self->subsong_lengths[0], TRUE );
- return 2;
- } catch ( ... ) {
- self->delete_mod();
- return 0;
- }
- return 0;
- }
- // close the file
- static void WINAPI openmpt_Close() {
- xmpopenmpt_lock guard;
- self->delete_mod();
- }
- // set the sample format (in=user chosen format, out=file format if different)
- static void WINAPI openmpt_SetFormat( XMPFORMAT * form ) {
- if ( !form ) {
- return;
- }
- // SetFormat will only be called once after loading a file.
- // We cannot apply samplerate or numchannels changes afterwards during playback.
- self->set_format_called = true;
- if ( !self->mod ) {
- form->rate = 0;
- form->chan = 0;
- form->res = 0;
- return;
- }
- if ( self->settings.samplerate != 0 ) {
- form->rate = self->samplerate;
- } else {
- if ( form->rate > 0 ) {
- self->samplerate = form->rate;
- } else {
- form->rate = 48000;
- self->samplerate = 48000;
- }
- }
- if ( self->settings.channels != 0 ) {
- form->chan = self->num_channels;
- } else {
- if ( form->chan > 0 ) {
- if ( form->chan > 2 ) {
- form->chan = 4;
- self->num_channels = 4;
- } else {
- self->num_channels = form->chan;
- }
- } else {
- form->chan = 2;
- self->num_channels = 2;
- }
- }
- form->res = 4; // float
- }
- // get the tags
- static char * WINAPI openmpt_GetTags() {
- if ( !self->mod ) {
- char * tags = static_cast<char*>( xmpfmisc->Alloc( 1 ) );
- tags[0] = '\0';
- return tags;
- }
- return build_xmplay_tags( *self->mod, std::max( 0, self->mod->get_selected_subsong() ) );
- }
- // get the main panel info text
- static void WINAPI openmpt_GetInfoText( char * format, char * length ) {
- if ( !self->mod ) {
- clear_xmplay_string( format );
- clear_xmplay_string( length );
- return;
- }
- if ( format ) {
- std::ostringstream str;
- str
- << StringUpperCase( self->mod->get_metadata("type") )
- << " - "
- << self->mod->get_num_channels() << " ch"
- << " - "
- << "(via " << SHORTER_TITLE << ")"
- ;
- write_xmplay_string( format, sanitize_xmplay_info_string( str.str() ) );
- }
- if ( length ) {
- std::ostringstream str;
- str
- << length
- << " - "
- << self->mod->get_num_orders() << " orders"
- ;
- write_xmplay_string( length, sanitize_xmplay_info_string( str.str() ) );
- }
- }
- // get text for "General" info window
- // separate headings and values with a tab (\t), end each line with a carriage-return (\r)
- static void WINAPI openmpt_GetGeneralInfo( char * buf ) {
- if ( !self->mod ) {
- clear_xmplay_string( buf );
- return;
- }
- std::ostringstream str;
- str << "\r";
- bool metadatainfo = false;
- if ( !self->mod->get_metadata("artist").empty() ) {
- metadatainfo = true;
- str << "Artist" << "\t" << sanitize_xmplay_info_string( self->mod->get_metadata("artist") ) << "\r";
- }
- const std::string date = extract_date( *self->mod );
- if ( !date.empty() ) {
- metadatainfo = true;
- str << "Date" << "\t" << sanitize_xmplay_info_string( date ) << "\r";
- }
- if ( metadatainfo ) {
- str << "\r";
- }
- str << "Format" << "\t" << sanitize_xmplay_info_string( self->mod->get_metadata("type") ) << " (" << sanitize_xmplay_info_string( self->mod->get_metadata("type_long") ) << ")" << "\r";
- if ( !self->mod->get_metadata("originaltype").empty() ) {
- str << "Original Type" << "\t" << sanitize_xmplay_info_string( self->mod->get_metadata("originaltype") ) << " (" << sanitize_xmplay_info_string( self->mod->get_metadata("originaltype_long") ) << ")" << "\r";
- }
- if ( !self->mod->get_metadata("container").empty() ) {
- str << "Container" << "\t" << sanitize_xmplay_info_string( self->mod->get_metadata("container") ) << " (" << sanitize_xmplay_info_string( self->mod->get_metadata("container_long") ) << ")" << "\r";
- }
- str
- << "Channels" << "\t" << self->mod->get_num_channels() << "\r"
- << "Orders" << "\t" << self->mod->get_num_orders() << "\r"
- << "Patterns" << "\t" << self->mod->get_num_patterns() << "\r";
- if ( self->mod->get_num_instruments() != 0 ) {
- str << "Instruments" << "\t" << self->mod->get_num_instruments() << "\r";
- }
- str << "Samples" << "\t" << self->mod->get_num_samples() << "\r";
- if( !self->single_subsong_mode && self->subsong_lengths.size() > 1 ) {
- for ( std::size_t i = 0; i < self->subsong_lengths.size(); ++i ) {
- str << ( i == 0 ? "Subsongs\t" : "\t" ) << (i + 1) << ". " << seconds_to_string( self->subsong_lengths[i]) << " " << self->subsong_names[i] << "\r";
- }
- }
- str
- << "\r"
- << "Tracker" << "\t" << sanitize_xmplay_info_string( self->mod->get_metadata("tracker") ) << "\r"
- << "Player" << "\t" << "xmp-openmpt" << " version " << openmpt::string::get( "library_version" ) << "\r"
- ;
- std::string warnings = self->mod->get_metadata("warnings");
- if ( !warnings.empty() ) {
- str << "Warnings" << "\t" << sanitize_xmplay_multiline_info_string( warnings ) << "\r";
- }
- str << "\r";
- write_xmplay_string( buf, str.str() );
- }
- // get text for "Message" info window
- // separate tag names and values with a tab (\t), and end each line with a carriage-return (\r)
- static void WINAPI openmpt_GetMessage( char * buf ) {
- if ( !self->mod ) {
- clear_xmplay_string( buf );
- return;
- }
- write_xmplay_string( buf, convert_to_native( sanitize_xmplay_multiline_string( string_replace( self->mod->get_metadata("message"), "\n", "\r" ) ) ) );
- }
- // Seek to a position (in granularity units)
- // return the new position in seconds (-1 = failed)
- static double WINAPI openmpt_SetPosition( DWORD pos ) {
- if ( !self->mod ) {
- return -1.0;
- }
- if ( pos == static_cast<DWORD>(static_cast<LONG>(XMPIN_POS_LOOP)) ) {
- // If the time of the loop start position is known, that should be returned, otherwise -2 can be returned to let the time run on.
- // There is currently no way to easily figure out at which time the loop restarts.
- return -2;
- } else if ( pos == static_cast<DWORD>(static_cast<LONG>(XMPIN_POS_AUTOLOOP)) ) {
- // In the auto-looping case, the function should only loop when a loop has been detected, and otherwise return -1
- // If the time of the loop start position is known, that should be returned, otherwise -2 can be returned to let the time run on.
- // There is currently no way to easily figure out at which time the loop restarts.
- return -2;
- }
- if ( pos & XMPIN_POS_SUBSONG ) {
- self->single_subsong_mode = ( pos & XMPIN_POS_SUBSONG1 ) != 0;
- const int32_t subsong = pos & 0xffff;
- try {
- self->mod->select_subsong( subsong );
- } catch ( ... ) {
- return 0.0;
- }
- openmpt::ext::interactive *interactive = static_cast<openmpt::ext::interactive *>( self->mod->get_interface( openmpt::ext::interactive_id ) );
- xmpfin->SetLength( static_cast<float>( self->subsong_lengths[ subsong ] / interactive->get_tempo_factor() ), TRUE );
- xmpfin->UpdateTitle( nullptr );
- reset_timeinfos( 0 );
- return 0.0;
- }
- double new_position = self->mod->set_position_seconds( static_cast<double>( pos ) * 0.001 );
- reset_timeinfos( new_position );
- return new_position;
- }
- // Get the seeking granularity in seconds
- static double WINAPI openmpt_GetGranularity() {
- return 0.001;
- }
- // get some sample data, always floating-point
- // count=number of floats to write (not bytes or samples)
- // return number of floats written. if it's less than requested, playback is ended...
- // so wait for more if there is more to come (use CheckCancel function to check if user wants to cancel)
- static DWORD WINAPI openmpt_Process( float * dstbuf, DWORD count ) {
- xmpopenmpt_lock guard;
- if ( !self->mod || self->num_channels == 0 ) {
- return 0;
- }
- update_timeinfos( self->samplerate, 0 );
- std::size_t frames = count / self->num_channels;
- std::size_t frames_to_render = frames;
- std::size_t frames_rendered = 0;
- while ( frames_to_render > 0 ) {
- std::size_t frames_chunk = std::min( frames_to_render, static_cast<std::size_t>( ( self->samplerate + 99 ) / 100 ) ); // 100 Hz timing info update interval
- switch ( self->num_channels ) {
- case 1:
- {
- frames_chunk = self->mod->read( self->samplerate, frames_chunk, dstbuf );
- }
- break;
- case 2:
- {
- frames_chunk = self->mod->read_interleaved_stereo( self->samplerate, frames_chunk, dstbuf );
- }
- break;
- case 4:
- {
- frames_chunk = self->mod->read_interleaved_quad( self->samplerate, frames_chunk, dstbuf );
- }
- break;
- }
- dstbuf += frames_chunk * self->num_channels;
- if ( frames_chunk == 0 ) {
- break;
- }
- update_timeinfos( self->samplerate, frames_chunk );
- frames_to_render -= frames_chunk;
- frames_rendered += frames_chunk;
- }
- if ( frames_rendered == 0 ) {
- return 0;
- }
- return frames_rendered * self->num_channels;
- }
- static void add_names( std::ostream & str, const std::string & title, const std::vector<std::string> & names, int display_offset ) {
- if ( names.size() > 0 ) {
- bool valid = false;
- for ( std::size_t i = 0; i < names.size(); i++ ) {
- if ( names[i] != "" ) {
- valid = true;
- }
- }
- if ( !valid ) {
- return;
- }
- str << title << " Names:" << "\r";
- for ( std::size_t i = 0; i < names.size(); i++ ) {
- str << std::setfill('0') << std::setw(2) << (display_offset + i) << std::setw(0) << "\t" << convert_to_native( names[i] ) << "\r";
- }
- str << "\r";
- }
- }
- static void WINAPI openmpt_GetSamples( char * buf ) {
- if ( !self->mod ) {
- clear_xmplay_string( buf );
- return;
- }
- std::ostringstream str;
- add_names( str, "Instrument", self->mod->get_instrument_names(), 1 );
- add_names( str, "Sample", self->mod->get_sample_names(), 1 );
- add_names( str, "Channel", self->mod->get_channel_names(), 1 );
- add_names( str, "Order", self->mod->get_order_names(), 0 );
- add_names( str, "Pattern", self->mod->get_pattern_names(), 0 );
- write_xmplay_string( buf, str.str() );
- }
- static DWORD WINAPI openmpt_GetSubSongs( float * length ) {
- *length = 0.0f;
- for ( auto sub_length : self->subsong_lengths ) {
- *length += sub_length;
- }
- return static_cast<DWORD>( self->subsong_lengths.size() );
- }
- enum ColorIndex
- {
- col_background = 0,
- col_unknown,
- col_text,
- col_empty,
- col_instr,
- col_vol,
- col_pitch,
- col_global,
- col_max
- };
- static ColorIndex effect_type_to_color_index( openmpt::ext::pattern_vis::effect_type effect_type ) {
- switch ( effect_type ) {
- case openmpt::ext::pattern_vis::effect_unknown: return col_unknown; break;
- case openmpt::ext::pattern_vis::effect_general: return col_text ; break;
- case openmpt::ext::pattern_vis::effect_global : return col_global ; break;
- case openmpt::ext::pattern_vis::effect_volume : return col_vol ; break;
- case openmpt::ext::pattern_vis::effect_panning: return col_instr ; break;
- case openmpt::ext::pattern_vis::effect_pitch : return col_pitch ; break;
- default: return col_unknown; break;
- }
- }
- const struct Columns
- {
- int num_chars;
- int color;
- } pattern_columns[] = {
- { 3, col_text }, // C-5
- { 2, col_instr }, // 01
- { 3, col_vol }, // v64
- { 3, col_pitch }, // EFF
- };
- static const int max_cols = 4;
- static void assure_width( std::string & str, std::size_t width ) {
- if ( str.length() == width ) {
- return;
- } else if ( str.length() < width ) {
- str += std::string( width - str.length(), ' ' );
- } else if ( str.length() > width ) {
- str = str.substr( 0, width );
- }
- }
- struct ColorRGBA
- {
- uint8_t r, g, b, a;
- };
- union Color
- {
- ColorRGBA rgba;
- COLORREF dw;
- };
- static_assert(sizeof(Color) == 4);
- HDC visDC;
- HGDIOBJ visbitmap;
- Color viscolors[col_max];
- HPEN vispens[col_max];
- HBRUSH visbrushs[col_max];
- HFONT visfont;
- static int last_pattern = -1;
- static Color invert_color( Color c ) {
- Color res;
- res.rgba.a = c.rgba.a;
- res.rgba.r = 255 - c.rgba.r;
- res.rgba.g = 255 - c.rgba.g;
- res.rgba.b = 255 - c.rgba.b;
- return res;
- }
- static BOOL WINAPI VisOpen(DWORD colors[3]) {
- xmpopenmpt_lock guard;
- visDC = 0;
- visbitmap = 0;
- visfont = 0;
- viscolors[col_background].dw = colors[0];
- viscolors[col_unknown].dw = colors[1];
- viscolors[col_text].dw = colors[2];
- viscolors[col_global] = invert_color( viscolors[col_background] );
- const int r = viscolors[col_text].rgba.r, g = viscolors[col_text].rgba.g, b = viscolors[col_text].rgba.b;
- viscolors[col_empty].rgba.r = static_cast<std::uint8_t>( (r + viscolors[col_background].rgba.r) / 2 );
- viscolors[col_empty].rgba.g = static_cast<std::uint8_t>( (g + viscolors[col_background].rgba.g) / 2 );
- viscolors[col_empty].rgba.b = static_cast<std::uint8_t>( (b + viscolors[col_background].rgba.b) / 2 );
- viscolors[col_empty].rgba.a = 0;
- #define MIXCOLOR(col, c1, c2, c3) { \
- viscolors[col] = viscolors[col_text]; \
- int mix = viscolors[col].rgba.c1 + 0xA0; \
- viscolors[col].rgba.c1 = static_cast<std::uint8_t>( mix ); \
- if ( mix > 0xFF ) { \
- viscolors[col].rgba.c2 = std::max( static_cast<std::uint8_t>( c2 - viscolors[col].rgba.c1 / 2 ), std::uint8_t(0) ); \
- viscolors[col].rgba.c3 = std::max( static_cast<std::uint8_t>( c3 - viscolors[col].rgba.c1 / 2 ), std::uint8_t(0) ); \
- viscolors[col].rgba.c1 = 0xFF; \
- } }
- MIXCOLOR(col_instr, g, r, b);
- MIXCOLOR(col_vol, b, r, g);
- MIXCOLOR(col_pitch, r, g, b);
- #undef MIXCOLOR
- for( int i = 0; i < col_max; ++i ) {
- vispens[i] = CreatePen( PS_SOLID, 1, viscolors[i].dw );
- visbrushs[i] = CreateSolidBrush( viscolors[i].dw );
- }
- clear_current_timeinfo();
- if ( !self->mod ) {
- return FALSE;
- }
- return TRUE;
- }
- static void WINAPI VisClose() {
- xmpopenmpt_lock guard;
- for( int i = 0; i < col_max; ++i ) {
- DeletePen( vispens[i] );
- DeleteBrush( visbrushs[i] );
- }
- DeleteFont( visfont );
- DeleteBitmap( visbitmap );
- if ( visDC ) {
- DeleteDC( visDC );
- }
- }
- static void WINAPI VisSize( HDC /* dc */ , SIZE * /* size */ ) {
- xmpopenmpt_lock guard;
- last_pattern = -1; // Force redraw
- }
- #if 0
- static BOOL WINAPI VisRender( DWORD * /* buf */ , SIZE /* size */ , DWORD /* flags */ ) {
- xmpopenmpt_lock guard;
- return FALSE;
- }
- #endif
- static int get_pattern_width( int chars_per_channel, int spaces_per_channel, int num_cols, int text_size, int channels )
- {
- int pattern_width = ((chars_per_channel * channels + 4) * text_size) + (spaces_per_channel * channels + channels - (num_cols == 1 ? 1 : 2)) * (text_size / 2);
- return pattern_width;
- }
- static BOOL WINAPI VisRenderDC( HDC dc, SIZE size, DWORD flags ) {
- xmpopenmpt_lock guard;
- RECT rect;
- if ( !visfont ) {
- // Force usage of a nice monospace font
- LOGFONT logfont;
- GetObject ( GetCurrentObject( dc, OBJ_FONT ), sizeof(logfont), &logfont );
- _tcscpy( logfont.lfFaceName, TEXT("Lucida Console") );
- visfont = CreateFontIndirect( &logfont );
- }
- SIZE text_size;
- SelectFont( dc, visfont );
- if ( GetTextExtentPoint32( dc, TEXT("W"), 1, &text_size ) == FALSE ) {
- return FALSE;
- }
- if ( flags & XMPIN_VIS_INIT ) {
- last_pattern = -1;
- }
- timeinfo info = lookup_timeinfo( xmpfstatus->GetTime() );
- if ( !info.valid ) {
- RECT bgrect;
- bgrect.top = 0;
- bgrect.left = 0;
- bgrect.right = size.cx;
- bgrect.bottom = size.cy;
- FillRect(dc, &bgrect, visbrushs[col_background]);
- return TRUE;
- }
- int pattern = info.pattern;
- int current_row = info.row;
- const std::size_t channels = self->mod->get_num_channels();
- const std::size_t rows = self->mod->get_pattern_num_rows( pattern );
- const std::size_t num_half_chars = std::max( static_cast<std::size_t>( 2 * size.cx / text_size.cx ), std::size_t(8) ) - 8;
- //const std::size_t num_rows = size.cy / text_size.cy;
- // Spaces between pattern components are half width, full space at channel end
- const std::size_t half_chars_per_channel = num_half_chars / channels;
- std::size_t chars_per_channel, spaces_per_channel;
- std::size_t num_cols;
- std::size_t col0_width = pattern_columns[0].num_chars;
- for ( num_cols = sizeof ( pattern_columns ) / sizeof ( pattern_columns[0] ); num_cols >= 1; num_cols-- ) {
- chars_per_channel = 0;
- spaces_per_channel = num_cols > 1 ? num_cols : 0; // No extra space if we only display notes
- for ( std::size_t i = 0; i < num_cols; i++ ) {
- chars_per_channel += pattern_columns[i].num_chars;
- }
- if ( half_chars_per_channel >= chars_per_channel * 2 + spaces_per_channel + 1 || num_cols == 1 ) {
- break;
- }
- }
- if ( !self->settings.vis_allow_scroll ) {
- if ( num_cols == 1 ) {
- spaces_per_channel = 0;
- while ( get_pattern_width( chars_per_channel, spaces_per_channel, num_cols, text_size.cx, channels ) > size.cx && chars_per_channel > 1 ) {
- chars_per_channel -= 1;
- }
- col0_width = chars_per_channel;
- chars_per_channel = col0_width;
- }
- }
- int pattern_width = get_pattern_width( chars_per_channel, spaces_per_channel, num_cols, text_size.cx, channels );
- int pattern_height = rows * text_size.cy;
- if ( !visDC || last_pattern != pattern ) {
- DeleteBitmap( visbitmap );
- if ( visDC ) {
- DeleteDC( visDC );
- }
- visDC = CreateCompatibleDC( dc );
- visbitmap = CreateCompatibleBitmap( dc, pattern_width, pattern_height );
- SelectBitmap( visDC, visbitmap );
- SelectBrush( visDC, vispens[col_unknown] );
- SelectBrush( visDC, visbrushs[col_background] );
- SelectFont( visDC, visfont );
- rect.top = 0;
- rect.left = 0;
- rect.right = pattern_width;
- rect.bottom = pattern_height;
- FillRect( visDC, &rect, visbrushs[col_background] );
- SetBkColor( visDC, viscolors[col_background].dw );
- POINT pos;
- pos.y = 0;
- for ( std::size_t row = 0; row < rows; row++ ) {
- pos.x = 0;
- std::ostringstream s;
- s.imbue(std::locale::classic());
- s << std::setfill('0') << std::setw(3) << row;
- const std::string rowstr = s.str();
- SetTextColor( visDC, viscolors[1].dw );
- TextOutA( visDC, pos.x, pos.y, rowstr.c_str(), rowstr.length() );
- pos.x += 4 * text_size.cx;
- for ( std::size_t channel = 0; channel < channels; ++channel ) {
- struct coldata {
- std::string text;
- bool is_empty;
- ColorIndex color;
- coldata()
- : is_empty(false)
- , color(col_unknown)
- {
- return;
- }
- };
- coldata cols[max_cols];
- for ( std::size_t col = 0; col < max_cols; ++col ) {
- switch ( col ) {
- case 0:
- cols[col].text = self->mod->format_pattern_row_channel_command( pattern, row, channel, openmpt::module::command_note );
- break;
- case 1:
- cols[col].text = self->mod->format_pattern_row_channel_command( pattern, row, channel, openmpt::module::command_instrument );
- break;
- case 2:
- cols[col].text = self->mod->format_pattern_row_channel_command( pattern, row, channel, openmpt::module::command_volumeffect )
- + self->mod->format_pattern_row_channel_command( pattern, row, channel, openmpt::module::command_volume );
- break;
- case 3:
- cols[col].text = self->mod->format_pattern_row_channel_command( pattern, row, channel, openmpt::module::command_effect )
- + self->mod->format_pattern_row_channel_command( pattern, row, channel, openmpt::module::command_parameter );
- break;
- }
- int color = pattern_columns[col].color;
- if ( self->pattern_vis && ( col == 2 || col == 3 ) ) {
- if ( col == 2 ) {
- color = effect_type_to_color_index( self->pattern_vis->get_pattern_row_channel_volume_effect_type( pattern, row, channel ) );
- }
- if ( col == 3 ) {
- color = effect_type_to_color_index( self->pattern_vis->get_pattern_row_channel_effect_type( pattern, row, channel ) );
- }
- }
- switch ( cols[col].text[0] ) {
- case ' ':
- [[fallthrough]];
- case '.':
- cols[col].is_empty = true;
- [[fallthrough]];
- case '^':
- [[fallthrough]];
- case '=':
- [[fallthrough]];
- case '~':
- color = col_empty;
- break;
- }
- cols[col].color = (ColorIndex)color;
- }
- if ( num_cols <= 3 && !cols[3].is_empty ) {
- if ( cols[2].is_empty ) {
- cols[2] = cols[3];
- } else if ( cols[0].is_empty ) {
- cols[0] = cols[3];
- }
- }
- if ( num_cols <= 2 && !cols[2].is_empty ) {
- if ( cols[0].is_empty ) {
- cols[0] = cols[2];
- }
- }
- for ( std::size_t col = 0; col < num_cols; ++col ) {
- std::size_t col_width = ( num_cols > 1 ) ? pattern_columns[col].num_chars : col0_width;
- assure_width( cols[col].text, col_width );
- SetTextColor( visDC, viscolors[cols[col].color].dw );
- TextOutA( visDC, pos.x, pos.y, cols[col].text.c_str(), cols[col].text.length() );
- pos.x += col_width * text_size.cx + text_size.cx / 2;
- }
- // Extra padding
- if ( num_cols > 1 ) {
- pos.x += text_size.cx / 2;
- }
- }
- pos.y += text_size.cy;
- }
- }
- rect.top = 0;
- rect.left = 0;
- rect.right = size.cx;
- rect.bottom = size.cy;
- FillRect( dc, &rect, visbrushs[col_background] );
- int offset_x = (size.cx - pattern_width) / 2;
- int offset_y = (size.cy - text_size.cy) / 2 - current_row * text_size.cy;
- int src_offset_x = 0;
- int src_offset_y = 0;
- if ( offset_x < 0 ) {
- src_offset_x -= offset_x;
- pattern_width = std::min( static_cast<int>( pattern_width + offset_x ), static_cast<int>( size.cx ) );
- offset_x = 0;
- }
- if ( offset_y < 0 ) {
- src_offset_y -= offset_y;
- pattern_height = std::min( static_cast<int>( pattern_height + offset_y ), static_cast<int>( size.cy ) );
- offset_y = 0;
- }
- BitBlt( dc, offset_x, offset_y, pattern_width, pattern_height, visDC, src_offset_x, src_offset_y , SRCCOPY );
- // Highlight current row
- rect.left = offset_x;
- rect.top = (size.cy - text_size.cy) / 2;
- rect.right = rect.left + pattern_width;
- rect.bottom = rect.top + text_size.cy;
- InvertRect( dc, &rect );
-
- last_pattern = pattern;
- return TRUE;
- }
- #if 0
- static void WINAPI VisButton( DWORD /* x */ , DWORD /* y */ ) {
- //xmpopenmpt_lock guard;
- }
- #endif
- static XMPIN xmpin = {
- #ifdef USE_XMPLAY_FILE_IO
- 0 |
- #else
- XMPIN_FLAG_NOXMPFILE |
- #endif
- XMPIN_FLAG_CONFIG | XMPIN_FLAG_LOOP,
- xmp_openmpt_string,
- nullptr, // "libopenmpt\0mptm/mptmz",
- openmpt_About,
- openmpt_Config,
- openmpt_CheckFile,
- openmpt_GetFileInfo,
- openmpt_Open,
- openmpt_Close,
- nullptr, // reserved
- openmpt_SetFormat,
- openmpt_GetTags,
- openmpt_GetInfoText,
- openmpt_GetGeneralInfo,
- openmpt_GetMessage,
- openmpt_SetPosition,
- openmpt_GetGranularity,
- nullptr, // GetBuffering
- openmpt_Process,
- nullptr, // WriteFile
- openmpt_GetSamples,
- openmpt_GetSubSongs, // GetSubSongs
- nullptr, // GetCues
- nullptr, // GetDownloaded
- "OpenMPT Pattern Display",
- VisOpen,
- VisClose,
- VisSize,
- /*VisRender,*/nullptr,
- VisRenderDC,
- /*VisButton,*/nullptr,
- nullptr, // reserved2
- openmpt_GetConfig,
- openmpt_SetConfig
- };
- static const char * xmp_openmpt_default_exts = "OpenMPT\0mptm/mptmz";
- static char * file_formats;
- static void xmp_openmpt_on_dll_load() {
- ZeroMemory( &xmpopenmpt_mutex, sizeof( xmpopenmpt_mutex ) );
- #if defined(_MSC_VER)
- #pragma warning(push)
- #pragma warning(disable:28125) // The function 'InitializeCriticalSection' must be called from within a try/except block: The requirement might be conditional.
- #endif // _MSC_VER
- InitializeCriticalSection( &xmpopenmpt_mutex );
- #if defined(_MSC_VER)
- #pragma warning(pop)
- #endif // _MSC_VER
- std::vector<std::string> extensions = openmpt::get_supported_extensions();
- std::string filetypes_string = "OpenMPT";
- filetypes_string.push_back('\0');
- bool first = true;
- for ( const auto & ext : extensions ) {
- if ( first ) {
- first = false;
- } else {
- filetypes_string.push_back('/');
- }
- filetypes_string += ext;
- }
- filetypes_string.push_back('\0');
- file_formats = (char*)HeapAlloc( GetProcessHeap(), 0, filetypes_string.size() );
- if ( file_formats ) {
- std::copy( filetypes_string.begin(), filetypes_string.end(), file_formats );
- xmpin.exts = file_formats;
- } else {
- xmpin.exts = xmp_openmpt_default_exts;
- }
- self = new self_xmplay_t();
- }
- static void xmp_openmpt_on_dll_unload() {
- delete self;
- self = nullptr;
- if ( xmpin.exts != xmp_openmpt_default_exts ) {
- HeapFree(GetProcessHeap(), 0, (LPVOID)const_cast<char*>(xmpin.exts));
- }
- xmpin.exts = nullptr;
- DeleteCriticalSection( &xmpopenmpt_mutex );
- }
- static XMPIN * XMPIN_GetInterface_cxx( DWORD face, InterfaceProc faceproc ) {
- if ( face != XMPIN_FACE ) return nullptr;
- xmpfin=(XMPFUNC_IN*)faceproc(XMPFUNC_IN_FACE);
- xmpfmisc=(XMPFUNC_MISC*)faceproc(XMPFUNC_MISC_FACE);
- xmpfregistry=(XMPFUNC_REGISTRY*)faceproc(XMPFUNC_REGISTRY_FACE);
- xmpffile=(XMPFUNC_FILE*)faceproc(XMPFUNC_FILE_FACE);
- xmpftext=(XMPFUNC_TEXT*)faceproc(XMPFUNC_TEXT_FACE);
- xmpfstatus=(XMPFUNC_STATUS*)faceproc(XMPFUNC_STATUS_FACE);
- // Register keyboard shortcuts
- static constexpr std::pair<DWORD, const char *> shortcuts[] = {
- { openmpt_shortcut_ex | openmpt_shortcut_tempo_decrease, "OpenMPT - Decrease Tempo" },
- { openmpt_shortcut_ex | openmpt_shortcut_tempo_increase, "OpenMPT - Increase Tempo" },
- { openmpt_shortcut_ex | openmpt_shortcut_pitch_decrease, "OpenMPT - Decrease Pitch" },
- { openmpt_shortcut_ex | openmpt_shortcut_pitch_increase, "OpenMPT - Increase Pitch" },
- { openmpt_shortcut_ex | openmpt_shortcut_switch_interpolation, "OpenMPT - Switch Interpolation" },
- };
- XMPSHORTCUT cut;
- cut.procex = &ShortcutHandler;
- for ( const auto & shortcut : shortcuts ) {
- cut.id = shortcut.first;
- cut.text = shortcut.second;
- xmpfmisc->RegisterShortcut( &cut );
- }
- self->settings.load();
- return &xmpin;
- }
- extern "C" {
- // XMPLAY expects a WINAPI (which is __stdcall) function using an undecorated symbol name.
- #if defined(__GNUC__)
- XMPIN * WINAPI XMPIN_GetInterface_( DWORD face, InterfaceProc faceproc );
- XMPIN * WINAPI XMPIN_GetInterface_( DWORD face, InterfaceProc faceproc ) {
- return XMPIN_GetInterface_cxx( face, faceproc );
- }
- #pragma GCC diagnostic push
- #pragma GCC diagnostic ignored "-Wattribute-alias"
- // clang-format off
- __declspec(dllexport) void XMPIN_GetInterface() __attribute__((alias("XMPIN_GetInterface_@8")));
- // clang-format on
- #pragma GCC diagnostic pop
- #else
- XMPIN * WINAPI XMPIN_GetInterface( DWORD face, InterfaceProc faceproc ) {
- return XMPIN_GetInterface_cxx( face, faceproc );
- }
- #pragma comment(linker, "/EXPORT:XMPIN_GetInterface=_XMPIN_GetInterface@8")
- #endif
- } // extern "C"
- #if defined(MPT_WITH_MFC) && defined(_MFC_VER)
- namespace libopenmpt {
- namespace plugin {
- void DllMainAttach() {
- xmp_openmpt_on_dll_load();
- }
- void DllMainDetach() {
- xmp_openmpt_on_dll_unload();
- }
- } // namespace plugin
- } // namespace libopenmpt
- #else
- BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved );
- BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ) {
- static_cast<void>(hinstDLL);
- static_cast<void>(lpvReserved);
- switch ( fdwReason ) {
- case DLL_PROCESS_ATTACH:
- xmp_openmpt_on_dll_load();
- break;
- case DLL_PROCESS_DETACH:
- xmp_openmpt_on_dll_unload();
- break;
- }
- return TRUE;
- }
- #endif
- #endif // NO_XMPLAY
|