xmp-openmpt.cpp 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893
  1. /*
  2. * xmp-openmpt.cpp
  3. * ---------------
  4. * Purpose: libopenmpt xmplay input plugin implementation
  5. * Notes : (currently none)
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #ifndef NO_XMPLAY
  10. #ifndef _CRT_SECURE_NO_WARNINGS
  11. #define _CRT_SECURE_NO_WARNINGS
  12. #endif
  13. #if !defined(WINVER) && !defined(_WIN32_WINDOWS)
  14. #ifndef _WIN32_WINNT
  15. #define _WIN32_WINNT 0x0501 // _WIN32_WINNT_WINXP
  16. #endif
  17. #endif
  18. #if !defined(MPT_BUILD_RETRO)
  19. #if defined(_MSC_VER)
  20. #define MPT_WITH_MFC
  21. #endif
  22. #else
  23. #if defined(_WIN32_WINNT)
  24. #if (_WIN32_WINNT >= 0x0501)
  25. #if defined(_MSC_VER)
  26. #define MPT_WITH_MFC
  27. #endif
  28. #endif
  29. #endif
  30. #endif
  31. #if defined(MPT_WITH_MFC)
  32. #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS // Avoid binary bloat from linking unused MFC controls
  33. #endif // MPT_WITH_MFC
  34. #ifndef NOMINMAX
  35. #define NOMINMAX
  36. #endif
  37. #if defined(MPT_WITH_MFC)
  38. #include <afxwin.h>
  39. #include <afxcmn.h>
  40. #endif // MPT_WITH_MFC
  41. #include <windows.h>
  42. #include <WindowsX.h>
  43. #ifdef LIBOPENMPT_BUILD_DLL
  44. #undef LIBOPENMPT_BUILD_DLL
  45. #endif
  46. #ifdef _MSC_VER
  47. #ifndef _CRT_SECURE_NO_WARNINGS
  48. #define _CRT_SECURE_NO_WARNINGS
  49. #endif
  50. #ifndef _SCL_SECURE_NO_WARNINGS
  51. #define _SCL_SECURE_NO_WARNINGS
  52. #endif
  53. #endif // _MSC_VER
  54. #include <cctype>
  55. #include <cstring>
  56. #include <tchar.h>
  57. #include "libopenmpt.hpp"
  58. #include "libopenmpt_ext.hpp"
  59. #include "libopenmpt_plugin_settings.hpp"
  60. #include "libopenmpt_plugin_gui.hpp"
  61. #include "svn_version.h"
  62. #if defined(OPENMPT_VERSION_REVISION)
  63. static const char * xmp_openmpt_string = "OpenMPT (" OPENMPT_API_VERSION_STRING "." OPENMPT_API_VERSION_STRINGIZE(OPENMPT_VERSION_REVISION) ")";
  64. #else
  65. static const char * xmp_openmpt_string = "OpenMPT (" OPENMPT_API_VERSION_STRING ")";
  66. #endif
  67. #define USE_XMPLAY_FILE_IO
  68. #define USE_XMPLAY_ISTREAM
  69. #include "xmplay/xmpin.h"
  70. // Shortcut block assigned to the OpenMPT plugin by un4seen.
  71. enum {
  72. openmpt_shortcut_first = 0x21000,
  73. openmpt_shortcut_tempo_decrease = openmpt_shortcut_first,
  74. openmpt_shortcut_tempo_increase,
  75. openmpt_shortcut_pitch_decrease,
  76. openmpt_shortcut_pitch_increase,
  77. openmpt_shortcut_switch_interpolation,
  78. openmpt_shortcut_last = 0x21fff,
  79. openmpt_shortcut_ex = 0x80000000, // Use extended version of the shortcut callback
  80. };
  81. #include <algorithm>
  82. #include <fstream>
  83. #include <iomanip>
  84. #include <iostream>
  85. #include <iterator>
  86. #include <map>
  87. #include <queue>
  88. #include <sstream>
  89. #include <string>
  90. #include <cmath>
  91. #include <pugixml.hpp>
  92. #define SHORT_TITLE "xmp-openmpt"
  93. #define SHORTER_TITLE "openmpt"
  94. static CRITICAL_SECTION xmpopenmpt_mutex;
  95. class xmpopenmpt_lock {
  96. public:
  97. xmpopenmpt_lock() {
  98. EnterCriticalSection( &xmpopenmpt_mutex );
  99. }
  100. ~xmpopenmpt_lock() {
  101. LeaveCriticalSection( &xmpopenmpt_mutex );
  102. }
  103. };
  104. static XMPFUNC_IN * xmpfin = nullptr;
  105. static XMPFUNC_MISC * xmpfmisc = nullptr;
  106. static XMPFUNC_REGISTRY * xmpfregistry = nullptr;
  107. static XMPFUNC_FILE * xmpffile = nullptr;
  108. static XMPFUNC_TEXT * xmpftext = nullptr;
  109. static XMPFUNC_STATUS * xmpfstatus = nullptr;
  110. struct self_xmplay_t;
  111. static self_xmplay_t * self = 0;
  112. static void save_options();
  113. static void apply_and_save_options();
  114. static std::string convert_to_native( const std::string & str );
  115. static std::string StringEncode( const std::wstring &src, UINT codepage );
  116. static std::wstring StringDecode( const std::string & src, UINT codepage );
  117. #if defined(UNICODE)
  118. static std::wstring StringToWINAPI( const std::wstring & src );
  119. #else
  120. static std::string StringToWINAPI( const std::wstring & src );
  121. #endif
  122. class xmp_openmpt_settings
  123. : public libopenmpt::plugin::settings
  124. {
  125. protected:
  126. void read_setting( const std::string & key, const std::basic_string<TCHAR> & keyW, int & val ) override {
  127. libopenmpt::plugin::settings::read_setting( key, keyW, val );
  128. int storedVal = 0;
  129. if ( xmpfregistry->GetInt( "OpenMPT", key.c_str(), &storedVal ) ) {
  130. val = storedVal;
  131. }
  132. }
  133. void write_setting( const std::string & key, const std::basic_string<TCHAR> & /* keyW */ , int val ) override {
  134. if ( !xmpfregistry->SetInt( "OpenMPT", key.c_str(), &val ) ) {
  135. // error
  136. }
  137. // ok
  138. }
  139. public:
  140. xmp_openmpt_settings()
  141. : libopenmpt::plugin::settings(TEXT(SHORT_TITLE), false)
  142. {
  143. return;
  144. }
  145. virtual ~xmp_openmpt_settings()
  146. {
  147. return;
  148. }
  149. };
  150. struct self_xmplay_t {
  151. std::vector<float> subsong_lengths;
  152. std::vector<std::string> subsong_names;
  153. std::size_t samplerate = 48000;
  154. std::size_t num_channels = 2;
  155. xmp_openmpt_settings settings;
  156. openmpt::module_ext * mod = nullptr;
  157. bool set_format_called = false;
  158. openmpt::ext::pattern_vis * pattern_vis = nullptr;
  159. std::int32_t tempo_factor = 0, pitch_factor = 0;
  160. bool single_subsong_mode = false;
  161. self_xmplay_t() {
  162. settings.changed = apply_and_save_options;
  163. }
  164. void on_new_mod() {
  165. set_format_called = false;
  166. self->pattern_vis = static_cast<openmpt::ext::pattern_vis *>( self->mod->get_interface( openmpt::ext::pattern_vis_id ) );
  167. }
  168. void delete_mod() {
  169. if ( mod ) {
  170. pattern_vis = 0;
  171. set_format_called = false;
  172. delete mod;
  173. mod = 0;
  174. }
  175. }
  176. ~self_xmplay_t() {
  177. return;
  178. }
  179. };
  180. static std::string convert_to_native( const std::string & str ) {
  181. char * native_string = xmpftext->Utf8( str.c_str(), -1 );
  182. std::string result = native_string ? native_string : "";
  183. if ( native_string ) {
  184. xmpfmisc->Free( native_string );
  185. native_string = 0;
  186. }
  187. return result;
  188. }
  189. static std::string StringEncode( const std::wstring &src, UINT codepage )
  190. {
  191. int required_size = WideCharToMultiByte( codepage, 0, src.c_str(), -1, nullptr, 0, nullptr, nullptr);
  192. if(required_size <= 0)
  193. {
  194. return std::string();
  195. }
  196. std::vector<CHAR> encoded_string( required_size );
  197. WideCharToMultiByte( codepage, 0, src.c_str(), -1, &encoded_string[0], encoded_string.size(), nullptr, nullptr);
  198. return &encoded_string[0];
  199. }
  200. static std::wstring StringDecode( const std::string & src, UINT codepage )
  201. {
  202. int required_size = MultiByteToWideChar( codepage, 0, src.c_str(), -1, nullptr, 0 );
  203. if(required_size <= 0)
  204. {
  205. return std::wstring();
  206. }
  207. std::vector<WCHAR> decoded_string( required_size );
  208. MultiByteToWideChar( codepage, 0, src.c_str(), -1, &decoded_string[0], decoded_string.size() );
  209. return &decoded_string[0];
  210. }
  211. #if defined(UNICODE)
  212. static std::wstring StringToWINAPI( const std::wstring & src )
  213. {
  214. return src;
  215. }
  216. #else
  217. static std::string StringToWINAPI( const std::wstring & src )
  218. {
  219. return StringEncode( src, CP_ACP );
  220. }
  221. #endif
  222. template <typename Tstring, typename Tstring2, typename Tstring3>
  223. static inline Tstring StringReplace( Tstring str, const Tstring2 & oldStr_, const Tstring3 & newStr_ ) {
  224. std::size_t pos = 0;
  225. const Tstring oldStr = oldStr_;
  226. const Tstring newStr = newStr_;
  227. while ( ( pos = str.find( oldStr, pos ) ) != Tstring::npos ) {
  228. str.replace( pos, oldStr.length(), newStr );
  229. pos += newStr.length();
  230. }
  231. return str;
  232. }
  233. static std::string StringUpperCase( std::string str ) {
  234. std::transform( str.begin(), str.end(), str.begin(), []( char c ) { return static_cast<char>( std::toupper( c ) ); } );
  235. return str;
  236. }
  237. static std::string seconds_to_string( float time ) {
  238. std::int64_t time_ms = static_cast<std::int64_t>( time * 1000 );
  239. std::int64_t seconds = ( time_ms / 1000 ) % 60;
  240. std::int64_t minutes = ( time_ms / ( 1000 * 60 ) ) % 60;
  241. std::int64_t hours = ( time_ms / ( 1000 * 60 * 60 ) );
  242. std::ostringstream str;
  243. if ( hours > 0 ) {
  244. str << hours << ":";
  245. }
  246. str << std::setfill('0') << std::setw(2) << minutes;
  247. str << ":";
  248. str << std::setfill('0') << std::setw(2) << seconds;
  249. return str.str();
  250. }
  251. static void save_settings_to_map( std::map<std::string,int> & result, const libopenmpt::plugin::settings & s ) {
  252. result.clear();
  253. result[ "Samplerate_Hz" ] = s.samplerate;
  254. result[ "Channels" ] = s.channels;
  255. result[ "MasterGain_milliBel" ] = s.mastergain_millibel;
  256. result[ "StereoSeparation_Percent" ] = s.stereoseparation;
  257. result[ "RepeatCount" ] = s.repeatcount;
  258. result[ "InterpolationFilterLength" ] = s.interpolationfilterlength;
  259. result[ "UseAmigaResampler" ] = s.use_amiga_resampler;
  260. result[ "AmigaFilterType" ] = s.amiga_filter_type;
  261. result[ "VolumeRampingStrength" ] = s.ramping;
  262. }
  263. static inline void load_map_setting( const std::map<std::string,int> & map, const std::string & key, int & val ) {
  264. auto it = map.find( key );
  265. if ( it != map.end() ) {
  266. val = it->second;
  267. }
  268. }
  269. static void load_settings_from_map( libopenmpt::plugin::settings & s, const std::map<std::string,int> & map ) {
  270. load_map_setting( map, "Samplerate_Hz", s.samplerate );
  271. load_map_setting( map, "Channels", s.channels );
  272. load_map_setting( map, "MasterGain_milliBel", s.mastergain_millibel );
  273. load_map_setting( map, "StereoSeparation_Percent", s.stereoseparation );
  274. load_map_setting( map, "RepeatCount", s.repeatcount );
  275. load_map_setting( map, "InterpolationFilterLength", s.interpolationfilterlength );
  276. load_map_setting( map, "UseAmigaResampler", s.use_amiga_resampler );
  277. load_map_setting( map, "AmigaFilterType", s.amiga_filter_type );
  278. load_map_setting( map, "VolumeRampingStrength", s.ramping );
  279. }
  280. static void load_settings_from_xml( libopenmpt::plugin::settings & s, const std::string & xml ) {
  281. pugi::xml_document doc;
  282. doc.load_string( xml.c_str() );
  283. pugi::xml_node settings_node = doc.child( "settings" );
  284. std::map<std::string,int> map;
  285. for ( const auto & attr : settings_node.attributes() ) {
  286. map[ attr.name() ] = attr.as_int();
  287. }
  288. load_settings_from_map( s, map );
  289. }
  290. static void save_settings_to_xml( std::string & xml, const libopenmpt::plugin::settings & s ) {
  291. std::map<std::string,int> map;
  292. save_settings_to_map( map, s );
  293. pugi::xml_document doc;
  294. pugi::xml_node settings_node = doc.append_child( "settings" );
  295. for ( const auto & setting : map ) {
  296. settings_node.append_attribute( setting.first.c_str() ).set_value( setting.second );
  297. }
  298. std::ostringstream buf;
  299. doc.save( buf );
  300. xml = buf.str();
  301. }
  302. static void apply_options() {
  303. if ( self->mod ) {
  304. if ( !self->set_format_called ) {
  305. // SetFormat will only be called once after loading a file.
  306. // We cannot apply samplerate or numchannels changes afterwards during playback.
  307. self->samplerate = self->settings.samplerate;
  308. self->num_channels = self->settings.channels;
  309. }
  310. self->mod->set_repeat_count( self->settings.repeatcount );
  311. self->mod->set_render_param( openmpt::module::RENDER_MASTERGAIN_MILLIBEL, self->settings.mastergain_millibel );
  312. self->mod->set_render_param( openmpt::module::RENDER_STEREOSEPARATION_PERCENT, self->settings.stereoseparation );
  313. self->mod->set_render_param( openmpt::module::RENDER_INTERPOLATIONFILTER_LENGTH, self->settings.interpolationfilterlength );
  314. self->mod->set_render_param( openmpt::module::RENDER_VOLUMERAMPING_STRENGTH, self->settings.ramping );
  315. self->mod->ctl_set_boolean( "render.resampler.emulate_amiga", self->settings.use_amiga_resampler ? true : false );
  316. switch ( self->settings.amiga_filter_type ) {
  317. case 0:
  318. self->mod->ctl_set_text( "render.resampler.emulate_amiga_type", "auto" );
  319. break;
  320. case 1:
  321. self->mod->ctl_set_text( "render.resampler.emulate_amiga_type", "unfiltered" );
  322. break;
  323. case 0xA500:
  324. self->mod->ctl_set_text( "render.resampler.emulate_amiga_type", "a500" );
  325. break;
  326. case 0xA1200:
  327. self->mod->ctl_set_text( "render.resampler.emulate_amiga_type", "a1200" );
  328. break;
  329. }
  330. }
  331. }
  332. static void save_options() {
  333. self->settings.save();
  334. }
  335. static void apply_and_save_options() {
  336. apply_options();
  337. save_options();
  338. }
  339. static void reset_options() {
  340. self->settings = xmp_openmpt_settings();
  341. self->settings.changed = apply_and_save_options;
  342. self->settings.load();
  343. }
  344. // get config (return size of config data) (OPTIONAL)
  345. static DWORD WINAPI openmpt_GetConfig( void * config ) {
  346. std::string xml;
  347. save_settings_to_xml( xml, self->settings );
  348. if ( config ) {
  349. std::memcpy( config, xml.c_str(), xml.length() + 1 );
  350. }
  351. return xml.length() + 1;
  352. }
  353. // apply config (OPTIONAL)
  354. static void WINAPI openmpt_SetConfig( void * config, DWORD size ) {
  355. reset_options();
  356. if ( config ) {
  357. load_settings_from_xml( self->settings, std::string( (char*)config, (char*)config + size ) );
  358. apply_options();
  359. }
  360. }
  361. static void WINAPI ShortcutHandler( DWORD id ) {
  362. if ( !self->mod ) {
  363. return;
  364. }
  365. bool tempo_changed = false, pitch_changed = false;
  366. switch ( id ) {
  367. case openmpt_shortcut_tempo_decrease: self->tempo_factor--; tempo_changed = true; break;
  368. case openmpt_shortcut_tempo_increase: self->tempo_factor++; tempo_changed = true; break;
  369. case openmpt_shortcut_pitch_decrease: self->pitch_factor--; pitch_changed = true; break;
  370. case openmpt_shortcut_pitch_increase: self->pitch_factor++; pitch_changed = true; break;
  371. case openmpt_shortcut_switch_interpolation:
  372. self->settings.interpolationfilterlength *= 2;
  373. if ( self->settings.interpolationfilterlength > 8 ) {
  374. self->settings.interpolationfilterlength = 1;
  375. }
  376. apply_and_save_options();
  377. const char *s = nullptr;
  378. switch ( self->settings.interpolationfilterlength )
  379. {
  380. case 1: s = "Interpolation: Off"; break;
  381. case 2: s = "Interpolation: Linear"; break;
  382. case 4: s = "Interpolation: Cubic"; break;
  383. case 8: s = "Interpolation: Polyphase"; break;
  384. }
  385. if ( s ) {
  386. xmpfmisc->ShowBubble( s, 0 );
  387. }
  388. break;
  389. }
  390. self->tempo_factor = std::min( 48, std::max( -48, self->tempo_factor ) );
  391. self->pitch_factor = std::min( 48, std::max( -48, self->pitch_factor ) );
  392. const double tempo_factor = std::pow( 2.0, self->tempo_factor / 24.0 );
  393. const double pitch_factor = std::pow( 2.0, self->pitch_factor / 24.0 );
  394. if ( tempo_changed ) {
  395. std::ostringstream s;
  396. s << "Tempo: " << static_cast<std::int32_t>( 100.0 * tempo_factor ) << "%";
  397. xmpfmisc->ShowBubble( s.str().c_str(), 0 );
  398. } else if ( pitch_changed) {
  399. std::ostringstream s;
  400. s << "Pitch: ";
  401. if ( self->pitch_factor > 0 )
  402. s << "+";
  403. else if ( self->pitch_factor == 0 )
  404. s << "+/-";
  405. s << (self->pitch_factor * 0.5) << " semitones";
  406. xmpfmisc->ShowBubble( s.str().c_str(), 0 );
  407. }
  408. openmpt::ext::interactive *interactive = static_cast<openmpt::ext::interactive *>( self->mod->get_interface( openmpt::ext::interactive_id ) );
  409. interactive->set_tempo_factor( tempo_factor );
  410. interactive->set_pitch_factor( pitch_factor );
  411. xmpfin->SetLength( static_cast<float>( self->mod->get_duration_seconds() / tempo_factor ), TRUE );
  412. }
  413. static double timeinfo_position = 0.0;
  414. struct timeinfo {
  415. bool valid;
  416. double seconds;
  417. std::int32_t pattern;
  418. std::int32_t row;
  419. };
  420. static std::queue<timeinfo> timeinfos;
  421. static void reset_timeinfos( double position = 0.0 ) {
  422. while ( !timeinfos.empty() ) {
  423. timeinfos.pop();
  424. }
  425. timeinfo_position = position;
  426. }
  427. static void update_timeinfos( std::int32_t samplerate, std::int32_t count ) {
  428. timeinfo_position += (double)count / (double)samplerate;
  429. timeinfo info;
  430. info.valid = true;
  431. info.seconds = timeinfo_position;
  432. info.pattern = self->mod->get_current_pattern();
  433. info.row = self->mod->get_current_row();
  434. timeinfos.push( info );
  435. }
  436. static timeinfo current_timeinfo;
  437. static timeinfo lookup_timeinfo( double seconds ) {
  438. timeinfo info = current_timeinfo;
  439. #if 0
  440. info.seconds = timeinfo_position;
  441. info.pattern = self->mod->get_current_pattern();
  442. info.row = self->mod->get_current_row();
  443. #endif
  444. while ( timeinfos.size() > 0 && timeinfos.front().seconds <= seconds ) {
  445. info = timeinfos.front();
  446. timeinfos.pop();
  447. }
  448. current_timeinfo = info;
  449. return current_timeinfo;
  450. }
  451. static void clear_current_timeinfo() {
  452. current_timeinfo = timeinfo();
  453. }
  454. static void WINAPI openmpt_About( HWND win ) {
  455. std::ostringstream about;
  456. about << SHORT_TITLE << " version " << openmpt::string::get( "library_version" ) << " " << "(built " << openmpt::string::get( "build" ) << ")" << std::endl;
  457. about << " Copyright (c) 2013-2022 OpenMPT Project Developers and Contributors (https://lib.openmpt.org/)" << std::endl;
  458. about << " OpenMPT version " << openmpt::string::get( "core_version" ) << std::endl;
  459. about << std::endl;
  460. about << openmpt::string::get( "contact" ) << std::endl;
  461. about << std::endl;
  462. about << "Show full credits?" << std::endl;
  463. if ( MessageBox( win, StringToWINAPI( StringDecode( about.str(), CP_UTF8 ) ).c_str(), TEXT(SHORT_TITLE), MB_ICONINFORMATION | MB_YESNOCANCEL | MB_DEFBUTTON1 ) != IDYES ) {
  464. return;
  465. }
  466. std::ostringstream credits;
  467. credits << openmpt::string::get( "credits" );
  468. credits << "Additional thanks to:" << std::endl;
  469. credits << std::endl;
  470. credits << "Arseny Kapoulkine for pugixml" << std::endl;
  471. credits << "https://pugixml.org/" << std::endl;
  472. #if 1
  473. libopenmpt::plugin::gui_show_file_info( win, TEXT(SHORT_TITLE), StringToWINAPI( StringReplace( StringDecode( credits.str(), CP_UTF8 ), L"\n", L"\r\n" ) ) );
  474. #else
  475. MessageBox( win, StringToWINAPI( StringReplace( StringDecode( credits.str(), CP_UTF8 ), L"\n", L"\r\n" ) ).c_str(), TEXT(SHORT_TITLE), MB_OK );
  476. #endif
  477. }
  478. static void WINAPI openmpt_Config( HWND win ) {
  479. #if 1
  480. libopenmpt::plugin::gui_edit_settings( &self->settings, win, TEXT(SHORT_TITLE) );
  481. #else
  482. static_cast<void>(win);
  483. #endif
  484. apply_and_save_options();
  485. }
  486. #ifdef USE_XMPLAY_FILE_IO
  487. #ifdef USE_XMPLAY_ISTREAM
  488. class xmplay_streambuf : public std::streambuf {
  489. public:
  490. explicit xmplay_streambuf( XMPFILE & file );
  491. private:
  492. int_type underflow() override;
  493. xmplay_streambuf( const xmplay_streambuf & );
  494. xmplay_streambuf & operator = ( const xmplay_streambuf & );
  495. private:
  496. XMPFILE & file;
  497. static const std::size_t put_back = 4096;
  498. static const std::size_t buf_size = 65536;
  499. std::vector<char> buffer;
  500. }; // class xmplay_streambuf
  501. xmplay_streambuf::xmplay_streambuf( XMPFILE & file_ ) : file(file_), buffer(buf_size) {
  502. char * end = &buffer.front() + buffer.size();
  503. setg( end, end, end );
  504. }
  505. std::streambuf::int_type xmplay_streambuf::underflow() {
  506. if ( gptr() < egptr() ) {
  507. return traits_type::to_int_type( *gptr() );
  508. }
  509. char * base = &buffer.front();
  510. char * start = base;
  511. if ( eback() == base ) {
  512. std::size_t put_back_count = std::min( put_back, static_cast<std::size_t>( egptr() - base ) );
  513. std::memmove( base, egptr() - put_back_count, put_back_count );
  514. start += put_back_count;
  515. }
  516. std::size_t n = xmpffile->Read( file, start, buffer.size() - ( start - base ) );
  517. if ( n == 0 ) {
  518. return traits_type::eof();
  519. }
  520. setg( base, start, start + n );
  521. return traits_type::to_int_type( *gptr() );
  522. }
  523. class xmplay_istream : public std::istream {
  524. private:
  525. xmplay_streambuf buf;
  526. private:
  527. xmplay_istream( const xmplay_istream & );
  528. xmplay_istream & operator = ( const xmplay_istream & );
  529. public:
  530. xmplay_istream( XMPFILE & file ) : std::istream(&buf), buf(file) {
  531. return;
  532. }
  533. ~xmplay_istream() {
  534. return;
  535. }
  536. }; // class xmplay_istream
  537. // Stream for memory-based files (required for could_open_probability)
  538. struct xmplay_membuf : std::streambuf {
  539. xmplay_membuf( const char * base, size_t size ) {
  540. char* p( const_cast<char *>( base ) );
  541. setg(p, p, p + size);
  542. }
  543. };
  544. struct xmplay_imemstream : virtual xmplay_membuf, std::istream {
  545. xmplay_imemstream( const char * base, size_t size )
  546. : xmplay_membuf( base, size )
  547. , std::istream( static_cast<std::streambuf *>(this)) {
  548. return;
  549. }
  550. };
  551. #else // !USE_XMPLAY_ISTREAM
  552. static std::vector<char> read_XMPFILE_vector( XMPFILE & file ) {
  553. std::vector<char> data( xmpffile->GetSize( file ) );
  554. if ( data.size() != xmpffile->Read( file, data.data(), data.size() ) ) {
  555. return std::vector<char>();
  556. }
  557. return data;
  558. }
  559. static std::string read_XMPFILE_string( XMPFILE & file ) {
  560. std::vector<char> data = read_XMPFILE_vector( file );
  561. return std::string( data.begin(), data.end() );
  562. }
  563. #endif // USE_XMPLAY_ISTREAM
  564. #endif // USE_XMPLAY_FILE_IO
  565. static std::string string_replace( std::string str, const std::string & oldStr, const std::string & newStr ) {
  566. std::size_t pos = 0;
  567. while((pos = str.find(oldStr, pos)) != std::string::npos)
  568. {
  569. str.replace(pos, oldStr.length(), newStr);
  570. pos += newStr.length();
  571. }
  572. return str;
  573. }
  574. static void write_xmplay_string( char * dst, std::string src ) {
  575. // xmplay buffers are ~40kB, be conservative and truncate at 32kB-2
  576. if ( !dst ) {
  577. return;
  578. }
  579. src = src.substr( 0, (1<<15) - 2 );
  580. std::strcpy( dst, src.c_str() );
  581. }
  582. static std::string extract_date( const openmpt::module & mod ) {
  583. std::string result = mod.get_metadata("date");
  584. if ( result.empty() ) {
  585. // Search the sample, instrument and message texts for possible release years.
  586. // We'll look for things that may vaguely resemble a release year, such as 4-digit numbers
  587. // or 2-digit numbers with a leading apostrophe. Generally, only years between
  588. // 1988 (release of Ultimate SoundTracker) and the current year + 1 (safety margin) will
  589. // be considered.
  590. std::string s = " " + mod.get_metadata("message");
  591. auto names = mod.get_sample_names();
  592. for ( const auto & name : names ) {
  593. s += " " + name;
  594. }
  595. names = mod.get_instrument_names();
  596. for ( const auto & name : names ) {
  597. s += " " + name;
  598. }
  599. s += " ";
  600. int32_t best_year = 0;
  601. SYSTEMTIME time;
  602. GetSystemTime( &time );
  603. const int32_t current_year = time.wYear + 1;
  604. #define MPT_NUMERIC( x ) ( ( x >= '0' ) && ( x <= '9' ) )
  605. for ( auto i = s.cbegin(); i != s.cend(); ++i ) {
  606. std::size_t len = s.length();
  607. std::size_t idx = i - s.begin();
  608. std::size_t remaining = len - idx;
  609. 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] ) ) {
  610. // Four-digit year
  611. const int32_t year = ( i[1] - '0' ) * 1000 + ( i[2] - '0' ) * 100 + ( i[3] - '0' ) * 10 + ( i[4] - '0' );
  612. if ( year >= 1988 && year <= current_year ) {
  613. best_year = std::max( year, best_year );
  614. }
  615. } else if ( ( remaining >= 4 ) && ( i[0] == '\'' ) && MPT_NUMERIC( i[1] ) && MPT_NUMERIC( i[2] ) && !MPT_NUMERIC( i[3] ) ) {
  616. // Apostrophe + two-digit year
  617. const int32_t year = ( i[1] - '0' ) * 10 + ( i[2] - '0' );
  618. if ( year >= 88 && year <= 99 ) {
  619. best_year = std::max( 1900 + year, best_year );
  620. } else if ( year >= 00 && ( 2000 + year ) <= current_year ) {
  621. best_year = std::max( 2000 + year, best_year );
  622. }
  623. }
  624. }
  625. #undef MPT_NUMERIC
  626. if ( best_year != 0 ) {
  627. std::ostringstream os;
  628. os << best_year;
  629. result = os.str();
  630. }
  631. }
  632. return result;
  633. }
  634. static void append_xmplay_tag( std::string & tags, const std::string & tag, const std::string & val ) {
  635. if ( tag.empty() ) {
  636. return;
  637. }
  638. if ( val.empty() ) {
  639. return;
  640. }
  641. tags.append( tag );
  642. tags.append( 1, '\0' );
  643. tags.append( val );
  644. tags.append( 1, '\0' );
  645. }
  646. static char * build_xmplay_tags( const openmpt::module & mod, int32_t subsong = -1 ) {
  647. std::string tags;
  648. const std::string title = mod.get_metadata("title");
  649. const auto subsong_names = mod.get_subsong_names();
  650. auto first_subsong = subsong_names.cbegin(), last_subsong = subsong_names.cend();
  651. if ( subsong >= 0 && static_cast<size_t>( subsong ) < subsong_names.size() ) {
  652. first_subsong += subsong;
  653. last_subsong = first_subsong + 1;
  654. } else
  655. {
  656. last_subsong = first_subsong + 1;
  657. }
  658. for ( auto subsong_name = first_subsong; subsong_name != last_subsong; subsong_name++ ) {
  659. append_xmplay_tag( tags, "filetype", convert_to_native( StringUpperCase( mod.get_metadata( "type" ) ) ) );
  660. append_xmplay_tag( tags, "title", convert_to_native( ( subsong_name->empty() || subsong == -1 ) ? title : *subsong_name ) );
  661. append_xmplay_tag( tags, "artist", convert_to_native( mod.get_metadata( "artist" ) ) );
  662. append_xmplay_tag( tags, "album", convert_to_native( mod.get_metadata( "xmplay-album" ) ) ); // todo, libopenmpt does not support that
  663. append_xmplay_tag( tags, "date", convert_to_native( extract_date( mod ) ) );
  664. append_xmplay_tag( tags, "track", convert_to_native( mod.get_metadata( "xmplay-tracknumber" ) ) ); // todo, libopenmpt does not support that
  665. append_xmplay_tag( tags, "genre", convert_to_native( mod.get_metadata( "xmplay-genre" ) ) ); // todo, libopenmpt does not support that
  666. append_xmplay_tag( tags, "comment", convert_to_native( mod.get_metadata( "message" ) ) );
  667. tags.append( 1, '\0' );
  668. }
  669. char * result = static_cast<char*>( xmpfmisc->Alloc( tags.size() ) );
  670. if ( !result ) {
  671. return nullptr;
  672. }
  673. std::copy( tags.data(), tags.data() + tags.size(), result );
  674. return result;
  675. }
  676. static float * build_xmplay_length( const openmpt::module & /* mod */ ) {
  677. float * result = static_cast<float*>( xmpfmisc->Alloc( sizeof( float ) * self->subsong_lengths.size() ) );
  678. if ( !result ) {
  679. return nullptr;
  680. }
  681. for ( std::size_t i = 0; i < self->subsong_lengths.size(); ++i ) {
  682. result[i] = self->subsong_lengths[i];
  683. }
  684. return result;
  685. }
  686. static void clear_xmplay_string( char * str ) {
  687. if ( !str ) {
  688. return;
  689. }
  690. str[0] = '\0';
  691. }
  692. static std::string sanitize_xmplay_info_string( const std::string & str ) {
  693. std::string result;
  694. result.reserve(str.size());
  695. for ( auto c : str ) {
  696. switch ( c ) {
  697. case '\0':
  698. case '\t':
  699. case '\r':
  700. case '\n':
  701. break;
  702. default:
  703. result.push_back( c );
  704. break;
  705. }
  706. }
  707. return result;
  708. }
  709. static std::string sanitize_xmplay_multiline_info_string( const std::string & str ) {
  710. std::string result;
  711. result.reserve(str.size());
  712. for ( auto c : str ) {
  713. switch ( c ) {
  714. case '\0':
  715. case '\t':
  716. case '\r':
  717. break;
  718. case '\n':
  719. result.push_back( '\r' );
  720. result.push_back( '\t' );
  721. break;
  722. default:
  723. result.push_back( c );
  724. break;
  725. }
  726. }
  727. return result;
  728. }
  729. static std::string sanitize_xmplay_multiline_string( const std::string & str ) {
  730. std::string result;
  731. result.reserve(str.size());
  732. for ( auto c : str ) {
  733. switch ( c ) {
  734. case '\0':
  735. case '\t':
  736. break;
  737. default:
  738. result.push_back( c );
  739. break;
  740. }
  741. }
  742. return result;
  743. }
  744. // check if a file is playable by this plugin
  745. // more thorough checks can be saved for the GetFileInfo and Open functions
  746. static BOOL WINAPI openmpt_CheckFile( const char * filename, XMPFILE file ) {
  747. static_cast<void>( filename );
  748. try {
  749. #ifdef USE_XMPLAY_FILE_IO
  750. #ifdef USE_XMPLAY_ISTREAM
  751. switch ( xmpffile->GetType( file ) ) {
  752. case XMPFILE_TYPE_MEMORY:
  753. {
  754. xmplay_imemstream s( reinterpret_cast<const char *>( xmpffile->GetMemory( file ) ), xmpffile->GetSize( file ) );
  755. return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE;
  756. }
  757. break;
  758. case XMPFILE_TYPE_FILE:
  759. case XMPFILE_TYPE_NETFILE:
  760. case XMPFILE_TYPE_NETSTREAM:
  761. default:
  762. {
  763. xmplay_istream s( file );
  764. return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE;
  765. }
  766. break;
  767. }
  768. #else
  769. if ( xmpffile->GetType( file ) == XMPFILE_TYPE_MEMORY ) {
  770. std::string data( reinterpret_cast<const char*>( xmpffile->GetMemory( file ) ), xmpffile->GetSize( file ) );
  771. std::istringstream s( data );
  772. return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE;
  773. } else {
  774. std::string data = read_XMPFILE_string( file );
  775. std::istringstream s(data);
  776. return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE;
  777. }
  778. #endif
  779. #else
  780. std::ifstream s( filename, std::ios_base::binary );
  781. return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE;
  782. #endif
  783. } catch ( ... ) {
  784. return FALSE;
  785. }
  786. return FALSE;
  787. }
  788. static DWORD WINAPI openmpt_GetFileInfo( const char * filename, XMPFILE file, float * * length, char * * tags ) {
  789. static_cast<void>( filename );
  790. try {
  791. std::map< std::string, std::string > ctls
  792. {
  793. { "load.skip_plugins", "1" },
  794. { "load.skip_samples", "1" },
  795. };
  796. #ifdef USE_XMPLAY_FILE_IO
  797. #ifdef USE_XMPLAY_ISTREAM
  798. switch ( xmpffile->GetType( file ) ) {
  799. case XMPFILE_TYPE_MEMORY:
  800. {
  801. openmpt::module mod( xmpffile->GetMemory( file ), xmpffile->GetSize( file ), std::clog, ctls );
  802. if ( length ) {
  803. *length = build_xmplay_length( mod );
  804. }
  805. if ( tags ) {
  806. *tags = build_xmplay_tags( mod );
  807. }
  808. }
  809. break;
  810. case XMPFILE_TYPE_FILE:
  811. case XMPFILE_TYPE_NETFILE:
  812. case XMPFILE_TYPE_NETSTREAM:
  813. default:
  814. {
  815. xmplay_istream s( file );
  816. openmpt::module mod( s, std::clog, ctls );
  817. if ( length ) {
  818. *length = build_xmplay_length( mod );
  819. }
  820. if ( tags ) {
  821. *tags = build_xmplay_tags( mod );
  822. }
  823. }
  824. break;
  825. }
  826. #else
  827. if ( xmpffile->GetType( file ) == XMPFILE_TYPE_MEMORY ) {
  828. openmpt::module mod( xmpffile->GetMemory( file ), xmpffile->GetSize( file ), std::clog, ctls );
  829. if ( length ) {
  830. *length = build_xmplay_length( mod );
  831. }
  832. if ( tags ) {
  833. *tags = build_xmplay_tags( mod );
  834. }
  835. } else {
  836. openmpt::module mod( read_XMPFILE_vector( file ), std::clog, ctls );
  837. if ( length ) {
  838. *length = build_xmplay_length( mod );
  839. }
  840. if ( tags ) {
  841. *tags = build_xmplay_tags( mod );
  842. }
  843. }
  844. #endif
  845. #else
  846. std::ifstream s( filename, std::ios_base::binary );
  847. openmpt::module mod( s, std::clog, ctls );
  848. if ( length ) {
  849. *length = build_xmplay_length( mod );
  850. }
  851. if ( tags ) {
  852. *tags = build_xmplay_tags( mod );
  853. }
  854. #endif
  855. } catch ( ... ) {
  856. if ( length ) *length = nullptr;
  857. if ( tags ) *tags = nullptr;
  858. return 0;
  859. }
  860. return self->subsong_lengths.size() + XMPIN_INFO_NOSUBTAGS;
  861. }
  862. // open a file for playback
  863. // return: 0=failed, 1=success, 2=success and XMPlay can close the file
  864. static DWORD WINAPI openmpt_Open( const char * filename, XMPFILE file ) {
  865. static_cast<void>( filename );
  866. xmpopenmpt_lock guard;
  867. reset_options();
  868. try {
  869. std::map< std::string, std::string > ctls
  870. {
  871. { "seek.sync_samples", "1" },
  872. { "play.at_end", "continue" },
  873. };
  874. self->delete_mod();
  875. #ifdef USE_XMPLAY_FILE_IO
  876. #ifdef USE_XMPLAY_ISTREAM
  877. switch ( xmpffile->GetType( file ) ) {
  878. case XMPFILE_TYPE_MEMORY:
  879. self->mod = new openmpt::module_ext( xmpffile->GetMemory( file ), xmpffile->GetSize( file ), std::clog, ctls );
  880. break;
  881. case XMPFILE_TYPE_FILE:
  882. case XMPFILE_TYPE_NETFILE:
  883. case XMPFILE_TYPE_NETSTREAM:
  884. default:
  885. {
  886. xmplay_istream s( file );
  887. self->mod = new openmpt::module_ext( s, std::clog, ctls );
  888. }
  889. break;
  890. }
  891. #else
  892. if ( xmpffile->GetType( file ) == XMPFILE_TYPE_MEMORY ) {
  893. self->mod = new openmpt::module_ext( xmpffile->GetMemory( file ), xmpffile->GetSize( file ), std::clog, ctls );
  894. } else {
  895. self->mod = new openmpt::module_ext( read_XMPFILE_vector( file ), std::clog, ctls );
  896. }
  897. #endif
  898. #else
  899. self->mod = new openmpt::module_ext( std::ifstream( filename, std::ios_base::binary ), std::clog, ctls );
  900. #endif
  901. self->on_new_mod();
  902. clear_current_timeinfo();
  903. reset_timeinfos();
  904. apply_options();
  905. std::int32_t num_subsongs = self->mod->get_num_subsongs();
  906. self->subsong_lengths.resize( num_subsongs );
  907. for ( std::int32_t i = 0; i < num_subsongs; ++i ) {
  908. self->mod->select_subsong( i );
  909. self->subsong_lengths[i] = static_cast<float>( self->mod->get_duration_seconds() );
  910. }
  911. self->subsong_names = self->mod->get_subsong_names();
  912. self->mod->select_subsong( 0 );
  913. self->tempo_factor = 0;
  914. self->pitch_factor = 0;
  915. xmpfin->SetLength( self->subsong_lengths[0], TRUE );
  916. return 2;
  917. } catch ( ... ) {
  918. self->delete_mod();
  919. return 0;
  920. }
  921. return 0;
  922. }
  923. // close the file
  924. static void WINAPI openmpt_Close() {
  925. xmpopenmpt_lock guard;
  926. self->delete_mod();
  927. }
  928. // set the sample format (in=user chosen format, out=file format if different)
  929. static void WINAPI openmpt_SetFormat( XMPFORMAT * form ) {
  930. if ( !form ) {
  931. return;
  932. }
  933. // SetFormat will only be called once after loading a file.
  934. // We cannot apply samplerate or numchannels changes afterwards during playback.
  935. self->set_format_called = true;
  936. if ( !self->mod ) {
  937. form->rate = 0;
  938. form->chan = 0;
  939. form->res = 0;
  940. return;
  941. }
  942. if ( self->settings.samplerate != 0 ) {
  943. form->rate = self->samplerate;
  944. } else {
  945. if ( form->rate > 0 ) {
  946. self->samplerate = form->rate;
  947. } else {
  948. form->rate = 48000;
  949. self->samplerate = 48000;
  950. }
  951. }
  952. if ( self->settings.channels != 0 ) {
  953. form->chan = self->num_channels;
  954. } else {
  955. if ( form->chan > 0 ) {
  956. if ( form->chan > 2 ) {
  957. form->chan = 4;
  958. self->num_channels = 4;
  959. } else {
  960. self->num_channels = form->chan;
  961. }
  962. } else {
  963. form->chan = 2;
  964. self->num_channels = 2;
  965. }
  966. }
  967. form->res = 4; // float
  968. }
  969. // get the tags
  970. static char * WINAPI openmpt_GetTags() {
  971. if ( !self->mod ) {
  972. char * tags = static_cast<char*>( xmpfmisc->Alloc( 1 ) );
  973. tags[0] = '\0';
  974. return tags;
  975. }
  976. return build_xmplay_tags( *self->mod, std::max( 0, self->mod->get_selected_subsong() ) );
  977. }
  978. // get the main panel info text
  979. static void WINAPI openmpt_GetInfoText( char * format, char * length ) {
  980. if ( !self->mod ) {
  981. clear_xmplay_string( format );
  982. clear_xmplay_string( length );
  983. return;
  984. }
  985. if ( format ) {
  986. std::ostringstream str;
  987. str
  988. << StringUpperCase( self->mod->get_metadata("type") )
  989. << " - "
  990. << self->mod->get_num_channels() << " ch"
  991. << " - "
  992. << "(via " << SHORTER_TITLE << ")"
  993. ;
  994. write_xmplay_string( format, sanitize_xmplay_info_string( str.str() ) );
  995. }
  996. if ( length ) {
  997. std::ostringstream str;
  998. str
  999. << length
  1000. << " - "
  1001. << self->mod->get_num_orders() << " orders"
  1002. ;
  1003. write_xmplay_string( length, sanitize_xmplay_info_string( str.str() ) );
  1004. }
  1005. }
  1006. // get text for "General" info window
  1007. // separate headings and values with a tab (\t), end each line with a carriage-return (\r)
  1008. static void WINAPI openmpt_GetGeneralInfo( char * buf ) {
  1009. if ( !self->mod ) {
  1010. clear_xmplay_string( buf );
  1011. return;
  1012. }
  1013. std::ostringstream str;
  1014. str << "\r";
  1015. bool metadatainfo = false;
  1016. if ( !self->mod->get_metadata("artist").empty() ) {
  1017. metadatainfo = true;
  1018. str << "Artist" << "\t" << sanitize_xmplay_info_string( self->mod->get_metadata("artist") ) << "\r";
  1019. }
  1020. const std::string date = extract_date( *self->mod );
  1021. if ( !date.empty() ) {
  1022. metadatainfo = true;
  1023. str << "Date" << "\t" << sanitize_xmplay_info_string( date ) << "\r";
  1024. }
  1025. if ( metadatainfo ) {
  1026. str << "\r";
  1027. }
  1028. str << "Format" << "\t" << sanitize_xmplay_info_string( self->mod->get_metadata("type") ) << " (" << sanitize_xmplay_info_string( self->mod->get_metadata("type_long") ) << ")" << "\r";
  1029. if ( !self->mod->get_metadata("originaltype").empty() ) {
  1030. str << "Original Type" << "\t" << sanitize_xmplay_info_string( self->mod->get_metadata("originaltype") ) << " (" << sanitize_xmplay_info_string( self->mod->get_metadata("originaltype_long") ) << ")" << "\r";
  1031. }
  1032. if ( !self->mod->get_metadata("container").empty() ) {
  1033. str << "Container" << "\t" << sanitize_xmplay_info_string( self->mod->get_metadata("container") ) << " (" << sanitize_xmplay_info_string( self->mod->get_metadata("container_long") ) << ")" << "\r";
  1034. }
  1035. str
  1036. << "Channels" << "\t" << self->mod->get_num_channels() << "\r"
  1037. << "Orders" << "\t" << self->mod->get_num_orders() << "\r"
  1038. << "Patterns" << "\t" << self->mod->get_num_patterns() << "\r";
  1039. if ( self->mod->get_num_instruments() != 0 ) {
  1040. str << "Instruments" << "\t" << self->mod->get_num_instruments() << "\r";
  1041. }
  1042. str << "Samples" << "\t" << self->mod->get_num_samples() << "\r";
  1043. if( !self->single_subsong_mode && self->subsong_lengths.size() > 1 ) {
  1044. for ( std::size_t i = 0; i < self->subsong_lengths.size(); ++i ) {
  1045. str << ( i == 0 ? "Subsongs\t" : "\t" ) << (i + 1) << ". " << seconds_to_string( self->subsong_lengths[i]) << " " << self->subsong_names[i] << "\r";
  1046. }
  1047. }
  1048. str
  1049. << "\r"
  1050. << "Tracker" << "\t" << sanitize_xmplay_info_string( self->mod->get_metadata("tracker") ) << "\r"
  1051. << "Player" << "\t" << "xmp-openmpt" << " version " << openmpt::string::get( "library_version" ) << "\r"
  1052. ;
  1053. std::string warnings = self->mod->get_metadata("warnings");
  1054. if ( !warnings.empty() ) {
  1055. str << "Warnings" << "\t" << sanitize_xmplay_multiline_info_string( warnings ) << "\r";
  1056. }
  1057. str << "\r";
  1058. write_xmplay_string( buf, str.str() );
  1059. }
  1060. // get text for "Message" info window
  1061. // separate tag names and values with a tab (\t), and end each line with a carriage-return (\r)
  1062. static void WINAPI openmpt_GetMessage( char * buf ) {
  1063. if ( !self->mod ) {
  1064. clear_xmplay_string( buf );
  1065. return;
  1066. }
  1067. write_xmplay_string( buf, convert_to_native( sanitize_xmplay_multiline_string( string_replace( self->mod->get_metadata("message"), "\n", "\r" ) ) ) );
  1068. }
  1069. // Seek to a position (in granularity units)
  1070. // return the new position in seconds (-1 = failed)
  1071. static double WINAPI openmpt_SetPosition( DWORD pos ) {
  1072. if ( !self->mod ) {
  1073. return -1.0;
  1074. }
  1075. if ( pos == static_cast<DWORD>(static_cast<LONG>(XMPIN_POS_LOOP)) ) {
  1076. // 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.
  1077. // There is currently no way to easily figure out at which time the loop restarts.
  1078. return -2;
  1079. } else if ( pos == static_cast<DWORD>(static_cast<LONG>(XMPIN_POS_AUTOLOOP)) ) {
  1080. // In the auto-looping case, the function should only loop when a loop has been detected, and otherwise return -1
  1081. // 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.
  1082. // There is currently no way to easily figure out at which time the loop restarts.
  1083. return -2;
  1084. }
  1085. if ( pos & XMPIN_POS_SUBSONG ) {
  1086. self->single_subsong_mode = ( pos & XMPIN_POS_SUBSONG1 ) != 0;
  1087. const int32_t subsong = pos & 0xffff;
  1088. try {
  1089. self->mod->select_subsong( subsong );
  1090. } catch ( ... ) {
  1091. return 0.0;
  1092. }
  1093. openmpt::ext::interactive *interactive = static_cast<openmpt::ext::interactive *>( self->mod->get_interface( openmpt::ext::interactive_id ) );
  1094. xmpfin->SetLength( static_cast<float>( self->subsong_lengths[ subsong ] / interactive->get_tempo_factor() ), TRUE );
  1095. xmpfin->UpdateTitle( nullptr );
  1096. reset_timeinfos( 0 );
  1097. return 0.0;
  1098. }
  1099. double new_position = self->mod->set_position_seconds( static_cast<double>( pos ) * 0.001 );
  1100. reset_timeinfos( new_position );
  1101. return new_position;
  1102. }
  1103. // Get the seeking granularity in seconds
  1104. static double WINAPI openmpt_GetGranularity() {
  1105. return 0.001;
  1106. }
  1107. // get some sample data, always floating-point
  1108. // count=number of floats to write (not bytes or samples)
  1109. // return number of floats written. if it's less than requested, playback is ended...
  1110. // so wait for more if there is more to come (use CheckCancel function to check if user wants to cancel)
  1111. static DWORD WINAPI openmpt_Process( float * dstbuf, DWORD count ) {
  1112. xmpopenmpt_lock guard;
  1113. if ( !self->mod || self->num_channels == 0 ) {
  1114. return 0;
  1115. }
  1116. update_timeinfos( self->samplerate, 0 );
  1117. std::size_t frames = count / self->num_channels;
  1118. std::size_t frames_to_render = frames;
  1119. std::size_t frames_rendered = 0;
  1120. while ( frames_to_render > 0 ) {
  1121. 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
  1122. switch ( self->num_channels ) {
  1123. case 1:
  1124. {
  1125. frames_chunk = self->mod->read( self->samplerate, frames_chunk, dstbuf );
  1126. }
  1127. break;
  1128. case 2:
  1129. {
  1130. frames_chunk = self->mod->read_interleaved_stereo( self->samplerate, frames_chunk, dstbuf );
  1131. }
  1132. break;
  1133. case 4:
  1134. {
  1135. frames_chunk = self->mod->read_interleaved_quad( self->samplerate, frames_chunk, dstbuf );
  1136. }
  1137. break;
  1138. }
  1139. dstbuf += frames_chunk * self->num_channels;
  1140. if ( frames_chunk == 0 ) {
  1141. break;
  1142. }
  1143. update_timeinfos( self->samplerate, frames_chunk );
  1144. frames_to_render -= frames_chunk;
  1145. frames_rendered += frames_chunk;
  1146. }
  1147. if ( frames_rendered == 0 ) {
  1148. return 0;
  1149. }
  1150. return frames_rendered * self->num_channels;
  1151. }
  1152. static void add_names( std::ostream & str, const std::string & title, const std::vector<std::string> & names, int display_offset ) {
  1153. if ( names.size() > 0 ) {
  1154. bool valid = false;
  1155. for ( std::size_t i = 0; i < names.size(); i++ ) {
  1156. if ( names[i] != "" ) {
  1157. valid = true;
  1158. }
  1159. }
  1160. if ( !valid ) {
  1161. return;
  1162. }
  1163. str << title << " Names:" << "\r";
  1164. for ( std::size_t i = 0; i < names.size(); i++ ) {
  1165. str << std::setfill('0') << std::setw(2) << (display_offset + i) << std::setw(0) << "\t" << convert_to_native( names[i] ) << "\r";
  1166. }
  1167. str << "\r";
  1168. }
  1169. }
  1170. static void WINAPI openmpt_GetSamples( char * buf ) {
  1171. if ( !self->mod ) {
  1172. clear_xmplay_string( buf );
  1173. return;
  1174. }
  1175. std::ostringstream str;
  1176. add_names( str, "Instrument", self->mod->get_instrument_names(), 1 );
  1177. add_names( str, "Sample", self->mod->get_sample_names(), 1 );
  1178. add_names( str, "Channel", self->mod->get_channel_names(), 1 );
  1179. add_names( str, "Order", self->mod->get_order_names(), 0 );
  1180. add_names( str, "Pattern", self->mod->get_pattern_names(), 0 );
  1181. write_xmplay_string( buf, str.str() );
  1182. }
  1183. static DWORD WINAPI openmpt_GetSubSongs( float * length ) {
  1184. *length = 0.0f;
  1185. for ( auto sub_length : self->subsong_lengths ) {
  1186. *length += sub_length;
  1187. }
  1188. return static_cast<DWORD>( self->subsong_lengths.size() );
  1189. }
  1190. enum ColorIndex
  1191. {
  1192. col_background = 0,
  1193. col_unknown,
  1194. col_text,
  1195. col_empty,
  1196. col_instr,
  1197. col_vol,
  1198. col_pitch,
  1199. col_global,
  1200. col_max
  1201. };
  1202. static ColorIndex effect_type_to_color_index( openmpt::ext::pattern_vis::effect_type effect_type ) {
  1203. switch ( effect_type ) {
  1204. case openmpt::ext::pattern_vis::effect_unknown: return col_unknown; break;
  1205. case openmpt::ext::pattern_vis::effect_general: return col_text ; break;
  1206. case openmpt::ext::pattern_vis::effect_global : return col_global ; break;
  1207. case openmpt::ext::pattern_vis::effect_volume : return col_vol ; break;
  1208. case openmpt::ext::pattern_vis::effect_panning: return col_instr ; break;
  1209. case openmpt::ext::pattern_vis::effect_pitch : return col_pitch ; break;
  1210. default: return col_unknown; break;
  1211. }
  1212. }
  1213. const struct Columns
  1214. {
  1215. int num_chars;
  1216. int color;
  1217. } pattern_columns[] = {
  1218. { 3, col_text }, // C-5
  1219. { 2, col_instr }, // 01
  1220. { 3, col_vol }, // v64
  1221. { 3, col_pitch }, // EFF
  1222. };
  1223. static const int max_cols = 4;
  1224. static void assure_width( std::string & str, std::size_t width ) {
  1225. if ( str.length() == width ) {
  1226. return;
  1227. } else if ( str.length() < width ) {
  1228. str += std::string( width - str.length(), ' ' );
  1229. } else if ( str.length() > width ) {
  1230. str = str.substr( 0, width );
  1231. }
  1232. }
  1233. struct ColorRGBA
  1234. {
  1235. uint8_t r, g, b, a;
  1236. };
  1237. union Color
  1238. {
  1239. ColorRGBA rgba;
  1240. COLORREF dw;
  1241. };
  1242. static_assert(sizeof(Color) == 4);
  1243. HDC visDC;
  1244. HGDIOBJ visbitmap;
  1245. Color viscolors[col_max];
  1246. HPEN vispens[col_max];
  1247. HBRUSH visbrushs[col_max];
  1248. HFONT visfont;
  1249. static int last_pattern = -1;
  1250. static Color invert_color( Color c ) {
  1251. Color res;
  1252. res.rgba.a = c.rgba.a;
  1253. res.rgba.r = 255 - c.rgba.r;
  1254. res.rgba.g = 255 - c.rgba.g;
  1255. res.rgba.b = 255 - c.rgba.b;
  1256. return res;
  1257. }
  1258. static BOOL WINAPI VisOpen(DWORD colors[3]) {
  1259. xmpopenmpt_lock guard;
  1260. visDC = 0;
  1261. visbitmap = 0;
  1262. visfont = 0;
  1263. viscolors[col_background].dw = colors[0];
  1264. viscolors[col_unknown].dw = colors[1];
  1265. viscolors[col_text].dw = colors[2];
  1266. viscolors[col_global] = invert_color( viscolors[col_background] );
  1267. const int r = viscolors[col_text].rgba.r, g = viscolors[col_text].rgba.g, b = viscolors[col_text].rgba.b;
  1268. viscolors[col_empty].rgba.r = static_cast<std::uint8_t>( (r + viscolors[col_background].rgba.r) / 2 );
  1269. viscolors[col_empty].rgba.g = static_cast<std::uint8_t>( (g + viscolors[col_background].rgba.g) / 2 );
  1270. viscolors[col_empty].rgba.b = static_cast<std::uint8_t>( (b + viscolors[col_background].rgba.b) / 2 );
  1271. viscolors[col_empty].rgba.a = 0;
  1272. #define MIXCOLOR(col, c1, c2, c3) { \
  1273. viscolors[col] = viscolors[col_text]; \
  1274. int mix = viscolors[col].rgba.c1 + 0xA0; \
  1275. viscolors[col].rgba.c1 = static_cast<std::uint8_t>( mix ); \
  1276. if ( mix > 0xFF ) { \
  1277. viscolors[col].rgba.c2 = std::max( static_cast<std::uint8_t>( c2 - viscolors[col].rgba.c1 / 2 ), std::uint8_t(0) ); \
  1278. viscolors[col].rgba.c3 = std::max( static_cast<std::uint8_t>( c3 - viscolors[col].rgba.c1 / 2 ), std::uint8_t(0) ); \
  1279. viscolors[col].rgba.c1 = 0xFF; \
  1280. } }
  1281. MIXCOLOR(col_instr, g, r, b);
  1282. MIXCOLOR(col_vol, b, r, g);
  1283. MIXCOLOR(col_pitch, r, g, b);
  1284. #undef MIXCOLOR
  1285. for( int i = 0; i < col_max; ++i ) {
  1286. vispens[i] = CreatePen( PS_SOLID, 1, viscolors[i].dw );
  1287. visbrushs[i] = CreateSolidBrush( viscolors[i].dw );
  1288. }
  1289. clear_current_timeinfo();
  1290. if ( !self->mod ) {
  1291. return FALSE;
  1292. }
  1293. return TRUE;
  1294. }
  1295. static void WINAPI VisClose() {
  1296. xmpopenmpt_lock guard;
  1297. for( int i = 0; i < col_max; ++i ) {
  1298. DeletePen( vispens[i] );
  1299. DeleteBrush( visbrushs[i] );
  1300. }
  1301. DeleteFont( visfont );
  1302. DeleteBitmap( visbitmap );
  1303. if ( visDC ) {
  1304. DeleteDC( visDC );
  1305. }
  1306. }
  1307. static void WINAPI VisSize( HDC /* dc */ , SIZE * /* size */ ) {
  1308. xmpopenmpt_lock guard;
  1309. last_pattern = -1; // Force redraw
  1310. }
  1311. #if 0
  1312. static BOOL WINAPI VisRender( DWORD * /* buf */ , SIZE /* size */ , DWORD /* flags */ ) {
  1313. xmpopenmpt_lock guard;
  1314. return FALSE;
  1315. }
  1316. #endif
  1317. static int get_pattern_width( int chars_per_channel, int spaces_per_channel, int num_cols, int text_size, int channels )
  1318. {
  1319. int pattern_width = ((chars_per_channel * channels + 4) * text_size) + (spaces_per_channel * channels + channels - (num_cols == 1 ? 1 : 2)) * (text_size / 2);
  1320. return pattern_width;
  1321. }
  1322. static BOOL WINAPI VisRenderDC( HDC dc, SIZE size, DWORD flags ) {
  1323. xmpopenmpt_lock guard;
  1324. RECT rect;
  1325. if ( !visfont ) {
  1326. // Force usage of a nice monospace font
  1327. LOGFONT logfont;
  1328. GetObject ( GetCurrentObject( dc, OBJ_FONT ), sizeof(logfont), &logfont );
  1329. _tcscpy( logfont.lfFaceName, TEXT("Lucida Console") );
  1330. visfont = CreateFontIndirect( &logfont );
  1331. }
  1332. SIZE text_size;
  1333. SelectFont( dc, visfont );
  1334. if ( GetTextExtentPoint32( dc, TEXT("W"), 1, &text_size ) == FALSE ) {
  1335. return FALSE;
  1336. }
  1337. if ( flags & XMPIN_VIS_INIT ) {
  1338. last_pattern = -1;
  1339. }
  1340. timeinfo info = lookup_timeinfo( xmpfstatus->GetTime() );
  1341. if ( !info.valid ) {
  1342. RECT bgrect;
  1343. bgrect.top = 0;
  1344. bgrect.left = 0;
  1345. bgrect.right = size.cx;
  1346. bgrect.bottom = size.cy;
  1347. FillRect(dc, &bgrect, visbrushs[col_background]);
  1348. return TRUE;
  1349. }
  1350. int pattern = info.pattern;
  1351. int current_row = info.row;
  1352. const std::size_t channels = self->mod->get_num_channels();
  1353. const std::size_t rows = self->mod->get_pattern_num_rows( pattern );
  1354. 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;
  1355. //const std::size_t num_rows = size.cy / text_size.cy;
  1356. // Spaces between pattern components are half width, full space at channel end
  1357. const std::size_t half_chars_per_channel = num_half_chars / channels;
  1358. std::size_t chars_per_channel, spaces_per_channel;
  1359. std::size_t num_cols;
  1360. std::size_t col0_width = pattern_columns[0].num_chars;
  1361. for ( num_cols = sizeof ( pattern_columns ) / sizeof ( pattern_columns[0] ); num_cols >= 1; num_cols-- ) {
  1362. chars_per_channel = 0;
  1363. spaces_per_channel = num_cols > 1 ? num_cols : 0; // No extra space if we only display notes
  1364. for ( std::size_t i = 0; i < num_cols; i++ ) {
  1365. chars_per_channel += pattern_columns[i].num_chars;
  1366. }
  1367. if ( half_chars_per_channel >= chars_per_channel * 2 + spaces_per_channel + 1 || num_cols == 1 ) {
  1368. break;
  1369. }
  1370. }
  1371. if ( !self->settings.vis_allow_scroll ) {
  1372. if ( num_cols == 1 ) {
  1373. spaces_per_channel = 0;
  1374. while ( get_pattern_width( chars_per_channel, spaces_per_channel, num_cols, text_size.cx, channels ) > size.cx && chars_per_channel > 1 ) {
  1375. chars_per_channel -= 1;
  1376. }
  1377. col0_width = chars_per_channel;
  1378. chars_per_channel = col0_width;
  1379. }
  1380. }
  1381. int pattern_width = get_pattern_width( chars_per_channel, spaces_per_channel, num_cols, text_size.cx, channels );
  1382. int pattern_height = rows * text_size.cy;
  1383. if ( !visDC || last_pattern != pattern ) {
  1384. DeleteBitmap( visbitmap );
  1385. if ( visDC ) {
  1386. DeleteDC( visDC );
  1387. }
  1388. visDC = CreateCompatibleDC( dc );
  1389. visbitmap = CreateCompatibleBitmap( dc, pattern_width, pattern_height );
  1390. SelectBitmap( visDC, visbitmap );
  1391. SelectBrush( visDC, vispens[col_unknown] );
  1392. SelectBrush( visDC, visbrushs[col_background] );
  1393. SelectFont( visDC, visfont );
  1394. rect.top = 0;
  1395. rect.left = 0;
  1396. rect.right = pattern_width;
  1397. rect.bottom = pattern_height;
  1398. FillRect( visDC, &rect, visbrushs[col_background] );
  1399. SetBkColor( visDC, viscolors[col_background].dw );
  1400. POINT pos;
  1401. pos.y = 0;
  1402. for ( std::size_t row = 0; row < rows; row++ ) {
  1403. pos.x = 0;
  1404. std::ostringstream s;
  1405. s.imbue(std::locale::classic());
  1406. s << std::setfill('0') << std::setw(3) << row;
  1407. const std::string rowstr = s.str();
  1408. SetTextColor( visDC, viscolors[1].dw );
  1409. TextOutA( visDC, pos.x, pos.y, rowstr.c_str(), rowstr.length() );
  1410. pos.x += 4 * text_size.cx;
  1411. for ( std::size_t channel = 0; channel < channels; ++channel ) {
  1412. struct coldata {
  1413. std::string text;
  1414. bool is_empty;
  1415. ColorIndex color;
  1416. coldata()
  1417. : is_empty(false)
  1418. , color(col_unknown)
  1419. {
  1420. return;
  1421. }
  1422. };
  1423. coldata cols[max_cols];
  1424. for ( std::size_t col = 0; col < max_cols; ++col ) {
  1425. switch ( col ) {
  1426. case 0:
  1427. cols[col].text = self->mod->format_pattern_row_channel_command( pattern, row, channel, openmpt::module::command_note );
  1428. break;
  1429. case 1:
  1430. cols[col].text = self->mod->format_pattern_row_channel_command( pattern, row, channel, openmpt::module::command_instrument );
  1431. break;
  1432. case 2:
  1433. cols[col].text = self->mod->format_pattern_row_channel_command( pattern, row, channel, openmpt::module::command_volumeffect )
  1434. + self->mod->format_pattern_row_channel_command( pattern, row, channel, openmpt::module::command_volume );
  1435. break;
  1436. case 3:
  1437. cols[col].text = self->mod->format_pattern_row_channel_command( pattern, row, channel, openmpt::module::command_effect )
  1438. + self->mod->format_pattern_row_channel_command( pattern, row, channel, openmpt::module::command_parameter );
  1439. break;
  1440. }
  1441. int color = pattern_columns[col].color;
  1442. if ( self->pattern_vis && ( col == 2 || col == 3 ) ) {
  1443. if ( col == 2 ) {
  1444. color = effect_type_to_color_index( self->pattern_vis->get_pattern_row_channel_volume_effect_type( pattern, row, channel ) );
  1445. }
  1446. if ( col == 3 ) {
  1447. color = effect_type_to_color_index( self->pattern_vis->get_pattern_row_channel_effect_type( pattern, row, channel ) );
  1448. }
  1449. }
  1450. switch ( cols[col].text[0] ) {
  1451. case ' ':
  1452. [[fallthrough]];
  1453. case '.':
  1454. cols[col].is_empty = true;
  1455. [[fallthrough]];
  1456. case '^':
  1457. [[fallthrough]];
  1458. case '=':
  1459. [[fallthrough]];
  1460. case '~':
  1461. color = col_empty;
  1462. break;
  1463. }
  1464. cols[col].color = (ColorIndex)color;
  1465. }
  1466. if ( num_cols <= 3 && !cols[3].is_empty ) {
  1467. if ( cols[2].is_empty ) {
  1468. cols[2] = cols[3];
  1469. } else if ( cols[0].is_empty ) {
  1470. cols[0] = cols[3];
  1471. }
  1472. }
  1473. if ( num_cols <= 2 && !cols[2].is_empty ) {
  1474. if ( cols[0].is_empty ) {
  1475. cols[0] = cols[2];
  1476. }
  1477. }
  1478. for ( std::size_t col = 0; col < num_cols; ++col ) {
  1479. std::size_t col_width = ( num_cols > 1 ) ? pattern_columns[col].num_chars : col0_width;
  1480. assure_width( cols[col].text, col_width );
  1481. SetTextColor( visDC, viscolors[cols[col].color].dw );
  1482. TextOutA( visDC, pos.x, pos.y, cols[col].text.c_str(), cols[col].text.length() );
  1483. pos.x += col_width * text_size.cx + text_size.cx / 2;
  1484. }
  1485. // Extra padding
  1486. if ( num_cols > 1 ) {
  1487. pos.x += text_size.cx / 2;
  1488. }
  1489. }
  1490. pos.y += text_size.cy;
  1491. }
  1492. }
  1493. rect.top = 0;
  1494. rect.left = 0;
  1495. rect.right = size.cx;
  1496. rect.bottom = size.cy;
  1497. FillRect( dc, &rect, visbrushs[col_background] );
  1498. int offset_x = (size.cx - pattern_width) / 2;
  1499. int offset_y = (size.cy - text_size.cy) / 2 - current_row * text_size.cy;
  1500. int src_offset_x = 0;
  1501. int src_offset_y = 0;
  1502. if ( offset_x < 0 ) {
  1503. src_offset_x -= offset_x;
  1504. pattern_width = std::min( static_cast<int>( pattern_width + offset_x ), static_cast<int>( size.cx ) );
  1505. offset_x = 0;
  1506. }
  1507. if ( offset_y < 0 ) {
  1508. src_offset_y -= offset_y;
  1509. pattern_height = std::min( static_cast<int>( pattern_height + offset_y ), static_cast<int>( size.cy ) );
  1510. offset_y = 0;
  1511. }
  1512. BitBlt( dc, offset_x, offset_y, pattern_width, pattern_height, visDC, src_offset_x, src_offset_y , SRCCOPY );
  1513. // Highlight current row
  1514. rect.left = offset_x;
  1515. rect.top = (size.cy - text_size.cy) / 2;
  1516. rect.right = rect.left + pattern_width;
  1517. rect.bottom = rect.top + text_size.cy;
  1518. InvertRect( dc, &rect );
  1519. last_pattern = pattern;
  1520. return TRUE;
  1521. }
  1522. #if 0
  1523. static void WINAPI VisButton( DWORD /* x */ , DWORD /* y */ ) {
  1524. //xmpopenmpt_lock guard;
  1525. }
  1526. #endif
  1527. static XMPIN xmpin = {
  1528. #ifdef USE_XMPLAY_FILE_IO
  1529. 0 |
  1530. #else
  1531. XMPIN_FLAG_NOXMPFILE |
  1532. #endif
  1533. XMPIN_FLAG_CONFIG | XMPIN_FLAG_LOOP,
  1534. xmp_openmpt_string,
  1535. nullptr, // "libopenmpt\0mptm/mptmz",
  1536. openmpt_About,
  1537. openmpt_Config,
  1538. openmpt_CheckFile,
  1539. openmpt_GetFileInfo,
  1540. openmpt_Open,
  1541. openmpt_Close,
  1542. nullptr, // reserved
  1543. openmpt_SetFormat,
  1544. openmpt_GetTags,
  1545. openmpt_GetInfoText,
  1546. openmpt_GetGeneralInfo,
  1547. openmpt_GetMessage,
  1548. openmpt_SetPosition,
  1549. openmpt_GetGranularity,
  1550. nullptr, // GetBuffering
  1551. openmpt_Process,
  1552. nullptr, // WriteFile
  1553. openmpt_GetSamples,
  1554. openmpt_GetSubSongs, // GetSubSongs
  1555. nullptr, // GetCues
  1556. nullptr, // GetDownloaded
  1557. "OpenMPT Pattern Display",
  1558. VisOpen,
  1559. VisClose,
  1560. VisSize,
  1561. /*VisRender,*/nullptr,
  1562. VisRenderDC,
  1563. /*VisButton,*/nullptr,
  1564. nullptr, // reserved2
  1565. openmpt_GetConfig,
  1566. openmpt_SetConfig
  1567. };
  1568. static const char * xmp_openmpt_default_exts = "OpenMPT\0mptm/mptmz";
  1569. static char * file_formats;
  1570. static void xmp_openmpt_on_dll_load() {
  1571. ZeroMemory( &xmpopenmpt_mutex, sizeof( xmpopenmpt_mutex ) );
  1572. #if defined(_MSC_VER)
  1573. #pragma warning(push)
  1574. #pragma warning(disable:28125) // The function 'InitializeCriticalSection' must be called from within a try/except block: The requirement might be conditional.
  1575. #endif // _MSC_VER
  1576. InitializeCriticalSection( &xmpopenmpt_mutex );
  1577. #if defined(_MSC_VER)
  1578. #pragma warning(pop)
  1579. #endif // _MSC_VER
  1580. std::vector<std::string> extensions = openmpt::get_supported_extensions();
  1581. std::string filetypes_string = "OpenMPT";
  1582. filetypes_string.push_back('\0');
  1583. bool first = true;
  1584. for ( const auto & ext : extensions ) {
  1585. if ( first ) {
  1586. first = false;
  1587. } else {
  1588. filetypes_string.push_back('/');
  1589. }
  1590. filetypes_string += ext;
  1591. }
  1592. filetypes_string.push_back('\0');
  1593. file_formats = (char*)HeapAlloc( GetProcessHeap(), 0, filetypes_string.size() );
  1594. if ( file_formats ) {
  1595. std::copy( filetypes_string.begin(), filetypes_string.end(), file_formats );
  1596. xmpin.exts = file_formats;
  1597. } else {
  1598. xmpin.exts = xmp_openmpt_default_exts;
  1599. }
  1600. self = new self_xmplay_t();
  1601. }
  1602. static void xmp_openmpt_on_dll_unload() {
  1603. delete self;
  1604. self = nullptr;
  1605. if ( xmpin.exts != xmp_openmpt_default_exts ) {
  1606. HeapFree(GetProcessHeap(), 0, (LPVOID)const_cast<char*>(xmpin.exts));
  1607. }
  1608. xmpin.exts = nullptr;
  1609. DeleteCriticalSection( &xmpopenmpt_mutex );
  1610. }
  1611. static XMPIN * XMPIN_GetInterface_cxx( DWORD face, InterfaceProc faceproc ) {
  1612. if ( face != XMPIN_FACE ) return nullptr;
  1613. xmpfin=(XMPFUNC_IN*)faceproc(XMPFUNC_IN_FACE);
  1614. xmpfmisc=(XMPFUNC_MISC*)faceproc(XMPFUNC_MISC_FACE);
  1615. xmpfregistry=(XMPFUNC_REGISTRY*)faceproc(XMPFUNC_REGISTRY_FACE);
  1616. xmpffile=(XMPFUNC_FILE*)faceproc(XMPFUNC_FILE_FACE);
  1617. xmpftext=(XMPFUNC_TEXT*)faceproc(XMPFUNC_TEXT_FACE);
  1618. xmpfstatus=(XMPFUNC_STATUS*)faceproc(XMPFUNC_STATUS_FACE);
  1619. // Register keyboard shortcuts
  1620. static constexpr std::pair<DWORD, const char *> shortcuts[] = {
  1621. { openmpt_shortcut_ex | openmpt_shortcut_tempo_decrease, "OpenMPT - Decrease Tempo" },
  1622. { openmpt_shortcut_ex | openmpt_shortcut_tempo_increase, "OpenMPT - Increase Tempo" },
  1623. { openmpt_shortcut_ex | openmpt_shortcut_pitch_decrease, "OpenMPT - Decrease Pitch" },
  1624. { openmpt_shortcut_ex | openmpt_shortcut_pitch_increase, "OpenMPT - Increase Pitch" },
  1625. { openmpt_shortcut_ex | openmpt_shortcut_switch_interpolation, "OpenMPT - Switch Interpolation" },
  1626. };
  1627. XMPSHORTCUT cut;
  1628. cut.procex = &ShortcutHandler;
  1629. for ( const auto & shortcut : shortcuts ) {
  1630. cut.id = shortcut.first;
  1631. cut.text = shortcut.second;
  1632. xmpfmisc->RegisterShortcut( &cut );
  1633. }
  1634. self->settings.load();
  1635. return &xmpin;
  1636. }
  1637. extern "C" {
  1638. // XMPLAY expects a WINAPI (which is __stdcall) function using an undecorated symbol name.
  1639. #if defined(__GNUC__)
  1640. XMPIN * WINAPI XMPIN_GetInterface_( DWORD face, InterfaceProc faceproc );
  1641. XMPIN * WINAPI XMPIN_GetInterface_( DWORD face, InterfaceProc faceproc ) {
  1642. return XMPIN_GetInterface_cxx( face, faceproc );
  1643. }
  1644. #pragma GCC diagnostic push
  1645. #pragma GCC diagnostic ignored "-Wattribute-alias"
  1646. // clang-format off
  1647. __declspec(dllexport) void XMPIN_GetInterface() __attribute__((alias("XMPIN_GetInterface_@8")));
  1648. // clang-format on
  1649. #pragma GCC diagnostic pop
  1650. #else
  1651. XMPIN * WINAPI XMPIN_GetInterface( DWORD face, InterfaceProc faceproc ) {
  1652. return XMPIN_GetInterface_cxx( face, faceproc );
  1653. }
  1654. #pragma comment(linker, "/EXPORT:XMPIN_GetInterface=_XMPIN_GetInterface@8")
  1655. #endif
  1656. } // extern "C"
  1657. #if defined(MPT_WITH_MFC) && defined(_MFC_VER)
  1658. namespace libopenmpt {
  1659. namespace plugin {
  1660. void DllMainAttach() {
  1661. xmp_openmpt_on_dll_load();
  1662. }
  1663. void DllMainDetach() {
  1664. xmp_openmpt_on_dll_unload();
  1665. }
  1666. } // namespace plugin
  1667. } // namespace libopenmpt
  1668. #else
  1669. BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved );
  1670. BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ) {
  1671. static_cast<void>(hinstDLL);
  1672. static_cast<void>(lpvReserved);
  1673. switch ( fdwReason ) {
  1674. case DLL_PROCESS_ATTACH:
  1675. xmp_openmpt_on_dll_load();
  1676. break;
  1677. case DLL_PROCESS_DETACH:
  1678. xmp_openmpt_on_dll_unload();
  1679. break;
  1680. }
  1681. return TRUE;
  1682. }
  1683. #endif
  1684. #endif // NO_XMPLAY