1 /*
2 * openmpt123.hpp
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
10 #ifndef OPENMPT123_HPP
11 #define OPENMPT123_HPP
12
13 #include "openmpt123_config.hpp"
14
15 #include "mpt/base/compiletime_warning.hpp"
16 #include "mpt/base/floatingpoint.hpp"
17 #include "mpt/base/preprocessor.hpp"
18 #include "mpt/string_transcode/transcode.hpp"
19
20 #include <string>
21
22 namespace openmpt123 {
23
24 struct exception : public openmpt::exception {
exceptionopenmpt123::exception25 exception( const std::string & text ) : openmpt::exception(text) { }
26 };
27
28 struct show_help_exception {
29 std::string message;
30 bool longhelp;
show_help_exceptionopenmpt123::show_help_exception31 show_help_exception( const std::string & msg = "", bool longhelp_ = true ) : message(msg), longhelp(longhelp_) { }
32 };
33
34 struct args_error_exception {
args_error_exceptionopenmpt123::args_error_exception35 args_error_exception() { }
36 };
37
38 struct show_help_keyboard_exception { };
39
40 #if defined(WIN32)
41 bool IsConsole( DWORD stdHandle );
42 #endif
43 bool IsTerminal( int fd );
44
45
46
47 struct field {
48 std::string key;
49 std::string val;
fieldopenmpt123::field50 field( const std::string & key )
51 : key(key)
52 {
53 return;
54 }
55 };
56
57 class textout : public std::ostringstream {
58 public:
textout()59 textout() {
60 return;
61 }
~textout()62 virtual ~textout() {
63 return;
64 }
65 protected:
pop()66 std::string pop() {
67 std::string text = str();
68 str(std::string());
69 return text;
70 }
71 public:
72 virtual void writeout() = 0;
cursor_up(std::size_t lines)73 virtual void cursor_up( std::size_t lines ) {
74 static_cast<void>( lines );
75 }
76 };
77
78 class textout_dummy : public textout {
79 public:
textout_dummy()80 textout_dummy() {
81 return;
82 }
~textout_dummy()83 virtual ~textout_dummy() {
84 return;
85 }
86 public:
writeout()87 void writeout() override {
88 static_cast<void>( pop() );
89 }
90 };
91
92 class textout_ostream : public textout {
93 private:
94 std::ostream & s;
95 #if defined(__DJGPP__)
96 mpt::common_encoding codepage;
97 #endif
98 public:
textout_ostream(std::ostream & s_)99 textout_ostream( std::ostream & s_ )
100 : s(s_)
101 #if defined(__DJGPP__)
102 , codepage(mpt::common_encoding::cp437)
103 #endif
104 {
105 #if defined(__DJGPP__)
106 codepage = mpt::djgpp_get_locale_encoding();
107 #endif
108 return;
109 }
~textout_ostream()110 virtual ~textout_ostream() {
111 writeout_impl();
112 }
113 private:
writeout_impl()114 void writeout_impl() {
115 std::string text = pop();
116 if ( text.length() > 0 ) {
117 #if defined(__DJGPP__)
118 s << mpt::transcode<std::string>( codepage, mpt::common_encoding::utf8, text );
119 #elif defined(__EMSCRIPTEN__)
120 s << text;
121 #else
122 s << mpt::transcode<std::string>( mpt::logical_encoding::locale, mpt::common_encoding::utf8, text );
123 #endif
124 s.flush();
125 }
126 }
127 public:
writeout()128 void writeout() override {
129 writeout_impl();
130 }
cursor_up(std::size_t lines)131 void cursor_up( std::size_t lines ) override {
132 s.flush();
133 for ( std::size_t line = 0; line < lines; ++line ) {
134 *this << "\x1b[1A";
135 }
136 }
137 };
138
139 #if defined(WIN32)
140
141 class textout_ostream_console : public textout {
142 private:
143 #if defined(UNICODE)
144 std::wostream & s;
145 #else
146 std::ostream & s;
147 #endif
148 HANDLE handle;
149 bool console;
150 public:
151 #if defined(UNICODE)
textout_ostream_console(std::wostream & s_,DWORD stdHandle_)152 textout_ostream_console( std::wostream & s_, DWORD stdHandle_ )
153 #else
154 textout_ostream_console( std::ostream & s_, DWORD stdHandle_ )
155 #endif
156 : s(s_)
157 , handle(GetStdHandle( stdHandle_ ))
158 , console(IsConsole( stdHandle_ ))
159 {
160 return;
161 }
~textout_ostream_console()162 virtual ~textout_ostream_console() {
163 writeout_impl();
164 }
165 private:
writeout_impl()166 void writeout_impl() {
167 std::string text = pop();
168 if ( text.length() > 0 ) {
169 if ( console ) {
170 #if defined(UNICODE)
171 std::wstring wtext = mpt::transcode<std::wstring>( mpt::common_encoding::utf8, text );
172 WriteConsole( handle, wtext.data(), static_cast<DWORD>( wtext.size() ), NULL, NULL );
173 #else
174 std::string ltext = mpt::transcode<std::string>( mpt::logical_encoding::locale, mpt::common_encoding::utf8, text );
175 WriteConsole( handle, ltext.data(), static_cast<DWORD>( ltext.size() ), NULL, NULL );
176 #endif
177 } else {
178 #if defined(UNICODE)
179 s << mpt::transcode<std::wstring>( mpt::common_encoding::utf8, text );
180 #else
181 s << mpt::transcode<std::string>( mpt::logical_encoding::locale, mpt::common_encoding::utf8, text );
182 #endif
183 s.flush();
184 }
185 }
186 }
187 public:
writeout()188 void writeout() override {
189 writeout_impl();
190 }
cursor_up(std::size_t lines)191 void cursor_up( std::size_t lines ) override {
192 if ( console ) {
193 s.flush();
194 CONSOLE_SCREEN_BUFFER_INFO csbi;
195 ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) );
196 COORD coord_cursor = COORD();
197 if ( GetConsoleScreenBufferInfo( handle, &csbi ) != FALSE ) {
198 coord_cursor = csbi.dwCursorPosition;
199 coord_cursor.X = 1;
200 coord_cursor.Y -= static_cast<SHORT>( lines );
201 SetConsoleCursorPosition( handle, coord_cursor );
202 }
203 }
204 }
205 };
206
207 #endif // WIN32
208
mpt_round(float val)209 static inline float mpt_round( float val ) {
210 if ( val >= 0.0f ) {
211 return std::floor( val + 0.5f );
212 } else {
213 return std::ceil( val - 0.5f );
214 }
215 }
216
mpt_lround(float val)217 static inline long mpt_lround( float val ) {
218 return static_cast< long >( mpt_round( val ) );
219 }
220
append_software_tag(std::string software)221 static inline std::string append_software_tag( std::string software ) {
222 std::string openmpt123 = std::string() + "openmpt123 " + OPENMPT123_VERSION_STRING + " (libopenmpt " + openmpt::string::get( "library_version" ) + ", OpenMPT " + openmpt::string::get( "core_version" ) + ")";
223 if ( software.empty() ) {
224 software = openmpt123;
225 } else {
226 software += " (via " + openmpt123 + ")";
227 }
228 return software;
229 }
230
get_encoder_tag()231 static inline std::string get_encoder_tag() {
232 return std::string() + "openmpt123 " + OPENMPT123_VERSION_STRING + " (libopenmpt " + openmpt::string::get( "library_version" ) + ", OpenMPT " + openmpt::string::get( "core_version" ) + ")";
233 }
234
get_extension(std::string filename)235 static inline std::string get_extension( std::string filename ) {
236 if ( filename.find_last_of( "." ) != std::string::npos ) {
237 return filename.substr( filename.find_last_of( "." ) + 1 );
238 }
239 return "";
240 }
241
242 enum class Mode {
243 None,
244 Probe,
245 Info,
246 UI,
247 Batch,
248 Render
249 };
250
mode_to_string(Mode mode)251 static inline std::string mode_to_string( Mode mode ) {
252 switch ( mode ) {
253 case Mode::None: return "none"; break;
254 case Mode::Probe: return "probe"; break;
255 case Mode::Info: return "info"; break;
256 case Mode::UI: return "ui"; break;
257 case Mode::Batch: return "batch"; break;
258 case Mode::Render: return "render"; break;
259 }
260 return "";
261 }
262
263 static const std::int32_t default_low = -2;
264 static const std::int32_t default_high = -1;
265
266 struct commandlineflags {
267 Mode mode;
268 bool canUI;
269 std::int32_t ui_redraw_interval;
270 bool canProgress;
271 std::string driver;
272 std::string device;
273 std::int32_t buffer;
274 std::int32_t period;
275 std::int32_t samplerate;
276 std::int32_t channels;
277 std::int32_t gain;
278 std::int32_t separation;
279 std::int32_t filtertaps;
280 std::int32_t ramping; // ramping strength : -1:default 0:off 1 2 3 4 5 // roughly milliseconds
281 std::int32_t tempo;
282 std::int32_t pitch;
283 std::int32_t dither;
284 std::int32_t repeatcount;
285 std::int32_t subsong;
286 std::map<std::string, std::string> ctls;
287 double seek_target;
288 double end_time;
289 bool quiet;
290 bool verbose;
291 int terminal_width;
292 int terminal_height;
293 bool show_details;
294 bool show_message;
295 bool show_ui;
296 bool show_progress;
297 bool show_meters;
298 bool show_channel_meters;
299 bool show_pattern;
300 bool use_float;
301 bool use_stdout;
302 bool randomize;
303 bool shuffle;
304 bool restart;
305 std::size_t playlist_index;
306 std::vector<std::string> filenames;
307 std::string output_filename;
308 std::string output_extension;
309 bool force_overwrite;
310 bool paused;
311 std::string warnings;
apply_default_buffer_sizesopenmpt123::commandlineflags312 void apply_default_buffer_sizes() {
313 if ( ui_redraw_interval == default_high ) {
314 ui_redraw_interval = 50;
315 } else if ( ui_redraw_interval == default_low ) {
316 ui_redraw_interval = 10;
317 }
318 if ( buffer == default_high ) {
319 buffer = 250;
320 } else if ( buffer == default_low ) {
321 buffer = 50;
322 }
323 if ( period == default_high ) {
324 period = 50;
325 } else if ( period == default_low ) {
326 period = 10;
327 }
328 }
commandlineflagsopenmpt123::commandlineflags329 commandlineflags() {
330 mode = Mode::UI;
331 ui_redraw_interval = default_high;
332 driver = "";
333 device = "";
334 buffer = default_high;
335 period = default_high;
336 #if defined(__DJGPP__)
337 samplerate = 44100;
338 channels = 2;
339 use_float = false;
340 #else
341 samplerate = 48000;
342 channels = 2;
343 use_float = mpt::float_traits<float>::is_hard && mpt::float_traits<float>::is_ieee754_binary;
344 #endif
345 gain = 0;
346 separation = 100;
347 filtertaps = 8;
348 ramping = -1;
349 tempo = 0;
350 pitch = 0;
351 dither = 1;
352 repeatcount = 0;
353 subsong = -1;
354 seek_target = 0.0;
355 end_time = 0.0;
356 quiet = false;
357 verbose = false;
358 #if defined(__DJGPP__)
359 terminal_width = 80;
360 terminal_height = 25;
361 #else
362 terminal_width = 72;
363 terminal_height = 23;
364 #endif
365 #if defined(WIN32)
366 terminal_width = 72;
367 terminal_height = 23;
368 HANDLE hStdOutput = GetStdHandle( STD_OUTPUT_HANDLE );
369 if ( ( hStdOutput != NULL ) && ( hStdOutput != INVALID_HANDLE_VALUE ) ) {
370 CONSOLE_SCREEN_BUFFER_INFO csbi;
371 ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) );
372 if ( GetConsoleScreenBufferInfo( hStdOutput, &csbi ) != FALSE ) {
373 terminal_width = std::min( static_cast<int>( 1 + csbi.srWindow.Right - csbi.srWindow.Left ), static_cast<int>( csbi.dwSize.X ) );
374 terminal_height = std::min( static_cast<int>( 1 + csbi.srWindow.Bottom - csbi.srWindow.Top ), static_cast<int>( csbi.dwSize.Y ) );
375 }
376 }
377 #else // WIN32
378 if ( isatty( STDERR_FILENO ) ) {
379 if ( std::getenv( "COLUMNS" ) ) {
380 std::istringstream istr( std::getenv( "COLUMNS" ) );
381 int tmp = 0;
382 istr >> tmp;
383 if ( tmp > 0 ) {
384 terminal_width = tmp;
385 }
386 }
387 if ( std::getenv( "ROWS" ) ) {
388 std::istringstream istr( std::getenv( "ROWS" ) );
389 int tmp = 0;
390 istr >> tmp;
391 if ( tmp > 0 ) {
392 terminal_height = tmp;
393 }
394 }
395 #if defined(TIOCGWINSZ)
396 struct winsize ts;
397 if ( ioctl( STDERR_FILENO, TIOCGWINSZ, &ts ) >= 0 ) {
398 terminal_width = ts.ws_col;
399 terminal_height = ts.ws_row;
400 }
401 #elif defined(TIOCGSIZE)
402 struct ttysize ts;
403 if ( ioctl( STDERR_FILENO, TIOCGSIZE, &ts ) >= 0 ) {
404 terminal_width = ts.ts_cols;
405 terminal_height = ts.ts_rows;
406 }
407 #endif
408 }
409 #endif
410 show_details = true;
411 show_message = false;
412 #if defined(WIN32)
413 canUI = IsTerminal( 0 ) ? true : false;
414 canProgress = IsTerminal( 2 ) ? true : false;
415 #else // !WIN32
416 canUI = isatty( STDIN_FILENO ) ? true : false;
417 canProgress = isatty( STDERR_FILENO ) ? true : false;
418 #endif // WIN32
419 show_ui = canUI;
420 show_progress = canProgress;
421 show_meters = canUI && canProgress;
422 show_channel_meters = false;
423 show_pattern = false;
424 use_stdout = false;
425 randomize = false;
426 shuffle = false;
427 restart = false;
428 playlist_index = 0;
429 output_extension = "auto";
430 force_overwrite = false;
431 paused = false;
432 }
check_and_sanitizeopenmpt123::commandlineflags433 void check_and_sanitize() {
434 if ( filenames.size() == 0 ) {
435 throw args_error_exception();
436 }
437 if ( use_stdout && ( device != commandlineflags().device || !output_filename.empty() ) ) {
438 throw args_error_exception();
439 }
440 if ( !output_filename.empty() && ( device != commandlineflags().device || use_stdout ) ) {
441 throw args_error_exception();
442 }
443 for ( const auto & filename : filenames ) {
444 if ( filename == "-" ) {
445 canUI = false;
446 }
447 }
448 show_ui = canUI;
449 if ( mode == Mode::None ) {
450 if ( canUI ) {
451 mode = Mode::UI;
452 } else {
453 mode = Mode::Batch;
454 }
455 }
456 if ( mode == Mode::UI && !canUI ) {
457 throw args_error_exception();
458 }
459 if ( show_progress && !canProgress ) {
460 throw args_error_exception();
461 }
462 switch ( mode ) {
463 case Mode::None:
464 throw args_error_exception();
465 break;
466 case Mode::Probe:
467 show_ui = false;
468 show_progress = false;
469 show_meters = false;
470 show_channel_meters = false;
471 show_pattern = false;
472 break;
473 case Mode::Info:
474 show_ui = false;
475 show_progress = false;
476 show_meters = false;
477 show_channel_meters = false;
478 show_pattern = false;
479 break;
480 case Mode::UI:
481 break;
482 case Mode::Batch:
483 show_meters = false;
484 show_channel_meters = false;
485 show_pattern = false;
486 break;
487 case Mode::Render:
488 show_meters = false;
489 show_channel_meters = false;
490 show_pattern = false;
491 show_ui = false;
492 break;
493 }
494 if ( quiet ) {
495 verbose = false;
496 show_ui = false;
497 show_details = false;
498 show_progress = false;
499 show_channel_meters = false;
500 }
501 if ( verbose ) {
502 show_details = true;
503 }
504 if ( channels != 1 && channels != 2 && channels != 4 ) {
505 channels = commandlineflags().channels;
506 }
507 if ( samplerate < 0 ) {
508 samplerate = commandlineflags().samplerate;
509 }
510 if ( output_extension == "auto" ) {
511 output_extension = "";
512 }
513 if ( mode != Mode::Render && !output_extension.empty() ) {
514 throw args_error_exception();
515 }
516 if ( mode == Mode::Render && !output_filename.empty() ) {
517 throw args_error_exception();
518 }
519 if ( mode != Mode::Render && !output_filename.empty() ) {
520 output_extension = get_extension( output_filename );
521 }
522 if ( output_extension.empty() ) {
523 output_extension = "wav";
524 }
525 }
526 };
527
528 template < typename Tsample > Tsample convert_sample_to( float val );
convert_sample_to(float val)529 template < > float convert_sample_to( float val ) {
530 return val;
531 }
convert_sample_to(float val)532 template < > std::int16_t convert_sample_to( float val ) {
533 std::int32_t tmp = static_cast<std::int32_t>( val * 32768.0f );
534 tmp = std::min( tmp, std::int32_t( 32767 ) );
535 tmp = std::max( tmp, std::int32_t( -32768 ) );
536 return static_cast<std::int16_t>( tmp );
537 }
538
539 class write_buffers_interface {
540 protected:
~write_buffers_interface()541 virtual ~write_buffers_interface() {
542 return;
543 }
544 public:
write_metadata(std::map<std::string,std::string> metadata)545 virtual void write_metadata( std::map<std::string,std::string> metadata ) {
546 (void)metadata;
547 return;
548 }
write_updated_metadata(std::map<std::string,std::string> metadata)549 virtual void write_updated_metadata( std::map<std::string,std::string> metadata ) {
550 (void)metadata;
551 return;
552 }
553 virtual void write( const std::vector<float*> buffers, std::size_t frames ) = 0;
554 virtual void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) = 0;
pause()555 virtual bool pause() {
556 return false;
557 }
unpause()558 virtual bool unpause() {
559 return false;
560 }
sleep(int)561 virtual bool sleep( int /*ms*/ ) {
562 return false;
563 }
is_dummy() const564 virtual bool is_dummy() const {
565 return false;
566 }
567 };
568
569 class write_buffers_polling_wrapper : public write_buffers_interface {
570 protected:
571 std::size_t channels;
572 std::size_t sampleQueueMaxFrames;
573 std::deque<float> sampleQueue;
574 protected:
~write_buffers_polling_wrapper()575 virtual ~write_buffers_polling_wrapper() {
576 return;
577 }
578 protected:
write_buffers_polling_wrapper(const commandlineflags & flags)579 write_buffers_polling_wrapper( const commandlineflags & flags )
580 : channels(flags.channels)
581 , sampleQueueMaxFrames(0)
582 {
583 return;
584 }
set_queue_size_frames(std::size_t frames)585 void set_queue_size_frames( std::size_t frames ) {
586 sampleQueueMaxFrames = frames;
587 }
588 template < typename Tsample >
pop_queue()589 Tsample pop_queue() {
590 float val = 0.0f;
591 if ( !sampleQueue.empty() ) {
592 val = sampleQueue.front();
593 sampleQueue.pop_front();
594 }
595 return convert_sample_to<Tsample>( val );
596 }
597 public:
write(const std::vector<float * > buffers,std::size_t frames)598 void write( const std::vector<float*> buffers, std::size_t frames ) override {
599 for ( std::size_t frame = 0; frame < frames; ++frame ) {
600 for ( std::size_t channel = 0; channel < channels; ++channel ) {
601 sampleQueue.push_back( buffers[channel][frame] );
602 }
603 while ( sampleQueue.size() >= sampleQueueMaxFrames * channels ) {
604 while ( !forward_queue() ) {
605 sleep( 1 );
606 }
607 }
608 }
609 }
write(const std::vector<std::int16_t * > buffers,std::size_t frames)610 void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override {
611 for ( std::size_t frame = 0; frame < frames; ++frame ) {
612 for ( std::size_t channel = 0; channel < channels; ++channel ) {
613 sampleQueue.push_back( buffers[channel][frame] * (1.0f/32768.0f) );
614 }
615 while ( sampleQueue.size() >= sampleQueueMaxFrames * channels ) {
616 while ( !forward_queue() ) {
617 sleep( 1 );
618 }
619 }
620 }
621 }
622 virtual bool forward_queue() = 0;
623 bool sleep( int ms ) override = 0;
624 };
625
626 class write_buffers_polling_wrapper_int : public write_buffers_interface {
627 protected:
628 std::size_t channels;
629 std::size_t sampleQueueMaxFrames;
630 std::deque<std::int16_t> sampleQueue;
631 protected:
~write_buffers_polling_wrapper_int()632 virtual ~write_buffers_polling_wrapper_int() {
633 return;
634 }
635 protected:
write_buffers_polling_wrapper_int(const commandlineflags & flags)636 write_buffers_polling_wrapper_int( const commandlineflags & flags )
637 : channels(flags.channels)
638 , sampleQueueMaxFrames(0)
639 {
640 return;
641 }
set_queue_size_frames(std::size_t frames)642 void set_queue_size_frames( std::size_t frames ) {
643 sampleQueueMaxFrames = frames;
644 }
pop_queue()645 std::int16_t pop_queue() {
646 std::int16_t val = 0;
647 if ( !sampleQueue.empty() ) {
648 val = sampleQueue.front();
649 sampleQueue.pop_front();
650 }
651 return val;
652 }
653 public:
write(const std::vector<float * > buffers,std::size_t frames)654 void write( const std::vector<float*> buffers, std::size_t frames ) override {
655 for ( std::size_t frame = 0; frame < frames; ++frame ) {
656 for ( std::size_t channel = 0; channel < channels; ++channel ) {
657 sampleQueue.push_back( convert_sample_to<std::int16_t>( buffers[channel][frame] ) );
658 }
659 while ( sampleQueue.size() >= sampleQueueMaxFrames * channels ) {
660 while ( !forward_queue() ) {
661 sleep( 1 );
662 }
663 }
664 }
665 }
write(const std::vector<std::int16_t * > buffers,std::size_t frames)666 void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override {
667 for ( std::size_t frame = 0; frame < frames; ++frame ) {
668 for ( std::size_t channel = 0; channel < channels; ++channel ) {
669 sampleQueue.push_back( buffers[channel][frame] );
670 }
671 while ( sampleQueue.size() >= sampleQueueMaxFrames * channels ) {
672 while ( !forward_queue() ) {
673 sleep( 1 );
674 }
675 }
676 }
677 }
678 virtual bool forward_queue() = 0;
679 bool sleep( int ms ) override = 0;
680 };
681
682 class void_audio_stream : public write_buffers_interface {
683 public:
~void_audio_stream()684 virtual ~void_audio_stream() {
685 return;
686 }
687 public:
write(const std::vector<float * > buffers,std::size_t frames)688 void write( const std::vector<float*> buffers, std::size_t frames ) override {
689 (void)buffers;
690 (void)frames;
691 }
write(const std::vector<std::int16_t * > buffers,std::size_t frames)692 void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override {
693 (void)buffers;
694 (void)frames;
695 }
is_dummy() const696 bool is_dummy() const override {
697 return true;
698 }
699 };
700
701 class file_audio_stream_base : public write_buffers_interface {
702 protected:
file_audio_stream_base()703 file_audio_stream_base() {
704 return;
705 }
706 public:
write_metadata(std::map<std::string,std::string> metadata)707 void write_metadata( std::map<std::string,std::string> metadata ) override {
708 (void)metadata;
709 return;
710 }
write_updated_metadata(std::map<std::string,std::string> metadata)711 void write_updated_metadata( std::map<std::string,std::string> metadata ) override {
712 (void)metadata;
713 return;
714 }
715 void write( const std::vector<float*> buffers, std::size_t frames ) override = 0;
716 void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override = 0;
~file_audio_stream_base()717 virtual ~file_audio_stream_base() {
718 return;
719 }
720 };
721
722 } // namespace openmpt123
723
724 #endif // OPENMPT123_HPP
725