summaryrefslogtreecommitdiff
path: root/openmpt123
diff options
context:
space:
mode:
authorJames Cowgill <jcowgill@debian.org>2017-09-29 21:07:02 +0100
committerJames Cowgill <jcowgill@debian.org>2017-09-29 21:07:02 +0100
commit24b4ac9afef21e7b350bafb876e0134857788d10 (patch)
treed3f87b16d0473bfb7b29d364c3c7214f7a4dc19d /openmpt123
parentf32cdc3ad7caac3602d9e0dab4282b267dc0cad2 (diff)
New upstream version 0.3.1
Diffstat (limited to 'openmpt123')
-rw-r--r--openmpt123/openmpt123.cpp2516
-rw-r--r--openmpt123/openmpt123.hpp650
-rw-r--r--openmpt123/openmpt123_config.hpp68
-rw-r--r--openmpt123/openmpt123_flac.hpp135
-rw-r--r--openmpt123/openmpt123_mmio.hpp138
-rw-r--r--openmpt123/openmpt123_portaudio.hpp288
-rw-r--r--openmpt123/openmpt123_pulseaudio.hpp166
-rw-r--r--openmpt123/openmpt123_raw.hpp58
-rw-r--r--openmpt123/openmpt123_sdl.hpp133
-rw-r--r--openmpt123/openmpt123_sdl2.hpp192
-rw-r--r--openmpt123/openmpt123_sndfile.hpp208
-rw-r--r--openmpt123/openmpt123_stdout.hpp49
-rw-r--r--openmpt123/openmpt123_waveout.hpp198
13 files changed, 4799 insertions, 0 deletions
diff --git a/openmpt123/openmpt123.cpp b/openmpt123/openmpt123.cpp
new file mode 100644
index 0000000..529079f
--- /dev/null
+++ b/openmpt123/openmpt123.cpp
@@ -0,0 +1,2516 @@
+/*
+ * openmpt123.cpp
+ * --------------
+ * 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.
+ */
+
+static const char * const license =
+"The OpenMPT code is licensed under the BSD license." "\n"
+"" "\n"
+"Copyright (c) 2004-2017, OpenMPT contributors" "\n"
+"Copyright (c) 1997-2003, Olivier Lapicque" "\n"
+"All rights reserved." "\n"
+"" "\n"
+"Redistribution and use in source and binary forms, with or without" "\n"
+"modification, are permitted provided that the following conditions are met:" "\n"
+" * Redistributions of source code must retain the above copyright" "\n"
+" notice, this list of conditions and the following disclaimer." "\n"
+" * Redistributions in binary form must reproduce the above copyright" "\n"
+" notice, this list of conditions and the following disclaimer in the" "\n"
+" documentation and/or other materials provided with the distribution." "\n"
+" * Neither the name of the OpenMPT project nor the" "\n"
+" names of its contributors may be used to endorse or promote products" "\n"
+" derived from this software without specific prior written permission." "\n"
+"" "\n"
+"THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY" "\n"
+"EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED" "\n"
+"WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE" "\n"
+"DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY" "\n"
+"DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES" "\n"
+"(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;" "\n"
+"LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND" "\n"
+"ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT" "\n"
+"(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS" "\n"
+"SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." "\n"
+;
+
+#include "openmpt123_config.hpp"
+
+#include <algorithm>
+#include <deque>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <iterator>
+#include <limits>
+#include <map>
+#include <set>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <cmath>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+
+#if defined(WIN32)
+#include <conio.h>
+#include <fcntl.h>
+#include <io.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <windows.h>
+#include <mmsystem.h>
+#include <mmreg.h>
+#else
+#if defined(MPT_NEEDS_THREADS)
+#include <pthread.h>
+#endif
+#include <termios.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#endif
+
+#include <libopenmpt/libopenmpt.hpp>
+
+#include "openmpt123.hpp"
+
+#include "openmpt123_flac.hpp"
+#include "openmpt123_mmio.hpp"
+#include "openmpt123_sndfile.hpp"
+#include "openmpt123_raw.hpp"
+#include "openmpt123_stdout.hpp"
+#include "openmpt123_portaudio.hpp"
+#include "openmpt123_pulseaudio.hpp"
+#include "openmpt123_sdl.hpp"
+#include "openmpt123_sdl2.hpp"
+#include "openmpt123_waveout.hpp"
+
+namespace openmpt123 {
+
+struct silent_exit_exception : public std::exception {
+ silent_exit_exception() throw() { }
+};
+
+struct show_license_exception : public std::exception {
+ show_license_exception() throw() { }
+};
+
+struct show_credits_exception : public std::exception {
+ show_credits_exception() throw() { }
+};
+
+struct show_man_version_exception : public std::exception {
+ show_man_version_exception() throw() { }
+};
+
+struct show_man_help_exception : public std::exception {
+ show_man_help_exception() throw() { }
+};
+
+struct show_short_version_number_exception : public std::exception {
+ show_short_version_number_exception() throw() { }
+};
+
+struct show_version_number_exception : public std::exception {
+ show_version_number_exception() throw() { }
+};
+
+struct show_long_version_number_exception : public std::exception {
+ show_long_version_number_exception() throw() { }
+};
+
+bool IsTerminal( int fd ) {
+#if defined( WIN32 )
+ return true
+ && ( _isatty( fd ) ? true : false )
+ && GetConsoleWindow() != NULL
+ ;
+#else
+ return isatty( fd ) ? true : false;
+#endif
+}
+
+#if !defined( WIN32 )
+
+static termios saved_attributes;
+
+static void reset_input_mode() {
+ tcsetattr( STDIN_FILENO, TCSANOW, &saved_attributes );
+}
+
+static void set_input_mode() {
+ termios tattr;
+ if ( !isatty( STDIN_FILENO ) ) {
+ return;
+ }
+ tcgetattr( STDIN_FILENO, &saved_attributes );
+ atexit( reset_input_mode );
+ tcgetattr( STDIN_FILENO, &tattr );
+ tattr.c_lflag &= ~( ICANON | ECHO );
+ tattr.c_cc[VMIN] = 1;
+ tattr.c_cc[VTIME] = 0;
+ tcsetattr( STDIN_FILENO, TCSAFLUSH, &tattr );
+}
+
+#endif
+
+class file_audio_stream_raii : public file_audio_stream_base {
+private:
+ file_audio_stream_base * impl;
+public:
+ file_audio_stream_raii( const commandlineflags & flags, const std::string & filename, std::ostream & log )
+ : impl(0)
+ {
+ if ( !flags.force_overwrite ) {
+ std::ifstream testfile( filename, std::ios::binary );
+ if ( testfile ) {
+ throw exception( "file already exists" );
+ }
+ }
+ if ( false ) {
+ // nothing
+ } else if ( flags.output_extension == "raw" ) {
+ impl = new raw_stream_raii( filename, flags, log );
+#ifdef MPT_WITH_MMIO
+ } else if ( flags.output_extension == "wav" ) {
+ impl = new mmio_stream_raii( filename, flags, log );
+#endif
+#ifdef MPT_WITH_FLAC
+ } else if ( flags.output_extension == "flac" ) {
+ impl = new flac_stream_raii( filename, flags, log );
+#endif
+#ifdef MPT_WITH_SNDFILE
+ } else {
+ impl = new sndfile_stream_raii( filename, flags, log );
+#endif
+ }
+ if ( !impl ) {
+ throw exception( "file format handler '" + flags.output_extension + "' not found" );
+ }
+ }
+ virtual ~file_audio_stream_raii() {
+ if ( impl ) {
+ delete impl;
+ impl = 0;
+ }
+ }
+ virtual void write_metadata( std::map<std::string,std::string> metadata ) {
+ impl->write_metadata( metadata );
+ }
+ virtual void write_updated_metadata( std::map<std::string,std::string> metadata ) {
+ impl->write_updated_metadata( metadata );
+ }
+ virtual void write( const std::vector<float*> buffers, std::size_t frames ) {
+ impl->write( buffers, frames );
+ }
+ virtual void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) {
+ impl->write( buffers, frames );
+ }
+};
+
+static std::string ctls_to_string( const std::map<std::string, std::string> & ctls ) {
+ std::string result;
+ for ( std::map<std::string, std::string>::const_iterator it = ctls.begin(); it != ctls.end(); ++it ) {
+ if ( !result.empty() ) {
+ result += "; ";
+ }
+ result += it->first + "=" + it->second;
+ }
+ return result;
+}
+
+static double tempo_flag_to_double( std::int32_t tempo ) {
+ return std::pow( 2.0, tempo / 24.0 );
+}
+
+static double pitch_flag_to_double( std::int32_t pitch ) {
+ return std::pow( 2.0, pitch / 24.0 );
+}
+
+static double my_round( double val ) {
+ if ( val >= 0.0 ) {
+ return std::floor( val + 0.5 );
+ } else {
+ return std::ceil( val - 0.5 );
+ }
+}
+
+static std::int32_t double_to_tempo_flag( double factor ) {
+ return static_cast<std::int32_t>( my_round( std::log( factor ) / std::log( 2.0 ) * 24.0 ) );
+}
+
+static std::int32_t double_to_pitch_flag( double factor ) {
+ return static_cast<std::int32_t>( my_round( std::log( factor ) / std::log( 2.0 ) * 24.0 ) );
+}
+
+static std::ostream & operator << ( std::ostream & s, const commandlineflags & flags ) {
+ s << "Quiet: " << flags.quiet << std::endl;
+ s << "Verbose: " << flags.verbose << std::endl;
+ s << "Mode : " << mode_to_string( flags.mode ) << std::endl;
+ s << "Show progress: " << flags.show_progress << std::endl;
+ s << "Show peak meters: " << flags.show_meters << std::endl;
+ s << "Show channel peak meters: " << flags.show_channel_meters << std::endl;
+ s << "Show details: " << flags.show_details << std::endl;
+ s << "Show message: " << flags.show_message << std::endl;
+ s << "Update: " << flags.ui_redraw_interval << "ms" << std::endl;
+ s << "Device: " << flags.device << std::endl;
+ s << "Buffer: " << flags.buffer << "ms" << std::endl;
+ s << "Period: " << flags.period << "ms" << std::endl;
+ s << "Samplerate: " << flags.samplerate << std::endl;
+ s << "Channels: " << flags.channels << std::endl;
+ s << "Float: " << flags.use_float << std::endl;
+ s << "Gain: " << flags.gain / 100.0 << std::endl;
+ s << "Stereo separation: " << flags.separation << std::endl;
+ s << "Interpolation filter taps: " << flags.filtertaps << std::endl;
+ s << "Volume ramping strength: " << flags.ramping << std::endl;
+ s << "Tempo: " << tempo_flag_to_double( flags.tempo ) << std::endl;
+ s << "Pitch: " << pitch_flag_to_double( flags.pitch ) << std::endl;
+ s << "Output dithering: " << flags.dither << std::endl;
+ s << "Repeat count: " << flags.repeatcount << std::endl;
+ s << "Seek target: " << flags.seek_target << std::endl;
+ s << "End time: " << flags.end_time << std::endl;
+ s << "Standard output: " << flags.use_stdout << std::endl;
+ s << "Output filename: " << flags.output_filename << std::endl;
+ s << "Force overwrite output file: " << flags.force_overwrite << std::endl;
+ s << "Ctls: " << ctls_to_string( flags.ctls ) << std::endl;
+ s << std::endl;
+ s << "Files: " << std::endl;
+ for ( std::vector<std::string>::const_iterator filename = flags.filenames.begin(); filename != flags.filenames.end(); ++filename ) {
+ s << " " << *filename << std::endl;
+ }
+ s << std::endl;
+ return s;
+}
+
+static std::string replace( std::string str, const std::string & oldstr, const std::string & newstr ) {
+ std::size_t pos = 0;
+ while ( ( pos = str.find( oldstr, pos ) ) != std::string::npos ) {
+ str.replace( pos, oldstr.length(), newstr );
+ pos += newstr.length();
+ }
+ return str;
+}
+
+static bool begins_with( const std::string & str, const std::string & match ) {
+ return ( str.find( match ) == 0 );
+}
+
+static bool ends_with( const std::string & str, const std::string & match ) {
+ return ( str.rfind( match ) == ( str.length() - match.length() ) );
+}
+
+static std::string trim_left(std::string str, const std::string &whitespace = std::string()) {
+ std::string::size_type pos = str.find_first_not_of(whitespace);
+ if(pos != std::string::npos) {
+ str.erase(str.begin(), str.begin() + pos);
+ } else if(pos == std::string::npos && str.length() > 0 && str.find_last_of(whitespace) == str.length() - 1) {
+ return std::string();
+ }
+ return str;
+}
+
+static std::string trim_right(std::string str, const std::string &whitespace = std::string()) {
+ std::string::size_type pos = str.find_last_not_of(whitespace);
+ if(pos != std::string::npos) {
+ str.erase(str.begin() + pos + 1, str.end());
+ } else if(pos == std::string::npos && str.length() > 0 && str.find_first_of(whitespace) == 0) {
+ return std::string();
+ }
+ return str;
+}
+
+static std::string trim(std::string str, const std::string &whitespace = std::string()) {
+ return trim_right(trim_left(str, whitespace), whitespace);
+}
+
+static std::string trim_eol( const std::string & str ) {
+ return trim( str, "\r\n" );
+}
+
+static std::string default_path_separator() {
+#if defined(WIN32)
+ return "\\";
+#else
+ return "/";
+#endif
+}
+
+static std::string path_separators() {
+#if defined(WIN32)
+ return "\\/";
+#else
+ return "/";
+#endif
+}
+
+static bool is_path_separator( char c ) {
+#if defined(WIN32)
+ return ( c == '\\' ) || ( c == '/' );
+#else
+ return c == '/';
+#endif
+}
+
+static std::string get_basepath( std::string filename ) {
+ std::string::size_type pos = filename.find_last_of( path_separators() );
+ if ( pos == std::string::npos ) {
+ return std::string();
+ }
+ return filename.substr( 0, pos ) + default_path_separator();
+}
+
+static bool is_absolute( std::string filename ) {
+#if defined(WIN32)
+ if ( begins_with( filename, "\\\\?\\UNC\\" ) ) {
+ return true;
+ }
+ if ( begins_with( filename, "\\\\?\\" ) ) {
+ return true;
+ }
+ if ( begins_with( filename, "\\\\" ) ) {
+ return true; // UNC
+ }
+ if ( begins_with( filename, "//" ) ) {
+ return true; // UNC
+ }
+ return ( filename.length() ) >= 3 && ( filename[1] == ':' ) && is_path_separator( filename[2] );
+#else
+ return ( filename.length() >= 1 ) && is_path_separator( filename[0] );
+#endif
+}
+
+static std::string get_filename( const std::string & filepath ) {
+ if ( filepath.find_last_of( path_separators() ) == std::string::npos ) {
+ return filepath;
+ }
+ return filepath.substr( filepath.find_last_of( path_separators() ) + 1 );
+}
+
+static std::string prepend_lines( std::string str, const std::string & prefix ) {
+ if ( str.empty() ) {
+ return str;
+ }
+ if ( str.substr( str.length() - 1, 1 ) == std::string("\n") ) {
+ str = str.substr( 0, str.length() - 1 );
+ }
+ return replace( str, std::string("\n"), std::string("\n") + prefix );
+}
+
+static std::string bytes_to_string( std::uint64_t bytes ) {
+ static const char * const suffixes[] = { "B", "kB", "MB", "GB", "TB", "PB" };
+ int offset = 0;
+ while ( bytes > 9999 ) {
+ bytes /= 1000;
+ offset += 1;
+ if ( offset == 5 ) {
+ break;
+ }
+ }
+ std::ostringstream result;
+ result << bytes << suffixes[offset];
+ return result.str();
+}
+
+static std::string seconds_to_string( double time ) {
+ std::int64_t time_ms = static_cast<std::int64_t>( time * 1000 );
+ std::int64_t milliseconds = time_ms % 1000;
+ std::int64_t seconds = ( time_ms / 1000 ) % 60;
+ std::int64_t minutes = ( time_ms / ( 1000 * 60 ) ) % 60;
+ std::int64_t hours = ( time_ms / ( 1000 * 60 * 60 ) );
+ std::ostringstream str;
+ if ( hours > 0 ) {
+ str << hours << ":";
+ }
+ str << std::setfill('0') << std::setw(2) << minutes;
+ str << ":";
+ str << std::setfill('0') << std::setw(2) << seconds;
+ str << ".";
+ str << std::setfill('0') << std::setw(3) << milliseconds;
+ return str.str();
+}
+
+static void show_info( std::ostream & log, bool verbose ) {
+ log << "openmpt123" << " v" << OPENMPT123_VERSION_STRING << ", libopenmpt " << openmpt::string::get( "library_version" ) << " (" << "OpenMPT " << openmpt::string::get( "core_version" ) << ")" << std::endl;
+ log << "Copyright (c) 2013-2017 OpenMPT developers <https://lib.openmpt.org/>" << std::endl;
+ if ( !verbose ) {
+ log << std::endl;
+ return;
+ }
+ log << " libopenmpt source..: " << openmpt::string::get( "source_url" ) << std::endl;
+ log << " libopenmpt date....: " << openmpt::string::get( "source_date" ) << std::endl;
+ log << " libopenmpt srcinfo.: ";
+ {
+ std::vector<std::string> fields;
+ if ( openmpt::string::get( "source_is_package" ) == "1" ) {
+ fields.push_back( "package" );
+ }
+ if ( openmpt::string::get( "source_is_release" ) == "1" ) {
+ fields.push_back( "release" );
+ }
+ if ( ( !openmpt::string::get( "source_revision" ).empty() ) && ( openmpt::string::get( "source_revision" ) != "0" ) ) {
+ std::string field = "rev" + openmpt::string::get( "source_revision" );
+ if ( openmpt::string::get( "source_has_mixed_revisions" ) == "1" ) {
+ field += "+mixed";
+ }
+ if ( openmpt::string::get( "source_is_modified" ) == "1" ) {
+ field += "+modified";
+ }
+ fields.push_back( field );
+ }
+ bool first = true;
+ for ( std::vector<std::string>::const_iterator it = fields.begin(); it != fields.end(); ++it ) {
+ if ( first ) {
+ first = false;
+ } else {
+ log << ", ";
+ }
+ log << (*it);
+ }
+ }
+ log << std::endl;
+ log << " libopenmpt compiler: " << openmpt::string::get( "build_compiler" ) << std::endl;
+ log << " libopenmpt features: " << openmpt::string::get( "library_features" ) << std::endl;
+#ifdef MPT_WITH_SDL2
+ log << " libSDL2 ";
+ SDL_version sdlver;
+ std::memset( &sdlver, 0, sizeof( SDL_version ) );
+ SDL_GetVersion( &sdlver );
+ log << static_cast<int>( sdlver.major ) << "." << static_cast<int>( sdlver.minor ) << "." << static_cast<int>( sdlver.patch ) << "." << SDL_GetRevisionNumber();
+ const char * revision = SDL_GetRevision();
+ if ( revision ) {
+ log << " (" << revision << ")";
+ }
+ log << ", ";
+ std::memset( &sdlver, 0, sizeof( SDL_version ) );
+ SDL_VERSION( &sdlver );
+ log << "API: " << static_cast<int>( sdlver.major ) << "." << static_cast<int>( sdlver.minor ) << "." << static_cast<int>( sdlver.patch ) << "";
+ log << " <https://libsdl.org/>" << std::endl;
+#endif
+#ifdef MPT_WITH_SDL
+ const SDL_version * linked_sdlver = SDL_Linked_Version();
+ log << " libSDL ";
+ if ( linked_sdlver ) {
+ log << static_cast<int>( linked_sdlver->major ) << "." << static_cast<int>( linked_sdlver->minor ) << "." << static_cast<int>( linked_sdlver->patch ) << " ";
+ }
+ SDL_version sdlver;
+ std::memset( &sdlver, 0, sizeof( SDL_version ) );
+ SDL_VERSION( &sdlver );
+ log << "(API: " << static_cast<int>( sdlver.major ) << "." << static_cast<int>( sdlver.minor ) << "." << static_cast<int>( sdlver.patch ) << ")";
+ log << " <https://libsdl.org/>" << std::endl;
+#endif
+#ifdef MPT_WITH_PULSEAUDIO
+ 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() : "unkown" ) << ") <https://www.freedesktop.org/wiki/Software/PulseAudio/>" << std::endl;
+#endif
+#ifdef MPT_WITH_PORTAUDIO
+ log << " " << Pa_GetVersionText() << " (" << Pa_GetVersion() << ") <http://portaudio.com/>" << std::endl;
+#endif
+#ifdef MPT_WITH_FLAC
+ 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;
+#endif
+#ifdef MPT_WITH_SNDFILE
+ char sndfile_info[128];
+ std::memset( sndfile_info, 0, sizeof( sndfile_info ) );
+ sf_command( 0, SFC_GET_LIB_VERSION, sndfile_info, sizeof( sndfile_info ) );
+ sndfile_info[127] = '\0';
+ log << " libsndfile " << sndfile_info << " <http://mega-nerd.com/libsndfile/>" << std::endl;
+#endif
+ log << std::endl;
+}
+
+static void show_man_version( textout & log ) {
+ log << "openmpt123" << " v" << OPENMPT123_VERSION_STRING << std::endl;
+ log << std::endl;
+ log << "Copyright (c) 2013-2017 OpenMPT developers <https://lib.openmpt.org/>" << std::endl;
+}
+
+static void show_short_version( textout & log ) {
+ log << OPENMPT123_VERSION_STRING << " / " << openmpt::string::get( "library_version" ) << " / " << openmpt::string::get( "core_version" ) << std::endl;
+ log.writeout();
+}
+
+static void show_version( textout & log ) {
+ show_info( log, false );
+ log.writeout();
+}
+
+static void show_long_version( textout & log ) {
+ show_info( log, true );
+ log.writeout();
+}
+
+static void show_credits( textout & log ) {
+ show_info( log, false );
+ log << openmpt::string::get( "contact" ) << std::endl;
+ log << std::endl;
+ log << openmpt::string::get( "credits" ) << std::endl;
+ log.writeout();
+}
+
+static void show_license( textout & log ) {
+ show_info( log, false );
+ log << license << std::endl;
+ log.writeout();
+}
+
+static std::string get_driver_string( const std::string & driver ) {
+ if ( driver.empty() ) {
+ return "default";
+ }
+ return driver;
+}
+
+static std::string get_device_string( const std::string & device ) {
+ if ( device.empty() ) {
+ return "default";
+ }
+ return device;
+}
+
+static void show_help( textout & log, bool with_info = true, bool longhelp = false, bool man_version = false, const std::string & message = std::string() ) {
+ if ( with_info ) {
+ show_info( log, false );
+ }
+ {
+ log << "Usage: openmpt123 [options] [--] file1 [file2] ..." << std::endl;
+ log << std::endl;
+ if ( man_version ) {
+ log << "openmpt123 plays module music files." << std::endl;
+ log << std::endl;
+ }
+ if ( man_version ) {
+ log << "Options:" << std::endl;
+ }
+ log << " -h, --help Show help" << std::endl;
+ log << " --help-keyboard Show keyboard hotkeys in ui mode" << std::endl;
+ log << " -q, --quiet Suppress non-error screen output" << std::endl;
+ log << " -v, --verbose Show more screen output" << std::endl;
+ log << " --version Show version information and exit" << std::endl;
+ log << " --short-version Show version number and nothing else" << std::endl;
+ log << " --long-version Show long version information and exit" << std::endl;
+ log << " --credits Show elaborate contributors list" << std::endl;
+ log << " --license Show license" << std::endl;
+ log << std::endl;
+ log << " --probe Probe each file whether it is a supported file format" << std::endl;
+ log << " --info Display information about each file" << std::endl;
+ log << " --ui Interactively play each file" << std::endl;
+ log << " --batch Play each file" << std::endl;
+ log << " --render Render each file to PCM data" << std::endl;
+ if ( !longhelp ) {
+ log << std::endl;
+ log.writeout();
+ return;
+ }
+ log << std::endl;
+ log << " --terminal-width n Assume terminal is n characters wide [default: " << commandlineflags().terminal_width << "]" << std::endl;
+ log << " --terminal-height n Assume terminal is n characters high [default: " << commandlineflags().terminal_height << "]" << std::endl;
+ log << std::endl;
+ log << " --[no-]progress Show playback progress [default: " << commandlineflags().show_progress << "]" << std::endl;
+ log << " --[no-]meters Show peak meters [default: " << commandlineflags().show_meters << "]" << std::endl;
+ log << " --[no-]channel-meters Show channel peak meters (EXPERIMENTAL) [default: " << commandlineflags().show_channel_meters << "]" << std::endl;
+ log << " --[no-]pattern Show pattern (EXPERIMENTAL) [default: " << commandlineflags().show_pattern << "]" << std::endl;
+ log << std::endl;
+ log << " --[no-]details Show song details [default: " << commandlineflags().show_details << "]" << std::endl;
+ log << " --[no-]message Show song message [default: " << commandlineflags().show_message << "]" << std::endl;
+ log << std::endl;
+ log << " --update n Set output update interval to n ms [default: " << commandlineflags().ui_redraw_interval << "]" << std::endl;
+ log << std::endl;
+ log << " --samplerate n Set samplerate to n Hz [default: " << commandlineflags().samplerate << "]" << std::endl;
+ log << " --channels n use n [1,2,4] output channels [default: " << commandlineflags().channels << "]" << std::endl;
+ log << " --[no-]float Output 32bit floating point instead of 16bit integer [default: " << commandlineflags().use_float << "]" << std::endl;
+ log << std::endl;
+ log << " --gain n Set output gain to n dB [default: " << commandlineflags().gain / 100.0 << "]" << std::endl;
+ log << " --stereo n Set stereo separation to n % [default: " << commandlineflags().separation << "]" << std::endl;
+ log << " --filter n Set interpolation filter taps to n [1,2,4,8] [default: " << commandlineflags().filtertaps << "]" << std::endl;
+ log << " --ramping n Set volume ramping strength n [0..5] [default: " << commandlineflags().ramping << "]" << std::endl;
+ log << " --tempo f Set tempo factor f [default: " << tempo_flag_to_double( commandlineflags().tempo ) << "]" << std::endl;
+ log << " --pitch f Set pitch factor f [default: " << pitch_flag_to_double( commandlineflags().pitch ) << "]" << std::endl;
+ 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;
+ log << std::endl;
+ log << " --playlist file Load playlist from file" << std::endl;
+ log << " --[no-]randomize Randomize playlist [default: " << commandlineflags().randomize << "]" << std::endl;
+ log << " --[no-]shuffle Shuffle through playlist [default: " << commandlineflags().shuffle << "]" << std::endl;
+ log << " --[no-]restart Restart playlist when finished [default: " << commandlineflags().restart << "]" << std::endl;
+ log << std::endl;
+ log << " --subsong n Select subsong n (-1 means play all subsongs consecutively) [default: " << commandlineflags().subsong << "]" << std::endl;
+ log << " --repeat n Repeat song n times (-1 means forever) [default: " << commandlineflags().repeatcount << "]" << std::endl;
+ log << " --seek n Seek to n seconds on start [default: " << commandlineflags().seek_target << "]" << std::endl;
+ log << " --end-time n Play until position is n seconds (0 means until the end) [default: " << commandlineflags().end_time << "]" << std::endl;
+ log << std::endl;
+ log << " --ctl c=v Set libopenmpt ctl c to value v" << std::endl;
+ log << std::endl;
+ log << " --driver n Set output driver [default: " << get_driver_string( commandlineflags().driver ) << "]," << std::endl;
+ log << " --device n Set output device [default: " << get_device_string( commandlineflags().device ) << "]," << std::endl;
+ log << " use --device help to show available devices" << std::endl;
+ log << " --buffer n Set output buffer size to n ms [default: " << commandlineflags().buffer << "]" << std::endl;
+ log << " --period n Set output period size to n ms [default: " << commandlineflags().period << "]" << std::endl;
+ log << " --stdout Write raw audio data to stdout [default: " << commandlineflags().use_stdout << "]" << std::endl;
+ log << " --output-type t Use output format t when writing to a PCM file [default: " << commandlineflags().output_extension << "]" << std::endl;
+ log << " -o, --output f Write PCM output to file f instead of streaming to audio device [default: " << commandlineflags().output_filename << "]" << std::endl;
+ log << " --force Force overwriting of output file [default: " << commandlineflags().force_overwrite << "]" << std::endl;
+ log << std::endl;
+ log << " -- Interpret further arguments as filenames" << std::endl;
+ log << std::endl;
+ if ( !man_version ) {
+ log << " Supported file formats: " << std::endl;
+ log << " ";
+ std::vector<std::string> extensions = openmpt::get_supported_extensions();
+ bool first = true;
+ for ( std::vector<std::string>::iterator i = extensions.begin(); i != extensions.end(); ++i ) {
+ if ( first ) {
+ first = false;
+ } else {
+ log << ", ";
+ }
+ log << *i;
+ }
+ log << std::endl;
+ }
+ }
+
+ log << std::endl;
+
+ if ( message.size() > 0 ) {
+ log << message;
+ log << std::endl;
+ }
+ log.writeout();
+}
+
+static void show_help_keyboard( textout & log ) {
+ show_info( log, false );
+ log << "Keyboard hotkeys (use 'openmpt123 --ui'):" << std::endl;
+ log << std::endl;
+ log << " [q] quit" << std::endl;
+ log << " [ ] pause / unpause" << std::endl;
+ log << " [N] skip 10 files backward" << std::endl;
+ log << " [n] prev file" << std::endl;
+ log << " [m] next file" << std::endl;
+ log << " [M] skip 10 files forward" << std::endl;
+ log << " [h] seek 10 seconds backward" << std::endl;
+ log << " [j] seek 1 seconds backward" << std::endl;
+ log << " [k] seek 1 seconds forward" << std::endl;
+ log << " [l] seek 10 seconds forward" << std::endl;
+ log << " [u]|[i] +/- tempo" << std::endl;
+ log << " [o]|[p] +/- pitch" << std::endl;
+ log << " [3]|[4] +/- gain" << std::endl;
+ log << " [5]|[6] +/- stereo separation" << std::endl;
+ log << " [7]|[8] +/- filter taps" << std::endl;
+ log << " [9]|[0] +/- volume ramping" << std::endl;
+ log << std::endl;
+ log.writeout();
+}
+
+
+template < typename T, typename Tmod >
+T ctl_get( Tmod & mod, const std::string & ctl ) {
+ T result = T();
+ try {
+ std::istringstream str;
+ str.imbue( std::locale::classic() );
+ str.str( mod.ctl_get( ctl ) );
+ str >> std::fixed >> std::setprecision(16) >> result;
+ } catch ( const openmpt::exception & ) {
+ // ignore
+ }
+ return result;
+}
+
+template < typename T, typename Tmod >
+void ctl_set( Tmod & mod, const std::string & ctl, const T & val ) {
+ try {
+ std::ostringstream str;
+ str.imbue( std::locale::classic() );
+ str << std::fixed << std::setprecision(16) << val;
+ mod.ctl_set( ctl, str.str() );
+ } catch ( const openmpt::exception & ) {
+ // ignore
+ }
+ return;
+}
+
+template < typename Tmod >
+static void apply_mod_settings( commandlineflags & flags, Tmod & mod ) {
+ flags.separation = std::max( flags.separation, 0 );
+ flags.filtertaps = std::max( flags.filtertaps, 1 );
+ flags.filtertaps = std::min( flags.filtertaps, 8 );
+ flags.ramping = std::max( flags.ramping, -1 );
+ flags.ramping = std::min( flags.ramping, 10 );
+ flags.tempo = std::max( flags.tempo, -48 );
+ flags.tempo = std::min( flags.tempo, 48 );
+ flags.pitch = std::max( flags.pitch, -48 );
+ flags.pitch = std::min( flags.pitch, 48 );
+ mod.set_render_param( openmpt::module::RENDER_MASTERGAIN_MILLIBEL, flags.gain );
+ mod.set_render_param( openmpt::module::RENDER_STEREOSEPARATION_PERCENT, flags.separation );
+ mod.set_render_param( openmpt::module::RENDER_INTERPOLATIONFILTER_LENGTH, flags.filtertaps );
+ mod.set_render_param( openmpt::module::RENDER_VOLUMERAMPING_STRENGTH, flags.ramping );
+ ctl_set( mod, "play.tempo_factor", tempo_flag_to_double( flags.tempo ) );
+ ctl_set( mod, "play.pitch_factor", pitch_flag_to_double( flags.pitch ) );
+ std::ostringstream dither_str;
+ dither_str.imbue( std::locale::classic() );
+ dither_str << flags.dither;
+ mod.ctl_set( "dither", dither_str.str() );
+}
+
+struct prev_file { int count; prev_file( int c ) : count(c) { } };
+struct next_file { int count; next_file( int c ) : count(c) { } };
+
+template < typename Tmod >
+static bool handle_keypress( int c, commandlineflags & flags, Tmod & mod, write_buffers_interface & audio_stream ) {
+ switch ( c ) {
+ case 'q': throw silent_exit_exception(); break;
+ case 'N': throw prev_file(10); break;
+ case 'n': throw prev_file(1); break;
+ case ' ': if ( !flags.paused ) { flags.paused = audio_stream.pause(); } else { flags.paused = false; audio_stream.unpause(); } break;
+ case 'h': mod.set_position_seconds( mod.get_position_seconds() - 10.0 ); break;
+ case 'j': mod.set_position_seconds( mod.get_position_seconds() - 1.0 ); break;
+ case 'k': mod.set_position_seconds( mod.get_position_seconds() + 1.0 ); break;
+ case 'l': mod.set_position_seconds( mod.get_position_seconds() + 10.0 ); break;
+ case 'H': mod.set_position_order_row( mod.get_current_order() - 1, 0 ); break;
+ case 'J': mod.set_position_order_row( mod.get_current_order(), mod.get_current_row() - 1 ); break;
+ case 'K': mod.set_position_order_row( mod.get_current_order(), mod.get_current_row() + 1 ); break;
+ case 'L': mod.set_position_order_row( mod.get_current_order() + 1, 0 ); break;
+ case 'm': throw next_file(1); break;
+ case 'M': throw next_file(10); break;
+ case 'u': flags.tempo -= 1; apply_mod_settings( flags, mod ); break;
+ case 'i': flags.tempo += 1; apply_mod_settings( flags, mod ); break;
+ case 'o': flags.pitch -= 1; apply_mod_settings( flags, mod ); break;
+ case 'p': flags.pitch += 1; apply_mod_settings( flags, mod ); break;
+ case '3': flags.gain -=100; apply_mod_settings( flags, mod ); break;
+ case '4': flags.gain +=100; apply_mod_settings( flags, mod ); break;
+ case '5': flags.separation -= 5; apply_mod_settings( flags, mod ); break;
+ case '6': flags.separation += 5; apply_mod_settings( flags, mod ); break;
+ case '7': flags.filtertaps /= 2; apply_mod_settings( flags, mod ); break;
+ case '8': flags.filtertaps *= 2; apply_mod_settings( flags, mod ); break;
+ case '9': flags.ramping -= 1; apply_mod_settings( flags, mod ); break;
+ case '0': flags.ramping += 1; apply_mod_settings( flags, mod ); break;
+ }
+ return true;
+}
+
+struct meter_channel {
+ float peak;
+ float clip;
+ float hold;
+ float hold_age;
+ meter_channel()
+ : peak(0.0f)
+ , clip(0.0f)
+ , hold(0.0f)
+ , hold_age(0.0f)
+ {
+ return;
+ }
+};
+
+struct meter_type {
+ meter_channel channels[4];
+};
+
+static const float falloff_rate = 20.0f / 1.7f;
+
+static void update_meter( meter_type & meter, const commandlineflags & flags, std::size_t count, const std::int16_t * const * buffers ) {
+ float falloff_factor = std::pow( 10.0f, -falloff_rate / flags.samplerate / 20.0f );
+ for ( int channel = 0; channel < flags.channels; ++channel ) {
+ meter.channels[channel].peak = 0.0f;
+ for ( std::size_t frame = 0; frame < count; ++frame ) {
+ if ( meter.channels[channel].clip != 0.0f ) {
+ meter.channels[channel].clip -= ( 1.0f / 2.0f ) * 1.0f / static_cast<float>( flags.samplerate );
+ if ( meter.channels[channel].clip <= 0.0f ) {
+ meter.channels[channel].clip = 0.0f;
+ }
+ }
+ float val = std::fabs( buffers[channel][frame] / 32768.0f );
+ if ( val >= 1.0f ) {
+ meter.channels[channel].clip = 1.0f;
+ }
+ if ( val > meter.channels[channel].peak ) {
+ meter.channels[channel].peak = val;
+ }
+ meter.channels[channel].hold *= falloff_factor;
+ if ( val > meter.channels[channel].hold ) {
+ meter.channels[channel].hold = val;
+ meter.channels[channel].hold_age = 0.0f;
+ } else {
+ meter.channels[channel].hold_age += 1.0f / static_cast<float>( flags.samplerate );
+ }
+ }
+ }
+}
+
+static void update_meter( meter_type & meter, const commandlineflags & flags, std::size_t count, const float * const * buffers ) {
+ float falloff_factor = std::pow( 10.0f, -falloff_rate / flags.samplerate / 20.0f );
+ for ( int channel = 0; channel < flags.channels; ++channel ) {
+ if ( !count ) {
+ meter = meter_type();
+ }
+ meter.channels[channel].peak = 0.0f;
+ for ( std::size_t frame = 0; frame < count; ++frame ) {
+ if ( meter.channels[channel].clip != 0.0f ) {
+ meter.channels[channel].clip -= ( 1.0f / 2.0f ) * 1.0f / static_cast<float>( flags.samplerate );
+ if ( meter.channels[channel].clip <= 0.0f ) {
+ meter.channels[channel].clip = 0.0f;
+ }
+ }
+ float val = std::fabs( buffers[channel][frame] );
+ if ( val >= 1.0f ) {
+ meter.channels[channel].clip = 1.0f;
+ }
+ if ( val > meter.channels[channel].peak ) {
+ meter.channels[channel].peak = val;
+ }
+ meter.channels[channel].hold *= falloff_factor;
+ if ( val > meter.channels[channel].hold ) {
+ meter.channels[channel].hold = val;
+ meter.channels[channel].hold_age = 0.0f;
+ } else {
+ meter.channels[channel].hold_age += 1.0f / static_cast<float>( flags.samplerate );
+ }
+ }
+ }
+}
+
+static const char * const channel_tags[4][4] = {
+ { " C", " ", " ", " " },
+ { " L", " R", " ", " " },
+ { "FL", "FR", "RC", " " },
+ { "FL", "FR", "RL", "RR" },
+};
+
+static std::string channel_to_string( int channels, int channel, const meter_channel & meter, bool tiny = false ) {
+ float db = 20.0f * std::log10( meter.peak );
+ float db_hold = 20.0f * std::log10( meter.hold );
+ int val = static_cast<int>( db + 48.0f );
+ int hold_pos = static_cast<int>( db_hold + 48.0f );
+ if ( val < 0 ) {
+ val = 0;
+ }
+ int headroom = val;
+ if ( val > 48 ) {
+ val = 48;
+ }
+ headroom -= val;
+ if ( headroom < 0 ) {
+ headroom = 0;
+ }
+ if ( headroom > 12 ) {
+ headroom = 12;
+ }
+ headroom -= 1; // clip indicator
+ if ( headroom < 0 ) {
+ headroom = 0;
+ }
+ if ( tiny ) {
+ if ( meter.clip != 0.0f || db >= 0.0f ) {
+ return "#";
+ } else if ( db > -6.0f ) {
+ return "O";
+ } else if ( db > -12.0f ) {
+ return "o";
+ } else if ( db > -18.0f ) {
+ return ".";
+ } else {
+ return " ";
+ }
+ } else {
+ std::ostringstream res1;
+ std::ostringstream res2;
+ res1
+ << " "
+ << channel_tags[channels-1][channel]
+ << " : "
+ ;
+ res2
+ << std::string(val,'>') << std::string(48-val,' ')
+ << ( ( meter.clip != 0.0f ) ? "#" : ":" )
+ << std::string(headroom,'>') << std::string(12-headroom,' ')
+ ;
+ std::string tmp = res2.str();
+ if ( 0 <= hold_pos && hold_pos <= 60 ) {
+ if ( hold_pos == 48 ) {
+ tmp[hold_pos] = '#';
+ } else {
+ tmp[hold_pos] = ':';
+ }
+ }
+ return res1.str() + tmp;
+ }
+}
+
+static char peak_to_char( float peak ) {
+ if ( peak >= 1.0f ) {
+ return '#';
+ } else if ( peak >= 0.5f ) {
+ return 'O';
+ } else if ( peak >= 0.25f ) {
+ return 'o';
+ } else if ( peak >= 0.125f ) {
+ return '.';
+ } else {
+ return ' ';
+ }
+}
+
+static std::string peak_to_string_left( float peak, int width ) {
+ std::string result;
+ float thresh = 1.0f;
+ while ( width-- ) {
+ if ( peak >= thresh ) {
+ if ( thresh == 1.0f ) {
+ result.push_back( '#' );
+ } else {
+ result.push_back( '<' );
+ }
+ } else {
+ result.push_back( ' ' );
+ }
+ thresh *= 0.5f;
+ }
+ return result;
+}
+
+static std::string peak_to_string_right( float peak, int width ) {
+ std::string result;
+ float thresh = 1.0f;
+ while ( width-- ) {
+ if ( peak >= thresh ) {
+ if ( thresh == 1.0f ) {
+ result.push_back( '#' );
+ } else {
+ result.push_back( '>' );
+ }
+ } else {
+ result.push_back( ' ' );
+ }
+ thresh *= 0.5f;
+ }
+ std::reverse( result.begin(), result.end() );
+ return result;
+}
+
+static void draw_meters( std::ostream & log, const meter_type & meter, const commandlineflags & flags ) {
+ for ( int channel = 0; channel < flags.channels; ++channel ) {
+ log << channel_to_string( flags.channels, channel, meter.channels[channel] ) << std::endl;
+ }
+}
+
+static void draw_meters_tiny( std::ostream & log, const meter_type & meter, const commandlineflags & flags ) {
+ for ( int channel = 0; channel < flags.channels; ++channel ) {
+ log << channel_to_string( flags.channels, channel, meter.channels[channel], true );
+ }
+}
+
+static void draw_channel_meters_tiny( std::ostream & log, float peak ) {
+ log << peak_to_char( peak );
+}
+
+static void draw_channel_meters_tiny( std::ostream & log, float peak_left, float peak_right ) {
+ log << peak_to_char( peak_left ) << peak_to_char( peak_right );
+}
+
+static void draw_channel_meters( std::ostream & log, float peak_left, float peak_right, int width ) {
+ if ( width >= 8 + 1 + 8 ) {
+ width = 8 + 1 + 8;
+ }
+ log << peak_to_string_left( peak_left, width / 2 ) << ( width % 2 == 1 ? ":" : "" ) << peak_to_string_right( peak_right, width / 2 );
+}
+
+template < typename Tsample, typename Tmod >
+void render_loop( commandlineflags & flags, Tmod & mod, double & duration, textout & log, write_buffers_interface & audio_stream ) {
+
+ log.writeout();
+
+ std::size_t bufsize;
+ if ( flags.mode == ModeUI ) {
+ bufsize = std::min( flags.ui_redraw_interval, flags.period ) * flags.samplerate / 1000;
+ } else if ( flags.mode == ModeBatch ) {
+ bufsize = flags.period * flags.samplerate / 1000;
+ } else {
+ bufsize = 1024;
+ }
+
+ std::int64_t last_redraw_frame = 0 - flags.ui_redraw_interval;
+ std::int64_t rendered_frames = 0;
+
+ std::vector<Tsample> left( bufsize );
+ std::vector<Tsample> right( bufsize );
+ std::vector<Tsample> rear_left( bufsize );
+ std::vector<Tsample> rear_right( bufsize );
+ std::vector<Tsample*> buffers( 4 ) ;
+ buffers[0] = left.data();
+ buffers[1] = right.data();
+ buffers[2] = rear_left.data();
+ buffers[3] = rear_right.data();
+ buffers.resize( flags.channels );
+
+ meter_type meter;
+
+ const bool multiline = flags.show_ui;
+
+ int lines = 0;
+
+ int pattern_lines = 0;
+
+ if ( multiline ) {
+ lines += 1;
+ if ( flags.show_ui ) {
+ lines += 1;
+ }
+ if ( flags.show_meters ) {
+ for ( int channel = 0; channel < flags.channels; ++channel ) {
+ lines += 1;
+ }
+ }
+ if ( flags.show_channel_meters ) {
+ lines += 1;
+ }
+ if ( flags.show_details ) {
+ lines += 1;
+ if ( flags.show_progress ) {
+ lines += 1;
+ }
+ }
+ if ( flags.show_progress ) {
+ lines += 1;
+ }
+ if ( flags.show_pattern ) {
+ pattern_lines = flags.terminal_height - lines - 1;
+ lines = flags.terminal_height - 1;
+ }
+ } else if ( flags.show_ui || flags.show_details || flags.show_progress ) {
+ log << std::endl;
+ }
+ for ( int line = 0; line < lines; ++line ) {
+ log << std::endl;
+ }
+
+ log.writeout();
+
+#if defined( WIN32 )
+ HANDLE hStdErr = NULL;
+ COORD coord_cursor = COORD();
+ if ( multiline ) {
+ log.flush();
+ hStdErr = GetStdHandle( STD_ERROR_HANDLE );
+ if ( hStdErr ) {
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) );
+ GetConsoleScreenBufferInfo( hStdErr, &csbi );
+ coord_cursor = csbi.dwCursorPosition;
+ coord_cursor.X = 1;
+ coord_cursor.Y -= lines;
+ }
+ }
+#endif
+
+ double cpu_smooth = 0.0;
+
+ while ( true ) {
+
+ if ( flags.mode == ModeUI ) {
+
+#if defined( WIN32 )
+
+ while ( _kbhit() ) {
+ int c = _getch();
+ if ( !handle_keypress( c, flags, mod, audio_stream ) ) {
+ return;
+ }
+ }
+
+#else
+
+ while ( true ) {
+ pollfd pollfds;
+ pollfds.fd = STDIN_FILENO;
+ pollfds.events = POLLIN;
+ poll(&pollfds, 1, 0);
+ if ( !( pollfds.revents & POLLIN ) ) {
+ break;
+ }
+ char c = 0;
+ if ( read( STDIN_FILENO, &c, 1 ) != 1 ) {
+ break;
+ }
+ if ( !handle_keypress( c, flags, mod, audio_stream ) ) {
+ return;
+ }
+ }
+
+#endif
+
+ if ( flags.paused ) {
+ audio_stream.sleep( flags.ui_redraw_interval );
+ continue;
+ }
+
+ }
+
+ std::clock_t cpu_beg = 0;
+ std::clock_t cpu_end = 0;
+ if ( flags.show_details ) {
+ cpu_beg = std::clock();
+ }
+
+ std::size_t count = 0;
+
+ switch ( flags.channels ) {
+ case 1: count = mod.read( flags.samplerate, bufsize, left.data() ); break;
+ case 2: count = mod.read( flags.samplerate, bufsize, left.data(), right.data() ); break;
+ case 4: count = mod.read( flags.samplerate, bufsize, left.data(), right.data(), rear_left.data(), rear_right.data() ); break;
+ }
+
+ char cpu_str[64] = "";
+ if ( flags.show_details ) {
+ cpu_end = std::clock();
+ if ( count > 0 ) {
+ double cpu = 1.0;
+ cpu *= ( static_cast<double>( cpu_end ) - static_cast<double>( cpu_beg ) ) / static_cast<double>( CLOCKS_PER_SEC );
+ cpu /= ( static_cast<double>( count ) ) / static_cast<double>( flags.samplerate );
+ double mix = ( static_cast<double>( count ) ) / static_cast<double>( flags.samplerate );
+ cpu_smooth = ( 1.0 - mix ) * cpu_smooth + mix * cpu;
+ sprintf( cpu_str, "%.2f%%", cpu_smooth * 100.0 );
+ }
+ }
+
+ if ( flags.show_meters ) {
+ update_meter( meter, flags, count, buffers.data() );
+ }
+
+ if ( count > 0 ) {
+ audio_stream.write( buffers, count );
+ }
+
+ if ( count > 0 ) {
+ rendered_frames += count;
+ if ( rendered_frames >= last_redraw_frame + ( flags.ui_redraw_interval * flags.samplerate / 1000 ) ) {
+ last_redraw_frame = rendered_frames;
+ } else {
+ continue;
+ }
+ }
+
+ if ( multiline ) {
+#if defined( WIN32 )
+ log.flush();
+ if ( hStdErr ) {
+ SetConsoleCursorPosition( hStdErr, coord_cursor );
+ }
+#else
+ for ( int line = 0; line < lines; ++line ) {
+ log << "\x1b[1A";
+ }
+#endif
+ log << std::endl;
+ if ( flags.show_meters ) {
+ draw_meters( log, meter, flags );
+ }
+ if ( flags.show_channel_meters ) {
+ int width = ( flags.terminal_width - 3 ) / mod.get_num_channels();
+ if ( width > 11 ) {
+ width = 11;
+ }
+ log << " ";
+ for ( std::int32_t channel = 0; channel < mod.get_num_channels(); ++channel ) {
+ if ( width >= 3 ) {
+ log << ":";
+ }
+ if ( width == 1 ) {
+ 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)) );
+ } else if ( width <= 4 ) {
+ draw_channel_meters_tiny( log, mod.get_current_channel_vu_left( channel ), mod.get_current_channel_vu_right( channel ) );
+ } else {
+ draw_channel_meters( log, mod.get_current_channel_vu_left( channel ), mod.get_current_channel_vu_right( channel ), width - 1 );
+ }
+ }
+ if ( width >= 3 ) {
+ log << ":";
+ }
+ log << std::endl;
+ }
+ if ( flags.show_pattern ) {
+ int width = ( flags.terminal_width - 3 ) / mod.get_num_channels();
+ if ( width > 13 + 1 ) {
+ width = 13 + 1;
+ }
+ for ( std::int32_t line = 0; line < pattern_lines; ++line ) {
+ std::int32_t row = mod.get_current_row() - ( pattern_lines / 2 ) + line;
+ if ( row == mod.get_current_row() ) {
+ log << ">";
+ } else {
+ log << " ";
+ }
+ if ( row < 0 || row >= mod.get_pattern_num_rows( mod.get_current_pattern() ) ) {
+ for ( std::int32_t channel = 0; channel < mod.get_num_channels(); ++channel ) {
+ if ( width >= 3 ) {
+ log << ":";
+ }
+ log << std::string( width >= 3 ? width - 1 : width, ' ' );
+ }
+ } else {
+ for ( std::int32_t channel = 0; channel < mod.get_num_channels(); ++channel ) {
+ if ( width >= 3 ) {
+ if ( row == mod.get_current_row() ) {
+ log << "+";
+ } else {
+ log << ":";
+ }
+ }
+ log << mod.format_pattern_row_channel( mod.get_current_pattern(), row, channel, width >= 3 ? width - 1 : width );
+ }
+ }
+ if ( width >= 3 ) {
+ log << ":";
+ }
+ log << std::endl;
+ }
+ }
+ if ( flags.show_ui ) {
+ log << "Settings...: ";
+ log << "Gain: " << flags.gain * 0.01f << " dB" << " ";
+ log << "Stereo: " << flags.separation << " %" << " ";
+ log << "Filter: " << flags.filtertaps << " taps" << " ";
+ log << "Ramping: " << flags.ramping << " ";
+ log << std::endl;
+ }
+ if ( flags.show_details ) {
+ log << "Mixer......: ";
+ log << "CPU:" << std::setw(6) << std::setfill(':') << cpu_str;
+ log << " ";
+ log << "Chn:" << std::setw(3) << std::setfill(':') << mod.get_current_playing_channels();
+ log << " ";
+ log << std::endl;
+ if ( flags.show_progress ) {
+ log << "Player.....: ";
+ log << "Ord:" << std::setw(3) << std::setfill(':') << mod.get_current_order() << "/" << std::setw(3) << std::setfill(':') << mod.get_num_orders();
+ log << " ";
+ log << "Pat:" << std::setw(3) << std::setfill(':') << mod.get_current_pattern();
+ log << " ";
+ log << "Row:" << std::setw(3) << std::setfill(':') << mod.get_current_row();
+ log << " ";
+ log << "Spd:" << std::setw(2) << std::setfill(':') << mod.get_current_speed();
+ log << " ";
+ log << "Tmp:" << std::setw(3) << std::setfill(':') << mod.get_current_tempo();
+ log << " ";
+ log << std::endl;
+ }
+ }
+ if ( flags.show_progress ) {
+ log << "Position...: " << seconds_to_string( mod.get_position_seconds() ) << " / " << seconds_to_string( duration ) << " " << std::endl;
+ }
+ } else if ( flags.show_channel_meters ) {
+ if ( flags.show_ui || flags.show_details || flags.show_progress ) {
+ int width = ( flags.terminal_width - 3 ) / mod.get_num_channels();
+ log << " ";
+ for ( std::int32_t channel = 0; channel < mod.get_num_channels(); ++channel ) {
+ if ( width >= 3 ) {
+ log << ":";
+ }
+ if ( width == 1 ) {
+ 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)) );
+ } else if ( width <= 4 ) {
+ draw_channel_meters_tiny( log, mod.get_current_channel_vu_left( channel ), mod.get_current_channel_vu_right( channel ) );
+ } else {
+ draw_channel_meters( log, mod.get_current_channel_vu_left( channel ), mod.get_current_channel_vu_right( channel ), width - 1 );
+ }
+ }
+ if ( width >= 3 ) {
+ log << ":";
+ }
+ }
+ log << " " << "\r";
+ } else {
+ if ( flags.show_ui ) {
+ log << " ";
+ log << std::setw(3) << std::setfill(':') << flags.gain * 0.01f << "dB";
+ log << "|";
+ log << std::setw(3) << std::setfill(':') << flags.separation << "%";
+ log << "|";
+ log << std::setw(2) << std::setfill(':') << flags.filtertaps << "taps";
+ log << "|";
+ log << std::setw(3) << std::setfill(':') << flags.ramping;
+ }
+ if ( flags.show_meters ) {
+ log << " ";
+ draw_meters_tiny( log, meter, flags );
+ }
+ if ( flags.show_details && flags.show_ui ) {
+ log << " ";
+ log << "CPU:" << std::setw(6) << std::setfill(':') << cpu_str;
+ log << "|";
+ log << "Chn:" << std::setw(3) << std::setfill(':') << mod.get_current_playing_channels();
+ }
+ if ( flags.show_details && !flags.show_ui ) {
+ if ( flags.show_progress ) {
+ log << " ";
+ log << "Ord:" << std::setw(3) << std::setfill(':') << mod.get_current_order() << "/" << std::setw(3) << std::setfill(':') << mod.get_num_orders();
+ log << "|";
+ log << "Pat:" << std::setw(3) << std::setfill(':') << mod.get_current_pattern();
+ log << "|";
+ log << "Row:" << std::setw(3) << std::setfill(':') << mod.get_current_row();
+ log << " ";
+ log << "Spd:" << std::setw(2) << std::setfill(':') << mod.get_current_speed();
+ log << "|";
+ log << "Tmp:" << std::setw(3) << std::setfill(':') << mod.get_current_tempo();
+ }
+ }
+ if ( flags.show_progress ) {
+ log << " ";
+ log << seconds_to_string( mod.get_position_seconds() );
+ log << "/";
+ log << seconds_to_string( duration );
+ }
+ if ( flags.show_ui || flags.show_details || flags.show_progress ) {
+ log << " " << "\r";
+ }
+ }
+
+ log.writeout();
+
+ if ( count == 0 ) {
+ break;
+ }
+
+ if ( flags.end_time > 0 && mod.get_position_seconds() >= flags.end_time ) {
+ break;
+ }
+
+ }
+
+ log.writeout();
+
+}
+
+template < typename Tmod >
+std::map<std::string,std::string> get_metadata( const Tmod & mod ) {
+ std::map<std::string,std::string> result;
+ const std::vector<std::string> metadata_keys = mod.get_metadata_keys();
+ for ( std::vector<std::string>::const_iterator key = metadata_keys.begin(); key != metadata_keys.end(); ++key ) {
+ result[ *key ] = mod.get_metadata( *key );
+ }
+ return result;
+}
+
+class set_field : private std::ostringstream {
+private:
+ std::vector<openmpt123::field> & fields;
+public:
+ set_field( std::vector<openmpt123::field> & fields, const std::string & name )
+ : fields(fields)
+ {
+ fields.push_back( name );
+ }
+ std::ostream & ostream() {
+ return *this;
+ }
+ ~set_field() {
+ fields.back().val = str();
+ }
+};
+
+static void show_fields( textout & log, const std::vector<field> & fields ) {
+ const std::size_t fw = 11;
+ for ( std::vector<field>::const_iterator it = fields.begin(); it != fields.end(); ++it ) {
+ std::string key = it->key;
+ std::string val = it->val;
+ if ( key.length() < fw ) {
+ key += std::string( fw - key.length(), '.' );
+ }
+ if ( key.length() > fw ) {
+ key = key.substr( 0, fw );
+ }
+ key += ": ";
+ val = prepend_lines( val, std::string( fw, ' ' ) + ": " );
+ log << key << val << std::endl;
+ }
+}
+
+static void probe_mod_file( commandlineflags & flags, const std::string & filename, std::uint64_t filesize, std::istream & data_stream, textout & log ) {
+
+ log.writeout();
+
+ std::vector<field> fields;
+
+ if ( flags.filenames.size() > 1 ) {
+ set_field( fields, "Playlist" ).ostream() << flags.playlist_index + 1 << "/" << flags.filenames.size();
+ set_field( fields, "Prev/Next" ).ostream()
+ << "'"
+ << ( flags.playlist_index > 0 ? get_filename( flags.filenames[ flags.playlist_index - 1 ] ) : std::string() )
+ << "'"
+ << " / "
+ << "['" << get_filename( filename ) << "']"
+ << " / "
+ << "'"
+ << ( flags.playlist_index + 1 < flags.filenames.size() ? get_filename( flags.filenames[ flags.playlist_index + 1 ] ) : std::string() )
+ << "'"
+ ;
+ }
+ if ( flags.verbose ) {
+ set_field( fields, "Path" ).ostream() << filename;
+ }
+ if ( flags.show_details ) {
+ set_field( fields, "Filename" ).ostream() << get_filename( filename );
+ set_field( fields, "Size" ).ostream() << bytes_to_string( filesize );
+ }
+
+ int probe_result = openmpt::probe_file_header( openmpt::probe_file_header_flags_default, data_stream );
+ std::string probe_result_string;
+ switch ( probe_result ) {
+ case openmpt::probe_file_header_result_success:
+ probe_result_string = "Success";
+ break;
+ case openmpt::probe_file_header_result_failure:
+ probe_result_string = "Failure";
+ break;
+ case openmpt::probe_file_header_result_wantmoredata:
+ probe_result_string = "Insufficient Data";
+ break;
+ default:
+ probe_result_string = "Internal Error";
+ break;
+ }
+ set_field( fields, "Probe" ).ostream() << probe_result_string;
+
+ show_fields( log, fields );
+
+ log.writeout();
+
+}
+
+template < typename Tmod >
+void render_mod_file( commandlineflags & flags, const std::string & filename, std::uint64_t filesize, Tmod & mod, textout & log, write_buffers_interface & audio_stream ) {
+
+ log.writeout();
+
+ if ( flags.mode != ModeProbe && flags.mode != ModeInfo ) {
+ mod.set_repeat_count( flags.repeatcount );
+ apply_mod_settings( flags, mod );
+ }
+
+ double duration = mod.get_duration_seconds();
+
+ std::vector<field> fields;
+
+ if ( flags.filenames.size() > 1 ) {
+ set_field( fields, "Playlist" ).ostream() << flags.playlist_index + 1 << "/" << flags.filenames.size();
+ set_field( fields, "Prev/Next" ).ostream()
+ << "'"
+ << ( flags.playlist_index > 0 ? get_filename( flags.filenames[ flags.playlist_index - 1 ] ) : std::string() )
+ << "'"
+ << " / "
+ << "['" << get_filename( filename ) << "']"
+ << " / "
+ << "'"
+ << ( flags.playlist_index + 1 < flags.filenames.size() ? get_filename( flags.filenames[ flags.playlist_index + 1 ] ) : std::string() )
+ << "'"
+ ;
+ }
+ if ( flags.verbose ) {
+ set_field( fields, "Path" ).ostream() << filename;
+ }
+ if ( flags.show_details ) {
+ set_field( fields, "Filename" ).ostream() << get_filename( filename );
+ set_field( fields, "Size" ).ostream() << bytes_to_string( filesize );
+ if ( !mod.get_metadata( "warnings" ).empty() ) {
+ set_field( fields, "Warnings" ).ostream() << mod.get_metadata( "warnings" );
+ }
+ if ( !mod.get_metadata( "container" ).empty() ) {
+ set_field( fields, "Container" ).ostream() << mod.get_metadata( "container" ) << " (" << mod.get_metadata( "container_long" ) << ")";
+ }
+ set_field( fields, "Type" ).ostream() << mod.get_metadata( "type" ) << " (" << mod.get_metadata( "type_long" ) << ")";
+ if ( ( mod.get_num_subsongs() > 1 ) && ( flags.subsong != -1 ) ) {
+ set_field( fields, "Subsong" ).ostream() << flags.subsong;
+ }
+ set_field( fields, "Tracker" ).ostream() << mod.get_metadata( "tracker" );
+ if ( !mod.get_metadata( "date" ).empty() ) {
+ set_field( fields, "Date" ).ostream() << mod.get_metadata( "date" );
+ }
+ if ( !mod.get_metadata( "artist" ).empty() ) {
+ set_field( fields, "Artist" ).ostream() << mod.get_metadata( "artist" );
+ }
+ }
+ if ( true ) {
+ set_field( fields, "Title" ).ostream() << mod.get_metadata( "title" );
+ set_field( fields, "Duration" ).ostream() << seconds_to_string( duration );
+ }
+ if ( flags.show_details ) {
+ set_field( fields, "Subsongs" ).ostream() << mod.get_num_subsongs();
+ set_field( fields, "Channels" ).ostream() << mod.get_num_channels();
+ set_field( fields, "Orders" ).ostream() << mod.get_num_orders();
+ set_field( fields, "Patterns" ).ostream() << mod.get_num_patterns();
+ set_field( fields, "Instruments" ).ostream() << mod.get_num_instruments();
+ set_field( fields, "Samples" ).ostream() << mod.get_num_samples();
+ }
+ if ( flags.show_message ) {
+ set_field( fields, "Message" ).ostream() << mod.get_metadata( "message" );
+ }
+
+ show_fields( log, fields );
+
+ log.writeout();
+
+ if ( flags.filenames.size() == 1 || flags.mode == ModeRender ) {
+ audio_stream.write_metadata( get_metadata( mod ) );
+ } else {
+ audio_stream.write_updated_metadata( get_metadata( mod ) );
+ }
+
+ if ( flags.mode == ModeProbe || flags.mode == ModeInfo ) {
+ return;
+ }
+
+ if ( flags.seek_target > 0.0 ) {
+ mod.set_position_seconds( flags.seek_target );
+ }
+
+ try {
+ if ( flags.use_float ) {
+ render_loop<float>( flags, mod, duration, log, audio_stream );
+ } else {
+ render_loop<std::int16_t>( flags, mod, duration, log, audio_stream );
+ }
+ if ( flags.show_progress ) {
+ log << std::endl;
+ }
+ } catch ( ... ) {
+ if ( flags.show_progress ) {
+ log << std::endl;
+ }
+ throw;
+ }
+
+ log.writeout();
+
+}
+
+static void probe_file( commandlineflags & flags, const std::string & filename, textout & log ) {
+
+ log.writeout();
+
+ std::ostringstream silentlog;
+
+ try {
+
+#if defined(WIN32) && defined(UNICODE) && !defined(_MSC_VER)
+ std::istringstream file_stream;
+#else
+ std::ifstream file_stream;
+#endif
+ std::uint64_t filesize = 0;
+ bool use_stdin = ( filename == "-" );
+ if ( !use_stdin ) {
+ #if defined(WIN32) && defined(UNICODE) && !defined(_MSC_VER)
+ // Only MSVC has std::ifstream::ifstream(std::wstring).
+ // Fake it for other compilers using _wfopen().
+ std::string data;
+ FILE * f = _wfopen( utf8_to_wstring( filename ).c_str(), L"rb" );
+ if ( f ) {
+ while ( !feof( f ) ) {
+ static const std::size_t BUFFER_SIZE = 4096;
+ char buffer[BUFFER_SIZE];
+ size_t data_read = fread( buffer, 1, BUFFER_SIZE, f );
+ std::copy( buffer, buffer + data_read, std::back_inserter( data ) );
+ }
+ fclose( f );
+ f = NULL;
+ }
+ file_stream.str( data );
+ filesize = data.length();
+ #elif defined(_MSC_VER) && defined(UNICODE)
+ file_stream.open( utf8_to_wstring( filename ), std::ios::binary );
+ file_stream.seekg( 0, std::ios::end );
+ filesize = file_stream.tellg();
+ file_stream.seekg( 0, std::ios::beg );
+ #else
+ file_stream.open( filename, std::ios::binary );
+ file_stream.seekg( 0, std::ios::end );
+ filesize = file_stream.tellg();
+ file_stream.seekg( 0, std::ios::beg );
+ #endif
+ }
+ std::istream & data_stream = use_stdin ? std::cin : file_stream;
+ if ( data_stream.fail() ) {
+ throw exception( "file open error" );
+ }
+
+ probe_mod_file( flags, filename, filesize, data_stream, log );
+
+ } catch ( silent_exit_exception & ) {
+ throw;
+ } catch ( std::exception & e ) {
+ if ( !silentlog.str().empty() ) {
+ log << "errors probing '" << filename << "': " << silentlog.str() << std::endl;
+ } else {
+ log << "errors probing '" << filename << "'" << std::endl;
+ }
+ log << "error probing '" << filename << "': " << e.what() << std::endl;
+ } catch ( ... ) {
+ if ( !silentlog.str().empty() ) {
+ log << "errors probing '" << filename << "': " << silentlog.str() << std::endl;
+ } else {
+ log << "errors probing '" << filename << "'" << std::endl;
+ }
+ log << "unknown error probing '" << filename << "'" << std::endl;
+ }
+
+ log << std::endl;
+
+ log.writeout();
+
+}
+
+static void render_file( commandlineflags & flags, const std::string & filename, textout & log, write_buffers_interface & audio_stream ) {
+
+ log.writeout();
+
+ std::ostringstream silentlog;
+
+ try {
+
+#if defined(WIN32) && defined(UNICODE) && !defined(_MSC_VER)
+ std::istringstream file_stream;
+#else
+ std::ifstream file_stream;
+#endif
+ std::uint64_t filesize = 0;
+ bool use_stdin = ( filename == "-" );
+ if ( !use_stdin ) {
+ #if defined(WIN32) && defined(UNICODE) && !defined(_MSC_VER)
+ // Only MSVC has std::ifstream::ifstream(std::wstring).
+ // Fake it for other compilers using _wfopen().
+ std::string data;
+ FILE * f = _wfopen( utf8_to_wstring( filename ).c_str(), L"rb" );
+ if ( f ) {
+ while ( !feof( f ) ) {
+ static const std::size_t BUFFER_SIZE = 4096;
+ char buffer[BUFFER_SIZE];
+ size_t data_read = fread( buffer, 1, BUFFER_SIZE, f );
+ std::copy( buffer, buffer + data_read, std::back_inserter( data ) );
+ }
+ fclose( f );
+ f = NULL;
+ }
+ file_stream.str( data );
+ filesize = data.length();
+ #elif defined(_MSC_VER) && defined(UNICODE)
+ file_stream.open( utf8_to_wstring( filename ), std::ios::binary );
+ file_stream.seekg( 0, std::ios::end );
+ filesize = file_stream.tellg();
+ file_stream.seekg( 0, std::ios::beg );
+ #else
+ file_stream.open( filename, std::ios::binary );
+ file_stream.seekg( 0, std::ios::end );
+ filesize = file_stream.tellg();
+ file_stream.seekg( 0, std::ios::beg );
+ #endif
+ }
+ std::istream & data_stream = use_stdin ? std::cin : file_stream;
+ if ( data_stream.fail() ) {
+ throw exception( "file open error" );
+ }
+
+ {
+ openmpt::module mod( data_stream, silentlog, flags.ctls );
+ mod.select_subsong( flags.subsong );
+ silentlog.str( std::string() ); // clear, loader messages get stored to get_metadata( "warnings" ) by libopenmpt internally
+ render_mod_file( flags, filename, filesize, mod, log, audio_stream );
+ }
+
+ } catch ( prev_file & ) {
+ throw;
+ } catch ( next_file & ) {
+ throw;
+ } catch ( silent_exit_exception & ) {
+ throw;
+ } catch ( std::exception & e ) {
+ if ( !silentlog.str().empty() ) {
+ log << "errors loading '" << filename << "': " << silentlog.str() << std::endl;
+ } else {
+ log << "errors loading '" << filename << "'" << std::endl;
+ }
+ log << "error playing '" << filename << "': " << e.what() << std::endl;
+ } catch ( ... ) {
+ if ( !silentlog.str().empty() ) {
+ log << "errors loading '" << filename << "': " << silentlog.str() << std::endl;
+ } else {
+ log << "errors loading '" << filename << "'" << std::endl;
+ }
+ log << "unknown error playing '" << filename << "'" << std::endl;
+ }
+
+ log << std::endl;
+
+ log.writeout();
+
+}
+
+
+static std::string get_random_filename(std::set<std::string> & filenames) {
+ // TODO: actually use a useful random distribution
+ std::size_t index = std::rand() % filenames.size();
+ std::set<std::string>::iterator it = filenames.begin();
+ std::advance( it, index );
+ return *it;
+}
+
+
+static void render_files( commandlineflags & flags, textout & log, write_buffers_interface & audio_stream ) {
+ if ( flags.randomize ) {
+ std::random_shuffle( flags.filenames.begin(), flags.filenames.end() );
+ }
+ try {
+ while ( true ) {
+ if ( flags.shuffle ) {
+ // TODO: improve prev/next logic
+ std::set<std::string> shuffle_set;
+ shuffle_set.insert( flags.filenames.begin(), flags.filenames.end() );
+ while ( true ) {
+ if ( shuffle_set.empty() ) {
+ break;
+ }
+ std::string filename = get_random_filename( shuffle_set );
+ try {
+ flags.playlist_index = std::find( flags.filenames.begin(), flags.filenames.end(), filename ) - flags.filenames.begin();
+ render_file( flags, filename, log, audio_stream );
+ shuffle_set.erase( filename );
+ continue;
+ } catch ( prev_file & ) {
+ shuffle_set.erase( filename );
+ continue;
+ } catch ( next_file & ) {
+ shuffle_set.erase( filename );
+ continue;
+ } catch ( ... ) {
+ throw;
+ }
+ }
+ } else {
+ std::vector<std::string>::iterator filename = flags.filenames.begin();
+ while ( true ) {
+ if ( filename == flags.filenames.end() ) {
+ break;
+ }
+ try {
+ flags.playlist_index = filename - flags.filenames.begin();
+ render_file( flags, *filename, log, audio_stream );
+ filename++;
+ continue;
+ } catch ( prev_file & e ) {
+ while ( filename != flags.filenames.begin() && e.count ) {
+ e.count--;
+ --filename;
+ }
+ continue;
+ } catch ( next_file & e ) {
+ while ( filename != flags.filenames.end() && e.count ) {
+ e.count--;
+ ++filename;
+ }
+ continue;
+ } catch ( ... ) {
+ throw;
+ }
+ }
+ }
+ if ( !flags.restart ) {
+ break;
+ }
+ }
+ } catch ( ... ) {
+ throw;
+ }
+}
+
+
+static bool parse_playlist( commandlineflags & flags, std::string filename, std::ostream & log ) {
+ log.flush();
+ bool is_playlist = false;
+ bool m3u8 = false;
+ if ( ends_with( filename, ".m3u") || ends_with( filename, ".m3U") || ends_with( filename, ".M3u") || ends_with( filename, ".M3U") ) {
+ is_playlist = true;
+ }
+ if ( ends_with( filename, ".m3u8") || ends_with( filename, ".m3U8") || ends_with( filename, ".M3u8") || ends_with( filename, ".M3U8") ) {
+ is_playlist = true;
+ m3u8 = true;
+ }
+ 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") ) {
+ is_playlist = true;
+ }
+ std::string basepath = get_basepath( filename );
+ try {
+#if defined(WIN32) && defined(UNICODE) && !defined(_MSC_VER)
+ std::istringstream file_stream;
+#else
+ std::ifstream file_stream;
+#endif
+ #if defined(WIN32) && defined(UNICODE) && !defined(_MSC_VER)
+ // Only MSVC has std::ifstream::ifstream(std::wstring).
+ // Fake it for other compilers using _wfopen().
+ std::string data;
+ FILE * f = _wfopen( utf8_to_wstring( filename ).c_str(), L"rb" );
+ if ( f ) {
+ while ( !feof( f ) ) {
+ static const std::size_t BUFFER_SIZE = 4096;
+ char buffer[BUFFER_SIZE];
+ size_t data_read = fread( buffer, 1, BUFFER_SIZE, f );
+ std::copy( buffer, buffer + data_read, std::back_inserter( data ) );
+ }
+ fclose( f );
+ f = NULL;
+ }
+ file_stream.str( data );
+ #elif defined(_MSC_VER) && defined(UNICODE)
+ file_stream.open( utf8_to_wstring( filename ), std::ios::binary );
+ #else
+ file_stream.open( filename, std::ios::binary );
+ #endif
+ std::string line;
+ bool first = true;
+ bool extm3u = false;
+ bool pls = false;
+ while ( std::getline( file_stream, line ) ) {
+ std::string newfile;
+ line = trim_eol( line );
+ if ( first ) {
+ first = false;
+ if ( line == "#EXTM3U" ) {
+ extm3u = true;
+ continue;
+ } else if ( line == "[playlist]" ) {
+ pls = true;
+ }
+ }
+ if ( line.empty() ) {
+ continue;
+ }
+ if ( pls ) {
+ if ( begins_with( line, "File" ) ) {
+ if ( line.find( "=" ) != std::string::npos ) {
+ flags.filenames.push_back( line.substr( line.find( "=" ) + 1 ) );
+ }
+ } else if ( begins_with( line, "Title" ) ) {
+ continue;
+ } else if ( begins_with( line, "Length" ) ) {
+ continue;
+ } else if ( begins_with( line, "NumberOfEntries" ) ) {
+ continue;
+ } else if ( begins_with( line, "Version" ) ) {
+ continue;
+ } else {
+ continue;
+ }
+ } else if ( extm3u ) {
+ if ( begins_with( line, "#EXTINF" ) ) {
+ continue;
+ } else if ( begins_with( line, "#" ) ) {
+ continue;
+ }
+ if ( m3u8 ) {
+ newfile = line;
+ } else {
+#if defined(WIN32)
+ newfile = wstring_to_utf8( locale_to_wstring( line ) );
+#else
+ newfile = line;
+#endif
+ }
+ } else {
+ if ( m3u8 ) {
+ newfile = line;
+ } else {
+#if defined(WIN32)
+ newfile = wstring_to_utf8( locale_to_wstring( line ) );
+#else
+ newfile = line;
+#endif
+ }
+ }
+ if ( !newfile.empty() ) {
+ if ( !is_absolute( newfile ) ) {
+ newfile = basepath + newfile;
+ }
+ flags.filenames.push_back( newfile );
+ }
+ }
+ } catch ( std::exception & e ) {
+ log << "error loading '" << filename << "': " << e.what() << std::endl;
+ } catch ( ... ) {
+ log << "unknown error loading '" << filename << "'" << std::endl;
+ }
+ log.flush();
+ return is_playlist;
+}
+
+
+static commandlineflags parse_openmpt123( const std::vector<std::string> & args, std::ostream & log ) {
+
+ log.flush();
+
+ if ( args.size() <= 1 ) {
+ throw args_error_exception();
+ }
+
+ commandlineflags flags;
+
+ bool files_only = false;
+ for ( std::vector<std::string>::const_iterator i = args.begin(); i != args.end(); ++i ) {
+ if ( i == args.begin() ) {
+ // skip program name
+ continue;
+ }
+ std::string arg = *i;
+ std::string nextarg = ( i+1 != args.end() ) ? *(i+1) : "";
+ if ( files_only ) {
+ flags.filenames.push_back( arg );
+ } else if ( arg.substr( 0, 1 ) != "-" ) {
+ flags.filenames.push_back( arg );
+ } else {
+ if ( arg == "--" ) {
+ files_only = true;
+ } else if ( arg == "-h" || arg == "--help" ) {
+ throw show_help_exception();
+ } else if ( arg == "--help-keyboard" ) {
+ throw show_help_keyboard_exception();
+ } else if ( arg == "-q" || arg == "--quiet" ) {
+ flags.quiet = true;
+ } else if ( arg == "-v" || arg == "--verbose" ) {
+ flags.verbose = true;
+ } else if ( arg == "--man-version" ) {
+ throw show_man_version_exception();
+ } else if ( arg == "--man-help" ) {
+ throw show_man_help_exception();
+ } else if ( arg == "--version" ) {
+ throw show_version_number_exception();
+ } else if ( arg == "--short-version" ) {
+ throw show_short_version_number_exception();
+ } else if ( arg == "--long-version" ) {
+ throw show_long_version_number_exception();
+ } else if ( arg == "--credits" ) {
+ throw show_credits_exception();
+ } else if ( arg == "--license" ) {
+ throw show_license_exception();
+ } else if ( arg == "--probe" ) {
+ flags.mode = ModeProbe;
+ } else if ( arg == "--info" ) {
+ flags.mode = ModeInfo;
+ } else if ( arg == "--ui" ) {
+ flags.mode = ModeUI;
+ } else if ( arg == "--batch" ) {
+ flags.mode = ModeBatch;
+ } else if ( arg == "--render" ) {
+ flags.mode = ModeRender;
+ } else if ( arg == "--terminal-width" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.terminal_width;
+ ++i;
+ } else if ( arg == "--terminal-height" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.terminal_height;
+ ++i;
+ } else if ( arg == "--progress" ) {
+ flags.show_progress = true;
+ } else if ( arg == "--no-progress" ) {
+ flags.show_progress = false;
+ } else if ( arg == "--meters" ) {
+ flags.show_meters = true;
+ } else if ( arg == "--no-meters" ) {
+ flags.show_meters = false;
+ } else if ( arg == "--channel-meters" ) {
+ flags.show_channel_meters = true;
+ } else if ( arg == "--no-channel-meters" ) {
+ flags.show_channel_meters = false;
+ } else if ( arg == "--pattern" ) {
+ flags.show_pattern = true;
+ } else if ( arg == "--no-pattern" ) {
+ flags.show_pattern = false;
+ } else if ( arg == "--details" ) {
+ flags.show_details = true;
+ } else if ( arg == "--no-details" ) {
+ flags.show_details = false;
+ } else if ( arg == "--message" ) {
+ flags.show_message = true;
+ } else if ( arg == "--no-message" ) {
+ flags.show_message = false;
+ } else if ( arg == "--driver" && nextarg != "" ) {
+ if ( false ) {
+ // nothing
+ } else if ( nextarg == "help" ) {
+ std::ostringstream drivers;
+ drivers << " Available drivers:" << std::endl;
+ drivers << " " << "default" << std::endl;
+#if defined( MPT_WITH_PULSEAUDIO )
+ drivers << " " << "pulseaudio" << std::endl;
+#endif
+#if defined( MPT_WITH_SDL2 )
+ drivers << " " << "sdl2" << std::endl;
+#endif
+#if defined( MPT_WITH_SDL )
+ drivers << " " << "sdl" << std::endl;
+#endif
+#if defined( MPT_WITH_PORTAUDIO )
+ drivers << " " << "portaudio" << std::endl;
+#endif
+#if defined( WIN32 )
+ drivers << " " << "waveout" << std::endl;
+#endif
+ throw show_help_exception( drivers.str() );
+ } else if ( nextarg == "default" ) {
+ flags.driver = "";
+ } else {
+ flags.driver = nextarg;
+ }
+ ++i;
+ } else if ( arg == "--device" && nextarg != "" ) {
+ if ( false ) {
+ // nothing
+ } else if ( nextarg == "help" ) {
+ std::ostringstream devices;
+ devices << " Available devices:" << std::endl;
+ devices << " " << "default" << ": " << "default" << std::endl;
+#if defined( MPT_WITH_PULSEAUDIO )
+ devices << show_pulseaudio_devices( log );
+#endif
+#if defined( MPT_WITH_SDL2 )
+ devices << show_sdl2_devices( log );
+#endif
+#if defined( MPT_WITH_PORTAUDIO )
+ devices << show_portaudio_devices( log );
+#endif
+#if defined( WIN32 )
+ devices << show_waveout_devices( log );
+#endif
+ throw show_help_exception( devices.str() );
+ } else if ( nextarg == "default" ) {
+ flags.device = "";
+ } else {
+ flags.device = nextarg;
+ }
+ ++i;
+ } else if ( arg == "--buffer" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.buffer;
+ ++i;
+ } else if ( arg == "--period" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.period;
+ ++i;
+ } else if ( arg == "--update" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.ui_redraw_interval;
+ ++i;
+ } else if ( arg == "--stdout" ) {
+ flags.use_stdout = true;
+ } else if ( ( arg == "-o" || arg == "--output" ) && nextarg != "" ) {
+ flags.output_filename = nextarg;
+ ++i;
+ } else if ( arg == "--force" ) {
+ flags.force_overwrite = true;
+ } else if ( arg == "--output-type" && nextarg != "" ) {
+ flags.output_extension = nextarg;
+ ++i;
+ } else if ( arg == "--samplerate" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.samplerate;
+ ++i;
+ } else if ( arg == "--channels" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.channels;
+ ++i;
+ } else if ( arg == "--float" ) {
+ flags.use_float = true;
+ } else if ( arg == "--no-float" ) {
+ flags.use_float = false;
+ } else if ( arg == "--gain" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ double gain = 0.0;
+ istr >> gain;
+ flags.gain = static_cast<std::int32_t>( gain * 100.0 );
+ ++i;
+ } else if ( arg == "--stereo" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.separation;
+ ++i;
+ } else if ( arg == "--filter" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.filtertaps;
+ ++i;
+ } else if ( arg == "--ramping" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.ramping;
+ ++i;
+ } else if ( arg == "--tempo" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ double tmp = 1.0;
+ istr >> tmp;
+ flags.tempo = double_to_tempo_flag( tmp );
+ ++i;
+ } else if ( arg == "--pitch" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ double tmp = 1.0;
+ istr >> tmp;
+ flags.pitch = double_to_pitch_flag( tmp );
+ ++i;
+ } else if ( arg == "--dither" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.dither;
+ ++i;
+ } else if ( arg == "--playlist" && nextarg != "" ) {
+ parse_playlist( flags, nextarg, log );
+ ++i;
+ } else if ( arg == "--randomize" ) {
+ flags.randomize = true;
+ } else if ( arg == "--no-randomize" ) {
+ flags.randomize = false;
+ } else if ( arg == "--shuffle" ) {
+ flags.shuffle = true;
+ } else if ( arg == "--no-shuffle" ) {
+ flags.shuffle = false;
+ } else if ( arg == "--restart" ) {
+ flags.restart = true;
+ } else if ( arg == "--no-restart" ) {
+ flags.restart = false;
+ } else if ( arg == "--subsong" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.subsong;
+ ++i;
+ } else if ( arg == "--repeat" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.repeatcount;
+ ++i;
+ } else if ( arg == "--ctl" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ std::string ctl_c_v;
+ istr >> ctl_c_v;
+ if ( ctl_c_v.find( "=" ) == std::string::npos ) {
+ throw args_error_exception();
+ }
+ std::string ctl = ctl_c_v.substr( 0, ctl_c_v.find( "=" ) );
+ std::string val = ctl_c_v.substr( ctl_c_v.find( "=" ) + std::string("=").length(), std::string::npos );
+ if ( ctl.empty() ) {
+ throw args_error_exception();
+ }
+ flags.ctls[ ctl ] = val;
+ ++i;
+ } else if ( arg == "--seek" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.seek_target;
+ ++i;
+ } else if ( arg == "--end-time" && nextarg != "" ) {
+ std::istringstream istr( nextarg );
+ istr >> flags.end_time;
+ ++i;
+ } else if ( arg.size() > 0 && arg.substr( 0, 1 ) == "-" ) {
+ throw args_error_exception();
+ }
+ }
+ }
+
+ return flags;
+
+}
+
+#if defined(WIN32)
+
+class ConsoleCP_utf8_raii {
+private:
+ const UINT oldCP;
+ const UINT oldOutputCP;
+public:
+ ConsoleCP_utf8_raii()
+ : oldCP(GetConsoleCP())
+ , oldOutputCP(GetConsoleOutputCP())
+ {
+ SetConsoleCP( 65001 ); // UTF-8
+ SetConsoleOutputCP( 65001 ); // UTF-8
+ }
+ ~ConsoleCP_utf8_raii() {
+ SetConsoleCP( oldCP );
+ SetConsoleOutputCP( oldOutputCP );
+ }
+};
+
+class FD_binary_raii {
+private:
+ FILE * file;
+ int old_mode;
+public:
+ FD_binary_raii(FILE * file, bool set_binary)
+ : file(file)
+ , old_mode(-1)
+ {
+ if ( set_binary ) {
+ fflush( file );
+ old_mode = _setmode( _fileno( file ), _O_BINARY );
+ if ( old_mode == -1 ) {
+ throw exception( "failed to set binary mode on file descriptor" );
+ }
+ }
+ }
+ ~FD_binary_raii()
+ {
+ if ( old_mode != -1 ) {
+ fflush( file );
+ old_mode = _setmode( _fileno( file ), old_mode );
+ }
+ }
+};
+
+#endif
+
+#if defined(WIN32) && defined(UNICODE)
+static int wmain( int wargc, wchar_t * wargv [] ) {
+#else
+static int main( int argc, char * argv [] ) {
+#endif
+ std::vector<std::string> args;
+ #if defined(WIN32) && defined(UNICODE)
+ for ( int arg = 0; arg < wargc; ++arg ) {
+ args.push_back( wstring_to_utf8( wargv[arg] ) );
+ }
+ #else
+ args = std::vector<std::string>( argv, argv + argc );
+ #endif
+
+#if defined(WIN32)
+ ConsoleCP_utf8_raii console_cp;
+#endif
+ textout_dummy dummy_log;
+#if defined(WIN32)
+ textout_console std_out( GetStdHandle( STD_OUTPUT_HANDLE ) );
+ textout_console std_err( GetStdHandle( STD_ERROR_HANDLE ) );
+#else
+ textout_ostream std_out( std::cout );
+ textout_ostream std_err( std::clog );
+#endif
+
+ commandlineflags flags;
+
+ try {
+
+ flags = parse_openmpt123( args, std::cerr );
+
+ flags.check_and_sanitize();
+
+ } catch ( args_error_exception & ) {
+ show_help( std_out );
+ return 1;
+ } catch ( show_man_help_exception & ) {
+ show_help( std_out, false, true, true );
+ return 0;
+ } catch ( show_man_version_exception & ) {
+ show_man_version( std_out );
+ return 0;
+ } catch ( show_help_exception & e ) {
+ show_help( std_out, true, e.longhelp, false, e.message );
+ if ( flags.verbose ) {
+ show_credits( std_out );
+ }
+ return 0;
+ } catch ( show_help_keyboard_exception & ) {
+ show_help_keyboard( std_out );
+ return 0;
+ } catch ( show_long_version_number_exception & ) {
+ show_long_version( std_out );
+ return 0;
+ } catch ( show_version_number_exception & ) {
+ show_version( std_out );
+ return 0;
+ } catch ( show_short_version_number_exception & ) {
+ show_short_version( std_out );
+ return 0;
+ } catch ( show_credits_exception & ) {
+ show_credits( std_out );
+ return 0;
+ } catch ( show_license_exception & ) {
+ show_license( std_out );
+ return 0;
+ } catch ( silent_exit_exception & ) {
+ return 0;
+ } catch ( std::exception & e ) {
+ std_err << "error: " << e.what() << std::endl;
+ std_err.writeout();
+ return 1;
+ } catch ( ... ) {
+ std_err << "unknown error" << std::endl;
+ std_err.writeout();
+ return 1;
+ }
+
+ try {
+
+ bool stdin_can_ui = true;
+ for ( std::vector<std::string>::iterator filename = flags.filenames.begin(); filename != flags.filenames.end(); ++filename ) {
+ if ( *filename == "-" ) {
+ stdin_can_ui = false;
+ break;
+ }
+ }
+
+ bool stdout_can_ui = true;
+ if ( flags.use_stdout ) {
+ stdout_can_ui = false;
+ }
+
+ // set stdin binary
+#if defined(WIN32)
+ FD_binary_raii stdin_guard( stdin, !stdin_can_ui );
+#endif
+
+ // set stdout binary
+#if defined(WIN32)
+ FD_binary_raii stdout_guard( stdout, !stdout_can_ui );
+#endif
+
+ // setup terminal
+ #if !defined(WIN32)
+ if ( stdin_can_ui ) {
+ if ( flags.mode == ModeUI ) {
+ set_input_mode();
+ }
+ }
+ #endif
+
+ textout & log = flags.quiet ? *static_cast<textout*>( &dummy_log ) : *static_cast<textout*>( stdout_can_ui ? &std_out : &std_err );
+
+ show_info( log, flags.verbose );
+
+ if ( flags.verbose ) {
+ log << flags;
+ }
+
+ log.writeout();
+
+ std::srand( static_cast<unsigned int>( std::time( NULL ) ) );
+
+ switch ( flags.mode ) {
+ case ModeProbe: {
+ for ( std::vector<std::string>::iterator filename = flags.filenames.begin(); filename != flags.filenames.end(); ++filename ) {
+ probe_file( flags, *filename, log );
+ flags.playlist_index++;
+ }
+ } break;
+ case ModeInfo: {
+ void_audio_stream dummy;
+ render_files( flags, log, dummy );
+ } break;
+ case ModeUI:
+ case ModeBatch: {
+ if ( flags.use_stdout ) {
+ flags.apply_default_buffer_sizes();
+ stdout_stream_raii stdout_audio_stream;
+ render_files( flags, log, stdout_audio_stream );
+ } else if ( !flags.output_filename.empty() ) {
+ flags.apply_default_buffer_sizes();
+ file_audio_stream_raii file_audio_stream( flags, flags.output_filename, log );
+ render_files( flags, log, file_audio_stream );
+#if defined( MPT_WITH_PULSEAUDIO )
+ } else if ( flags.driver == "pulseaudio" || flags.driver.empty() ) {
+ pulseaudio_stream_raii pulseaudio_stream( flags, log );
+ render_files( flags, log, pulseaudio_stream );
+#endif
+#if defined( MPT_WITH_SDL2 )
+ } else if ( flags.driver == "sdl2" || flags.driver.empty() ) {
+ sdl2_stream_raii sdl2_stream( flags, log );
+ render_files( flags, log, sdl2_stream );
+#endif
+#if defined( MPT_WITH_SDL )
+ } else if ( flags.driver == "sdl" || flags.driver.empty() ) {
+ sdl_stream_raii sdl_stream( flags, log );
+ render_files( flags, log, sdl_stream );
+#endif
+#if defined( MPT_WITH_PORTAUDIO )
+ } else if ( flags.driver == "portaudio" || flags.driver.empty() ) {
+ portaudio_stream_raii portaudio_stream( flags, log );
+ render_files( flags, log, portaudio_stream );
+#endif
+#if defined( WIN32 )
+ } else if ( flags.driver == "waveout" || flags.driver.empty() ) {
+ waveout_stream_raii waveout_stream( flags );
+ render_files( flags, log, waveout_stream );
+#endif
+ } else {
+ if ( flags.driver.empty() ) {
+ throw exception( "openmpt123 is compiled without any audio driver" );
+ } else {
+ throw exception( "audio driver '" + flags.driver + "' not found" );
+ }
+ }
+ } break;
+ case ModeRender: {
+ for ( std::vector<std::string>::iterator filename = flags.filenames.begin(); filename != flags.filenames.end(); ++filename ) {
+ flags.apply_default_buffer_sizes();
+ file_audio_stream_raii file_audio_stream( flags, *filename + std::string(".") + flags.output_extension, log );
+ render_file( flags, *filename, log, file_audio_stream );
+ flags.playlist_index++;
+ }
+ } break;
+ case ModeNone:
+ break;
+ }
+
+ } catch ( args_error_exception & ) {
+ show_help( std_out );
+ return 1;
+#ifdef MPT_WITH_PULSEAUDIO
+ } catch ( pulseaudio_exception & e ) {
+ std_err << "PulseAudio error: " << e.what() << std::endl;
+ std_err.writeout();
+ return 1;
+#endif
+#ifdef MPT_WITH_PORTAUDIO
+ } catch ( portaudio_exception & e ) {
+ std_err << "PortAudio error: " << e.what() << std::endl;
+ std_err.writeout();
+ return 1;
+#endif
+#ifdef MPT_WITH_SDL
+ } catch ( sdl_exception & e ) {
+ std_err << "SDL error: " << e.what() << std::endl;
+ std_err.writeout();
+ return 1;
+#endif
+#ifdef MPT_WITH_SDL2
+ } catch ( sdl2_exception & e ) {
+ std_err << "SDL2 error: " << e.what() << std::endl;
+ std_err.writeout();
+ return 1;
+#endif
+ } catch ( silent_exit_exception & ) {
+ return 0;
+ } catch ( std::exception & e ) {
+ std_err << "error: " << e.what() << std::endl;
+ std_err.writeout();
+ return 1;
+ } catch ( ... ) {
+ std_err << "unknown error" << std::endl;
+ std_err.writeout();
+ return 1;
+ }
+
+ return 0;
+}
+
+} // namespace openmpt123
+
+#if defined(WIN32) && defined(UNICODE)
+#if defined(__GNUC__)
+// mingw64 does only default to special C linkage for "main", but not for "wmain".
+extern "C"
+#endif
+int wmain( int wargc, wchar_t * wargv [] ) {
+ return openmpt123::wmain( wargc, wargv );
+}
+#else
+int main( int argc, char * argv [] ) {
+ return openmpt123::main( argc, argv );
+}
+#endif
diff --git a/openmpt123/openmpt123.hpp b/openmpt123/openmpt123.hpp
new file mode 100644
index 0000000..edb6553
--- /dev/null
+++ b/openmpt123/openmpt123.hpp
@@ -0,0 +1,650 @@
+/*
+ * 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"
+
+namespace openmpt123 {
+
+struct exception : public openmpt::exception {
+ exception( const std::string & text ) throw() : 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)
+
+std::string wstring_to_utf8( const std::wstring & unicode_string ) {
+ int required_size = WideCharToMultiByte( CP_UTF8, 0, unicode_string.c_str(), -1, NULL, 0, NULL, NULL );
+ if ( required_size <= 0 ) {
+ return std::string();
+ }
+ std::vector<char> utf8_buf( required_size );
+ WideCharToMultiByte( CP_UTF8, 0, unicode_string.c_str(), -1, &utf8_buf[0], required_size, NULL, NULL );
+ return &utf8_buf[0];
+}
+
+std::wstring utf8_to_wstring( const std::string & utf8_string ) {
+ int required_size = MultiByteToWideChar( CP_UTF8, 0, utf8_string.c_str(), -1, NULL, 0 );
+ if ( required_size <= 0 ) {
+ return std::wstring();
+ }
+ std::vector<wchar_t> unicode_buf( required_size );
+ MultiByteToWideChar( CP_UTF8, 0, utf8_string.data(), -1, &unicode_buf[0], required_size );
+ return &unicode_buf[0];
+}
+
+std::string wstring_to_locale( const std::wstring & unicode_string ) {
+ int required_size = WideCharToMultiByte( CP_ACP, 0, unicode_string.c_str(), -1, NULL, 0, NULL, NULL );
+ if ( required_size <= 0 ) {
+ return std::string();
+ }
+ std::vector<char> locale_buf( required_size );
+ WideCharToMultiByte( CP_ACP, 0, unicode_string.c_str(), -1, &locale_buf[0], required_size, NULL, NULL );
+ return &locale_buf[0];
+}
+
+std::wstring locale_to_wstring( const std::string & locale_string ) {
+ int required_size = MultiByteToWideChar( CP_ACP, 0, locale_string.c_str(), -1, NULL, 0 );
+ if ( required_size <= 0 ) {
+ return std::wstring();
+ }
+ std::vector<wchar_t> unicode_buf( required_size );
+ MultiByteToWideChar( CP_ACP, 0, locale_string.data(), -1, &unicode_buf[0], required_size );
+ return &unicode_buf[0];
+}
+
+#endif // WIN32
+
+#if defined(MPT_NEEDS_THREADS)
+
+#if defined(WIN32)
+
+class mutex {
+private:
+ CRITICAL_SECTION impl;
+public:
+ mutex() { InitializeCriticalSection(&impl); }
+ ~mutex() { DeleteCriticalSection(&impl); }
+ void lock() { EnterCriticalSection(&impl); }
+ void unlock() { LeaveCriticalSection(&impl); }
+};
+
+#else
+
+class mutex {
+private:
+ pthread_mutex_t impl;
+public:
+ mutex() {
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init( &attr );
+ pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_NORMAL );
+ pthread_mutex_init( &impl, &attr );
+ pthread_mutexattr_destroy( &attr );
+ }
+ ~mutex() { pthread_mutex_destroy( &impl ); }
+ void lock() { pthread_mutex_lock( &impl ); }
+ void unlock() { pthread_mutex_unlock( &impl ); }
+};
+
+#endif
+
+#endif
+
+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;
+ }
+public:
+ virtual void write( const std::string & text ) = 0;
+ virtual void writeout() {
+ write( str() );
+ str(std::string());
+ }
+};
+
+class textout_dummy : public textout {
+public:
+ textout_dummy() {
+ return;
+ }
+ virtual ~textout_dummy() {
+ return;
+ }
+public:
+ virtual void write( const std::string & /* text */ ) {
+ return;
+ }
+};
+
+class textout_ostream : public textout {
+private:
+ std::ostream & s;
+public:
+ textout_ostream( std::ostream & s_ )
+ : s(s_)
+ {
+ return;
+ }
+ virtual ~textout_ostream() {
+ writeout();
+ }
+public:
+ virtual void write( const std::string & text ) {
+ s << text;
+ }
+ virtual void writeout() {
+ textout::writeout();
+ s.flush();
+ }
+};
+
+#if defined(WIN32)
+
+class textout_console : public textout {
+private:
+ HANDLE handle;
+public:
+ textout_console( HANDLE handle_ )
+ : handle(handle_)
+ {
+ return;
+ }
+ virtual ~textout_console() {
+ writeout();
+ }
+public:
+ virtual void write( const std::string & text ) {
+ #if defined(UNICODE)
+ std::wstring wtext = utf8_to_wstring( text );
+ WriteConsole( handle, wtext.data(), static_cast<DWORD>( wtext.size() ), NULL, NULL );
+ #else
+ WriteConsole( handle, text.data(), static_cast<DWORD>( text.size() ), NULL, NULL );
+ #endif
+ }
+};
+
+#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 "";
+}
+
+bool IsTerminal( int fd );
+
+enum Mode {
+ ModeNone,
+ ModeProbe,
+ ModeInfo,
+ ModeUI,
+ ModeBatch,
+ ModeRender
+};
+
+static inline std::string mode_to_string( Mode mode ) {
+ switch ( mode ) {
+ case ModeNone: return "none"; break;
+ case ModeProbe: return "probe"; break;
+ case ModeInfo: return "info"; break;
+ case ModeUI: return "ui"; break;
+ case ModeBatch: return "batch"; break;
+ case ModeRender: 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;
+ 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 = ModeUI;
+ ui_redraw_interval = default_high;
+ driver = "";
+ device = "";
+ buffer = default_high;
+ period = default_high;
+ samplerate = 48000;
+ channels = 2;
+ use_float = true;
+ 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;
+ terminal_width = 72;
+ terminal_height = 23;
+#if defined(WIN32)
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) );
+ GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &csbi );
+ terminal_width = csbi.dwSize.X - 1;
+ terminal_height = 23; //csbi.dwSize.Y - 1;
+#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 = "wav";
+ 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 ( std::vector<std::string>::iterator i = filenames.begin(); i != filenames.end(); ++i ) {
+ if ( *i == "-" ) {
+ canUI = false;
+ }
+ }
+ show_ui = canUI;
+ if ( mode == ModeNone ) {
+ if ( canUI ) {
+ mode = ModeUI;
+ } else {
+ mode = ModeBatch;
+ }
+ }
+ if ( mode == ModeUI && !canUI ) {
+ throw args_error_exception();
+ }
+ if ( show_progress && !canProgress ) {
+ throw args_error_exception();
+ }
+ switch ( mode ) {
+ case ModeNone:
+ throw args_error_exception();
+ break;
+ case ModeProbe:
+ show_ui = false;
+ show_progress = false;
+ show_meters = false;
+ show_channel_meters = false;
+ show_pattern = false;
+ break;
+ case ModeInfo:
+ show_ui = false;
+ show_progress = false;
+ show_meters = false;
+ show_channel_meters = false;
+ show_pattern = false;
+ break;
+ case ModeUI:
+ break;
+ case ModeBatch:
+ show_meters = false;
+ show_channel_meters = false;
+ show_pattern = false;
+ break;
+ case ModeRender:
+ 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_filename.empty() ) {
+ output_extension = get_extension( output_filename );
+ }
+ if ( mode == ModeRender && output_extension.empty() ) {
+ throw args_error_exception();
+ }
+ }
+};
+
+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, 32767 );
+ tmp = std::max( tmp, -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_blocking_wrapper : public write_buffers_interface {
+protected:
+ std::size_t channels;
+ std::size_t sampleQueueMaxFrames;
+ std::deque<float> sampleQueue;
+protected:
+ virtual ~write_buffers_blocking_wrapper() {
+ return;
+ }
+protected:
+ write_buffers_blocking_wrapper( const commandlineflags & flags )
+ : channels(flags.channels)
+ , sampleQueueMaxFrames(0)
+ {
+ return;
+ }
+ void set_queue_size_frames( std::size_t frames ) {
+ sampleQueueMaxFrames = frames;
+ }
+ template < typename Tsample >
+ float pop_queue() {
+ float val = 0.0f;
+ if ( !sampleQueue.empty() ) {
+ val = sampleQueue.front();
+ sampleQueue.pop_front();
+ }
+ return convert_sample_to<Tsample>( val );
+ }
+ template < typename Tsample >
+ void fill_buffer( Tsample * buf, std::size_t framesToRender ) {
+ for ( std::size_t frame = 0; frame < framesToRender; ++frame ) {
+ for ( std::size_t channel = 0; channel < channels; ++channel ) {
+ *buf = pop_queue<Tsample>();
+ buf++;
+ }
+ }
+ }
+private:
+ void wait_for_queue_space() {
+ while ( sampleQueue.size() >= sampleQueueMaxFrames * channels ) {
+ unlock();
+ sleep( 1 );
+ lock();
+ }
+ }
+public:
+ void write( const std::vector<float*> buffers, std::size_t frames ) {
+ lock();
+ for ( std::size_t frame = 0; frame < frames; ++frame ) {
+ for ( std::size_t channel = 0; channel < channels; ++channel ) {
+ wait_for_queue_space();
+ sampleQueue.push_back( buffers[channel][frame] );
+ }
+ }
+ unlock();
+ }
+ void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) {
+ lock();
+ for ( std::size_t frame = 0; frame < frames; ++frame ) {
+ for ( std::size_t channel = 0; channel < channels; ++channel ) {
+ wait_for_queue_space();
+ sampleQueue.push_back( buffers[channel][frame] * (1.0f/32768.0f) );
+ }
+ }
+ unlock();
+ }
+ virtual void lock() = 0;
+ virtual void unlock() = 0;
+ virtual bool sleep( int ms ) = 0;
+};
+
+class void_audio_stream : public write_buffers_interface {
+public:
+ virtual ~void_audio_stream() {
+ return;
+ }
+public:
+ virtual void write( const std::vector<float*> buffers, std::size_t frames ) {
+ (void)buffers;
+ (void)frames;
+ }
+ virtual void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) {
+ (void)buffers;
+ (void)frames;
+ }
+ virtual bool is_dummy() const {
+ return true;
+ }
+};
+
+class file_audio_stream_base : public write_buffers_interface {
+protected:
+ file_audio_stream_base() {
+ 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 ~file_audio_stream_base() {
+ return;
+ }
+};
+
+} // namespace openmpt123
+
+#endif // OPENMPT123_HPP
diff --git a/openmpt123/openmpt123_config.hpp b/openmpt123/openmpt123_config.hpp
new file mode 100644
index 0000000..fc76f0f
--- /dev/null
+++ b/openmpt123/openmpt123_config.hpp
@@ -0,0 +1,68 @@
+/*
+ * openmpt123_config.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_CONFIG_HPP
+#define OPENMPT123_CONFIG_HPP
+
+#if defined(HAVE_CONFIG_H)
+// wrapper for autoconf macros
+#include "config.h"
+#endif // HAVE_CONFIG_H
+
+#if defined(_WIN32)
+#ifndef WIN32
+#define WIN32
+#endif
+#endif // _WIN32
+
+#if defined(WIN32)
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+#ifndef UNICODE
+#define UNICODE
+#endif
+#ifndef _UNICODE
+#define _UNICODE
+#endif
+#endif // WIN32
+
+#if defined(WIN32)
+#define MPT_WITH_MMIO
+#endif // WIN32
+
+#if defined(_MSC_VER)
+
+#pragma warning( disable : 4996 ) // 'foo': The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: _foo. See online help for details.
+
+#endif // _MSC_VER
+
+#if defined(MPT_BUILD_MSVC)
+
+#define MPT_WITH_FLAC
+#define MPT_WITH_PORTAUDIO
+
+#if defined(MPT_BUILD_MSVC_STATIC)
+#define FLAC__NO_DLL
+#endif
+
+#endif // MPT_BUILD_MSVC
+
+#if defined(MPT_WITH_SDL)
+#ifndef MPT_NEEDS_THREADS
+#define MPT_NEEDS_THREADS
+#endif
+#endif
+
+#define OPENMPT123_VERSION_STRING OPENMPT_API_VERSION_STRING
+
+#endif // OPENMPT123_CONFIG_HPP
diff --git a/openmpt123/openmpt123_flac.hpp b/openmpt123/openmpt123_flac.hpp
new file mode 100644
index 0000000..037931c
--- /dev/null
+++ b/openmpt123/openmpt123_flac.hpp
@@ -0,0 +1,135 @@
+/*
+ * openmpt123_flac.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_FLAC_HPP
+#define OPENMPT123_FLAC_HPP
+
+#include "openmpt123_config.hpp"
+#include "openmpt123.hpp"
+
+#if defined(MPT_WITH_FLAC)
+
+#if defined(_MSC_VER) && defined(__clang__) && defined(__c2__)
+#include <sys/types.h>
+#if __STDC__
+typedef _off_t off_t;
+#endif
+#endif
+#include <FLAC/metadata.h>
+#include <FLAC/format.h>
+#include <FLAC/stream_encoder.h>
+
+namespace openmpt123 {
+
+class flac_stream_raii : public file_audio_stream_base {
+private:
+ commandlineflags flags;
+ std::string filename;
+ bool called_init;
+ std::vector< std::pair< std::string, std::string > > tags;
+ FLAC__StreamMetadata * flac_metadata[1];
+ FLAC__StreamEncoder * encoder;
+ std::vector<FLAC__int32> interleaved_buffer;
+ void add_vorbiscomment_field( FLAC__StreamMetadata * vorbiscomment, const std::string & field, const std::string & value ) {
+ if ( !value.empty() ) {
+ FLAC__StreamMetadata_VorbisComment_Entry entry;
+ FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair( &entry, field.c_str(), value.c_str() );
+ FLAC__metadata_object_vorbiscomment_append_comment( vorbiscomment, entry, false );
+ }
+ }
+public:
+ flac_stream_raii( const std::string & filename_, const commandlineflags & flags_, std::ostream & /*log*/ ) : flags(flags_), filename(filename_), called_init(false), encoder(0) {
+ flac_metadata[0] = 0;
+ encoder = FLAC__stream_encoder_new();
+ if ( !encoder ) {
+ throw exception( "error creating flac encoder" );
+ }
+ FLAC__stream_encoder_set_channels( encoder, flags.channels );
+ FLAC__stream_encoder_set_bits_per_sample( encoder, flags.use_float ? 24 : 16 );
+ FLAC__stream_encoder_set_sample_rate( encoder, flags.samplerate );
+ FLAC__stream_encoder_set_compression_level( encoder, 8 );
+ }
+ ~flac_stream_raii() {
+ if ( encoder ) {
+ FLAC__stream_encoder_finish( encoder );
+ FLAC__stream_encoder_delete( encoder );
+ encoder = 0;
+ }
+ if ( flac_metadata[0] ) {
+ FLAC__metadata_object_delete( flac_metadata[0] );
+ flac_metadata[0] = 0;
+ }
+ }
+ void write_metadata( std::map<std::string,std::string> metadata ) {
+ if ( called_init ) {
+ return;
+ }
+ tags.clear();
+ tags.push_back( std::make_pair( "TITLE", metadata[ "title" ] ) );
+ tags.push_back( std::make_pair( "ARTIST", metadata[ "artist" ] ) );
+ tags.push_back( std::make_pair( "DATE", metadata[ "date" ] ) );
+ tags.push_back( std::make_pair( "COMMENT", metadata[ "message" ] ) );
+ if ( !metadata[ "type" ].empty() && !metadata[ "tracker" ].empty() ) {
+ tags.push_back( std::make_pair( "SOURCEMEDIA", std::string() + "'" + metadata[ "type" ] + "' tracked music file, made with '" + metadata[ "tracker" ] + "', rendered with '" + get_encoder_tag() + "'" ) );
+ } else if ( !metadata[ "type_long" ].empty() ) {
+ tags.push_back( std::make_pair( "SOURCEMEDIA", std::string() + "'" + metadata[ "type" ] + "' tracked music file, rendered with '" + get_encoder_tag() + "'" ) );
+ } else if ( !metadata[ "tracker" ].empty() ) {
+ tags.push_back( std::make_pair( "SOURCEMEDIA", std::string() + "tracked music file, made with '" + metadata[ "tracker" ] + "', rendered with '" + get_encoder_tag() + "'" ) );
+ } else {
+ tags.push_back( std::make_pair( "SOURCEMEDIA", std::string() + "tracked music file, rendered with '" + get_encoder_tag() + "'" ) );
+ }
+ tags.push_back( std::make_pair( "ENCODER", get_encoder_tag() ) );
+ flac_metadata[0] = FLAC__metadata_object_new( FLAC__METADATA_TYPE_VORBIS_COMMENT );
+ for ( std::vector< std::pair< std::string, std::string > >::iterator tag = tags.begin(); tag != tags.end(); ++tag ) {
+ add_vorbiscomment_field( flac_metadata[0], tag->first, tag->second );
+ }
+ FLAC__stream_encoder_set_metadata( encoder, flac_metadata, 1 );
+ }
+ void write( const std::vector<float*> buffers, std::size_t frames ) {
+ if ( !called_init ) {
+ FLAC__stream_encoder_init_file( encoder, filename.c_str(), NULL, 0 );
+ called_init = true;
+ }
+ interleaved_buffer.clear();
+ for ( std::size_t frame = 0; frame < frames; frame++ ) {
+ for ( std::size_t channel = 0; channel < buffers.size(); channel++ ) {
+ float in = buffers[channel][frame];
+ if ( in <= -1.0f ) {
+ in = -1.0f;
+ } else if ( in >= 1.0f ) {
+ in = 1.0f;
+ }
+ FLAC__int32 out = mpt_lround( in * (1<<23) );
+ out = std::max( 0 - (1<<23), out );
+ out = std::min( out, 0 + (1<<23) - 1 );
+ interleaved_buffer.push_back( out );
+ }
+ }
+ FLAC__stream_encoder_process_interleaved( encoder, interleaved_buffer.data(), static_cast<unsigned int>( frames ) );
+ }
+ void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) {
+ if ( !called_init ) {
+ FLAC__stream_encoder_init_file( encoder, filename.c_str(), NULL, 0 );
+ called_init = true;
+ }
+ interleaved_buffer.clear();
+ for ( std::size_t frame = 0; frame < frames; frame++ ) {
+ for ( std::size_t channel = 0; channel < buffers.size(); channel++ ) {
+ interleaved_buffer.push_back( buffers[channel][frame] );
+ }
+ }
+ FLAC__stream_encoder_process_interleaved( encoder, interleaved_buffer.data(), static_cast<unsigned int>( frames ) );
+ }
+};
+
+} // namespace openmpt123
+
+#endif // MPT_WITH_FLAC
+
+#endif // OPENMPT123_FLAC_HPP
diff --git a/openmpt123/openmpt123_mmio.hpp b/openmpt123/openmpt123_mmio.hpp
new file mode 100644
index 0000000..097d35c
--- /dev/null
+++ b/openmpt123/openmpt123_mmio.hpp
@@ -0,0 +1,138 @@
+/*
+ * openmpt123_mmio.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_MMIO_HPP
+#define OPENMPT123_MMIO_HPP
+
+#include "openmpt123_config.hpp"
+#include "openmpt123.hpp"
+
+#if defined(MPT_WITH_MMIO)
+
+namespace openmpt123 {
+
+#define CHECKED(x) do { \
+ HRESULT err = x; \
+ if ( err != 0 ) { \
+ throw exception( "error writing wave file" ); \
+ } \
+} while(0)
+
+#define UNCHECKED(x) do { \
+ HRESULT err = x; \
+ if ( err != 0 ) { \
+ log << "error writing wave file" << std::endl; \
+ } \
+} while(0)
+
+class mmio_stream_raii : public file_audio_stream_base {
+private:
+ std::ostream & log;
+ commandlineflags flags;
+ WAVEFORMATEX waveformatex;
+ HMMIO mmio;
+ MMCKINFO WAVE_chunk;
+ MMCKINFO fmt__chunk;
+ MMCKINFO data_chunk;
+ MMIOINFO data_info;
+public:
+ mmio_stream_raii( const std::string & filename, const commandlineflags & flags_, std::ostream & log_ ) : log(log_), flags(flags_), mmio(NULL) {
+
+ ZeroMemory( &waveformatex, sizeof( WAVEFORMATEX ) );
+ waveformatex.cbSize = 0;
+ waveformatex.wFormatTag = flags.use_float ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
+ waveformatex.nChannels = flags.channels;
+ waveformatex.nSamplesPerSec = flags.samplerate;
+ waveformatex.wBitsPerSample = flags.use_float ? 32 : 16;
+ waveformatex.nBlockAlign = flags.channels * ( waveformatex.wBitsPerSample / 8 );
+ waveformatex.nAvgBytesPerSec = waveformatex.nSamplesPerSec * waveformatex.nBlockAlign;
+
+ #if defined(WIN32) && defined(UNICODE)
+ wchar_t * tmp = _wcsdup( utf8_to_wstring( filename ).c_str() );
+ mmio = mmioOpen( tmp, NULL, MMIO_ALLOCBUF | MMIO_READWRITE | MMIO_CREATE );
+ free( tmp );
+ tmp = 0;
+ #else
+ char * tmp = strdup( filename.c_str() );
+ mmio = mmioOpen( tmp, NULL, MMIO_ALLOCBUF | MMIO_READWRITE | MMIO_CREATE );
+ free( tmp );
+ tmp = 0;
+ #endif
+
+ ZeroMemory( &WAVE_chunk, sizeof( MMCKINFO ) );
+ WAVE_chunk.fccType = mmioFOURCC('W', 'A', 'V', 'E');
+ CHECKED(mmioCreateChunk( mmio, &WAVE_chunk, MMIO_CREATERIFF ));
+
+ ZeroMemory( &fmt__chunk, sizeof( MMCKINFO ) );
+ fmt__chunk.ckid = mmioFOURCC('f', 'm', 't', ' ');
+ fmt__chunk.cksize = sizeof( WAVEFORMATEX );
+ CHECKED(mmioCreateChunk( mmio, &fmt__chunk, 0 ));
+
+ mmioWrite( mmio, (const char*)&waveformatex, sizeof( WAVEFORMATEX ) );
+
+ CHECKED(mmioAscend( mmio, &fmt__chunk, 0 ));
+
+ ZeroMemory( &data_chunk, sizeof( MMCKINFO ) );
+ data_chunk.ckid = mmioFOURCC('d', 'a', 't', 'a');
+ data_chunk.cksize = 0;
+ CHECKED(mmioCreateChunk( mmio, &data_chunk, 0 ));
+
+ ZeroMemory( &data_info, sizeof( MMIOINFO ) );
+ CHECKED(mmioGetInfo( mmio, &data_info, 0 ));
+
+ }
+
+ void write( const std::vector<float*> buffers, std::size_t frames ) {
+ for ( std::size_t frame = 0; frame < frames; frame++ ) {
+ for ( std::size_t channel = 0; channel < buffers.size(); channel++ ) {
+ if ( data_info.pchEndWrite - data_info.pchNext < static_cast<long>( sizeof( float ) ) ) {
+ data_info.dwFlags |= MMIO_DIRTY;
+ CHECKED(mmioAdvance( mmio, &data_info, MMIO_WRITE ));
+ }
+ std::memcpy( data_info.pchNext, &( buffers[channel][frame] ), sizeof( float ) );
+ data_info.pchNext += sizeof( float );
+ }
+ }
+ }
+
+ void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) {
+ for ( std::size_t frame = 0; frame < frames; frame++ ) {
+ for ( std::size_t channel = 0; channel < buffers.size(); channel++ ) {
+ if ( data_info.pchEndWrite - data_info.pchNext < static_cast<long>( sizeof( std::int16_t ) ) ) {
+ data_info.dwFlags |= MMIO_DIRTY;
+ CHECKED(mmioAdvance( mmio, &data_info, MMIO_WRITE ));
+ }
+ std::memcpy( data_info.pchNext, &( buffers[channel][frame] ), sizeof( std::int16_t ) );
+ data_info.pchNext += sizeof( std::int16_t );
+ }
+ }
+ }
+
+ ~mmio_stream_raii() {
+
+ data_info.dwFlags |= MMIO_DIRTY;
+ UNCHECKED(mmioSetInfo( mmio, &data_info, 0 ));
+
+ UNCHECKED(mmioAscend( mmio, &data_chunk, 0 ));
+
+ UNCHECKED(mmioAscend( mmio, &WAVE_chunk, 0 ));
+
+ UNCHECKED(mmioClose( mmio, 0 ));
+ mmio = NULL;
+
+ }
+};
+
+#undef CHECKED
+
+} // namespace openmpt123
+
+#endif // MPT_WITH_MMIO
+
+#endif // OPENMPT123_MMIO_HPP
diff --git a/openmpt123/openmpt123_portaudio.hpp b/openmpt123/openmpt123_portaudio.hpp
new file mode 100644
index 0000000..e15e2f2
--- /dev/null
+++ b/openmpt123/openmpt123_portaudio.hpp
@@ -0,0 +1,288 @@
+/*
+ * openmpt123_portaudio.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_PORTAUDIO_HPP
+#define OPENMPT123_PORTAUDIO_HPP
+
+#include "openmpt123_config.hpp"
+#include "openmpt123.hpp"
+
+#if defined(MPT_WITH_PORTAUDIO)
+
+#include <portaudio.h>
+
+namespace openmpt123 {
+
+struct portaudio_exception : public exception {
+ portaudio_exception( PaError code ) throw() : exception( Pa_GetErrorText( code ) ) { }
+};
+
+typedef void (*PaUtilLogCallback ) (const char *log);
+#ifdef _MSC_VER
+extern "C" void PaUtil_SetDebugPrintFunction(PaUtilLogCallback cb);
+#endif
+
+class portaudio_raii {
+private:
+ std::ostream & log;
+ bool log_set;
+ bool portaudio_initialized;
+ static std::ostream * portaudio_log_stream;
+private:
+ static void portaudio_log_function( const char * log ) {
+ if ( portaudio_log_stream ) {
+ *portaudio_log_stream << "PortAudio: " << log;
+ }
+ }
+protected:
+ void check_portaudio_error( PaError e ) {
+ if ( e > 0 ) {
+ return;
+ }
+ if ( e == paNoError ) {
+ return;
+ }
+ if ( e == paOutputUnderflowed ) {
+ log << "PortAudio warning: " << Pa_GetErrorText( e ) << std::endl;
+ return;
+ }
+ throw portaudio_exception( e );
+ }
+public:
+ portaudio_raii( bool verbose, std::ostream & log ) : log(log), log_set(false), portaudio_initialized(false) {
+ if ( verbose ) {
+ portaudio_log_stream = &log;
+ } else {
+ portaudio_log_stream = 0;
+ }
+#ifdef _MSC_VER
+ PaUtil_SetDebugPrintFunction( portaudio_log_function );
+ log_set = true;
+#endif
+ check_portaudio_error( Pa_Initialize() );
+ portaudio_initialized = true;
+ if ( verbose ) {
+ *portaudio_log_stream << std::endl;
+ }
+ }
+ ~portaudio_raii() {
+ if ( portaudio_initialized ) {
+ check_portaudio_error( Pa_Terminate() );
+ portaudio_initialized = false;
+ }
+ if ( log_set ) {
+#ifdef _MSC_VER
+ PaUtil_SetDebugPrintFunction( NULL );
+ log_set = false;
+#endif
+ }
+ portaudio_log_stream = 0;
+ }
+};
+
+std::ostream * portaudio_raii::portaudio_log_stream = 0;
+
+class portaudio_stream_blocking_raii : public portaudio_raii, public write_buffers_interface {
+private:
+ PaStream * stream;
+ bool interleaved;
+ std::size_t channels;
+ std::vector<float> sampleBufFloat;
+ std::vector<std::int16_t> sampleBufInt;
+public:
+ portaudio_stream_blocking_raii( commandlineflags & flags, std::ostream & log )
+ : portaudio_raii(flags.verbose, log)
+ , stream(NULL)
+ , interleaved(false)
+ , channels(flags.channels)
+ {
+ PaStreamParameters streamparameters;
+ std::memset( &streamparameters, 0, sizeof(PaStreamParameters) );
+ std::istringstream device_string( flags.device );
+ int device = -1;
+ device_string >> device;
+ streamparameters.device = ( device == -1 ) ? Pa_GetDefaultOutputDevice() : device;
+ streamparameters.channelCount = flags.channels;
+ streamparameters.sampleFormat = ( flags.use_float ? paFloat32 : paInt16 ) | paNonInterleaved;
+ if ( flags.buffer == default_high ) {
+ streamparameters.suggestedLatency = Pa_GetDeviceInfo( streamparameters.device )->defaultHighOutputLatency;
+ flags.buffer = static_cast<std::int32_t>( Pa_GetDeviceInfo( streamparameters.device )->defaultHighOutputLatency * 1000.0 );
+ } else if ( flags.buffer == default_low ) {
+ streamparameters.suggestedLatency = Pa_GetDeviceInfo( streamparameters.device )->defaultLowOutputLatency;
+ flags.buffer = static_cast<std::int32_t>( Pa_GetDeviceInfo( streamparameters.device )->defaultLowOutputLatency * 1000.0 );
+ } else {
+ streamparameters.suggestedLatency = flags.buffer * 0.001;
+ }
+ unsigned long framesperbuffer = 0;
+ if ( flags.mode != ModeUI ) {
+ framesperbuffer = paFramesPerBufferUnspecified;
+ flags.period = 50;
+ flags.period = std::min<std::int32_t>( flags.period, flags.buffer / 3 );
+ } else if ( flags.period == default_high ) {
+ framesperbuffer = paFramesPerBufferUnspecified;
+ flags.period = 50;
+ flags.period = std::min<std::int32_t>( flags.period, flags.buffer / 3 );
+ } else if ( flags.period == default_low ) {
+ framesperbuffer = paFramesPerBufferUnspecified;
+ flags.period = 10;
+ flags.period = std::min<std::int32_t>( flags.period, flags.buffer / 3 );
+ } else {
+ framesperbuffer = flags.period * flags.samplerate / 1000;
+ }
+ if ( flags.period <= 0 ) {
+ flags.period = 1;
+ }
+ flags.apply_default_buffer_sizes();
+ if ( flags.verbose ) {
+ log << "PortAudio:" << std::endl;
+ log << " device: "
+ << streamparameters.device
+ << " [ " << Pa_GetHostApiInfo( Pa_GetDeviceInfo( streamparameters.device )->hostApi )->name << " / " << Pa_GetDeviceInfo( streamparameters.device )->name << " ] "
+ << std::endl;
+ log << " low latency: " << Pa_GetDeviceInfo( streamparameters.device )->defaultLowOutputLatency << std::endl;
+ log << " high latency: " << Pa_GetDeviceInfo( streamparameters.device )->defaultHighOutputLatency << std::endl;
+ log << " suggested latency: " << streamparameters.suggestedLatency << std::endl;
+ log << " frames per buffer: " << framesperbuffer << std::endl;
+ log << " ui redraw: " << flags.period << std::endl;
+ }
+ PaError e = PaError();
+ e = Pa_OpenStream( &stream, NULL, &streamparameters, flags.samplerate, framesperbuffer, ( flags.dither > 0 ) ? paNoFlag : paDitherOff, NULL, NULL );
+ if ( e != paNoError ) {
+ // Non-interleaved failed, try interleaved next.
+ // This might help broken portaudio on MacOS X.
+ streamparameters.sampleFormat &= ~paNonInterleaved;
+ e = Pa_OpenStream( &stream, NULL, &streamparameters, flags.samplerate, framesperbuffer, ( flags.dither > 0 ) ? paNoFlag : paDitherOff, NULL, NULL );
+ if ( e == paNoError ) {
+ interleaved = true;
+ }
+ check_portaudio_error( e );
+ }
+ check_portaudio_error( Pa_StartStream( stream ) );
+ if ( flags.verbose ) {
+ log << " channels: " << streamparameters.channelCount << std::endl;
+ log << " sampleformat: " << ( ( ( streamparameters.sampleFormat & ~paNonInterleaved ) == paFloat32 ) ? "paFloat32" : "paInt16" ) << std::endl;
+ log << " latency: " << Pa_GetStreamInfo( stream )->outputLatency << std::endl;
+ log << " samplerate: " << Pa_GetStreamInfo( stream )->sampleRate << std::endl;
+ log << std::endl;
+ }
+ }
+ ~portaudio_stream_blocking_raii() {
+ if ( stream ) {
+ PaError stopped = Pa_IsStreamStopped( stream );
+ check_portaudio_error( stopped );
+ if ( !stopped ) {
+ check_portaudio_error( Pa_StopStream( stream ) );
+ }
+ check_portaudio_error( Pa_CloseStream( stream ) );
+ stream = NULL;
+ }
+ }
+private:
+ template<typename Tsample>
+ void write_frames( const Tsample * buffer, std::size_t frames ) {
+ while ( frames > 0 ) {
+ unsigned long chunk_frames = static_cast<unsigned long>( std::min<std::size_t>( frames, std::numeric_limits<unsigned long>::max() ) );
+ check_portaudio_error( Pa_WriteStream( stream, buffer, chunk_frames ) );
+ buffer += chunk_frames * channels;
+ frames -= chunk_frames;
+ }
+ }
+ template<typename Tsample>
+ void write_frames( std::vector<Tsample*> buffers, std::size_t frames ) {
+ while ( frames > 0 ) {
+ unsigned long chunk_frames = static_cast<unsigned long>( std::min<std::size_t>( frames, std::numeric_limits<unsigned long>::max() ) );
+ check_portaudio_error( Pa_WriteStream( stream, buffers.data(), chunk_frames ) );
+ for ( std::size_t channel = 0; channel < channels; ++channel ) {
+ buffers[channel] += chunk_frames;
+ }
+ frames -= chunk_frames;
+ }
+ }
+public:
+ void write( const std::vector<float*> buffers, std::size_t frames ) {
+ if ( interleaved ) {
+ sampleBufFloat.clear();
+ for ( std::size_t frame = 0; frame < frames; ++frame ) {
+ for ( std::size_t channel = 0; channel < channels; ++channel ) {
+ sampleBufFloat.push_back( buffers[channel][frame] );
+ }
+ }
+ write_frames( sampleBufFloat.data(), frames );
+ } else {
+ write_frames( buffers, frames );
+ }
+ }
+ void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) {
+ if ( interleaved ) {
+ sampleBufInt.clear();
+ for ( std::size_t frame = 0; frame < frames; ++frame ) {
+ for ( std::size_t channel = 0; channel < channels; ++channel ) {
+ sampleBufInt.push_back( buffers[channel][frame] );
+ }
+ }
+ write_frames( sampleBufInt.data(), frames );
+ } else {
+ write_frames( buffers, frames );
+ }
+ }
+ bool unpause() {
+ check_portaudio_error( Pa_StartStream( stream ) );
+ return true;
+ }
+ bool pause() {
+ check_portaudio_error( Pa_StopStream( stream ) );
+ return true;
+ }
+ bool sleep( int ms ) {
+ Pa_Sleep( ms );
+ return true;
+ }
+};
+
+#define portaudio_stream_raii portaudio_stream_blocking_raii
+
+static std::string show_portaudio_devices( std::ostream & log ) {
+ std::ostringstream devices;
+ devices << " portaudio:" << std::endl;
+ portaudio_raii portaudio( false, log );
+ for ( PaDeviceIndex i = 0; i < Pa_GetDeviceCount(); ++i ) {
+ if ( Pa_GetDeviceInfo( i ) && Pa_GetDeviceInfo( i )->maxOutputChannels > 0 ) {
+ devices << " " << i << ": ";
+ if ( Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi ) && Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi )->name ) {
+ devices << Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi )->name;
+ } else {
+ devices << "Host API " << Pa_GetDeviceInfo( i )->hostApi;
+ }
+ if ( Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi ) ) {
+ if ( i == Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi )->defaultOutputDevice ) {
+ devices << " (default)";
+ }
+ }
+ devices << " - ";
+ if ( Pa_GetDeviceInfo( i )->name ) {
+ devices << Pa_GetDeviceInfo( i )->name;
+ } else {
+ devices << "Device " << i;
+ }
+ devices << " (";
+ devices << "high latency: " << Pa_GetDeviceInfo( i )->defaultHighOutputLatency;
+ devices << ", ";
+ devices << "low latency: " << Pa_GetDeviceInfo( i )->defaultLowOutputLatency;
+ devices << ")";
+ devices << std::endl;
+ }
+ }
+ return devices.str();
+}
+
+} // namespace openmpt123
+
+#endif // MPT_WITH_PORTAUDIO
+
+#endif // OPENMPT123_PORTAUDIO_HPP
diff --git a/openmpt123/openmpt123_pulseaudio.hpp b/openmpt123/openmpt123_pulseaudio.hpp
new file mode 100644
index 0000000..f6d7cba
--- /dev/null
+++ b/openmpt123/openmpt123_pulseaudio.hpp
@@ -0,0 +1,166 @@
+/*
+ * openmpt123_pulseaudio.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_PULSEAUDIO_HPP
+#define OPENMPT123_PULSEAUDIO_HPP
+
+#include "openmpt123_config.hpp"
+#include "openmpt123.hpp"
+
+#if defined(MPT_WITH_PULSEAUDIO)
+
+#include <pulse/pulseaudio.h>
+#include <pulse/simple.h>
+
+namespace openmpt123 {
+
+struct pulseaudio_exception : public exception {
+ static std::string error_to_string( int error ) {
+ try {
+ if ( error == 0 ) {
+ return std::string();
+ }
+ std::ostringstream e;
+ const char * str = pa_strerror( error );
+ if ( !str ) {
+ e << "error=" << error;
+ return e.str();
+ }
+ if ( std::strlen(str) == 0 ) {
+ e << "error=" << error;
+ return e.str();
+ }
+ e << str << " (error=" << error << ")";
+ return e.str();
+ } catch ( const std::bad_alloc & ) {
+ return std::string();
+ }
+ }
+ pulseaudio_exception( int error ) throw() : exception( error_to_string( error ) ) { }
+};
+
+class pulseaudio_stream_raii : public write_buffers_interface {
+private:
+ pa_simple * stream;
+ std::size_t channels;
+ std::size_t sampleSize;
+ std::vector<float> sampleBufFloat;
+ std::vector<std::int16_t> sampleBufInt;
+public:
+ pulseaudio_stream_raii( commandlineflags & flags, std::ostream & /* log */ )
+ : stream(NULL)
+ , channels(flags.channels)
+ , sampleSize(flags.use_float ? sizeof( float ) : sizeof( std::int16_t ))
+ {
+ int error = 0;
+ pa_sample_spec ss;
+ std::memset( &ss, 0, sizeof( pa_sample_spec ) );
+ ss.format = ( flags.use_float ? PA_SAMPLE_FLOAT32 : PA_SAMPLE_S16NE );
+ ss.rate = flags.samplerate;
+ ss.channels = flags.channels;
+ pa_buffer_attr ba;
+ std::memset( &ba, 0, sizeof( pa_buffer_attr ) );
+ bool use_ba = false;
+ if ( flags.buffer != default_high && flags.buffer != default_low ) {
+ use_ba = true;
+ ba.maxlength = channels * sampleSize * ( flags.buffer * flags.samplerate / 1000 );
+ } else {
+ ba.maxlength = static_cast<std::uint32_t>(-1);
+ }
+ if ( flags.period != default_high && flags.period != default_low ) {
+ use_ba = true;
+ ba.minreq = channels * sampleSize * ( flags.period * flags.samplerate / 1000 );
+ if ( ba.maxlength != static_cast<std::uint32_t>(-1) ) {
+ ba.tlength = ba.maxlength - ba.minreq;
+ ba.prebuf = ba.tlength;
+ } else {
+ ba.tlength = static_cast<std::uint32_t>(-1);
+ ba.prebuf = static_cast<std::uint32_t>(-1);
+ }
+ } else {
+ ba.minreq = static_cast<std::uint32_t>(-1);
+ ba.tlength = static_cast<std::uint32_t>(-1);
+ ba.prebuf = static_cast<std::uint32_t>(-1);
+ }
+ ba.fragsize = 0;
+ flags.apply_default_buffer_sizes();
+ sampleBufFloat.resize( channels * ( flags.ui_redraw_interval * flags.samplerate / 1000 ) );
+ sampleBufInt.resize( channels * ( flags.ui_redraw_interval * flags.samplerate / 1000 ) );
+ stream = pa_simple_new( NULL, "openmpt123", PA_STREAM_PLAYBACK, NULL, "openmpt123", &ss, NULL, ( use_ba ? &ba : NULL ), &error );
+ if ( !stream ) {
+ throw pulseaudio_exception( error );
+ }
+ }
+ ~pulseaudio_stream_raii() {
+ int error = 0;
+ if ( stream ) {
+ error = 0;
+ if ( pa_simple_drain( stream, &error ) < 0 ) {
+ // throw pulseaudio_exception( error );
+ }
+ pa_simple_free( stream );
+ stream = NULL;
+ }
+ }
+private:
+ template<typename Tsample>
+ void write_frames( const Tsample * buffer, std::size_t frames ) {
+ int error = 0;
+ if ( pa_simple_write( stream, buffer, frames * channels * sampleSize, &error ) < 0 ) {
+ throw pulseaudio_exception( error );
+ }
+ }
+public:
+ void write( const std::vector<float*> buffers, std::size_t frames ) {
+ sampleBufFloat.clear();
+ for ( std::size_t frame = 0; frame < frames; ++frame ) {
+ for ( std::size_t channel = 0; channel < channels; ++channel ) {
+ sampleBufFloat.push_back( buffers[channel][frame] );
+ }
+ }
+ write_frames( sampleBufFloat.data(), frames );
+ }
+ void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) {
+ sampleBufInt.clear();
+ for ( std::size_t frame = 0; frame < frames; ++frame ) {
+ for ( std::size_t channel = 0; channel < channels; ++channel ) {
+ sampleBufInt.push_back( buffers[channel][frame] );
+ }
+ }
+ write_frames( sampleBufInt.data(), frames );
+ }
+ bool unpause() {
+ return true;
+ }
+ bool pause() {
+ int error = 0;
+ error = 0;
+ if ( pa_simple_drain( stream, &error ) < 0 ) {
+ throw pulseaudio_exception( error );
+ }
+ return true;
+ }
+ bool sleep( int ms ) {
+ pa_msleep( ms );
+ return true;
+ }
+};
+
+static std::string show_pulseaudio_devices( std::ostream & /* log */ ) {
+ std::ostringstream devices;
+ devices << " pulseaudio:" << std::endl;
+ devices << " " << "0" << ": Default Device" << std::endl;
+ return devices.str();
+}
+
+} // namespace openmpt123
+
+#endif // MPT_WITH_PULSEAUDIO
+
+#endif // OPENMPT123_PULSEAUDIO_HPP
diff --git a/openmpt123/openmpt123_raw.hpp b/openmpt123/openmpt123_raw.hpp
new file mode 100644
index 0000000..25b2e25
--- /dev/null
+++ b/openmpt123/openmpt123_raw.hpp
@@ -0,0 +1,58 @@
+/*
+ * openmpt123_raw.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_RAW_HPP
+#define OPENMPT123_RAW_HPP
+
+#include "openmpt123_config.hpp"
+#include "openmpt123.hpp"
+
+#include <fstream>
+
+namespace openmpt123 {
+
+class raw_stream_raii : public file_audio_stream_base {
+private:
+ commandlineflags flags;
+ std::ofstream file;
+ std::vector<float> interleaved_float_buffer;
+ std::vector<std::int16_t> interleaved_int_buffer;
+public:
+ raw_stream_raii( const std::string & filename, const commandlineflags & flags_, std::ostream & /*log*/ ) : flags(flags_), file(filename.c_str(), std::ios::binary) {
+ return;
+ }
+ ~raw_stream_raii() {
+ return;
+ }
+ void write_metadata( std::map<std::string,std::string> /* metadata */ ) {
+ return;
+ }
+ void write( const std::vector<float*> buffers, std::size_t frames ) {
+ interleaved_float_buffer.clear();
+ for ( std::size_t frame = 0; frame < frames; frame++ ) {
+ for ( std::size_t channel = 0; channel < buffers.size(); channel++ ) {
+ interleaved_float_buffer.push_back( buffers[channel][frame] );
+ }
+ }
+ file.write( reinterpret_cast<const char *>( interleaved_float_buffer.data() ), frames * buffers.size() * sizeof( float ) );
+ }
+ void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) {
+ interleaved_int_buffer.clear();
+ for ( std::size_t frame = 0; frame < frames; frame++ ) {
+ for ( std::size_t channel = 0; channel < buffers.size(); channel++ ) {
+ interleaved_int_buffer.push_back( buffers[channel][frame] );
+ }
+ }
+ file.write( reinterpret_cast<const char *>( interleaved_int_buffer.data() ), frames * buffers.size() * sizeof( std::int16_t ) );
+ }
+};
+
+} // namespace openmpt123
+
+#endif // OPENMPT123_RAW_HPP
diff --git a/openmpt123/openmpt123_sdl.hpp b/openmpt123/openmpt123_sdl.hpp
new file mode 100644
index 0000000..2dba5b2
--- /dev/null
+++ b/openmpt123/openmpt123_sdl.hpp
@@ -0,0 +1,133 @@
+/*
+ * openmpt123_sdl.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_SDL_HPP
+#define OPENMPT123_SDL_HPP
+
+#include "openmpt123_config.hpp"
+#include "openmpt123.hpp"
+
+#if defined(MPT_WITH_SDL)
+
+#include <SDL.h>
+#ifdef main
+#undef main
+#endif
+#ifdef SDL_main
+#undef SDL_main
+#endif
+
+namespace openmpt123 {
+
+struct sdl_exception : public exception {
+ sdl_exception( int /*code*/ ) throw() : exception( "SDL error" ) { }
+};
+
+class sdl_stream_raii : public write_buffers_blocking_wrapper {
+private:
+ std::ostream & log;
+ std::size_t channels;
+protected:
+ void check_sdl_error( int e ) {
+ if ( e < 0 ) {
+ throw sdl_exception( e );
+ return;
+ }
+ }
+ std::uint32_t round_up_power2(std::uint32_t x)
+ {
+ std::uint32_t result = 1;
+ while ( result < x ) {
+ result *= 2;
+ }
+ return result;
+ }
+public:
+ sdl_stream_raii( commandlineflags & flags, std::ostream & log_ )
+ : write_buffers_blocking_wrapper(flags)
+ , log(log_)
+ , channels(flags.channels)
+ {
+ if ( flags.buffer == default_high ) {
+ flags.buffer = 160;
+ } else if ( flags.buffer == default_low ) {
+ flags.buffer = 80;
+ }
+ if ( flags.period == default_high ) {
+ flags.period = 20;
+ } else if ( flags.period == default_low ) {
+ flags.period = 10;
+ }
+ flags.apply_default_buffer_sizes();
+ check_sdl_error( SDL_Init( SDL_INIT_NOPARACHUTE | SDL_INIT_TIMER | SDL_INIT_AUDIO ) );
+ SDL_AudioSpec audiospec;
+ std::memset( &audiospec, 0, sizeof( SDL_AudioSpec ) );
+ audiospec.freq = flags.samplerate;
+ audiospec.format = AUDIO_S16SYS;
+ audiospec.channels = flags.channels;
+ audiospec.silence = 0;
+ audiospec.samples = round_up_power2( ( flags.buffer * flags.samplerate ) / ( 1000 * 2 ) );
+ audiospec.size = audiospec.samples * audiospec.channels * sizeof( std::int16_t );
+ audiospec.callback = &sdl_callback_wrapper;
+ audiospec.userdata = this;
+ if ( flags.verbose ) {
+ log << "SDL:" << std::endl;
+ log << " latency: " << ( audiospec.samples * 2.0 / flags.samplerate ) << " (2 * " << audiospec.samples << ")" << std::endl;
+ log << std::endl;
+ }
+ set_queue_size_frames( round_up_power2( ( flags.buffer * flags.samplerate ) / ( 1000 * 2 ) ) );
+ check_sdl_error( SDL_OpenAudio( &audiospec, NULL ) );
+ SDL_PauseAudio( 0 );
+ }
+ ~sdl_stream_raii() {
+ SDL_PauseAudio( 1 );
+ SDL_CloseAudio();
+ SDL_Quit();
+ }
+private:
+ static void sdl_callback_wrapper( void * userdata, Uint8 * stream, int len ) {
+ return reinterpret_cast<sdl_stream_raii*>( userdata )->sdl_callback( stream, len );
+ }
+ void sdl_callback( Uint8 * stream, int len ) {
+ std::size_t framesToRender = len / sizeof( std::int16_t ) / channels;
+ for ( std::size_t frame = 0; frame < framesToRender; ++frame ) {
+ for ( std::size_t channel = 0; channel < channels; ++channel ) {
+ std::int16_t sample = pop_queue<std::int16_t>();
+ std::memcpy( stream, &sample, sizeof( std::int16_t ) );
+ stream += sizeof( std::int16_t );
+ }
+ }
+ }
+public:
+ bool pause() {
+ SDL_PauseAudio( 1 );
+ return true;
+ }
+ bool unpause() {
+ SDL_PauseAudio( 0 );
+ return true;
+ }
+ void lock() {
+ SDL_LockAudio();
+ }
+ void unlock() {
+ SDL_UnlockAudio();
+ }
+ bool sleep( int ms ) {
+ SDL_Delay( ms );
+ return true;
+ }
+};
+
+} // namespace openmpt123
+
+#endif // MPT_WITH_SDL
+
+#endif // OPENMPT123_SDL_HPP
+
diff --git a/openmpt123/openmpt123_sdl2.hpp b/openmpt123/openmpt123_sdl2.hpp
new file mode 100644
index 0000000..a16b55b
--- /dev/null
+++ b/openmpt123/openmpt123_sdl2.hpp
@@ -0,0 +1,192 @@
+/*
+ * openmpt123_sdl2.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_SDL2_HPP
+#define OPENMPT123_SDL2_HPP
+
+#include "openmpt123_config.hpp"
+#include "openmpt123.hpp"
+
+#if defined(MPT_WITH_SDL2)
+
+#include <SDL.h>
+#ifdef main
+#undef main
+#endif
+#ifdef SDL_main
+#undef SDL_main
+#endif
+
+namespace openmpt123 {
+
+struct sdl2_exception : public exception {
+ sdl2_exception( int /*code*/ ) throw() : exception( "SDL2 error" ) { }
+};
+
+static void check_sdl2_error( int e ) {
+ if ( e < 0 ) {
+ throw sdl2_exception( e );
+ return;
+ }
+}
+
+class sdl2_raii {
+public:
+ sdl2_raii( Uint32 flags ) {
+ check_sdl2_error( SDL_Init( flags ) );
+ }
+ ~sdl2_raii() {
+ SDL_Quit();
+ }
+};
+
+class sdl2_stream_raii : public write_buffers_blocking_wrapper {
+private:
+ std::ostream & log;
+ sdl2_raii sdl2;
+ int dev;
+ std::size_t channels;
+ bool use_float;
+protected:
+ std::uint32_t round_up_power2(std::uint32_t x)
+ {
+ std::uint32_t result = 1;
+ while ( result < x ) {
+ result *= 2;
+ }
+ return result;
+ }
+public:
+ sdl2_stream_raii( commandlineflags & flags, std::ostream & log_ )
+ : write_buffers_blocking_wrapper(flags)
+ , log(log_)
+ , sdl2( SDL_INIT_NOPARACHUTE | SDL_INIT_TIMER | SDL_INIT_AUDIO )
+ , dev(-1)
+ , channels(flags.channels)
+ , use_float(flags.use_float)
+ {
+ if ( flags.buffer == default_high ) {
+ flags.buffer = 160;
+ } else if ( flags.buffer == default_low ) {
+ flags.buffer = 80;
+ }
+ if ( flags.period == default_high ) {
+ flags.period = 20;
+ } else if ( flags.period == default_low ) {
+ flags.period = 10;
+ }
+ flags.apply_default_buffer_sizes();
+ SDL_AudioSpec audiospec;
+ std::memset( &audiospec, 0, sizeof( SDL_AudioSpec ) );
+ audiospec.freq = flags.samplerate;
+ audiospec.format = ( flags.use_float ? AUDIO_F32SYS : AUDIO_S16SYS );
+ audiospec.channels = flags.channels;
+ audiospec.silence = 0;
+ audiospec.samples = round_up_power2( ( flags.buffer * flags.samplerate ) / ( 1000 * 2 ) );
+ audiospec.size = audiospec.samples * audiospec.channels * ( flags.use_float ? sizeof( float ) : sizeof( std::int16_t ) );
+ audiospec.callback = &sdl2_callback_wrapper;
+ audiospec.userdata = this;
+ if ( flags.verbose ) {
+ log << "SDL2:" << std::endl;
+ log << " latency: " << ( audiospec.samples * 2.0 / flags.samplerate ) << " (2 * " << audiospec.samples << ")" << std::endl;
+ log << std::endl;
+ }
+ set_queue_size_frames( round_up_power2( ( flags.buffer * flags.samplerate ) / ( 1000 * 2 ) ) );
+ SDL_AudioSpec audiospec_obtained;
+ std::memset( &audiospec_obtained, 0, sizeof( SDL_AudioSpec ) );
+ std::memcpy( &audiospec_obtained, &audiospec, sizeof( SDL_AudioSpec ) );
+ dev = SDL_OpenAudioDevice( NULL, 0, &audiospec, &audiospec_obtained, 0 );
+ if ( dev < 0 ) {
+ check_sdl2_error( dev );
+ } else if ( dev == 0 ) {
+ check_sdl2_error( -1 );
+ }
+ SDL_PauseAudioDevice( dev, 0 );
+ }
+ ~sdl2_stream_raii() {
+ SDL_PauseAudioDevice( dev, 1 );
+ SDL_CloseAudioDevice( dev );
+ }
+private:
+ static void sdl2_callback_wrapper( void * userdata, Uint8 * stream, int len ) {
+ return reinterpret_cast<sdl2_stream_raii*>( userdata )->sdl2_callback( stream, len );
+ }
+ void sdl2_callback( Uint8 * stream, int len ) {
+ return ( use_float ? sdl2_callback_impl<float>( stream, len ) : sdl2_callback_impl<std::int16_t>( stream, len ) );
+ }
+ template < typename Tsample >
+ void sdl2_callback_impl( Uint8 * stream, int len ) {
+ std::size_t framesToRender = len / sizeof( Tsample ) / channels;
+ for ( std::size_t frame = 0; frame < framesToRender; ++frame ) {
+ for ( std::size_t channel = 0; channel < channels; ++channel ) {
+ Tsample sample = pop_queue<Tsample>();
+ std::memcpy( stream, &sample, sizeof( Tsample ) );
+ stream += sizeof( Tsample );
+ }
+ }
+ }
+public:
+ bool pause() {
+ SDL_PauseAudioDevice( dev, 1 );
+ return true;
+ }
+ bool unpause() {
+ SDL_PauseAudioDevice( dev, 0 );
+ return true;
+ }
+ void lock() {
+ SDL_LockAudioDevice( dev );
+ }
+ void unlock() {
+ SDL_UnlockAudioDevice( dev );
+ }
+ bool sleep( int ms ) {
+ SDL_Delay( ms );
+ return true;
+ }
+};
+
+static std::string show_sdl2_devices( std::ostream & /* log */ ) {
+ std::ostringstream devices;
+ std::size_t device_index = 0;
+ devices << " SDL2:" << std::endl;
+ sdl2_raii sdl2( SDL_INIT_NOPARACHUTE | SDL_INIT_AUDIO );
+ for ( int driver = 0; driver < SDL_GetNumAudioDrivers(); ++driver ) {
+ const char * driver_name = SDL_GetAudioDriver( driver );
+ if ( !driver_name ) {
+ continue;
+ }
+ if ( std::string( driver_name ).empty() ) {
+ continue;
+ }
+ if ( SDL_AudioInit( driver_name ) < 0 ) {
+ continue;
+ }
+ for ( int device = 0; device < SDL_GetNumAudioDevices( 0 ); ++device ) {
+ const char * device_name = SDL_GetAudioDeviceName( device, 0 );
+ if ( !device_name ) {
+ continue;
+ }
+ if ( std::string( device_name ).empty() ) {
+ continue;
+ }
+ devices << " " << device_index << ": " << driver_name << " - " << device_name << std::endl;
+ device_index++;
+ }
+ SDL_AudioQuit();
+ }
+ return devices.str();
+}
+
+} // namespace openmpt123
+
+#endif // MPT_WITH_SDL2
+
+#endif // OPENMPT123_SDL2_HPP
+
diff --git a/openmpt123/openmpt123_sndfile.hpp b/openmpt123/openmpt123_sndfile.hpp
new file mode 100644
index 0000000..533d2c2
--- /dev/null
+++ b/openmpt123/openmpt123_sndfile.hpp
@@ -0,0 +1,208 @@
+/*
+ * openmpt123_sndfile.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_SNDFILE_HPP
+#define OPENMPT123_SNDFILE_HPP
+
+#include "openmpt123_config.hpp"
+#include "openmpt123.hpp"
+
+#if defined(MPT_WITH_SNDFILE)
+
+#include <sndfile.h>
+
+namespace openmpt123 {
+
+class sndfile_stream_raii : public file_audio_stream_base {
+private:
+ commandlineflags flags;
+ std::ostream & log;
+ SNDFILE * sndfile;
+ std::vector<float> interleaved_float_buffer;
+ std::vector<std::int16_t> interleaved_int_buffer;
+private:
+ enum match_mode_enum {
+ match_print,
+ match_recurse,
+ match_exact,
+ match_better,
+ match_any
+ };
+ std::string match_mode_to_string( match_mode_enum match_mode ) {
+ switch ( match_mode ) {
+ case match_print : return "print" ; break;
+ case match_recurse: return "recurse"; break;
+ case match_exact : return "exact" ; break;
+ case match_better : return "better" ; break;
+ case match_any : return "any" ; break;
+ }
+ return "";
+ }
+ int matched_result( const SF_FORMAT_INFO & format_info, const SF_FORMAT_INFO & subformat_info, match_mode_enum match_mode ) {
+ if ( flags.verbose ) {
+ log << "sndfile: using format '"
+ << format_info.name << " (" << format_info.extension << ")" << " / " << subformat_info.name
+ << "', "
+ << "match: " << match_mode_to_string( match_mode )
+ << std::endl;
+ }
+ return ( format_info.format & SF_FORMAT_TYPEMASK ) | subformat_info.format;
+ }
+ int find_format( const std::string & extension, match_mode_enum match_mode ) {
+
+ if ( match_mode == match_recurse ) {
+ int result = 0;
+ result = find_format( extension, match_exact );
+ if ( result ) {
+ return result;
+ }
+ result = find_format( extension, match_better );
+ if ( result ) {
+ return result;
+ }
+ result = find_format( extension, match_any );
+ if ( result ) {
+ return result;
+ }
+ if ( result ) {
+ return result;
+ }
+ return 0;
+ }
+
+ int format = 0;
+ int major_count;
+ sf_command( 0, SFC_GET_FORMAT_MAJOR_COUNT, &major_count, sizeof( int ) );
+ for ( int m = 0; m < major_count; m++ ) {
+
+ SF_FORMAT_INFO format_info;
+ format_info.format = m;
+ sf_command( 0, SFC_GET_FORMAT_MAJOR, &format_info, sizeof( SF_FORMAT_INFO ) );
+ format = format_info.format;
+
+ int subtype_count;
+ sf_command( 0, SFC_GET_FORMAT_SUBTYPE_COUNT, &subtype_count, sizeof( int ) );
+ for ( int s = 0; s < subtype_count; s++ ) {
+
+ SF_FORMAT_INFO subformat_info;
+ subformat_info.format = s;
+ sf_command( 0, SFC_GET_FORMAT_SUBTYPE, &subformat_info, sizeof( SF_FORMAT_INFO ) );
+ format = ( format & SF_FORMAT_TYPEMASK ) | subformat_info.format;
+
+ SF_INFO sfinfo;
+ std::memset( &sfinfo, 0, sizeof( SF_INFO ) );
+ sfinfo.channels = flags.channels;
+ sfinfo.format = format;
+ if ( sf_format_check( &sfinfo ) ) {
+
+ switch ( match_mode ) {
+ case match_print:
+ log << "sndfile: "
+ << ( format_info.name ? format_info.name : "" ) << " (" << ( format_info.extension ? format_info.extension : "" ) << ")"
+ << " / "
+ << ( subformat_info.name ? subformat_info.name : "" )
+ << " ["
+ << std::setbase(16) << std::setw(8) << std::setfill('0') << format_info.format
+ << "|"
+ << std::setbase(16) << std::setw(8) << std::setfill('0') << subformat_info.format
+ << "]"
+ << std::endl;
+ break;
+ case match_recurse:
+ break;
+ case match_exact:
+ if ( extension == format_info.extension ) {
+ if ( flags.use_float && ( subformat_info.format == SF_FORMAT_FLOAT ) ) {
+ return matched_result( format_info, subformat_info, match_mode );
+ } else if ( !flags.use_float && ( subformat_info.format == SF_FORMAT_PCM_16 ) ) {
+ return matched_result( format_info, subformat_info, match_mode );
+ }
+ }
+ break;
+ case match_better:
+ if ( extension == format_info.extension ) {
+ if ( flags.use_float && ( subformat_info.format == SF_FORMAT_FLOAT || subformat_info.format == SF_FORMAT_DOUBLE ) ) {
+ return matched_result( format_info, subformat_info, match_mode );
+ } else if ( !flags.use_float && ( subformat_info.format & ( subformat_info.format == SF_FORMAT_PCM_16 || subformat_info.format == SF_FORMAT_PCM_24 || subformat_info.format == SF_FORMAT_PCM_32 ) ) ) {
+ return matched_result( format_info, subformat_info, match_mode );
+ }
+ }
+ break;
+ case match_any:
+ if ( extension == format_info.extension ) {
+ return matched_result( format_info, subformat_info, match_mode );
+ }
+ break;
+ }
+
+ }
+ }
+ }
+
+ return 0;
+
+ }
+ void write_metadata_field( int str_type, const std::string & str ) {
+ if ( !str.empty() ) {
+ sf_set_string( sndfile, str_type, str.c_str() );
+ }
+ }
+public:
+ sndfile_stream_raii( const std::string & filename, const commandlineflags & flags_, std::ostream & log_ ) : flags(flags_), log(log_), sndfile(0) {
+ if ( flags.verbose ) {
+ find_format( "", match_print );
+ log << std::endl;
+ }
+ int format = find_format( flags.output_extension, match_recurse );
+ if ( !format ) {
+ throw exception( "unknown file type" );
+ }
+ SF_INFO info;
+ std::memset( &info, 0, sizeof( SF_INFO ) );
+ info.samplerate = flags.samplerate;
+ info.channels = flags.channels;
+ info.format = format;
+ sndfile = sf_open( filename.c_str(), SFM_WRITE, &info );
+ }
+ ~sndfile_stream_raii() {
+ sf_close( sndfile );
+ sndfile = 0;
+ }
+ void write_metadata( std::map<std::string,std::string> metadata ) {
+ write_metadata_field( SF_STR_TITLE, metadata[ "title" ] );
+ write_metadata_field( SF_STR_ARTIST, metadata[ "artist" ] );
+ write_metadata_field( SF_STR_DATE, metadata[ "date" ] );
+ write_metadata_field( SF_STR_COMMENT, metadata[ "message" ] );
+ write_metadata_field( SF_STR_SOFTWARE, append_software_tag( metadata[ "tracker" ] ) );
+ }
+ void write( const std::vector<float*> buffers, std::size_t frames ) {
+ interleaved_float_buffer.clear();
+ for ( std::size_t frame = 0; frame < frames; frame++ ) {
+ for ( std::size_t channel = 0; channel < buffers.size(); channel++ ) {
+ interleaved_float_buffer.push_back( buffers[channel][frame] );
+ }
+ }
+ sf_writef_float( sndfile, interleaved_float_buffer.data(), frames );
+ }
+ void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) {
+ interleaved_int_buffer.clear();
+ for ( std::size_t frame = 0; frame < frames; frame++ ) {
+ for ( std::size_t channel = 0; channel < buffers.size(); channel++ ) {
+ interleaved_int_buffer.push_back( buffers[channel][frame] );
+ }
+ }
+ sf_writef_short( sndfile, interleaved_int_buffer.data(), frames );
+ }
+};
+
+} // namespace openmpt123
+
+#endif // MPT_WITH_SNDFILE
+
+#endif // OPENMPT123_SNDFILE_HPP
diff --git a/openmpt123/openmpt123_stdout.hpp b/openmpt123/openmpt123_stdout.hpp
new file mode 100644
index 0000000..6b18198
--- /dev/null
+++ b/openmpt123/openmpt123_stdout.hpp
@@ -0,0 +1,49 @@
+/*
+ * openmpt123_stdout.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_STDOUT_HPP
+#define OPENMPT123_STDOUT_HPP
+
+#include "openmpt123_config.hpp"
+#include "openmpt123.hpp"
+
+namespace openmpt123 {
+
+class stdout_stream_raii : public write_buffers_interface {
+private:
+ std::vector<float> interleaved_float_buffer;
+ std::vector<std::int16_t> interleaved_int_buffer;
+public:
+ stdout_stream_raii() {
+ return;
+ }
+public:
+ void write( const std::vector<float*> buffers, std::size_t frames ) {
+ interleaved_float_buffer.clear();
+ for ( std::size_t frame = 0; frame < frames; frame++ ) {
+ for ( std::size_t channel = 0; channel < buffers.size(); channel++ ) {
+ interleaved_float_buffer.push_back( buffers[channel][frame] );
+ }
+ }
+ std::cout.write( reinterpret_cast<const char *>( interleaved_float_buffer.data() ), interleaved_float_buffer.size() * sizeof( float ) );
+ }
+ void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) {
+ interleaved_int_buffer.clear();
+ for ( std::size_t frame = 0; frame < frames; frame++ ) {
+ for ( std::size_t channel = 0; channel < buffers.size(); channel++ ) {
+ interleaved_int_buffer.push_back( buffers[channel][frame] );
+ }
+ }
+ std::cout.write( reinterpret_cast<const char *>( interleaved_int_buffer.data() ), interleaved_int_buffer.size() * sizeof( std::int16_t ) );
+ }
+};
+
+} // namespace openmpt123
+
+#endif // OPENMPT123_STDOUT_HPP
diff --git a/openmpt123/openmpt123_waveout.hpp b/openmpt123/openmpt123_waveout.hpp
new file mode 100644
index 0000000..9a245fc
--- /dev/null
+++ b/openmpt123/openmpt123_waveout.hpp
@@ -0,0 +1,198 @@
+/*
+ * openmpt123_waveout.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_WAVEOUT_HPP
+#define OPENMPT123_WAVEOUT_HPP
+
+#include "openmpt123_config.hpp"
+#include "openmpt123.hpp"
+
+#if defined(WIN32)
+
+namespace openmpt123 {
+
+struct waveout_exception : public exception {
+ waveout_exception() throw() : exception( "waveout" ) { }
+};
+
+class waveout_stream_raii : public write_buffers_interface {
+private:
+ HWAVEOUT waveout;
+ std::size_t num_channels;
+ std::size_t num_chunks;
+ std::size_t frames_per_chunk;
+ std::size_t bytes_per_chunk;
+ std::vector<WAVEHDR> waveheaders;
+ std::vector<std::vector<char> > wavebuffers;
+ std::deque<char> byte_queue;
+public:
+ waveout_stream_raii( commandlineflags & flags )
+ : waveout(NULL)
+ , num_channels(0)
+ , num_chunks(0)
+ , frames_per_chunk(0)
+ , bytes_per_chunk(0)
+ {
+ if ( flags.buffer == default_high ) {
+ flags.buffer = 150;
+ } else if ( flags.buffer == default_low ) {
+ flags.buffer = 50;
+ }
+ if ( flags.period == default_high ) {
+ flags.period = 30;
+ } else if ( flags.period == default_low ) {
+ flags.period = 10;
+ }
+ flags.apply_default_buffer_sizes();
+ WAVEFORMATEX wfx;
+ ZeroMemory( &wfx, sizeof( wfx ) );
+ wfx.wFormatTag = flags.use_float ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
+ wfx.nChannels = flags.channels;
+ wfx.nSamplesPerSec = flags.samplerate;
+ wfx.wBitsPerSample = flags.use_float ? 32 : 16;
+ wfx.nBlockAlign = ( wfx.wBitsPerSample / 8 ) * wfx.nChannels;
+ wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
+ wfx.cbSize = 0;
+ std::istringstream device_string( flags.device );
+ int device = -1;
+ device_string >> device;
+ waveOutOpen( &waveout, device == -1 ? WAVE_MAPPER : device, &wfx, 0, 0, CALLBACK_NULL );
+ num_channels = flags.channels;
+ std::size_t frames_per_buffer = flags.samplerate * flags.buffer / 1000;
+ num_chunks = ( flags.buffer + flags.period - 1 ) / flags.period;
+ if ( num_chunks < 2 ) {
+ num_chunks = 2;
+ }
+ frames_per_chunk = ( frames_per_buffer + num_chunks - 1 ) / num_chunks;
+ bytes_per_chunk = wfx.nBlockAlign * frames_per_chunk;
+ waveheaders.resize( num_chunks );
+ wavebuffers.resize( num_chunks );
+ for ( std::size_t i = 0; i < num_chunks; ++i ) {
+ wavebuffers[i].resize( bytes_per_chunk );
+ waveheaders[i] = WAVEHDR();
+ waveheaders[i].lpData = wavebuffers[i].data();
+ waveheaders[i].dwBufferLength = static_cast<DWORD>( wavebuffers[i].size() );
+ waveheaders[i].dwFlags = 0;
+ waveOutPrepareHeader( waveout, &waveheaders[i], sizeof( WAVEHDR ) );
+ }
+ }
+ ~waveout_stream_raii() {
+ if ( waveout ) {
+ write_or_wait( true );
+ drain();
+ waveOutReset( waveout );
+ for ( std::size_t i = 0; i < num_chunks; ++i ) {
+ waveheaders[i].dwBufferLength = static_cast<DWORD>( wavebuffers[i].size() );
+ waveOutUnprepareHeader( waveout, &waveheaders[i], sizeof( WAVEHDR ) );
+ }
+ wavebuffers.clear();
+ waveheaders.clear();
+ frames_per_chunk = 0;
+ num_chunks = 0;
+ waveOutClose( waveout );
+ waveout = NULL;
+ }
+ }
+private:
+ void drain() {
+ std::size_t empty_chunks = 0;
+ while ( empty_chunks != num_chunks ) {
+ empty_chunks = 0;
+ for ( std::size_t chunk = 0; chunk < num_chunks; ++chunk ) {
+ DWORD flags = waveheaders[chunk].dwFlags;
+ if ( !(flags & WHDR_INQUEUE) || (flags & WHDR_DONE) ) {
+ empty_chunks++;
+ }
+ }
+ if ( empty_chunks != num_chunks ) {
+ Sleep( 1 );
+ }
+ }
+ }
+ std::size_t wait_for_empty_chunk() {
+ while ( true ) {
+ for ( std::size_t chunk = 0; chunk < num_chunks; ++chunk ) {
+ DWORD flags = waveheaders[chunk].dwFlags;
+ if ( !(flags & WHDR_INQUEUE) || (flags & WHDR_DONE) ) {
+ return chunk;
+ }
+ }
+ Sleep( 1 );
+ }
+ }
+ void write_chunk() {
+ std::size_t chunk = wait_for_empty_chunk();
+ std::size_t chunk_bytes = std::min( byte_queue.size(), bytes_per_chunk );
+ waveheaders[chunk].dwBufferLength = static_cast<DWORD>( chunk_bytes );
+ for ( std::size_t byte = 0; byte < chunk_bytes; ++byte ) {
+ wavebuffers[chunk][byte] = byte_queue.front();
+ byte_queue.pop_front();
+ }
+ waveOutWrite( waveout, &waveheaders[chunk], sizeof( WAVEHDR ) );
+ }
+ void write_or_wait( bool flush = false ) {
+ while ( byte_queue.size() >= bytes_per_chunk ) {
+ write_chunk();
+ }
+ if ( flush && !byte_queue.empty() ) {
+ write_chunk();
+ }
+ }
+ template < typename Tsample >
+ void write_buffers( const std::vector<Tsample*> buffers, std::size_t frames ) {
+ for ( std::size_t frame = 0; frame < frames; ++frame ) {
+ for ( std::size_t channel = 0; channel < buffers.size(); ++channel ) {
+ Tsample val = buffers[channel][frame];
+ char buf[ sizeof( Tsample ) ];
+ std::memcpy( buf, &val, sizeof( Tsample ) );
+ std::copy( buf, buf + sizeof( Tsample ), std::back_inserter( byte_queue ) );
+ }
+ }
+ write_or_wait();
+ }
+public:
+ void write( const std::vector<float*> buffers, std::size_t frames ) {
+ write_buffers( buffers, frames );
+ }
+ void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) {
+ write_buffers( buffers, frames );
+ }
+ bool pause() {
+ waveOutPause( waveout );
+ return true;
+ }
+ bool unpause() {
+ waveOutRestart( waveout );
+ return true;
+ }
+ bool sleep( int ms ) {
+ Sleep( ms );
+ return true;
+ }
+};
+
+static std::string show_waveout_devices( std::ostream & /*log*/ ) {
+ std::ostringstream devices;
+ devices << " waveout:" << std::endl;
+ for ( UINT i = 0; i < waveOutGetNumDevs(); ++i ) {
+ devices << " " << i << ": ";
+ WAVEOUTCAPSW caps;
+ ZeroMemory( &caps, sizeof( caps ) );
+ waveOutGetDevCapsW( i, &caps, sizeof( caps ) );
+ devices << wstring_to_utf8( caps.szPname );
+ devices << std::endl;
+ }
+ return devices.str();
+}
+
+} // namespace openmpt123
+
+#endif // WIN32
+
+#endif // OPENMPT123_WAVEOUT_HPP