Cog/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123.hpp
Christopher Snowhill 731e52c440 Build libOpenMPT from source once again
Bundle libOpenMPT as a dynamic framework, which should be safe once
again, now that there is only one version to bundle. Also, now it is
using the versions of libvorbisfile and libmpg123 that are bundled with
the player, instead of compiling minimp3 and stbvorbis.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-30 22:57:30 -07:00

724 lines
18 KiB
C++

/*
* openmpt123.hpp
* --------------
* Purpose: libopenmpt command line player
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#ifndef OPENMPT123_HPP
#define OPENMPT123_HPP
#include "openmpt123_config.hpp"
#include "mpt/base/compiletime_warning.hpp"
#include "mpt/base/floatingpoint.hpp"
#include "mpt/base/preprocessor.hpp"
#include "mpt/string_transcode/transcode.hpp"
#include <string>
namespace openmpt123 {
struct exception : public openmpt::exception {
exception( const std::string & text ) : openmpt::exception(text) { }
};
struct show_help_exception {
std::string message;
bool longhelp;
show_help_exception( const std::string & msg = "", bool longhelp_ = true ) : message(msg), longhelp(longhelp_) { }
};
struct args_error_exception {
args_error_exception() { }
};
struct show_help_keyboard_exception { };
#if defined(WIN32)
bool IsConsole( DWORD stdHandle );
#endif
bool IsTerminal( int fd );
struct field {
std::string key;
std::string val;
field( const std::string & key )
: key(key)
{
return;
}
};
class textout : public std::ostringstream {
public:
textout() {
return;
}
virtual ~textout() {
return;
}
protected:
std::string pop() {
std::string text = str();
str(std::string());
return text;
}
public:
virtual void writeout() = 0;
virtual void cursor_up( std::size_t lines ) {
static_cast<void>( lines );
}
};
class textout_dummy : public textout {
public:
textout_dummy() {
return;
}
virtual ~textout_dummy() {
return;
}
public:
void writeout() override {
static_cast<void>( pop() );
}
};
class textout_ostream : public textout {
private:
std::ostream & s;
#if defined(__DJGPP__)
mpt::common_encoding codepage;
#endif
public:
textout_ostream( std::ostream & s_ )
: s(s_)
#if defined(__DJGPP__)
, codepage(mpt::common_encoding::cp437)
#endif
{
#if defined(__DJGPP__)
codepage = mpt::djgpp_get_locale_encoding();
#endif
return;
}
virtual ~textout_ostream() {
writeout_impl();
}
private:
void writeout_impl() {
std::string text = pop();
if ( text.length() > 0 ) {
#if defined(__DJGPP__)
s << mpt::transcode<std::string>( codepage, mpt::common_encoding::utf8, text );
#elif defined(__EMSCRIPTEN__)
s << text;
#else
s << mpt::transcode<std::string>( mpt::logical_encoding::locale, mpt::common_encoding::utf8, text );
#endif
s.flush();
}
}
public:
void writeout() override {
writeout_impl();
}
void cursor_up( std::size_t lines ) override {
s.flush();
for ( std::size_t line = 0; line < lines; ++line ) {
*this << "\x1b[1A";
}
}
};
#if defined(WIN32)
class textout_ostream_console : public textout {
private:
#if defined(UNICODE)
std::wostream & s;
#else
std::ostream & s;
#endif
HANDLE handle;
bool console;
public:
#if defined(UNICODE)
textout_ostream_console( std::wostream & s_, DWORD stdHandle_ )
#else
textout_ostream_console( std::ostream & s_, DWORD stdHandle_ )
#endif
: s(s_)
, handle(GetStdHandle( stdHandle_ ))
, console(IsConsole( stdHandle_ ))
{
return;
}
virtual ~textout_ostream_console() {
writeout_impl();
}
private:
void writeout_impl() {
std::string text = pop();
if ( text.length() > 0 ) {
if ( console ) {
#if defined(UNICODE)
std::wstring wtext = mpt::transcode<std::wstring>( mpt::common_encoding::utf8, text );
WriteConsole( handle, wtext.data(), static_cast<DWORD>( wtext.size() ), NULL, NULL );
#else
std::string ltext = mpt::transcode<std::string>( mpt::logical_encoding::locale, mpt::common_encoding::utf8, text );
WriteConsole( handle, ltext.data(), static_cast<DWORD>( ltext.size() ), NULL, NULL );
#endif
} else {
#if defined(UNICODE)
s << mpt::transcode<std::wstring>( mpt::common_encoding::utf8, text );
#else
s << mpt::transcode<std::string>( mpt::logical_encoding::locale, mpt::common_encoding::utf8, text );
#endif
s.flush();
}
}
}
public:
void writeout() override {
writeout_impl();
}
void cursor_up( std::size_t lines ) override {
if ( console ) {
s.flush();
CONSOLE_SCREEN_BUFFER_INFO csbi;
ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) );
COORD coord_cursor = COORD();
if ( GetConsoleScreenBufferInfo( handle, &csbi ) != FALSE ) {
coord_cursor = csbi.dwCursorPosition;
coord_cursor.X = 1;
coord_cursor.Y -= static_cast<SHORT>( lines );
SetConsoleCursorPosition( handle, coord_cursor );
}
}
}
};
#endif // WIN32
static inline float mpt_round( float val ) {
if ( val >= 0.0f ) {
return std::floor( val + 0.5f );
} else {
return std::ceil( val - 0.5f );
}
}
static inline long mpt_lround( float val ) {
return static_cast< long >( mpt_round( val ) );
}
static inline std::string append_software_tag( std::string software ) {
std::string openmpt123 = std::string() + "openmpt123 " + OPENMPT123_VERSION_STRING + " (libopenmpt " + openmpt::string::get( "library_version" ) + ", OpenMPT " + openmpt::string::get( "core_version" ) + ")";
if ( software.empty() ) {
software = openmpt123;
} else {
software += " (via " + openmpt123 + ")";
}
return software;
}
static inline std::string get_encoder_tag() {
return std::string() + "openmpt123 " + OPENMPT123_VERSION_STRING + " (libopenmpt " + openmpt::string::get( "library_version" ) + ", OpenMPT " + openmpt::string::get( "core_version" ) + ")";
}
static inline std::string get_extension( std::string filename ) {
if ( filename.find_last_of( "." ) != std::string::npos ) {
return filename.substr( filename.find_last_of( "." ) + 1 );
}
return "";
}
enum class Mode {
None,
Probe,
Info,
UI,
Batch,
Render
};
static inline std::string mode_to_string( Mode mode ) {
switch ( mode ) {
case Mode::None: return "none"; break;
case Mode::Probe: return "probe"; break;
case Mode::Info: return "info"; break;
case Mode::UI: return "ui"; break;
case Mode::Batch: return "batch"; break;
case Mode::Render: return "render"; break;
}
return "";
}
static const std::int32_t default_low = -2;
static const std::int32_t default_high = -1;
struct commandlineflags {
Mode mode;
bool canUI;
std::int32_t ui_redraw_interval;
bool canProgress;
std::string driver;
std::string device;
std::int32_t buffer;
std::int32_t period;
std::int32_t samplerate;
std::int32_t channels;
std::int32_t gain;
std::int32_t separation;
std::int32_t filtertaps;
std::int32_t ramping; // ramping strength : -1:default 0:off 1 2 3 4 5 // roughly milliseconds
std::int32_t tempo;
std::int32_t pitch;
std::int32_t dither;
std::int32_t repeatcount;
std::int32_t subsong;
std::map<std::string, std::string> ctls;
double seek_target;
double end_time;
bool quiet;
bool verbose;
int terminal_width;
int terminal_height;
bool show_details;
bool show_message;
bool show_ui;
bool show_progress;
bool show_meters;
bool show_channel_meters;
bool show_pattern;
bool use_float;
bool use_stdout;
bool randomize;
bool shuffle;
bool restart;
std::size_t playlist_index;
std::vector<std::string> filenames;
std::string output_filename;
std::string output_extension;
bool force_overwrite;
bool paused;
std::string warnings;
void apply_default_buffer_sizes() {
if ( ui_redraw_interval == default_high ) {
ui_redraw_interval = 50;
} else if ( ui_redraw_interval == default_low ) {
ui_redraw_interval = 10;
}
if ( buffer == default_high ) {
buffer = 250;
} else if ( buffer == default_low ) {
buffer = 50;
}
if ( period == default_high ) {
period = 50;
} else if ( period == default_low ) {
period = 10;
}
}
commandlineflags() {
mode = Mode::UI;
ui_redraw_interval = default_high;
driver = "";
device = "";
buffer = default_high;
period = default_high;
#if defined(__DJGPP__)
samplerate = 44100;
channels = 2;
use_float = false;
#else
samplerate = 48000;
channels = 2;
use_float = mpt::float_traits<float>::is_hard && mpt::float_traits<float>::is_ieee754_binary;
#endif
gain = 0;
separation = 100;
filtertaps = 8;
ramping = -1;
tempo = 0;
pitch = 0;
dither = 1;
repeatcount = 0;
subsong = -1;
seek_target = 0.0;
end_time = 0.0;
quiet = false;
verbose = false;
#if defined(__DJGPP__)
terminal_width = 80;
terminal_height = 25;
#else
terminal_width = 72;
terminal_height = 23;
#endif
#if defined(WIN32)
terminal_width = 72;
terminal_height = 23;
HANDLE hStdOutput = GetStdHandle( STD_OUTPUT_HANDLE );
if ( ( hStdOutput != NULL ) && ( hStdOutput != INVALID_HANDLE_VALUE ) ) {
CONSOLE_SCREEN_BUFFER_INFO csbi;
ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) );
if ( GetConsoleScreenBufferInfo( hStdOutput, &csbi ) != FALSE ) {
terminal_width = std::min( static_cast<int>( 1 + csbi.srWindow.Right - csbi.srWindow.Left ), static_cast<int>( csbi.dwSize.X ) );
terminal_height = std::min( static_cast<int>( 1 + csbi.srWindow.Bottom - csbi.srWindow.Top ), static_cast<int>( csbi.dwSize.Y ) );
}
}
#else // WIN32
if ( isatty( STDERR_FILENO ) ) {
if ( std::getenv( "COLUMNS" ) ) {
std::istringstream istr( std::getenv( "COLUMNS" ) );
int tmp = 0;
istr >> tmp;
if ( tmp > 0 ) {
terminal_width = tmp;
}
}
if ( std::getenv( "ROWS" ) ) {
std::istringstream istr( std::getenv( "ROWS" ) );
int tmp = 0;
istr >> tmp;
if ( tmp > 0 ) {
terminal_height = tmp;
}
}
#if defined(TIOCGWINSZ)
struct winsize ts;
if ( ioctl( STDERR_FILENO, TIOCGWINSZ, &ts ) >= 0 ) {
terminal_width = ts.ws_col;
terminal_height = ts.ws_row;
}
#elif defined(TIOCGSIZE)
struct ttysize ts;
if ( ioctl( STDERR_FILENO, TIOCGSIZE, &ts ) >= 0 ) {
terminal_width = ts.ts_cols;
terminal_height = ts.ts_rows;
}
#endif
}
#endif
show_details = true;
show_message = false;
#if defined(WIN32)
canUI = IsTerminal( 0 ) ? true : false;
canProgress = IsTerminal( 2 ) ? true : false;
#else // !WIN32
canUI = isatty( STDIN_FILENO ) ? true : false;
canProgress = isatty( STDERR_FILENO ) ? true : false;
#endif // WIN32
show_ui = canUI;
show_progress = canProgress;
show_meters = canUI && canProgress;
show_channel_meters = false;
show_pattern = false;
use_stdout = false;
randomize = false;
shuffle = false;
restart = false;
playlist_index = 0;
output_extension = "auto";
force_overwrite = false;
paused = false;
}
void check_and_sanitize() {
if ( filenames.size() == 0 ) {
throw args_error_exception();
}
if ( use_stdout && ( device != commandlineflags().device || !output_filename.empty() ) ) {
throw args_error_exception();
}
if ( !output_filename.empty() && ( device != commandlineflags().device || use_stdout ) ) {
throw args_error_exception();
}
for ( const auto & filename : filenames ) {
if ( filename == "-" ) {
canUI = false;
}
}
show_ui = canUI;
if ( mode == Mode::None ) {
if ( canUI ) {
mode = Mode::UI;
} else {
mode = Mode::Batch;
}
}
if ( mode == Mode::UI && !canUI ) {
throw args_error_exception();
}
if ( show_progress && !canProgress ) {
throw args_error_exception();
}
switch ( mode ) {
case Mode::None:
throw args_error_exception();
break;
case Mode::Probe:
show_ui = false;
show_progress = false;
show_meters = false;
show_channel_meters = false;
show_pattern = false;
break;
case Mode::Info:
show_ui = false;
show_progress = false;
show_meters = false;
show_channel_meters = false;
show_pattern = false;
break;
case Mode::UI:
break;
case Mode::Batch:
show_meters = false;
show_channel_meters = false;
show_pattern = false;
break;
case Mode::Render:
show_meters = false;
show_channel_meters = false;
show_pattern = false;
show_ui = false;
break;
}
if ( quiet ) {
verbose = false;
show_ui = false;
show_details = false;
show_progress = false;
show_channel_meters = false;
}
if ( verbose ) {
show_details = true;
}
if ( channels != 1 && channels != 2 && channels != 4 ) {
channels = commandlineflags().channels;
}
if ( samplerate < 0 ) {
samplerate = commandlineflags().samplerate;
}
if ( output_extension == "auto" ) {
output_extension = "";
}
if ( mode != Mode::Render && !output_extension.empty() ) {
throw args_error_exception();
}
if ( mode == Mode::Render && !output_filename.empty() ) {
throw args_error_exception();
}
if ( mode != Mode::Render && !output_filename.empty() ) {
output_extension = get_extension( output_filename );
}
if ( output_extension.empty() ) {
output_extension = "wav";
}
}
};
template < typename Tsample > Tsample convert_sample_to( float val );
template < > float convert_sample_to( float val ) {
return val;
}
template < > std::int16_t convert_sample_to( float val ) {
std::int32_t tmp = static_cast<std::int32_t>( val * 32768.0f );
tmp = std::min( tmp, std::int32_t( 32767 ) );
tmp = std::max( tmp, std::int32_t( -32768 ) );
return static_cast<std::int16_t>( tmp );
}
class write_buffers_interface {
protected:
virtual ~write_buffers_interface() {
return;
}
public:
virtual void write_metadata( std::map<std::string,std::string> metadata ) {
(void)metadata;
return;
}
virtual void write_updated_metadata( std::map<std::string,std::string> metadata ) {
(void)metadata;
return;
}
virtual void write( const std::vector<float*> buffers, std::size_t frames ) = 0;
virtual void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) = 0;
virtual bool pause() {
return false;
}
virtual bool unpause() {
return false;
}
virtual bool sleep( int /*ms*/ ) {
return false;
}
virtual bool is_dummy() const {
return false;
}
};
class write_buffers_polling_wrapper : public write_buffers_interface {
protected:
std::size_t channels;
std::size_t sampleQueueMaxFrames;
std::deque<float> sampleQueue;
protected:
virtual ~write_buffers_polling_wrapper() {
return;
}
protected:
write_buffers_polling_wrapper( const commandlineflags & flags )
: channels(flags.channels)
, sampleQueueMaxFrames(0)
{
return;
}
void set_queue_size_frames( std::size_t frames ) {
sampleQueueMaxFrames = frames;
}
template < typename Tsample >
Tsample pop_queue() {
float val = 0.0f;
if ( !sampleQueue.empty() ) {
val = sampleQueue.front();
sampleQueue.pop_front();
}
return convert_sample_to<Tsample>( val );
}
public:
void write( const std::vector<float*> buffers, std::size_t frames ) override {
for ( std::size_t frame = 0; frame < frames; ++frame ) {
for ( std::size_t channel = 0; channel < channels; ++channel ) {
sampleQueue.push_back( buffers[channel][frame] );
}
while ( sampleQueue.size() >= sampleQueueMaxFrames * channels ) {
while ( !forward_queue() ) {
sleep( 1 );
}
}
}
}
void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override {
for ( std::size_t frame = 0; frame < frames; ++frame ) {
for ( std::size_t channel = 0; channel < channels; ++channel ) {
sampleQueue.push_back( buffers[channel][frame] * (1.0f/32768.0f) );
}
while ( sampleQueue.size() >= sampleQueueMaxFrames * channels ) {
while ( !forward_queue() ) {
sleep( 1 );
}
}
}
}
virtual bool forward_queue() = 0;
bool sleep( int ms ) override = 0;
};
class write_buffers_polling_wrapper_int : public write_buffers_interface {
protected:
std::size_t channels;
std::size_t sampleQueueMaxFrames;
std::deque<std::int16_t> sampleQueue;
protected:
virtual ~write_buffers_polling_wrapper_int() {
return;
}
protected:
write_buffers_polling_wrapper_int( const commandlineflags & flags )
: channels(flags.channels)
, sampleQueueMaxFrames(0)
{
return;
}
void set_queue_size_frames( std::size_t frames ) {
sampleQueueMaxFrames = frames;
}
std::int16_t pop_queue() {
std::int16_t val = 0;
if ( !sampleQueue.empty() ) {
val = sampleQueue.front();
sampleQueue.pop_front();
}
return val;
}
public:
void write( const std::vector<float*> buffers, std::size_t frames ) override {
for ( std::size_t frame = 0; frame < frames; ++frame ) {
for ( std::size_t channel = 0; channel < channels; ++channel ) {
sampleQueue.push_back( convert_sample_to<std::int16_t>( buffers[channel][frame] ) );
}
while ( sampleQueue.size() >= sampleQueueMaxFrames * channels ) {
while ( !forward_queue() ) {
sleep( 1 );
}
}
}
}
void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override {
for ( std::size_t frame = 0; frame < frames; ++frame ) {
for ( std::size_t channel = 0; channel < channels; ++channel ) {
sampleQueue.push_back( buffers[channel][frame] );
}
while ( sampleQueue.size() >= sampleQueueMaxFrames * channels ) {
while ( !forward_queue() ) {
sleep( 1 );
}
}
}
}
virtual bool forward_queue() = 0;
bool sleep( int ms ) override = 0;
};
class void_audio_stream : public write_buffers_interface {
public:
virtual ~void_audio_stream() {
return;
}
public:
void write( const std::vector<float*> buffers, std::size_t frames ) override {
(void)buffers;
(void)frames;
}
void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override {
(void)buffers;
(void)frames;
}
bool is_dummy() const override {
return true;
}
};
class file_audio_stream_base : public write_buffers_interface {
protected:
file_audio_stream_base() {
return;
}
public:
void write_metadata( std::map<std::string,std::string> metadata ) override {
(void)metadata;
return;
}
void write_updated_metadata( std::map<std::string,std::string> metadata ) override {
(void)metadata;
return;
}
void write( const std::vector<float*> buffers, std::size_t frames ) override = 0;
void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override = 0;
virtual ~file_audio_stream_base() {
return;
}
};
} // namespace openmpt123
#endif // OPENMPT123_HPP