openmpt123.cpp 82 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544
  1. /*
  2. * openmpt123.cpp
  3. * --------------
  4. * Purpose: libopenmpt command line player
  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. static const char * const license =
  10. "Copyright (c) 2004-2022, OpenMPT Project Developers and Contributors" "\n"
  11. "Copyright (c) 1997-2003, Olivier Lapicque" "\n"
  12. "All rights reserved." "\n"
  13. "" "\n"
  14. "Redistribution and use in source and binary forms, with or without" "\n"
  15. "modification, are permitted provided that the following conditions are met:" "\n"
  16. " * Redistributions of source code must retain the above copyright" "\n"
  17. " notice, this list of conditions and the following disclaimer." "\n"
  18. " * Redistributions in binary form must reproduce the above copyright" "\n"
  19. " notice, this list of conditions and the following disclaimer in the" "\n"
  20. " documentation and/or other materials provided with the distribution." "\n"
  21. " * Neither the name of the OpenMPT project nor the" "\n"
  22. " names of its contributors may be used to endorse or promote products" "\n"
  23. " derived from this software without specific prior written permission." "\n"
  24. "" "\n"
  25. "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"" "\n"
  26. "AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE" "\n"
  27. "IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE" "\n"
  28. "DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE" "\n"
  29. "FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL" "\n"
  30. "DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR" "\n"
  31. "SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER" "\n"
  32. "CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY," "\n"
  33. "OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE" "\n"
  34. "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." "\n"
  35. ;
  36. #include "openmpt123_config.hpp"
  37. #include <algorithm>
  38. #include <deque>
  39. #include <fstream>
  40. #include <iomanip>
  41. #include <iostream>
  42. #include <iterator>
  43. #include <limits>
  44. #include <locale>
  45. #include <map>
  46. #include <memory>
  47. #include <random>
  48. #include <set>
  49. #include <sstream>
  50. #include <stdexcept>
  51. #include <string>
  52. #include <vector>
  53. #include <cassert>
  54. #include <cmath>
  55. #include <cstdint>
  56. #include <cstdio>
  57. #include <cstdlib>
  58. #include <cstring>
  59. #include <ctime>
  60. #if defined(__DJGPP__)
  61. #include <conio.h>
  62. #include <dpmi.h>
  63. #include <fcntl.h>
  64. #include <io.h>
  65. #include <sys/stat.h>
  66. #include <sys/types.h>
  67. #include <termios.h>
  68. #include <unistd.h>
  69. #elif defined(WIN32)
  70. #include <conio.h>
  71. #include <fcntl.h>
  72. #include <io.h>
  73. #include <stdio.h>
  74. #if defined(__MINGW32__) && !defined(__MINGW64__)
  75. #include <string.h>
  76. #endif
  77. #include <sys/stat.h>
  78. #include <sys/types.h>
  79. #include <windows.h>
  80. #include <mmsystem.h>
  81. #include <mmreg.h>
  82. #else
  83. #include <termios.h>
  84. #include <unistd.h>
  85. #include <sys/ioctl.h>
  86. #include <sys/poll.h>
  87. #include <sys/stat.h>
  88. #include <sys/types.h>
  89. #endif
  90. #include <libopenmpt/libopenmpt.hpp>
  91. #include "openmpt123.hpp"
  92. #include "openmpt123_flac.hpp"
  93. #include "openmpt123_mmio.hpp"
  94. #include "openmpt123_sndfile.hpp"
  95. #include "openmpt123_raw.hpp"
  96. #include "openmpt123_stdout.hpp"
  97. #include "openmpt123_allegro42.hpp"
  98. #include "openmpt123_portaudio.hpp"
  99. #include "openmpt123_pulseaudio.hpp"
  100. #include "openmpt123_sdl2.hpp"
  101. #include "openmpt123_waveout.hpp"
  102. namespace openmpt123 {
  103. struct silent_exit_exception : public std::exception {
  104. };
  105. struct show_license_exception : public std::exception {
  106. };
  107. struct show_credits_exception : public std::exception {
  108. };
  109. struct show_man_version_exception : public std::exception {
  110. };
  111. struct show_man_help_exception : public std::exception {
  112. };
  113. struct show_short_version_number_exception : public std::exception {
  114. };
  115. struct show_version_number_exception : public std::exception {
  116. };
  117. struct show_long_version_number_exception : public std::exception {
  118. };
  119. #if defined( WIN32 )
  120. bool IsConsole( DWORD stdHandle ) {
  121. HANDLE hStd = GetStdHandle( stdHandle );
  122. if ( ( hStd != NULL ) && ( hStd != INVALID_HANDLE_VALUE ) ) {
  123. DWORD mode = 0;
  124. if ( GetConsoleMode( hStd, &mode ) != FALSE ) {
  125. return true;
  126. }
  127. }
  128. return false;
  129. }
  130. #endif
  131. bool IsTerminal( int fd ) {
  132. #if defined( WIN32 )
  133. if ( !_isatty( fd ) ) {
  134. return false;
  135. }
  136. DWORD stdHandle = 0;
  137. if ( fd == 0 ) {
  138. stdHandle = STD_INPUT_HANDLE;
  139. } else if ( fd == 1 ) {
  140. stdHandle = STD_OUTPUT_HANDLE;
  141. } else if ( fd == 2 ) {
  142. stdHandle = STD_ERROR_HANDLE;
  143. }
  144. return IsConsole( stdHandle );
  145. #else
  146. return isatty( fd ) ? true : false;
  147. #endif
  148. }
  149. #if !defined( WIN32 )
  150. static termios saved_attributes;
  151. static void reset_input_mode() {
  152. tcsetattr( STDIN_FILENO, TCSANOW, &saved_attributes );
  153. }
  154. static void set_input_mode() {
  155. termios tattr;
  156. if ( !isatty( STDIN_FILENO ) ) {
  157. return;
  158. }
  159. tcgetattr( STDIN_FILENO, &saved_attributes );
  160. atexit( reset_input_mode );
  161. tcgetattr( STDIN_FILENO, &tattr );
  162. tattr.c_lflag &= ~( ICANON | ECHO );
  163. tattr.c_cc[VMIN] = 1;
  164. tattr.c_cc[VTIME] = 0;
  165. tcsetattr( STDIN_FILENO, TCSAFLUSH, &tattr );
  166. }
  167. #endif
  168. class file_audio_stream_raii : public file_audio_stream_base {
  169. private:
  170. std::unique_ptr<file_audio_stream_base> impl;
  171. public:
  172. file_audio_stream_raii( const commandlineflags & flags, const std::string & filename, std::ostream & log )
  173. : impl(nullptr)
  174. {
  175. if ( !flags.force_overwrite ) {
  176. std::ifstream testfile( filename, std::ios::binary );
  177. if ( testfile ) {
  178. throw exception( "file already exists" );
  179. }
  180. }
  181. if ( false ) {
  182. // nothing
  183. } else if ( flags.output_extension == "raw" ) {
  184. impl = std::make_unique<raw_stream_raii>( filename, flags, log );
  185. #ifdef MPT_WITH_MMIO
  186. } else if ( flags.output_extension == "wav" ) {
  187. impl = std::make_unique<mmio_stream_raii>( filename, flags, log );
  188. #endif
  189. #ifdef MPT_WITH_FLAC
  190. } else if ( flags.output_extension == "flac" ) {
  191. impl = std::make_unique<flac_stream_raii>( filename, flags, log );
  192. #endif
  193. #ifdef MPT_WITH_SNDFILE
  194. } else {
  195. impl = std::make_unique<sndfile_stream_raii>( filename, flags, log );
  196. #endif
  197. }
  198. if ( !impl ) {
  199. throw exception( "file format handler '" + flags.output_extension + "' not found" );
  200. }
  201. }
  202. virtual ~file_audio_stream_raii() {
  203. return;
  204. }
  205. void write_metadata( std::map<std::string,std::string> metadata ) override {
  206. impl->write_metadata( metadata );
  207. }
  208. void write_updated_metadata( std::map<std::string,std::string> metadata ) override {
  209. impl->write_updated_metadata( metadata );
  210. }
  211. void write( const std::vector<float*> buffers, std::size_t frames ) override {
  212. impl->write( buffers, frames );
  213. }
  214. void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override {
  215. impl->write( buffers, frames );
  216. }
  217. };
  218. static std::string ctls_to_string( const std::map<std::string, std::string> & ctls ) {
  219. std::string result;
  220. for ( const auto & ctl : ctls ) {
  221. if ( !result.empty() ) {
  222. result += "; ";
  223. }
  224. result += ctl.first + "=" + ctl.second;
  225. }
  226. return result;
  227. }
  228. static double tempo_flag_to_double( std::int32_t tempo ) {
  229. return std::pow( 2.0, tempo / 24.0 );
  230. }
  231. static double pitch_flag_to_double( std::int32_t pitch ) {
  232. return std::pow( 2.0, pitch / 24.0 );
  233. }
  234. static double my_round( double val ) {
  235. if ( val >= 0.0 ) {
  236. return std::floor( val + 0.5 );
  237. } else {
  238. return std::ceil( val - 0.5 );
  239. }
  240. }
  241. static std::int32_t double_to_tempo_flag( double factor ) {
  242. return static_cast<std::int32_t>( my_round( std::log( factor ) / std::log( 2.0 ) * 24.0 ) );
  243. }
  244. static std::int32_t double_to_pitch_flag( double factor ) {
  245. return static_cast<std::int32_t>( my_round( std::log( factor ) / std::log( 2.0 ) * 24.0 ) );
  246. }
  247. static std::ostream & operator << ( std::ostream & s, const commandlineflags & flags ) {
  248. s << "Quiet: " << flags.quiet << std::endl;
  249. s << "Verbose: " << flags.verbose << std::endl;
  250. s << "Mode : " << mode_to_string( flags.mode ) << std::endl;
  251. s << "Show progress: " << flags.show_progress << std::endl;
  252. s << "Show peak meters: " << flags.show_meters << std::endl;
  253. s << "Show channel peak meters: " << flags.show_channel_meters << std::endl;
  254. s << "Show details: " << flags.show_details << std::endl;
  255. s << "Show message: " << flags.show_message << std::endl;
  256. s << "Update: " << flags.ui_redraw_interval << "ms" << std::endl;
  257. s << "Device: " << flags.device << std::endl;
  258. s << "Buffer: " << flags.buffer << "ms" << std::endl;
  259. s << "Period: " << flags.period << "ms" << std::endl;
  260. s << "Samplerate: " << flags.samplerate << std::endl;
  261. s << "Channels: " << flags.channels << std::endl;
  262. s << "Float: " << flags.use_float << std::endl;
  263. s << "Gain: " << flags.gain / 100.0 << std::endl;
  264. s << "Stereo separation: " << flags.separation << std::endl;
  265. s << "Interpolation filter taps: " << flags.filtertaps << std::endl;
  266. s << "Volume ramping strength: " << flags.ramping << std::endl;
  267. s << "Tempo: " << tempo_flag_to_double( flags.tempo ) << std::endl;
  268. s << "Pitch: " << pitch_flag_to_double( flags.pitch ) << std::endl;
  269. s << "Output dithering: " << flags.dither << std::endl;
  270. s << "Repeat count: " << flags.repeatcount << std::endl;
  271. s << "Seek target: " << flags.seek_target << std::endl;
  272. s << "End time: " << flags.end_time << std::endl;
  273. s << "Standard output: " << flags.use_stdout << std::endl;
  274. s << "Output filename: " << flags.output_filename << std::endl;
  275. s << "Force overwrite output file: " << flags.force_overwrite << std::endl;
  276. s << "Ctls: " << ctls_to_string( flags.ctls ) << std::endl;
  277. s << std::endl;
  278. s << "Files: " << std::endl;
  279. for ( const auto & filename : flags.filenames ) {
  280. s << " " << filename << std::endl;
  281. }
  282. s << std::endl;
  283. return s;
  284. }
  285. static std::string replace( std::string str, const std::string & oldstr, const std::string & newstr ) {
  286. std::size_t pos = 0;
  287. while ( ( pos = str.find( oldstr, pos ) ) != std::string::npos ) {
  288. str.replace( pos, oldstr.length(), newstr );
  289. pos += newstr.length();
  290. }
  291. return str;
  292. }
  293. static bool begins_with( const std::string & str, const std::string & match ) {
  294. return ( str.find( match ) == 0 );
  295. }
  296. static bool ends_with( const std::string & str, const std::string & match ) {
  297. return ( str.rfind( match ) == ( str.length() - match.length() ) );
  298. }
  299. static std::string trim_left(std::string str, const std::string &whitespace = std::string()) {
  300. std::string::size_type pos = str.find_first_not_of(whitespace);
  301. if(pos != std::string::npos) {
  302. str.erase(str.begin(), str.begin() + pos);
  303. } else if(pos == std::string::npos && str.length() > 0 && str.find_last_of(whitespace) == str.length() - 1) {
  304. return std::string();
  305. }
  306. return str;
  307. }
  308. static std::string trim_right(std::string str, const std::string &whitespace = std::string()) {
  309. std::string::size_type pos = str.find_last_not_of(whitespace);
  310. if(pos != std::string::npos) {
  311. str.erase(str.begin() + pos + 1, str.end());
  312. } else if(pos == std::string::npos && str.length() > 0 && str.find_first_of(whitespace) == 0) {
  313. return std::string();
  314. }
  315. return str;
  316. }
  317. static std::string trim(std::string str, const std::string &whitespace = std::string()) {
  318. return trim_right(trim_left(str, whitespace), whitespace);
  319. }
  320. static std::string trim_eol( const std::string & str ) {
  321. return trim( str, "\r\n" );
  322. }
  323. static std::string default_path_separator() {
  324. #if defined(WIN32)
  325. return "\\";
  326. #else
  327. return "/";
  328. #endif
  329. }
  330. static std::string path_separators() {
  331. #if defined(WIN32)
  332. return "\\/";
  333. #else
  334. return "/";
  335. #endif
  336. }
  337. static bool is_path_separator( char c ) {
  338. #if defined(WIN32)
  339. return ( c == '\\' ) || ( c == '/' );
  340. #else
  341. return c == '/';
  342. #endif
  343. }
  344. static std::string get_basepath( std::string filename ) {
  345. std::string::size_type pos = filename.find_last_of( path_separators() );
  346. if ( pos == std::string::npos ) {
  347. return std::string();
  348. }
  349. return filename.substr( 0, pos ) + default_path_separator();
  350. }
  351. static bool is_absolute( std::string filename ) {
  352. #if defined(WIN32)
  353. if ( begins_with( filename, "\\\\?\\UNC\\" ) ) {
  354. return true;
  355. }
  356. if ( begins_with( filename, "\\\\?\\" ) ) {
  357. return true;
  358. }
  359. if ( begins_with( filename, "\\\\" ) ) {
  360. return true; // UNC
  361. }
  362. if ( begins_with( filename, "//" ) ) {
  363. return true; // UNC
  364. }
  365. return ( filename.length() ) >= 3 && ( filename[1] == ':' ) && is_path_separator( filename[2] );
  366. #else
  367. return ( filename.length() >= 1 ) && is_path_separator( filename[0] );
  368. #endif
  369. }
  370. static std::string get_filename( const std::string & filepath ) {
  371. if ( filepath.find_last_of( path_separators() ) == std::string::npos ) {
  372. return filepath;
  373. }
  374. return filepath.substr( filepath.find_last_of( path_separators() ) + 1 );
  375. }
  376. static std::string prepend_lines( std::string str, const std::string & prefix ) {
  377. if ( str.empty() ) {
  378. return str;
  379. }
  380. if ( str.substr( str.length() - 1, 1 ) == std::string("\n") ) {
  381. str = str.substr( 0, str.length() - 1 );
  382. }
  383. return replace( str, std::string("\n"), std::string("\n") + prefix );
  384. }
  385. static std::string bytes_to_string( std::uint64_t bytes ) {
  386. static const char * const suffixes[] = { "B", "kB", "MB", "GB", "TB", "PB" };
  387. int offset = 0;
  388. while ( bytes > 9999 ) {
  389. bytes /= 1000;
  390. offset += 1;
  391. if ( offset == 5 ) {
  392. break;
  393. }
  394. }
  395. std::ostringstream result;
  396. result << bytes << suffixes[offset];
  397. return result.str();
  398. }
  399. static std::string seconds_to_string( double time ) {
  400. std::int64_t time_ms = static_cast<std::int64_t>( time * 1000 );
  401. std::int64_t milliseconds = time_ms % 1000;
  402. std::int64_t seconds = ( time_ms / 1000 ) % 60;
  403. std::int64_t minutes = ( time_ms / ( 1000 * 60 ) ) % 60;
  404. std::int64_t hours = ( time_ms / ( 1000 * 60 * 60 ) );
  405. std::ostringstream str;
  406. if ( hours > 0 ) {
  407. str << hours << ":";
  408. }
  409. str << std::setfill('0') << std::setw(2) << minutes;
  410. str << ":";
  411. str << std::setfill('0') << std::setw(2) << seconds;
  412. str << ".";
  413. str << std::setfill('0') << std::setw(3) << milliseconds;
  414. return str.str();
  415. }
  416. static void show_info( std::ostream & log, bool verbose ) {
  417. log << "openmpt123" << " v" << OPENMPT123_VERSION_STRING << ", libopenmpt " << openmpt::string::get( "library_version" ) << " (" << "OpenMPT " << openmpt::string::get( "core_version" ) << ")" << std::endl;
  418. log << "Copyright (c) 2013-2022 OpenMPT Project Developers and Contributors <https://lib.openmpt.org/>" << std::endl;
  419. if ( !verbose ) {
  420. log << std::endl;
  421. return;
  422. }
  423. log << " libopenmpt source..: " << openmpt::string::get( "source_url" ) << std::endl;
  424. log << " libopenmpt date....: " << openmpt::string::get( "source_date" ) << std::endl;
  425. log << " libopenmpt srcinfo.: ";
  426. {
  427. std::vector<std::string> fields;
  428. if ( openmpt::string::get( "source_is_package" ) == "1" ) {
  429. fields.push_back( "package" );
  430. }
  431. if ( openmpt::string::get( "source_is_release" ) == "1" ) {
  432. fields.push_back( "release" );
  433. }
  434. if ( ( !openmpt::string::get( "source_revision" ).empty() ) && ( openmpt::string::get( "source_revision" ) != "0" ) ) {
  435. std::string field = "rev" + openmpt::string::get( "source_revision" );
  436. if ( openmpt::string::get( "source_has_mixed_revisions" ) == "1" ) {
  437. field += "+mixed";
  438. }
  439. if ( openmpt::string::get( "source_is_modified" ) == "1" ) {
  440. field += "+modified";
  441. }
  442. fields.push_back( field );
  443. }
  444. bool first = true;
  445. for ( const auto & field : fields ) {
  446. if ( first ) {
  447. first = false;
  448. } else {
  449. log << ", ";
  450. }
  451. log << field;
  452. }
  453. }
  454. log << std::endl;
  455. log << " libopenmpt compiler: " << openmpt::string::get( "build_compiler" ) << std::endl;
  456. log << " libopenmpt features: " << openmpt::string::get( "library_features" ) << std::endl;
  457. #ifdef MPT_WITH_SDL2
  458. log << " libSDL2 ";
  459. SDL_version sdlver;
  460. std::memset( &sdlver, 0, sizeof( SDL_version ) );
  461. SDL_GetVersion( &sdlver );
  462. log << static_cast<int>( sdlver.major ) << "." << static_cast<int>( sdlver.minor ) << "." << static_cast<int>( sdlver.patch );
  463. const char * revision = SDL_GetRevision();
  464. if ( revision ) {
  465. log << " (" << revision << ")";
  466. }
  467. log << ", ";
  468. std::memset( &sdlver, 0, sizeof( SDL_version ) );
  469. SDL_VERSION( &sdlver );
  470. log << "API: " << static_cast<int>( sdlver.major ) << "." << static_cast<int>( sdlver.minor ) << "." << static_cast<int>( sdlver.patch ) << "";
  471. log << " <https://libsdl.org/>" << std::endl;
  472. #endif
  473. #ifdef MPT_WITH_PULSEAUDIO
  474. log << " " << "libpulse, libpulse-simple" << " (headers " << pa_get_headers_version() << ", API " << PA_API_VERSION << ", PROTOCOL " << PA_PROTOCOL_VERSION << ", library " << ( pa_get_library_version() ? pa_get_library_version() : "unknown" ) << ") <https://www.freedesktop.org/wiki/Software/PulseAudio/>" << std::endl;
  475. #endif
  476. #ifdef MPT_WITH_PORTAUDIO
  477. log << " " << Pa_GetVersionText() << " (" << Pa_GetVersion() << ") <http://portaudio.com/>" << std::endl;
  478. #endif
  479. #ifdef MPT_WITH_FLAC
  480. log << " FLAC " << FLAC__VERSION_STRING << ", " << FLAC__VENDOR_STRING << ", API " << FLAC_API_VERSION_CURRENT << "." << FLAC_API_VERSION_REVISION << "." << FLAC_API_VERSION_AGE << " <https://xiph.org/flac/>" << std::endl;
  481. #endif
  482. #ifdef MPT_WITH_SNDFILE
  483. char sndfile_info[128];
  484. std::memset( sndfile_info, 0, sizeof( sndfile_info ) );
  485. sf_command( 0, SFC_GET_LIB_VERSION, sndfile_info, sizeof( sndfile_info ) );
  486. sndfile_info[127] = '\0';
  487. log << " libsndfile " << sndfile_info << " <http://mega-nerd.com/libsndfile/>" << std::endl;
  488. #endif
  489. log << std::endl;
  490. }
  491. static void show_man_version( textout & log ) {
  492. log << "openmpt123" << " v" << OPENMPT123_VERSION_STRING << std::endl;
  493. log << std::endl;
  494. log << "Copyright (c) 2013-2022 OpenMPT Project Developers and Contributors <https://lib.openmpt.org/>" << std::endl;
  495. }
  496. static void show_short_version( textout & log ) {
  497. log << OPENMPT123_VERSION_STRING << " / " << openmpt::string::get( "library_version" ) << " / " << openmpt::string::get( "core_version" ) << std::endl;
  498. log.writeout();
  499. }
  500. static void show_version( textout & log ) {
  501. show_info( log, false );
  502. log.writeout();
  503. }
  504. static void show_long_version( textout & log ) {
  505. show_info( log, true );
  506. log.writeout();
  507. }
  508. static void show_credits( textout & log ) {
  509. show_info( log, false );
  510. log << openmpt::string::get( "contact" ) << std::endl;
  511. log << std::endl;
  512. log << openmpt::string::get( "credits" ) << std::endl;
  513. log.writeout();
  514. }
  515. static void show_license( textout & log ) {
  516. show_info( log, false );
  517. log << license << std::endl;
  518. log.writeout();
  519. }
  520. static std::string get_driver_string( const std::string & driver ) {
  521. if ( driver.empty() ) {
  522. return "default";
  523. }
  524. return driver;
  525. }
  526. static std::string get_device_string( const std::string & device ) {
  527. if ( device.empty() ) {
  528. return "default";
  529. }
  530. return device;
  531. }
  532. static void show_help( textout & log, bool with_info = true, bool longhelp = false, bool man_version = false, const std::string & message = std::string() ) {
  533. if ( with_info ) {
  534. show_info( log, false );
  535. }
  536. {
  537. log << "Usage: openmpt123 [options] [--] file1 [file2] ..." << std::endl;
  538. log << std::endl;
  539. if ( man_version ) {
  540. log << "openmpt123 plays module music files." << std::endl;
  541. log << std::endl;
  542. }
  543. if ( man_version ) {
  544. log << "Options:" << std::endl;
  545. }
  546. log << " -h, --help Show help" << std::endl;
  547. log << " --help-keyboard Show keyboard hotkeys in ui mode" << std::endl;
  548. log << " -q, --quiet Suppress non-error screen output" << std::endl;
  549. log << " -v, --verbose Show more screen output" << std::endl;
  550. log << " --version Show version information and exit" << std::endl;
  551. log << " --short-version Show version number and nothing else" << std::endl;
  552. log << " --long-version Show long version information and exit" << std::endl;
  553. log << " --credits Show elaborate contributors list" << std::endl;
  554. log << " --license Show license" << std::endl;
  555. log << std::endl;
  556. log << " --probe Probe each file whether it is a supported file format" << std::endl;
  557. log << " --info Display information about each file" << std::endl;
  558. log << " --ui Interactively play each file" << std::endl;
  559. log << " --batch Play each file" << std::endl;
  560. log << " --render Render each file to individual PCM data files" << std::endl;
  561. if ( !longhelp ) {
  562. log << std::endl;
  563. log.writeout();
  564. return;
  565. }
  566. log << std::endl;
  567. log << " --terminal-width n Assume terminal is n characters wide [default: " << commandlineflags().terminal_width << "]" << std::endl;
  568. log << " --terminal-height n Assume terminal is n characters high [default: " << commandlineflags().terminal_height << "]" << std::endl;
  569. log << std::endl;
  570. log << " --[no-]progress Show playback progress [default: " << commandlineflags().show_progress << "]" << std::endl;
  571. log << " --[no-]meters Show peak meters [default: " << commandlineflags().show_meters << "]" << std::endl;
  572. log << " --[no-]channel-meters Show channel peak meters (EXPERIMENTAL) [default: " << commandlineflags().show_channel_meters << "]" << std::endl;
  573. log << " --[no-]pattern Show pattern (EXPERIMENTAL) [default: " << commandlineflags().show_pattern << "]" << std::endl;
  574. log << std::endl;
  575. log << " --[no-]details Show song details [default: " << commandlineflags().show_details << "]" << std::endl;
  576. log << " --[no-]message Show song message [default: " << commandlineflags().show_message << "]" << std::endl;
  577. log << std::endl;
  578. log << " --update n Set output update interval to n ms [default: " << commandlineflags().ui_redraw_interval << "]" << std::endl;
  579. log << std::endl;
  580. log << " --samplerate n Set samplerate to n Hz [default: " << commandlineflags().samplerate << "]" << std::endl;
  581. log << " --channels n use n [1,2,4] output channels [default: " << commandlineflags().channels << "]" << std::endl;
  582. log << " --[no-]float Output 32bit floating point instead of 16bit integer [default: " << commandlineflags().use_float << "]" << std::endl;
  583. log << std::endl;
  584. log << " --gain n Set output gain to n dB [default: " << commandlineflags().gain / 100.0 << "]" << std::endl;
  585. log << " --stereo n Set stereo separation to n % [default: " << commandlineflags().separation << "]" << std::endl;
  586. log << " --filter n Set interpolation filter taps to n [1,2,4,8] [default: " << commandlineflags().filtertaps << "]" << std::endl;
  587. log << " --ramping n Set volume ramping strength n [0..5] [default: " << commandlineflags().ramping << "]" << std::endl;
  588. log << " --tempo f Set tempo factor f [default: " << tempo_flag_to_double( commandlineflags().tempo ) << "]" << std::endl;
  589. log << " --pitch f Set pitch factor f [default: " << pitch_flag_to_double( commandlineflags().pitch ) << "]" << std::endl;
  590. log << " --dither n Dither type to use (if applicable for selected output format): [0=off,1=auto,2=0.5bit,3=1bit] [default: " << commandlineflags().dither << "]" << std::endl;
  591. log << std::endl;
  592. log << " --playlist file Load playlist from file" << std::endl;
  593. log << " --[no-]randomize Randomize playlist [default: " << commandlineflags().randomize << "]" << std::endl;
  594. log << " --[no-]shuffle Shuffle through playlist [default: " << commandlineflags().shuffle << "]" << std::endl;
  595. log << " --[no-]restart Restart playlist when finished [default: " << commandlineflags().restart << "]" << std::endl;
  596. log << std::endl;
  597. log << " --subsong n Select subsong n (-1 means play all subsongs consecutively) [default: " << commandlineflags().subsong << "]" << std::endl;
  598. log << " --repeat n Repeat song n times (-1 means forever) [default: " << commandlineflags().repeatcount << "]" << std::endl;
  599. log << " --seek n Seek to n seconds on start [default: " << commandlineflags().seek_target << "]" << std::endl;
  600. log << " --end-time n Play until position is n seconds (0 means until the end) [default: " << commandlineflags().end_time << "]" << std::endl;
  601. log << std::endl;
  602. log << " --ctl c=v Set libopenmpt ctl c to value v" << std::endl;
  603. log << std::endl;
  604. log << " --driver n Set output driver [default: " << get_driver_string( commandlineflags().driver ) << "]," << std::endl;
  605. log << " --device n Set output device [default: " << get_device_string( commandlineflags().device ) << "]," << std::endl;
  606. log << " use --device help to show available devices" << std::endl;
  607. log << " --buffer n Set output buffer size to n ms [default: " << commandlineflags().buffer << "]" << std::endl;
  608. log << " --period n Set output period size to n ms [default: " << commandlineflags().period << "]" << std::endl;
  609. log << " --stdout Write raw audio data to stdout [default: " << commandlineflags().use_stdout << "]" << std::endl;
  610. log << " --output-type t Use output format t when writing to a individual PCM files (only applies to --render mode) [default: " << commandlineflags().output_extension << "]" << std::endl;
  611. log << " -o, --output f Write PCM output to file f instead of streaming to audio device (only applies to --ui and --batch modes) [default: " << commandlineflags().output_filename << "]" << std::endl;
  612. log << " --force Force overwriting of output file [default: " << commandlineflags().force_overwrite << "]" << std::endl;
  613. log << std::endl;
  614. log << " -- Interpret further arguments as filenames" << std::endl;
  615. log << std::endl;
  616. if ( !man_version ) {
  617. log << " Supported file formats: " << std::endl;
  618. log << " ";
  619. std::vector<std::string> extensions = openmpt::get_supported_extensions();
  620. bool first = true;
  621. for ( const auto & extension : extensions ) {
  622. if ( first ) {
  623. first = false;
  624. } else {
  625. log << ", ";
  626. }
  627. log << extension;
  628. }
  629. log << std::endl;
  630. }
  631. }
  632. log << std::endl;
  633. if ( message.size() > 0 ) {
  634. log << message;
  635. log << std::endl;
  636. }
  637. log.writeout();
  638. }
  639. static void show_help_keyboard( textout & log ) {
  640. show_info( log, false );
  641. log << "Keyboard hotkeys (use 'openmpt123 --ui'):" << std::endl;
  642. log << std::endl;
  643. log << " [q] quit" << std::endl;
  644. log << " [ ] pause / unpause" << std::endl;
  645. log << " [N] skip 10 files backward" << std::endl;
  646. log << " [n] prev file" << std::endl;
  647. log << " [m] next file" << std::endl;
  648. log << " [M] skip 10 files forward" << std::endl;
  649. log << " [h] seek 10 seconds backward" << std::endl;
  650. log << " [j] seek 1 seconds backward" << std::endl;
  651. log << " [k] seek 1 seconds forward" << std::endl;
  652. log << " [l] seek 10 seconds forward" << std::endl;
  653. log << " [u]|[i] +/- tempo" << std::endl;
  654. log << " [o]|[p] +/- pitch" << std::endl;
  655. log << " [3]|[4] +/- gain" << std::endl;
  656. log << " [5]|[6] +/- stereo separation" << std::endl;
  657. log << " [7]|[8] +/- filter taps" << std::endl;
  658. log << " [9]|[0] +/- volume ramping" << std::endl;
  659. log << std::endl;
  660. log.writeout();
  661. }
  662. template < typename Tmod >
  663. static void apply_mod_settings( commandlineflags & flags, Tmod & mod ) {
  664. flags.separation = std::max( flags.separation, std::int32_t( 0 ) );
  665. flags.filtertaps = std::max( flags.filtertaps, std::int32_t( 1 ) );
  666. flags.filtertaps = std::min( flags.filtertaps, std::int32_t( 8 ) );
  667. flags.ramping = std::max( flags.ramping, std::int32_t( -1 ) );
  668. flags.ramping = std::min( flags.ramping, std::int32_t( 10 ) );
  669. flags.tempo = std::max( flags.tempo, std::int32_t( -48 ) );
  670. flags.tempo = std::min( flags.tempo, std::int32_t( 48 ) );
  671. flags.pitch = std::max( flags.pitch, std::int32_t( -48 ) );
  672. flags.pitch = std::min( flags.pitch, std::int32_t( 48 ) );
  673. mod.set_render_param( openmpt::module::RENDER_MASTERGAIN_MILLIBEL, flags.gain );
  674. mod.set_render_param( openmpt::module::RENDER_STEREOSEPARATION_PERCENT, flags.separation );
  675. mod.set_render_param( openmpt::module::RENDER_INTERPOLATIONFILTER_LENGTH, flags.filtertaps );
  676. mod.set_render_param( openmpt::module::RENDER_VOLUMERAMPING_STRENGTH, flags.ramping );
  677. try {
  678. mod.ctl_set_floatingpoint( "play.tempo_factor", tempo_flag_to_double( flags.tempo ) );
  679. } catch ( const openmpt::exception & ) {
  680. // ignore
  681. }
  682. try {
  683. mod.ctl_set_floatingpoint( "play.pitch_factor", pitch_flag_to_double( flags.pitch ) );
  684. } catch ( const openmpt::exception & ) {
  685. // ignore
  686. }
  687. mod.ctl_set_integer( "dither", flags.dither );
  688. }
  689. struct prev_file { int count; prev_file( int c ) : count(c) { } };
  690. struct next_file { int count; next_file( int c ) : count(c) { } };
  691. template < typename Tmod >
  692. static bool handle_keypress( int c, commandlineflags & flags, Tmod & mod, write_buffers_interface & audio_stream ) {
  693. switch ( c ) {
  694. case 'q': throw silent_exit_exception(); break;
  695. case 'N': throw prev_file(10); break;
  696. case 'n': throw prev_file(1); break;
  697. case ' ': if ( !flags.paused ) { flags.paused = audio_stream.pause(); } else { flags.paused = false; audio_stream.unpause(); } break;
  698. case 'h': mod.set_position_seconds( mod.get_position_seconds() - 10.0 ); break;
  699. case 'j': mod.set_position_seconds( mod.get_position_seconds() - 1.0 ); break;
  700. case 'k': mod.set_position_seconds( mod.get_position_seconds() + 1.0 ); break;
  701. case 'l': mod.set_position_seconds( mod.get_position_seconds() + 10.0 ); break;
  702. case 'H': mod.set_position_order_row( mod.get_current_order() - 1, 0 ); break;
  703. case 'J': mod.set_position_order_row( mod.get_current_order(), mod.get_current_row() - 1 ); break;
  704. case 'K': mod.set_position_order_row( mod.get_current_order(), mod.get_current_row() + 1 ); break;
  705. case 'L': mod.set_position_order_row( mod.get_current_order() + 1, 0 ); break;
  706. case 'm': throw next_file(1); break;
  707. case 'M': throw next_file(10); break;
  708. case 'u': flags.tempo -= 1; apply_mod_settings( flags, mod ); break;
  709. case 'i': flags.tempo += 1; apply_mod_settings( flags, mod ); break;
  710. case 'o': flags.pitch -= 1; apply_mod_settings( flags, mod ); break;
  711. case 'p': flags.pitch += 1; apply_mod_settings( flags, mod ); break;
  712. case '3': flags.gain -=100; apply_mod_settings( flags, mod ); break;
  713. case '4': flags.gain +=100; apply_mod_settings( flags, mod ); break;
  714. case '5': flags.separation -= 5; apply_mod_settings( flags, mod ); break;
  715. case '6': flags.separation += 5; apply_mod_settings( flags, mod ); break;
  716. case '7': flags.filtertaps /= 2; apply_mod_settings( flags, mod ); break;
  717. case '8': flags.filtertaps *= 2; apply_mod_settings( flags, mod ); break;
  718. case '9': flags.ramping -= 1; apply_mod_settings( flags, mod ); break;
  719. case '0': flags.ramping += 1; apply_mod_settings( flags, mod ); break;
  720. }
  721. return true;
  722. }
  723. struct meter_channel {
  724. float peak;
  725. float clip;
  726. float hold;
  727. float hold_age;
  728. meter_channel()
  729. : peak(0.0f)
  730. , clip(0.0f)
  731. , hold(0.0f)
  732. , hold_age(0.0f)
  733. {
  734. return;
  735. }
  736. };
  737. struct meter_type {
  738. meter_channel channels[4];
  739. };
  740. static const float falloff_rate = 20.0f / 1.7f;
  741. static void update_meter( meter_type & meter, const commandlineflags & flags, std::size_t count, const std::int16_t * const * buffers ) {
  742. float falloff_factor = std::pow( 10.0f, -falloff_rate / flags.samplerate / 20.0f );
  743. for ( int channel = 0; channel < flags.channels; ++channel ) {
  744. meter.channels[channel].peak = 0.0f;
  745. for ( std::size_t frame = 0; frame < count; ++frame ) {
  746. if ( meter.channels[channel].clip != 0.0f ) {
  747. meter.channels[channel].clip -= ( 1.0f / 2.0f ) * 1.0f / static_cast<float>( flags.samplerate );
  748. if ( meter.channels[channel].clip <= 0.0f ) {
  749. meter.channels[channel].clip = 0.0f;
  750. }
  751. }
  752. float val = std::fabs( buffers[channel][frame] / 32768.0f );
  753. if ( val >= 1.0f ) {
  754. meter.channels[channel].clip = 1.0f;
  755. }
  756. if ( val > meter.channels[channel].peak ) {
  757. meter.channels[channel].peak = val;
  758. }
  759. meter.channels[channel].hold *= falloff_factor;
  760. if ( val > meter.channels[channel].hold ) {
  761. meter.channels[channel].hold = val;
  762. meter.channels[channel].hold_age = 0.0f;
  763. } else {
  764. meter.channels[channel].hold_age += 1.0f / static_cast<float>( flags.samplerate );
  765. }
  766. }
  767. }
  768. }
  769. static void update_meter( meter_type & meter, const commandlineflags & flags, std::size_t count, const float * const * buffers ) {
  770. float falloff_factor = std::pow( 10.0f, -falloff_rate / flags.samplerate / 20.0f );
  771. for ( int channel = 0; channel < flags.channels; ++channel ) {
  772. if ( !count ) {
  773. meter = meter_type();
  774. }
  775. meter.channels[channel].peak = 0.0f;
  776. for ( std::size_t frame = 0; frame < count; ++frame ) {
  777. if ( meter.channels[channel].clip != 0.0f ) {
  778. meter.channels[channel].clip -= ( 1.0f / 2.0f ) * 1.0f / static_cast<float>( flags.samplerate );
  779. if ( meter.channels[channel].clip <= 0.0f ) {
  780. meter.channels[channel].clip = 0.0f;
  781. }
  782. }
  783. float val = std::fabs( buffers[channel][frame] );
  784. if ( val >= 1.0f ) {
  785. meter.channels[channel].clip = 1.0f;
  786. }
  787. if ( val > meter.channels[channel].peak ) {
  788. meter.channels[channel].peak = val;
  789. }
  790. meter.channels[channel].hold *= falloff_factor;
  791. if ( val > meter.channels[channel].hold ) {
  792. meter.channels[channel].hold = val;
  793. meter.channels[channel].hold_age = 0.0f;
  794. } else {
  795. meter.channels[channel].hold_age += 1.0f / static_cast<float>( flags.samplerate );
  796. }
  797. }
  798. }
  799. }
  800. static const char * const channel_tags[4][4] = {
  801. { " C", " ", " ", " " },
  802. { " L", " R", " ", " " },
  803. { "FL", "FR", "RC", " " },
  804. { "FL", "FR", "RL", "RR" },
  805. };
  806. static std::string channel_to_string( int channels, int channel, const meter_channel & meter, bool tiny = false ) {
  807. int val = std::numeric_limits<int>::min();
  808. int hold_pos = std::numeric_limits<int>::min();
  809. if ( meter.peak > 0.0f ) {
  810. float db = 20.0f * std::log10( meter.peak );
  811. val = static_cast<int>( db + 48.0f );
  812. }
  813. if ( meter.hold > 0.0f ) {
  814. float db_hold = 20.0f * std::log10( meter.hold );
  815. hold_pos = static_cast<int>( db_hold + 48.0f );
  816. }
  817. if ( val < 0 ) {
  818. val = 0;
  819. }
  820. int headroom = val;
  821. if ( val > 48 ) {
  822. val = 48;
  823. }
  824. headroom -= val;
  825. if ( headroom < 0 ) {
  826. headroom = 0;
  827. }
  828. if ( headroom > 12 ) {
  829. headroom = 12;
  830. }
  831. headroom -= 1; // clip indicator
  832. if ( headroom < 0 ) {
  833. headroom = 0;
  834. }
  835. if ( tiny ) {
  836. if ( meter.clip != 0.0f || meter.peak >= 1.0f ) {
  837. return "#";
  838. } else if ( meter.peak > std::pow( 10.0f, -6.0f / 20.0f ) ) {
  839. return "O";
  840. } else if ( meter.peak > std::pow( 10.0f, -12.0f / 20.0f ) ) {
  841. return "o";
  842. } else if ( meter.peak > std::pow( 10.0f, -18.0f / 20.0f ) ) {
  843. return ".";
  844. } else {
  845. return " ";
  846. }
  847. } else {
  848. std::ostringstream res1;
  849. std::ostringstream res2;
  850. res1
  851. << " "
  852. << channel_tags[channels-1][channel]
  853. << " : "
  854. ;
  855. res2
  856. << std::string(val,'>') << std::string(std::size_t{48}-val,' ')
  857. << ( ( meter.clip != 0.0f ) ? "#" : ":" )
  858. << std::string(headroom,'>') << std::string(std::size_t{12}-headroom,' ')
  859. ;
  860. std::string tmp = res2.str();
  861. if ( 0 <= hold_pos && hold_pos <= 60 ) {
  862. if ( hold_pos == 48 ) {
  863. tmp[hold_pos] = '#';
  864. } else {
  865. tmp[hold_pos] = ':';
  866. }
  867. }
  868. return res1.str() + tmp;
  869. }
  870. }
  871. static char peak_to_char( float peak ) {
  872. if ( peak >= 1.0f ) {
  873. return '#';
  874. } else if ( peak >= 0.5f ) {
  875. return 'O';
  876. } else if ( peak >= 0.25f ) {
  877. return 'o';
  878. } else if ( peak >= 0.125f ) {
  879. return '.';
  880. } else {
  881. return ' ';
  882. }
  883. }
  884. static std::string peak_to_string_left( float peak, int width ) {
  885. std::string result;
  886. float thresh = 1.0f;
  887. while ( width-- ) {
  888. if ( peak >= thresh ) {
  889. if ( thresh == 1.0f ) {
  890. result.push_back( '#' );
  891. } else {
  892. result.push_back( '<' );
  893. }
  894. } else {
  895. result.push_back( ' ' );
  896. }
  897. thresh *= 0.5f;
  898. }
  899. return result;
  900. }
  901. static std::string peak_to_string_right( float peak, int width ) {
  902. std::string result;
  903. float thresh = 1.0f;
  904. while ( width-- ) {
  905. if ( peak >= thresh ) {
  906. if ( thresh == 1.0f ) {
  907. result.push_back( '#' );
  908. } else {
  909. result.push_back( '>' );
  910. }
  911. } else {
  912. result.push_back( ' ' );
  913. }
  914. thresh *= 0.5f;
  915. }
  916. std::reverse( result.begin(), result.end() );
  917. return result;
  918. }
  919. static void draw_meters( std::ostream & log, const meter_type & meter, const commandlineflags & flags ) {
  920. for ( int channel = 0; channel < flags.channels; ++channel ) {
  921. log << channel_to_string( flags.channels, channel, meter.channels[channel] ) << std::endl;
  922. }
  923. }
  924. static void draw_meters_tiny( std::ostream & log, const meter_type & meter, const commandlineflags & flags ) {
  925. for ( int channel = 0; channel < flags.channels; ++channel ) {
  926. log << channel_to_string( flags.channels, channel, meter.channels[channel], true );
  927. }
  928. }
  929. static void draw_channel_meters_tiny( std::ostream & log, float peak ) {
  930. log << peak_to_char( peak );
  931. }
  932. static void draw_channel_meters_tiny( std::ostream & log, float peak_left, float peak_right ) {
  933. log << peak_to_char( peak_left ) << peak_to_char( peak_right );
  934. }
  935. static void draw_channel_meters( std::ostream & log, float peak_left, float peak_right, int width ) {
  936. if ( width >= 8 + 1 + 8 ) {
  937. width = 8 + 1 + 8;
  938. }
  939. log << peak_to_string_left( peak_left, width / 2 ) << ( width % 2 == 1 ? ":" : "" ) << peak_to_string_right( peak_right, width / 2 );
  940. }
  941. template < typename Tsample, typename Tmod >
  942. void render_loop( commandlineflags & flags, Tmod & mod, double & duration, textout & log, write_buffers_interface & audio_stream ) {
  943. log.writeout();
  944. std::size_t bufsize;
  945. if ( flags.mode == Mode::UI ) {
  946. bufsize = std::min( flags.ui_redraw_interval, flags.period ) * flags.samplerate / 1000;
  947. } else if ( flags.mode == Mode::Batch ) {
  948. bufsize = flags.period * flags.samplerate / 1000;
  949. } else {
  950. bufsize = 1024;
  951. }
  952. std::int64_t last_redraw_frame = std::int64_t{0} - flags.ui_redraw_interval;
  953. std::int64_t rendered_frames = 0;
  954. std::vector<Tsample> left( bufsize );
  955. std::vector<Tsample> right( bufsize );
  956. std::vector<Tsample> rear_left( bufsize );
  957. std::vector<Tsample> rear_right( bufsize );
  958. std::vector<Tsample*> buffers( 4 ) ;
  959. buffers[0] = left.data();
  960. buffers[1] = right.data();
  961. buffers[2] = rear_left.data();
  962. buffers[3] = rear_right.data();
  963. buffers.resize( flags.channels );
  964. meter_type meter;
  965. const bool multiline = flags.show_ui;
  966. int lines = 0;
  967. int pattern_lines = 0;
  968. if ( multiline ) {
  969. lines += 1;
  970. // cppcheck-suppress identicalInnerCondition
  971. if ( flags.show_ui ) {
  972. lines += 1;
  973. }
  974. if ( flags.show_meters ) {
  975. for ( int channel = 0; channel < flags.channels; ++channel ) {
  976. lines += 1;
  977. }
  978. }
  979. if ( flags.show_channel_meters ) {
  980. lines += 1;
  981. }
  982. if ( flags.show_details ) {
  983. lines += 1;
  984. if ( flags.show_progress ) {
  985. lines += 1;
  986. }
  987. }
  988. if ( flags.show_progress ) {
  989. lines += 1;
  990. }
  991. if ( flags.show_pattern ) {
  992. pattern_lines = flags.terminal_height - lines - 1;
  993. lines = flags.terminal_height - 1;
  994. }
  995. } else if ( flags.show_ui || flags.show_details || flags.show_progress ) {
  996. log << std::endl;
  997. }
  998. for ( int line = 0; line < lines; ++line ) {
  999. log << std::endl;
  1000. }
  1001. log.writeout();
  1002. double cpu_smooth = 0.0;
  1003. while ( true ) {
  1004. if ( flags.mode == Mode::UI ) {
  1005. #if defined( __DJGPP__ )
  1006. while ( kbhit() ) {
  1007. int c = getch();
  1008. if ( !handle_keypress( c, flags, mod, audio_stream ) ) {
  1009. return;
  1010. }
  1011. }
  1012. #elif defined( WIN32 ) && defined( UNICODE )
  1013. while ( _kbhit() ) {
  1014. wint_t c = _getwch();
  1015. if ( !handle_keypress( c, flags, mod, audio_stream ) ) {
  1016. return;
  1017. }
  1018. }
  1019. #elif defined( WIN32 )
  1020. while ( _kbhit() ) {
  1021. int c = _getch();
  1022. if ( !handle_keypress( c, flags, mod, audio_stream ) ) {
  1023. return;
  1024. }
  1025. }
  1026. #else
  1027. while ( true ) {
  1028. pollfd pollfds;
  1029. pollfds.fd = STDIN_FILENO;
  1030. pollfds.events = POLLIN;
  1031. poll(&pollfds, 1, 0);
  1032. if ( !( pollfds.revents & POLLIN ) ) {
  1033. break;
  1034. }
  1035. char c = 0;
  1036. if ( read( STDIN_FILENO, &c, 1 ) != 1 ) {
  1037. break;
  1038. }
  1039. if ( !handle_keypress( c, flags, mod, audio_stream ) ) {
  1040. return;
  1041. }
  1042. }
  1043. #endif
  1044. if ( flags.paused ) {
  1045. audio_stream.sleep( flags.ui_redraw_interval );
  1046. continue;
  1047. }
  1048. }
  1049. std::clock_t cpu_beg = 0;
  1050. std::clock_t cpu_end = 0;
  1051. if ( flags.show_details ) {
  1052. cpu_beg = std::clock();
  1053. }
  1054. std::size_t count = 0;
  1055. switch ( flags.channels ) {
  1056. case 1: count = mod.read( flags.samplerate, bufsize, left.data() ); break;
  1057. case 2: count = mod.read( flags.samplerate, bufsize, left.data(), right.data() ); break;
  1058. case 4: count = mod.read( flags.samplerate, bufsize, left.data(), right.data(), rear_left.data(), rear_right.data() ); break;
  1059. }
  1060. char cpu_str[64] = "";
  1061. if ( flags.show_details ) {
  1062. cpu_end = std::clock();
  1063. if ( count > 0 ) {
  1064. double cpu = 1.0;
  1065. cpu *= ( static_cast<double>( cpu_end ) - static_cast<double>( cpu_beg ) ) / static_cast<double>( CLOCKS_PER_SEC );
  1066. cpu /= ( static_cast<double>( count ) ) / static_cast<double>( flags.samplerate );
  1067. double mix = ( static_cast<double>( count ) ) / static_cast<double>( flags.samplerate );
  1068. cpu_smooth = ( 1.0 - mix ) * cpu_smooth + mix * cpu;
  1069. std::snprintf( cpu_str, 64, "%.2f%%", cpu_smooth * 100.0 );
  1070. }
  1071. }
  1072. if ( flags.show_meters ) {
  1073. update_meter( meter, flags, count, buffers.data() );
  1074. }
  1075. if ( count > 0 ) {
  1076. audio_stream.write( buffers, count );
  1077. }
  1078. if ( count > 0 ) {
  1079. rendered_frames += count;
  1080. if ( rendered_frames >= last_redraw_frame + ( flags.ui_redraw_interval * flags.samplerate / 1000 ) ) {
  1081. last_redraw_frame = rendered_frames;
  1082. } else {
  1083. continue;
  1084. }
  1085. }
  1086. if ( multiline ) {
  1087. log.cursor_up( lines );
  1088. log << std::endl;
  1089. if ( flags.show_meters ) {
  1090. draw_meters( log, meter, flags );
  1091. }
  1092. if ( flags.show_channel_meters ) {
  1093. int width = ( flags.terminal_width - 3 ) / mod.get_num_channels();
  1094. if ( width > 11 ) {
  1095. width = 11;
  1096. }
  1097. log << " ";
  1098. for ( std::int32_t channel = 0; channel < mod.get_num_channels(); ++channel ) {
  1099. if ( width >= 3 ) {
  1100. log << ":";
  1101. }
  1102. if ( width == 1 ) {
  1103. draw_channel_meters_tiny( log, ( mod.get_current_channel_vu_left( channel ) + mod.get_current_channel_vu_right( channel ) ) * (1.0f/std::sqrt(2.0f)) );
  1104. } else if ( width <= 4 ) {
  1105. draw_channel_meters_tiny( log, mod.get_current_channel_vu_left( channel ), mod.get_current_channel_vu_right( channel ) );
  1106. } else {
  1107. draw_channel_meters( log, mod.get_current_channel_vu_left( channel ), mod.get_current_channel_vu_right( channel ), width - 1 );
  1108. }
  1109. }
  1110. if ( width >= 3 ) {
  1111. log << ":";
  1112. }
  1113. log << std::endl;
  1114. }
  1115. if ( flags.show_pattern ) {
  1116. int width = ( flags.terminal_width - 3 ) / mod.get_num_channels();
  1117. if ( width > 13 + 1 ) {
  1118. width = 13 + 1;
  1119. }
  1120. for ( std::int32_t line = 0; line < pattern_lines; ++line ) {
  1121. std::int32_t row = mod.get_current_row() - ( pattern_lines / 2 ) + line;
  1122. if ( row == mod.get_current_row() ) {
  1123. log << ">";
  1124. } else {
  1125. log << " ";
  1126. }
  1127. if ( row < 0 || row >= mod.get_pattern_num_rows( mod.get_current_pattern() ) ) {
  1128. for ( std::int32_t channel = 0; channel < mod.get_num_channels(); ++channel ) {
  1129. if ( width >= 3 ) {
  1130. log << ":";
  1131. }
  1132. log << std::string( width >= 3 ? width - 1 : width, ' ' );
  1133. }
  1134. } else {
  1135. for ( std::int32_t channel = 0; channel < mod.get_num_channels(); ++channel ) {
  1136. if ( width >= 3 ) {
  1137. if ( row == mod.get_current_row() ) {
  1138. log << "+";
  1139. } else {
  1140. log << ":";
  1141. }
  1142. }
  1143. log << mod.format_pattern_row_channel( mod.get_current_pattern(), row, channel, width >= 3 ? width - 1 : width );
  1144. }
  1145. }
  1146. if ( width >= 3 ) {
  1147. log << ":";
  1148. }
  1149. log << std::endl;
  1150. }
  1151. }
  1152. if ( flags.show_ui ) {
  1153. log << "Settings...: ";
  1154. log << "Gain: " << flags.gain * 0.01f << " dB" << " ";
  1155. log << "Stereo: " << flags.separation << " %" << " ";
  1156. log << "Filter: " << flags.filtertaps << " taps" << " ";
  1157. log << "Ramping: " << flags.ramping << " ";
  1158. log << std::endl;
  1159. }
  1160. if ( flags.show_details ) {
  1161. log << "Mixer......: ";
  1162. log << "CPU:" << std::setw(6) << std::setfill(':') << cpu_str;
  1163. log << " ";
  1164. log << "Chn:" << std::setw(3) << std::setfill(':') << mod.get_current_playing_channels();
  1165. log << " ";
  1166. log << std::endl;
  1167. if ( flags.show_progress ) {
  1168. log << "Player.....: ";
  1169. log << "Ord:" << std::setw(3) << std::setfill(':') << mod.get_current_order() << "/" << std::setw(3) << std::setfill(':') << mod.get_num_orders();
  1170. log << " ";
  1171. log << "Pat:" << std::setw(3) << std::setfill(':') << mod.get_current_pattern();
  1172. log << " ";
  1173. log << "Row:" << std::setw(3) << std::setfill(':') << mod.get_current_row();
  1174. log << " ";
  1175. log << "Spd:" << std::setw(2) << std::setfill(':') << mod.get_current_speed();
  1176. log << " ";
  1177. log << "Tmp:" << std::setw(3) << std::setfill(':') << mod.get_current_tempo();
  1178. log << " ";
  1179. log << std::endl;
  1180. }
  1181. }
  1182. if ( flags.show_progress ) {
  1183. log << "Position...: " << seconds_to_string( mod.get_position_seconds() ) << " / " << seconds_to_string( duration ) << " " << std::endl;
  1184. }
  1185. } else if ( flags.show_channel_meters ) {
  1186. if ( flags.show_ui || flags.show_details || flags.show_progress ) {
  1187. int width = ( flags.terminal_width - 3 ) / mod.get_num_channels();
  1188. log << " ";
  1189. for ( std::int32_t channel = 0; channel < mod.get_num_channels(); ++channel ) {
  1190. if ( width >= 3 ) {
  1191. log << ":";
  1192. }
  1193. if ( width == 1 ) {
  1194. draw_channel_meters_tiny( log, ( mod.get_current_channel_vu_left( channel ) + mod.get_current_channel_vu_right( channel ) ) * (1.0f/std::sqrt(2.0f)) );
  1195. } else if ( width <= 4 ) {
  1196. draw_channel_meters_tiny( log, mod.get_current_channel_vu_left( channel ), mod.get_current_channel_vu_right( channel ) );
  1197. } else {
  1198. draw_channel_meters( log, mod.get_current_channel_vu_left( channel ), mod.get_current_channel_vu_right( channel ), width - 1 );
  1199. }
  1200. }
  1201. if ( width >= 3 ) {
  1202. log << ":";
  1203. }
  1204. }
  1205. log << " " << "\r";
  1206. } else {
  1207. if ( flags.show_ui ) {
  1208. log << " ";
  1209. log << std::setw(3) << std::setfill(':') << flags.gain * 0.01f << "dB";
  1210. log << "|";
  1211. log << std::setw(3) << std::setfill(':') << flags.separation << "%";
  1212. log << "|";
  1213. log << std::setw(2) << std::setfill(':') << flags.filtertaps << "taps";
  1214. log << "|";
  1215. log << std::setw(3) << std::setfill(':') << flags.ramping;
  1216. }
  1217. if ( flags.show_meters ) {
  1218. log << " ";
  1219. draw_meters_tiny( log, meter, flags );
  1220. }
  1221. if ( flags.show_details && flags.show_ui ) {
  1222. log << " ";
  1223. log << "CPU:" << std::setw(6) << std::setfill(':') << cpu_str;
  1224. log << "|";
  1225. log << "Chn:" << std::setw(3) << std::setfill(':') << mod.get_current_playing_channels();
  1226. }
  1227. if ( flags.show_details && !flags.show_ui ) {
  1228. if ( flags.show_progress ) {
  1229. log << " ";
  1230. log << "Ord:" << std::setw(3) << std::setfill(':') << mod.get_current_order() << "/" << std::setw(3) << std::setfill(':') << mod.get_num_orders();
  1231. log << "|";
  1232. log << "Pat:" << std::setw(3) << std::setfill(':') << mod.get_current_pattern();
  1233. log << "|";
  1234. log << "Row:" << std::setw(3) << std::setfill(':') << mod.get_current_row();
  1235. log << " ";
  1236. log << "Spd:" << std::setw(2) << std::setfill(':') << mod.get_current_speed();
  1237. log << "|";
  1238. log << "Tmp:" << std::setw(3) << std::setfill(':') << mod.get_current_tempo();
  1239. }
  1240. }
  1241. if ( flags.show_progress ) {
  1242. log << " ";
  1243. log << seconds_to_string( mod.get_position_seconds() );
  1244. log << "/";
  1245. log << seconds_to_string( duration );
  1246. }
  1247. if ( flags.show_ui || flags.show_details || flags.show_progress ) {
  1248. log << " " << "\r";
  1249. }
  1250. }
  1251. log.writeout();
  1252. if ( count == 0 ) {
  1253. break;
  1254. }
  1255. if ( flags.end_time > 0 && mod.get_position_seconds() >= flags.end_time ) {
  1256. break;
  1257. }
  1258. }
  1259. log.writeout();
  1260. }
  1261. template < typename Tmod >
  1262. std::map<std::string,std::string> get_metadata( const Tmod & mod ) {
  1263. std::map<std::string,std::string> result;
  1264. const std::vector<std::string> metadata_keys = mod.get_metadata_keys();
  1265. for ( const auto & key : metadata_keys ) {
  1266. result[ key ] = mod.get_metadata( key );
  1267. }
  1268. return result;
  1269. }
  1270. class set_field : private std::ostringstream {
  1271. private:
  1272. std::vector<openmpt123::field> & fields;
  1273. public:
  1274. set_field( std::vector<openmpt123::field> & fields, const std::string & name )
  1275. : fields(fields)
  1276. {
  1277. fields.push_back( name );
  1278. }
  1279. std::ostream & ostream() {
  1280. return *this;
  1281. }
  1282. ~set_field() {
  1283. fields.back().val = str();
  1284. }
  1285. };
  1286. static void show_fields( textout & log, const std::vector<field> & fields ) {
  1287. const std::size_t fw = 11;
  1288. for ( const auto & field :fields ) {
  1289. std::string key = field.key;
  1290. std::string val = field.val;
  1291. if ( key.length() < fw ) {
  1292. key += std::string( fw - key.length(), '.' );
  1293. }
  1294. if ( key.length() > fw ) {
  1295. key = key.substr( 0, fw );
  1296. }
  1297. key += ": ";
  1298. val = prepend_lines( val, std::string( fw, ' ' ) + ": " );
  1299. log << key << val << std::endl;
  1300. }
  1301. }
  1302. static void probe_mod_file( commandlineflags & flags, const std::string & filename, std::uint64_t filesize, std::istream & data_stream, textout & log ) {
  1303. log.writeout();
  1304. std::vector<field> fields;
  1305. if ( flags.filenames.size() > 1 ) {
  1306. set_field( fields, "Playlist" ).ostream() << flags.playlist_index + 1 << "/" << flags.filenames.size();
  1307. set_field( fields, "Prev/Next" ).ostream()
  1308. << "'"
  1309. << ( flags.playlist_index > 0 ? get_filename( flags.filenames[ flags.playlist_index - 1 ] ) : std::string() )
  1310. << "'"
  1311. << " / "
  1312. << "['" << get_filename( filename ) << "']"
  1313. << " / "
  1314. << "'"
  1315. << ( flags.playlist_index + 1 < flags.filenames.size() ? get_filename( flags.filenames[ flags.playlist_index + 1 ] ) : std::string() )
  1316. << "'"
  1317. ;
  1318. }
  1319. if ( flags.verbose ) {
  1320. set_field( fields, "Path" ).ostream() << filename;
  1321. }
  1322. if ( flags.show_details ) {
  1323. set_field( fields, "Filename" ).ostream() << get_filename( filename );
  1324. set_field( fields, "Size" ).ostream() << bytes_to_string( filesize );
  1325. }
  1326. int probe_result = openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, data_stream );
  1327. std::string probe_result_string;
  1328. switch ( probe_result ) {
  1329. case openmpt::probe_file_header_result_success:
  1330. probe_result_string = "Success";
  1331. break;
  1332. case openmpt::probe_file_header_result_failure:
  1333. probe_result_string = "Failure";
  1334. break;
  1335. case openmpt::probe_file_header_result_wantmoredata:
  1336. probe_result_string = "Insufficient Data";
  1337. break;
  1338. default:
  1339. probe_result_string = "Internal Error";
  1340. break;
  1341. }
  1342. set_field( fields, "Probe" ).ostream() << probe_result_string;
  1343. show_fields( log, fields );
  1344. log.writeout();
  1345. }
  1346. template < typename Tmod >
  1347. void render_mod_file( commandlineflags & flags, const std::string & filename, std::uint64_t filesize, Tmod & mod, textout & log, write_buffers_interface & audio_stream ) {
  1348. log.writeout();
  1349. if ( flags.mode != Mode::Probe && flags.mode != Mode::Info ) {
  1350. mod.set_repeat_count( flags.repeatcount );
  1351. apply_mod_settings( flags, mod );
  1352. }
  1353. double duration = mod.get_duration_seconds();
  1354. std::vector<field> fields;
  1355. if ( flags.filenames.size() > 1 ) {
  1356. set_field( fields, "Playlist" ).ostream() << flags.playlist_index + 1 << "/" << flags.filenames.size();
  1357. set_field( fields, "Prev/Next" ).ostream()
  1358. << "'"
  1359. << ( flags.playlist_index > 0 ? get_filename( flags.filenames[ flags.playlist_index - 1 ] ) : std::string() )
  1360. << "'"
  1361. << " / "
  1362. << "['" << get_filename( filename ) << "']"
  1363. << " / "
  1364. << "'"
  1365. << ( flags.playlist_index + 1 < flags.filenames.size() ? get_filename( flags.filenames[ flags.playlist_index + 1 ] ) : std::string() )
  1366. << "'"
  1367. ;
  1368. }
  1369. if ( flags.verbose ) {
  1370. set_field( fields, "Path" ).ostream() << filename;
  1371. }
  1372. if ( flags.show_details ) {
  1373. set_field( fields, "Filename" ).ostream() << get_filename( filename );
  1374. set_field( fields, "Size" ).ostream() << bytes_to_string( filesize );
  1375. if ( !mod.get_metadata( "warnings" ).empty() ) {
  1376. set_field( fields, "Warnings" ).ostream() << mod.get_metadata( "warnings" );
  1377. }
  1378. if ( !mod.get_metadata( "container" ).empty() ) {
  1379. set_field( fields, "Container" ).ostream() << mod.get_metadata( "container" ) << " (" << mod.get_metadata( "container_long" ) << ")";
  1380. }
  1381. set_field( fields, "Type" ).ostream() << mod.get_metadata( "type" ) << " (" << mod.get_metadata( "type_long" ) << ")";
  1382. if ( !mod.get_metadata( "originaltype" ).empty() ) {
  1383. set_field( fields, "Orig. Type" ).ostream() << mod.get_metadata( "originaltype" ) << " (" << mod.get_metadata( "originaltype_long" ) << ")";
  1384. }
  1385. if ( ( mod.get_num_subsongs() > 1 ) && ( flags.subsong != -1 ) ) {
  1386. set_field( fields, "Subsong" ).ostream() << flags.subsong;
  1387. }
  1388. set_field( fields, "Tracker" ).ostream() << mod.get_metadata( "tracker" );
  1389. if ( !mod.get_metadata( "date" ).empty() ) {
  1390. set_field( fields, "Date" ).ostream() << mod.get_metadata( "date" );
  1391. }
  1392. if ( !mod.get_metadata( "artist" ).empty() ) {
  1393. set_field( fields, "Artist" ).ostream() << mod.get_metadata( "artist" );
  1394. }
  1395. }
  1396. if ( true ) {
  1397. set_field( fields, "Title" ).ostream() << mod.get_metadata( "title" );
  1398. set_field( fields, "Duration" ).ostream() << seconds_to_string( duration );
  1399. }
  1400. if ( flags.show_details ) {
  1401. set_field( fields, "Subsongs" ).ostream() << mod.get_num_subsongs();
  1402. set_field( fields, "Channels" ).ostream() << mod.get_num_channels();
  1403. set_field( fields, "Orders" ).ostream() << mod.get_num_orders();
  1404. set_field( fields, "Patterns" ).ostream() << mod.get_num_patterns();
  1405. set_field( fields, "Instruments" ).ostream() << mod.get_num_instruments();
  1406. set_field( fields, "Samples" ).ostream() << mod.get_num_samples();
  1407. }
  1408. if ( flags.show_message ) {
  1409. set_field( fields, "Message" ).ostream() << mod.get_metadata( "message" );
  1410. }
  1411. show_fields( log, fields );
  1412. log.writeout();
  1413. if ( flags.filenames.size() == 1 || flags.mode == Mode::Render ) {
  1414. audio_stream.write_metadata( get_metadata( mod ) );
  1415. } else {
  1416. audio_stream.write_updated_metadata( get_metadata( mod ) );
  1417. }
  1418. if ( flags.mode == Mode::Probe || flags.mode == Mode::Info ) {
  1419. return;
  1420. }
  1421. if ( flags.seek_target > 0.0 ) {
  1422. mod.set_position_seconds( flags.seek_target );
  1423. }
  1424. try {
  1425. if ( flags.use_float ) {
  1426. render_loop<float>( flags, mod, duration, log, audio_stream );
  1427. } else {
  1428. render_loop<std::int16_t>( flags, mod, duration, log, audio_stream );
  1429. }
  1430. if ( flags.show_progress ) {
  1431. log << std::endl;
  1432. }
  1433. } catch ( ... ) {
  1434. if ( flags.show_progress ) {
  1435. log << std::endl;
  1436. }
  1437. throw;
  1438. }
  1439. log.writeout();
  1440. }
  1441. static void probe_file( commandlineflags & flags, const std::string & filename, textout & log ) {
  1442. log.writeout();
  1443. std::ostringstream silentlog;
  1444. try {
  1445. #if defined(WIN32) && defined(UNICODE) && !defined(_MSC_VER)
  1446. std::istringstream file_stream;
  1447. #else
  1448. std::ifstream file_stream;
  1449. #endif
  1450. std::uint64_t filesize = 0;
  1451. bool use_stdin = ( filename == "-" );
  1452. if ( !use_stdin ) {
  1453. #if defined(WIN32) && defined(UNICODE) && !defined(_MSC_VER)
  1454. // Only MSVC has std::ifstream::ifstream(std::wstring).
  1455. // Fake it for other compilers using _wfopen().
  1456. std::string data;
  1457. FILE * f = _wfopen( mpt::transcode<std::wstring>( mpt::common_encoding::utf8, filename ).c_str(), L"rb" );
  1458. if ( f ) {
  1459. while ( !feof( f ) ) {
  1460. static const std::size_t BUFFER_SIZE = 4096;
  1461. char buffer[BUFFER_SIZE];
  1462. size_t data_read = fread( buffer, 1, BUFFER_SIZE, f );
  1463. std::copy( buffer, buffer + data_read, std::back_inserter( data ) );
  1464. }
  1465. fclose( f );
  1466. f = NULL;
  1467. }
  1468. file_stream.str( data );
  1469. filesize = data.length();
  1470. #elif defined(_MSC_VER) && defined(UNICODE)
  1471. file_stream.open( mpt::transcode<std::wstring>( mpt::common_encoding::utf8, filename ), std::ios::binary );
  1472. file_stream.seekg( 0, std::ios::end );
  1473. filesize = file_stream.tellg();
  1474. file_stream.seekg( 0, std::ios::beg );
  1475. #else
  1476. file_stream.open( filename, std::ios::binary );
  1477. file_stream.seekg( 0, std::ios::end );
  1478. filesize = file_stream.tellg();
  1479. file_stream.seekg( 0, std::ios::beg );
  1480. #endif
  1481. }
  1482. std::istream & data_stream = use_stdin ? std::cin : file_stream;
  1483. if ( data_stream.fail() ) {
  1484. throw exception( "file open error" );
  1485. }
  1486. probe_mod_file( flags, filename, filesize, data_stream, log );
  1487. } catch ( silent_exit_exception & ) {
  1488. throw;
  1489. } catch ( std::exception & e ) {
  1490. if ( !silentlog.str().empty() ) {
  1491. log << "errors probing '" << filename << "': " << silentlog.str() << std::endl;
  1492. } else {
  1493. log << "errors probing '" << filename << "'" << std::endl;
  1494. }
  1495. log << "error probing '" << filename << "': " << e.what() << std::endl;
  1496. } catch ( ... ) {
  1497. if ( !silentlog.str().empty() ) {
  1498. log << "errors probing '" << filename << "': " << silentlog.str() << std::endl;
  1499. } else {
  1500. log << "errors probing '" << filename << "'" << std::endl;
  1501. }
  1502. log << "unknown error probing '" << filename << "'" << std::endl;
  1503. }
  1504. log << std::endl;
  1505. log.writeout();
  1506. }
  1507. static void render_file( commandlineflags & flags, const std::string & filename, textout & log, write_buffers_interface & audio_stream ) {
  1508. log.writeout();
  1509. std::ostringstream silentlog;
  1510. try {
  1511. #if defined(WIN32) && defined(UNICODE) && !defined(_MSC_VER)
  1512. std::istringstream file_stream;
  1513. #else
  1514. std::ifstream file_stream;
  1515. #endif
  1516. std::uint64_t filesize = 0;
  1517. bool use_stdin = ( filename == "-" );
  1518. if ( !use_stdin ) {
  1519. #if defined(WIN32) && defined(UNICODE) && !defined(_MSC_VER)
  1520. // Only MSVC has std::ifstream::ifstream(std::wstring).
  1521. // Fake it for other compilers using _wfopen().
  1522. std::string data;
  1523. FILE * f = _wfopen( mpt::transcode<std::wstring>( mpt::common_encoding::utf8, filename ).c_str(), L"rb" );
  1524. if ( f ) {
  1525. while ( !feof( f ) ) {
  1526. static const std::size_t BUFFER_SIZE = 4096;
  1527. char buffer[BUFFER_SIZE];
  1528. size_t data_read = fread( buffer, 1, BUFFER_SIZE, f );
  1529. std::copy( buffer, buffer + data_read, std::back_inserter( data ) );
  1530. }
  1531. fclose( f );
  1532. f = NULL;
  1533. }
  1534. file_stream.str( data );
  1535. filesize = data.length();
  1536. #elif defined(_MSC_VER) && defined(UNICODE)
  1537. file_stream.open( mpt::transcode<std::wstring>( mpt::common_encoding::utf8, filename ), std::ios::binary );
  1538. file_stream.seekg( 0, std::ios::end );
  1539. filesize = file_stream.tellg();
  1540. file_stream.seekg( 0, std::ios::beg );
  1541. #else
  1542. file_stream.open( filename, std::ios::binary );
  1543. file_stream.seekg( 0, std::ios::end );
  1544. filesize = file_stream.tellg();
  1545. file_stream.seekg( 0, std::ios::beg );
  1546. #endif
  1547. }
  1548. std::istream & data_stream = use_stdin ? std::cin : file_stream;
  1549. if ( data_stream.fail() ) {
  1550. throw exception( "file open error" );
  1551. }
  1552. {
  1553. openmpt::module mod( data_stream, silentlog, flags.ctls );
  1554. mod.select_subsong( flags.subsong );
  1555. silentlog.str( std::string() ); // clear, loader messages get stored to get_metadata( "warnings" ) by libopenmpt internally
  1556. render_mod_file( flags, filename, filesize, mod, log, audio_stream );
  1557. }
  1558. } catch ( prev_file & ) {
  1559. throw;
  1560. } catch ( next_file & ) {
  1561. throw;
  1562. } catch ( silent_exit_exception & ) {
  1563. throw;
  1564. } catch ( std::exception & e ) {
  1565. if ( !silentlog.str().empty() ) {
  1566. log << "errors loading '" << filename << "': " << silentlog.str() << std::endl;
  1567. } else {
  1568. log << "errors loading '" << filename << "'" << std::endl;
  1569. }
  1570. log << "error playing '" << filename << "': " << e.what() << std::endl;
  1571. } catch ( ... ) {
  1572. if ( !silentlog.str().empty() ) {
  1573. log << "errors loading '" << filename << "': " << silentlog.str() << std::endl;
  1574. } else {
  1575. log << "errors loading '" << filename << "'" << std::endl;
  1576. }
  1577. log << "unknown error playing '" << filename << "'" << std::endl;
  1578. }
  1579. log << std::endl;
  1580. log.writeout();
  1581. }
  1582. static std::string get_random_filename( std::set<std::string> & filenames, std::default_random_engine & prng ) {
  1583. std::size_t index = std::uniform_int_distribution<std::size_t>( 0, filenames.size() - 1 )( prng );
  1584. std::set<std::string>::iterator it = filenames.begin();
  1585. std::advance( it, index );
  1586. return *it;
  1587. }
  1588. static void render_files( commandlineflags & flags, textout & log, write_buffers_interface & audio_stream, std::default_random_engine & prng ) {
  1589. if ( flags.randomize ) {
  1590. std::shuffle( flags.filenames.begin(), flags.filenames.end(), prng );
  1591. }
  1592. try {
  1593. while ( true ) {
  1594. if ( flags.shuffle ) {
  1595. // TODO: improve prev/next logic
  1596. std::set<std::string> shuffle_set;
  1597. shuffle_set.insert( flags.filenames.begin(), flags.filenames.end() );
  1598. while ( true ) {
  1599. if ( shuffle_set.empty() ) {
  1600. break;
  1601. }
  1602. std::string filename = get_random_filename( shuffle_set, prng );
  1603. try {
  1604. flags.playlist_index = std::find( flags.filenames.begin(), flags.filenames.end(), filename ) - flags.filenames.begin();
  1605. render_file( flags, filename, log, audio_stream );
  1606. shuffle_set.erase( filename );
  1607. continue;
  1608. } catch ( prev_file & ) {
  1609. shuffle_set.erase( filename );
  1610. continue;
  1611. } catch ( next_file & ) {
  1612. shuffle_set.erase( filename );
  1613. continue;
  1614. } catch ( ... ) {
  1615. throw;
  1616. }
  1617. }
  1618. } else {
  1619. std::vector<std::string>::iterator filename = flags.filenames.begin();
  1620. while ( true ) {
  1621. if ( filename == flags.filenames.end() ) {
  1622. break;
  1623. }
  1624. try {
  1625. flags.playlist_index = filename - flags.filenames.begin();
  1626. render_file( flags, *filename, log, audio_stream );
  1627. filename++;
  1628. continue;
  1629. } catch ( prev_file & e ) {
  1630. while ( filename != flags.filenames.begin() && e.count ) {
  1631. e.count--;
  1632. --filename;
  1633. }
  1634. continue;
  1635. } catch ( next_file & e ) {
  1636. while ( filename != flags.filenames.end() && e.count ) {
  1637. e.count--;
  1638. ++filename;
  1639. }
  1640. continue;
  1641. } catch ( ... ) {
  1642. throw;
  1643. }
  1644. }
  1645. }
  1646. if ( !flags.restart ) {
  1647. break;
  1648. }
  1649. }
  1650. } catch ( ... ) {
  1651. throw;
  1652. }
  1653. }
  1654. static bool parse_playlist( commandlineflags & flags, std::string filename, std::ostream & log ) {
  1655. log.flush();
  1656. bool is_playlist = false;
  1657. bool m3u8 = false;
  1658. if ( ends_with( filename, ".m3u") || ends_with( filename, ".m3U") || ends_with( filename, ".M3u") || ends_with( filename, ".M3U") ) {
  1659. is_playlist = true;
  1660. }
  1661. if ( ends_with( filename, ".m3u8") || ends_with( filename, ".m3U8") || ends_with( filename, ".M3u8") || ends_with( filename, ".M3U8") ) {
  1662. is_playlist = true;
  1663. m3u8 = true;
  1664. }
  1665. if ( ends_with( filename, ".pls") || ends_with( filename, ".plS") || ends_with( filename, ".pLs") || ends_with( filename, ".pLS") || ends_with( filename, ".Pls") || ends_with( filename, ".PlS") || ends_with( filename, ".PLs") || ends_with( filename, ".PLS") ) {
  1666. is_playlist = true;
  1667. }
  1668. std::string basepath = get_basepath( filename );
  1669. try {
  1670. #if defined(WIN32) && defined(UNICODE) && !defined(_MSC_VER)
  1671. std::istringstream file_stream;
  1672. #else
  1673. std::ifstream file_stream;
  1674. #endif
  1675. #if defined(WIN32) && defined(UNICODE) && !defined(_MSC_VER)
  1676. // Only MSVC has std::ifstream::ifstream(std::wstring).
  1677. // Fake it for other compilers using _wfopen().
  1678. std::string data;
  1679. FILE * f = _wfopen( mpt::transcode<std::wstring>( mpt::common_encoding::utf8, filename ).c_str(), L"rb" );
  1680. if ( f ) {
  1681. while ( !feof( f ) ) {
  1682. static const std::size_t BUFFER_SIZE = 4096;
  1683. char buffer[BUFFER_SIZE];
  1684. size_t data_read = fread( buffer, 1, BUFFER_SIZE, f );
  1685. std::copy( buffer, buffer + data_read, std::back_inserter( data ) );
  1686. }
  1687. fclose( f );
  1688. f = NULL;
  1689. }
  1690. file_stream.str( data );
  1691. #elif defined(_MSC_VER) && defined(UNICODE)
  1692. file_stream.open( mpt::transcode<std::wstring>( mpt::common_encoding::utf8, filename ), std::ios::binary );
  1693. #else
  1694. file_stream.open( filename, std::ios::binary );
  1695. #endif
  1696. std::string line;
  1697. bool first = true;
  1698. bool extm3u = false;
  1699. bool pls = false;
  1700. while ( std::getline( file_stream, line ) ) {
  1701. std::string newfile;
  1702. line = trim_eol( line );
  1703. if ( first ) {
  1704. first = false;
  1705. if ( line == "#EXTM3U" ) {
  1706. extm3u = true;
  1707. continue;
  1708. } else if ( line == "[playlist]" ) {
  1709. pls = true;
  1710. }
  1711. }
  1712. if ( line.empty() ) {
  1713. continue;
  1714. }
  1715. if ( pls ) {
  1716. if ( begins_with( line, "File" ) ) {
  1717. if ( line.find( "=" ) != std::string::npos ) {
  1718. flags.filenames.push_back( line.substr( line.find( "=" ) + 1 ) );
  1719. }
  1720. } else if ( begins_with( line, "Title" ) ) {
  1721. continue;
  1722. } else if ( begins_with( line, "Length" ) ) {
  1723. continue;
  1724. } else if ( begins_with( line, "NumberOfEntries" ) ) {
  1725. continue;
  1726. } else if ( begins_with( line, "Version" ) ) {
  1727. continue;
  1728. } else {
  1729. continue;
  1730. }
  1731. } else if ( extm3u ) {
  1732. if ( begins_with( line, "#EXTINF" ) ) {
  1733. continue;
  1734. } else if ( begins_with( line, "#" ) ) {
  1735. continue;
  1736. }
  1737. if ( m3u8 ) {
  1738. newfile = line;
  1739. } else {
  1740. #if defined(WIN32)
  1741. newfile = mpt::transcode<std::string>( mpt::common_encoding::utf8, mpt::logical_encoding::locale, line );
  1742. #else
  1743. newfile = line;
  1744. #endif
  1745. }
  1746. } else {
  1747. if ( m3u8 ) {
  1748. newfile = line;
  1749. } else {
  1750. #if defined(WIN32)
  1751. newfile = mpt::transcode<std::string>( mpt::common_encoding::utf8, mpt::logical_encoding::locale, line );
  1752. #else
  1753. newfile = line;
  1754. #endif
  1755. }
  1756. }
  1757. if ( !newfile.empty() ) {
  1758. if ( !is_absolute( newfile ) ) {
  1759. newfile = basepath + newfile;
  1760. }
  1761. flags.filenames.push_back( newfile );
  1762. }
  1763. }
  1764. } catch ( std::exception & e ) {
  1765. log << "error loading '" << filename << "': " << e.what() << std::endl;
  1766. } catch ( ... ) {
  1767. log << "unknown error loading '" << filename << "'" << std::endl;
  1768. }
  1769. log.flush();
  1770. return is_playlist;
  1771. }
  1772. static commandlineflags parse_openmpt123( const std::vector<std::string> & args, std::ostream & log ) {
  1773. log.flush();
  1774. if ( args.size() <= 1 ) {
  1775. throw args_error_exception();
  1776. }
  1777. commandlineflags flags;
  1778. bool files_only = false;
  1779. // cppcheck false-positive
  1780. // cppcheck-suppress StlMissingComparison
  1781. for ( auto i = args.begin(); i != args.end(); ++i ) {
  1782. if ( i == args.begin() ) {
  1783. // skip program name
  1784. continue;
  1785. }
  1786. std::string arg = *i;
  1787. std::string nextarg = ( i+1 != args.end() ) ? *(i+1) : "";
  1788. if ( files_only ) {
  1789. flags.filenames.push_back( arg );
  1790. } else if ( arg.substr( 0, 1 ) != "-" ) {
  1791. flags.filenames.push_back( arg );
  1792. } else {
  1793. if ( arg == "--" ) {
  1794. files_only = true;
  1795. } else if ( arg == "-h" || arg == "--help" ) {
  1796. throw show_help_exception();
  1797. } else if ( arg == "--help-keyboard" ) {
  1798. throw show_help_keyboard_exception();
  1799. } else if ( arg == "-q" || arg == "--quiet" ) {
  1800. flags.quiet = true;
  1801. } else if ( arg == "-v" || arg == "--verbose" ) {
  1802. flags.verbose = true;
  1803. } else if ( arg == "--man-version" ) {
  1804. throw show_man_version_exception();
  1805. } else if ( arg == "--man-help" ) {
  1806. throw show_man_help_exception();
  1807. } else if ( arg == "--version" ) {
  1808. throw show_version_number_exception();
  1809. } else if ( arg == "--short-version" ) {
  1810. throw show_short_version_number_exception();
  1811. } else if ( arg == "--long-version" ) {
  1812. throw show_long_version_number_exception();
  1813. } else if ( arg == "--credits" ) {
  1814. throw show_credits_exception();
  1815. } else if ( arg == "--license" ) {
  1816. throw show_license_exception();
  1817. } else if ( arg == "--probe" ) {
  1818. flags.mode = Mode::Probe;
  1819. } else if ( arg == "--info" ) {
  1820. flags.mode = Mode::Info;
  1821. } else if ( arg == "--ui" ) {
  1822. flags.mode = Mode::UI;
  1823. } else if ( arg == "--batch" ) {
  1824. flags.mode = Mode::Batch;
  1825. } else if ( arg == "--render" ) {
  1826. flags.mode = Mode::Render;
  1827. } else if ( arg == "--terminal-width" && nextarg != "" ) {
  1828. std::istringstream istr( nextarg );
  1829. istr >> flags.terminal_width;
  1830. ++i;
  1831. } else if ( arg == "--terminal-height" && nextarg != "" ) {
  1832. std::istringstream istr( nextarg );
  1833. istr >> flags.terminal_height;
  1834. ++i;
  1835. } else if ( arg == "--progress" ) {
  1836. flags.show_progress = true;
  1837. } else if ( arg == "--no-progress" ) {
  1838. flags.show_progress = false;
  1839. } else if ( arg == "--meters" ) {
  1840. flags.show_meters = true;
  1841. } else if ( arg == "--no-meters" ) {
  1842. flags.show_meters = false;
  1843. } else if ( arg == "--channel-meters" ) {
  1844. flags.show_channel_meters = true;
  1845. } else if ( arg == "--no-channel-meters" ) {
  1846. flags.show_channel_meters = false;
  1847. } else if ( arg == "--pattern" ) {
  1848. flags.show_pattern = true;
  1849. } else if ( arg == "--no-pattern" ) {
  1850. flags.show_pattern = false;
  1851. } else if ( arg == "--details" ) {
  1852. flags.show_details = true;
  1853. } else if ( arg == "--no-details" ) {
  1854. flags.show_details = false;
  1855. } else if ( arg == "--message" ) {
  1856. flags.show_message = true;
  1857. } else if ( arg == "--no-message" ) {
  1858. flags.show_message = false;
  1859. } else if ( arg == "--driver" && nextarg != "" ) {
  1860. if ( false ) {
  1861. // nothing
  1862. } else if ( nextarg == "help" ) {
  1863. std::ostringstream drivers;
  1864. drivers << " Available drivers:" << std::endl;
  1865. drivers << " " << "default" << std::endl;
  1866. #if defined( MPT_WITH_PULSEAUDIO )
  1867. drivers << " " << "pulseaudio" << std::endl;
  1868. #endif
  1869. #if defined( MPT_WITH_SDL2 )
  1870. drivers << " " << "sdl2" << std::endl;
  1871. #endif
  1872. #if defined( MPT_WITH_PORTAUDIO )
  1873. drivers << " " << "portaudio" << std::endl;
  1874. #endif
  1875. #if defined( WIN32 )
  1876. drivers << " " << "waveout" << std::endl;
  1877. #endif
  1878. #if defined( MPT_WITH_ALLEGRO42 )
  1879. drivers << " " << "allegro42" << std::endl;
  1880. #endif
  1881. throw show_help_exception( drivers.str() );
  1882. } else if ( nextarg == "default" ) {
  1883. flags.driver = "";
  1884. } else {
  1885. flags.driver = nextarg;
  1886. }
  1887. ++i;
  1888. } else if ( arg == "--device" && nextarg != "" ) {
  1889. if ( false ) {
  1890. // nothing
  1891. } else if ( nextarg == "help" ) {
  1892. std::ostringstream devices;
  1893. devices << " Available devices:" << std::endl;
  1894. devices << " " << "default" << ": " << "default" << std::endl;
  1895. #if defined( MPT_WITH_PULSEAUDIO )
  1896. devices << show_pulseaudio_devices( log );
  1897. #endif
  1898. #if defined( MPT_WITH_SDL2 )
  1899. devices << show_sdl2_devices( log );
  1900. #endif
  1901. #if defined( MPT_WITH_PORTAUDIO )
  1902. devices << show_portaudio_devices( log );
  1903. #endif
  1904. #if defined( WIN32 )
  1905. devices << show_waveout_devices( log );
  1906. #endif
  1907. #if defined( MPT_WITH_ALLEGRO42 )
  1908. devices << show_allegro42_devices( log );
  1909. #endif
  1910. throw show_help_exception( devices.str() );
  1911. } else if ( nextarg == "default" ) {
  1912. flags.device = "";
  1913. } else {
  1914. flags.device = nextarg;
  1915. }
  1916. ++i;
  1917. } else if ( arg == "--buffer" && nextarg != "" ) {
  1918. std::istringstream istr( nextarg );
  1919. istr >> flags.buffer;
  1920. ++i;
  1921. } else if ( arg == "--period" && nextarg != "" ) {
  1922. std::istringstream istr( nextarg );
  1923. istr >> flags.period;
  1924. ++i;
  1925. } else if ( arg == "--update" && nextarg != "" ) {
  1926. std::istringstream istr( nextarg );
  1927. istr >> flags.ui_redraw_interval;
  1928. ++i;
  1929. } else if ( arg == "--stdout" ) {
  1930. flags.use_stdout = true;
  1931. } else if ( ( arg == "-o" || arg == "--output" ) && nextarg != "" ) {
  1932. flags.output_filename = nextarg;
  1933. ++i;
  1934. } else if ( arg == "--force" ) {
  1935. flags.force_overwrite = true;
  1936. } else if ( arg == "--output-type" && nextarg != "" ) {
  1937. flags.output_extension = nextarg;
  1938. ++i;
  1939. } else if ( arg == "--samplerate" && nextarg != "" ) {
  1940. std::istringstream istr( nextarg );
  1941. istr >> flags.samplerate;
  1942. ++i;
  1943. } else if ( arg == "--channels" && nextarg != "" ) {
  1944. std::istringstream istr( nextarg );
  1945. istr >> flags.channels;
  1946. ++i;
  1947. } else if ( arg == "--float" ) {
  1948. flags.use_float = true;
  1949. } else if ( arg == "--no-float" ) {
  1950. flags.use_float = false;
  1951. } else if ( arg == "--gain" && nextarg != "" ) {
  1952. std::istringstream istr( nextarg );
  1953. double gain = 0.0;
  1954. istr >> gain;
  1955. flags.gain = static_cast<std::int32_t>( gain * 100.0 );
  1956. ++i;
  1957. } else if ( arg == "--stereo" && nextarg != "" ) {
  1958. std::istringstream istr( nextarg );
  1959. istr >> flags.separation;
  1960. ++i;
  1961. } else if ( arg == "--filter" && nextarg != "" ) {
  1962. std::istringstream istr( nextarg );
  1963. istr >> flags.filtertaps;
  1964. ++i;
  1965. } else if ( arg == "--ramping" && nextarg != "" ) {
  1966. std::istringstream istr( nextarg );
  1967. istr >> flags.ramping;
  1968. ++i;
  1969. } else if ( arg == "--tempo" && nextarg != "" ) {
  1970. std::istringstream istr( nextarg );
  1971. double tmp = 1.0;
  1972. istr >> tmp;
  1973. flags.tempo = double_to_tempo_flag( tmp );
  1974. ++i;
  1975. } else if ( arg == "--pitch" && nextarg != "" ) {
  1976. std::istringstream istr( nextarg );
  1977. double tmp = 1.0;
  1978. istr >> tmp;
  1979. flags.pitch = double_to_pitch_flag( tmp );
  1980. ++i;
  1981. } else if ( arg == "--dither" && nextarg != "" ) {
  1982. std::istringstream istr( nextarg );
  1983. istr >> flags.dither;
  1984. ++i;
  1985. } else if ( arg == "--playlist" && nextarg != "" ) {
  1986. parse_playlist( flags, nextarg, log );
  1987. ++i;
  1988. } else if ( arg == "--randomize" ) {
  1989. flags.randomize = true;
  1990. } else if ( arg == "--no-randomize" ) {
  1991. flags.randomize = false;
  1992. } else if ( arg == "--shuffle" ) {
  1993. flags.shuffle = true;
  1994. } else if ( arg == "--no-shuffle" ) {
  1995. flags.shuffle = false;
  1996. } else if ( arg == "--restart" ) {
  1997. flags.restart = true;
  1998. } else if ( arg == "--no-restart" ) {
  1999. flags.restart = false;
  2000. } else if ( arg == "--subsong" && nextarg != "" ) {
  2001. std::istringstream istr( nextarg );
  2002. istr >> flags.subsong;
  2003. ++i;
  2004. } else if ( arg == "--repeat" && nextarg != "" ) {
  2005. std::istringstream istr( nextarg );
  2006. istr >> flags.repeatcount;
  2007. ++i;
  2008. } else if ( arg == "--ctl" && nextarg != "" ) {
  2009. std::istringstream istr( nextarg );
  2010. std::string ctl_c_v;
  2011. istr >> ctl_c_v;
  2012. if ( ctl_c_v.find( "=" ) == std::string::npos ) {
  2013. throw args_error_exception();
  2014. }
  2015. std::string ctl = ctl_c_v.substr( 0, ctl_c_v.find( "=" ) );
  2016. std::string val = ctl_c_v.substr( ctl_c_v.find( "=" ) + std::string("=").length(), std::string::npos );
  2017. if ( ctl.empty() ) {
  2018. throw args_error_exception();
  2019. }
  2020. flags.ctls[ ctl ] = val;
  2021. ++i;
  2022. } else if ( arg == "--seek" && nextarg != "" ) {
  2023. std::istringstream istr( nextarg );
  2024. istr >> flags.seek_target;
  2025. ++i;
  2026. } else if ( arg == "--end-time" && nextarg != "" ) {
  2027. std::istringstream istr( nextarg );
  2028. istr >> flags.end_time;
  2029. ++i;
  2030. } else if ( arg.size() > 0 && arg.substr( 0, 1 ) == "-" ) {
  2031. throw args_error_exception();
  2032. }
  2033. }
  2034. }
  2035. return flags;
  2036. }
  2037. #if defined(WIN32)
  2038. class FD_utf8_raii {
  2039. private:
  2040. FILE * file;
  2041. int old_mode;
  2042. public:
  2043. FD_utf8_raii( FILE * file, bool set_utf8 )
  2044. : file(file)
  2045. , old_mode(-1)
  2046. {
  2047. if ( set_utf8 ) {
  2048. fflush( file );
  2049. #if defined(UNICODE)
  2050. old_mode = _setmode( _fileno( file ), _O_U8TEXT );
  2051. #else
  2052. old_mode = _setmode( _fileno( file ), _O_TEXT );
  2053. #endif
  2054. if ( old_mode == -1 ) {
  2055. throw exception( "failed to set TEXT mode on file descriptor" );
  2056. }
  2057. }
  2058. }
  2059. ~FD_utf8_raii()
  2060. {
  2061. if ( old_mode != -1 ) {
  2062. fflush( file );
  2063. old_mode = _setmode( _fileno( file ), old_mode );
  2064. }
  2065. }
  2066. };
  2067. class FD_binary_raii {
  2068. private:
  2069. FILE * file;
  2070. int old_mode;
  2071. public:
  2072. FD_binary_raii( FILE * file, bool set_binary )
  2073. : file(file)
  2074. , old_mode(-1)
  2075. {
  2076. if ( set_binary ) {
  2077. fflush( file );
  2078. old_mode = _setmode( _fileno( file ), _O_BINARY );
  2079. if ( old_mode == -1 ) {
  2080. throw exception( "failed to set binary mode on file descriptor" );
  2081. }
  2082. }
  2083. }
  2084. ~FD_binary_raii()
  2085. {
  2086. if ( old_mode != -1 ) {
  2087. fflush( file );
  2088. old_mode = _setmode( _fileno( file ), old_mode );
  2089. }
  2090. }
  2091. };
  2092. #endif
  2093. #if defined(WIN32) && defined(UNICODE)
  2094. static int wmain( int wargc, wchar_t * wargv [] ) {
  2095. #else
  2096. static int main( int argc, char * argv [] ) {
  2097. #endif
  2098. std::vector<std::string> args;
  2099. #if defined(WIN32) && defined(UNICODE)
  2100. for ( int arg = 0; arg < wargc; ++arg ) {
  2101. args.push_back( mpt::transcode<std::string>( mpt::common_encoding::utf8, wargv[arg] ) );
  2102. }
  2103. #else
  2104. args = std::vector<std::string>( argv, argv + argc );
  2105. #endif
  2106. #if defined(WIN32)
  2107. FD_utf8_raii stdin_utf8_guard( stdin, true );
  2108. FD_utf8_raii stdout_utf8_guard( stdout, true );
  2109. FD_utf8_raii stderr_utf8_guard( stderr, true );
  2110. #endif
  2111. textout_dummy dummy_log;
  2112. #if defined(WIN32)
  2113. #if defined(UNICODE)
  2114. textout_ostream_console std_out( std::wcout, STD_OUTPUT_HANDLE );
  2115. textout_ostream_console std_err( std::wclog, STD_ERROR_HANDLE );
  2116. #else
  2117. textout_ostream_console std_out( std::cout, STD_OUTPUT_HANDLE );
  2118. textout_ostream_console std_err( std::clog, STD_ERROR_HANDLE );
  2119. #endif
  2120. #else
  2121. textout_ostream std_out( std::cout );
  2122. textout_ostream std_err( std::clog );
  2123. #endif
  2124. commandlineflags flags;
  2125. try {
  2126. flags = parse_openmpt123( args, std::cerr );
  2127. flags.check_and_sanitize();
  2128. } catch ( args_error_exception & ) {
  2129. show_help( std_out );
  2130. return 1;
  2131. } catch ( show_man_help_exception & ) {
  2132. show_help( std_out, false, true, true );
  2133. return 0;
  2134. } catch ( show_man_version_exception & ) {
  2135. show_man_version( std_out );
  2136. return 0;
  2137. } catch ( show_help_exception & e ) {
  2138. show_help( std_out, true, e.longhelp, false, e.message );
  2139. if ( flags.verbose ) {
  2140. show_credits( std_out );
  2141. }
  2142. return 0;
  2143. } catch ( show_help_keyboard_exception & ) {
  2144. show_help_keyboard( std_out );
  2145. return 0;
  2146. } catch ( show_long_version_number_exception & ) {
  2147. show_long_version( std_out );
  2148. return 0;
  2149. } catch ( show_version_number_exception & ) {
  2150. show_version( std_out );
  2151. return 0;
  2152. } catch ( show_short_version_number_exception & ) {
  2153. show_short_version( std_out );
  2154. return 0;
  2155. } catch ( show_credits_exception & ) {
  2156. show_credits( std_out );
  2157. return 0;
  2158. } catch ( show_license_exception & ) {
  2159. show_license( std_out );
  2160. return 0;
  2161. } catch ( silent_exit_exception & ) {
  2162. return 0;
  2163. } catch ( std::exception & e ) {
  2164. std_err << "error: " << e.what() << std::endl;
  2165. std_err.writeout();
  2166. return 1;
  2167. } catch ( ... ) {
  2168. std_err << "unknown error" << std::endl;
  2169. std_err.writeout();
  2170. return 1;
  2171. }
  2172. try {
  2173. bool stdin_can_ui = true;
  2174. for ( const auto & filename : flags.filenames ) {
  2175. if ( filename == "-" ) {
  2176. stdin_can_ui = false;
  2177. break;
  2178. }
  2179. }
  2180. bool stdout_can_ui = true;
  2181. if ( flags.use_stdout ) {
  2182. stdout_can_ui = false;
  2183. }
  2184. // set stdin binary
  2185. #if defined(WIN32)
  2186. FD_binary_raii stdin_guard( stdin, !stdin_can_ui );
  2187. #endif
  2188. // set stdout binary
  2189. #if defined(WIN32)
  2190. FD_binary_raii stdout_guard( stdout, !stdout_can_ui );
  2191. #endif
  2192. // setup terminal
  2193. #if !defined(WIN32)
  2194. if ( stdin_can_ui ) {
  2195. if ( flags.mode == Mode::UI ) {
  2196. set_input_mode();
  2197. }
  2198. }
  2199. #endif
  2200. textout & log = flags.quiet ? static_cast<textout&>( dummy_log ) : static_cast<textout&>( stdout_can_ui ? std_out : std_err );
  2201. show_info( log, flags.verbose );
  2202. if ( !flags.warnings.empty() ) {
  2203. log << flags.warnings << std::endl;
  2204. }
  2205. if ( flags.verbose ) {
  2206. log << flags;
  2207. }
  2208. log.writeout();
  2209. std::default_random_engine prng;
  2210. try {
  2211. std::random_device rd;
  2212. std::seed_seq seq{ rd(), static_cast<unsigned int>( std::time( NULL ) ) };
  2213. prng = std::default_random_engine{ seq };
  2214. } catch ( const std::exception & ) {
  2215. std::seed_seq seq{ static_cast<unsigned int>( std::time( NULL ) ) };
  2216. prng = std::default_random_engine{ seq };
  2217. }
  2218. std::srand( std::uniform_int_distribution<unsigned int>()( prng ) );
  2219. switch ( flags.mode ) {
  2220. case Mode::Probe: {
  2221. for ( const auto & filename : flags.filenames ) {
  2222. probe_file( flags, filename, log );
  2223. flags.playlist_index++;
  2224. }
  2225. } break;
  2226. case Mode::Info: {
  2227. void_audio_stream dummy;
  2228. render_files( flags, log, dummy, prng );
  2229. } break;
  2230. case Mode::UI:
  2231. case Mode::Batch: {
  2232. if ( flags.use_stdout ) {
  2233. flags.apply_default_buffer_sizes();
  2234. stdout_stream_raii stdout_audio_stream;
  2235. render_files( flags, log, stdout_audio_stream, prng );
  2236. } else if ( !flags.output_filename.empty() ) {
  2237. flags.apply_default_buffer_sizes();
  2238. file_audio_stream_raii file_audio_stream( flags, flags.output_filename, log );
  2239. render_files( flags, log, file_audio_stream, prng );
  2240. #if defined( MPT_WITH_PULSEAUDIO )
  2241. } else if ( flags.driver == "pulseaudio" || flags.driver.empty() ) {
  2242. pulseaudio_stream_raii pulseaudio_stream( flags, log );
  2243. render_files( flags, log, pulseaudio_stream, prng );
  2244. #endif
  2245. #if defined( MPT_WITH_SDL2 )
  2246. } else if ( flags.driver == "sdl2" || flags.driver.empty() ) {
  2247. sdl2_stream_raii sdl2_stream( flags, log );
  2248. render_files( flags, log, sdl2_stream, prng );
  2249. #endif
  2250. #if defined( MPT_WITH_PORTAUDIO )
  2251. } else if ( flags.driver == "portaudio" || flags.driver.empty() ) {
  2252. portaudio_stream_raii portaudio_stream( flags, log );
  2253. render_files( flags, log, portaudio_stream, prng );
  2254. #endif
  2255. #if defined( WIN32 )
  2256. } else if ( flags.driver == "waveout" || flags.driver.empty() ) {
  2257. waveout_stream_raii waveout_stream( flags );
  2258. render_files( flags, log, waveout_stream, prng );
  2259. #endif
  2260. #if defined( MPT_WITH_ALLEGRO42 )
  2261. } else if ( flags.driver == "allegro42" || flags.driver.empty() ) {
  2262. allegro42_stream_raii allegro42_stream( flags, log );
  2263. render_files( flags, log, allegro42_stream, prng );
  2264. #endif
  2265. } else {
  2266. if ( flags.driver.empty() ) {
  2267. throw exception( "openmpt123 is compiled without any audio driver" );
  2268. } else {
  2269. throw exception( "audio driver '" + flags.driver + "' not found" );
  2270. }
  2271. }
  2272. } break;
  2273. case Mode::Render: {
  2274. for ( const auto & filename : flags.filenames ) {
  2275. flags.apply_default_buffer_sizes();
  2276. file_audio_stream_raii file_audio_stream( flags, filename + std::string(".") + flags.output_extension, log );
  2277. render_file( flags, filename, log, file_audio_stream );
  2278. flags.playlist_index++;
  2279. }
  2280. } break;
  2281. case Mode::None:
  2282. break;
  2283. }
  2284. } catch ( args_error_exception & ) {
  2285. show_help( std_out );
  2286. return 1;
  2287. #ifdef MPT_WITH_ALLEGRO42
  2288. } catch ( allegro42_exception & e ) {
  2289. std_err << "Allegro-4.2 error: " << e.what() << std::endl;
  2290. std_err.writeout();
  2291. return 1;
  2292. #endif
  2293. #ifdef MPT_WITH_PULSEAUDIO
  2294. } catch ( pulseaudio_exception & e ) {
  2295. std_err << "PulseAudio error: " << e.what() << std::endl;
  2296. std_err.writeout();
  2297. return 1;
  2298. #endif
  2299. #ifdef MPT_WITH_PORTAUDIO
  2300. } catch ( portaudio_exception & e ) {
  2301. std_err << "PortAudio error: " << e.what() << std::endl;
  2302. std_err.writeout();
  2303. return 1;
  2304. #endif
  2305. #ifdef MPT_WITH_SDL2
  2306. } catch ( sdl2_exception & e ) {
  2307. std_err << "SDL2 error: " << e.what() << std::endl;
  2308. std_err.writeout();
  2309. return 1;
  2310. #endif
  2311. } catch ( silent_exit_exception & ) {
  2312. return 0;
  2313. } catch ( std::exception & e ) {
  2314. std_err << "error: " << e.what() << std::endl;
  2315. std_err.writeout();
  2316. return 1;
  2317. } catch ( ... ) {
  2318. std_err << "unknown error" << std::endl;
  2319. std_err.writeout();
  2320. return 1;
  2321. }
  2322. return 0;
  2323. }
  2324. } // namespace openmpt123
  2325. #if defined(WIN32) && defined(UNICODE)
  2326. #if defined(__GNUC__) || (defined(__clang__) && !defined(_MSC_VER))
  2327. // mingw64 does only default to special C linkage for "main", but not for "wmain".
  2328. extern "C" int wmain( int wargc, wchar_t * wargv [] );
  2329. extern "C"
  2330. #endif
  2331. int wmain( int wargc, wchar_t * wargv [] ) {
  2332. return openmpt123::wmain( wargc, wargv );
  2333. }
  2334. #else
  2335. int main( int argc, char * argv [] ) {
  2336. return openmpt123::main( argc, argv );
  2337. }
  2338. #endif