diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/openmpt123 | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/openmpt123')
15 files changed, 5159 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/openmpt123/.clang-format b/Src/external_dependencies/openmpt-trunk/openmpt123/.clang-format new file mode 100644 index 00000000..ca83758a --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/openmpt123/.clang-format @@ -0,0 +1,169 @@ +# clang-format 14 + +Language: Cpp +Standard: c++20 + +AccessModifierOffset: -2 #? +AlignAfterOpenBracket: AlwaysBreak +AlignArrayOfStructures: Left +AlignConsecutiveAssignments: false +AlignConsecutiveBitFields: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: true +AlignEscapedNewlines: DontAlign +AlignOperands: AlignAfterOperator +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: [] +BinPackArguments: true +BinPackParameters: false +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: true + AfterClass: false + AfterControlStatement: MultiLine + AfterEnum: false + AfterFunction: false + AfterNamespace: false + #AfterObjCDeclaration + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: true + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: false + SplitEmptyNamespace: true +#BreakAfterJavaFieldAnnotations +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BreakBeforeConceptDeclarations: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: false +ColumnLimit: 0 +CommentPragmas: '' #? +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 2 #? +ContinuationIndentWidth: 2 #? +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +EmptyLineAfterAccessModifier: Leave +EmptyLineBeforeAccessModifier: Leave +FixNamespaceComments: true +ForEachMacros: [] +IfMacros: ['MPT_MAYBE_CONSTANT_IF'] +IncludeBlocks: Preserve +IncludeCategories: [] #? +IncludeIsMainRegex: '' #? +IncludeIsMainSourceRegex: '' #? +IndentAccessModifiers: false +IndentCaseBlocks: true +IndentCaseLabels: true +IndentExternBlock: NoIndent +IndentGotoLabels: false +IndentPPDirectives: None +#IndentRequiresClause: true +#BeforeHash +IndentWidth: 2 +IndentWrappedFunctionNames: true +InsertTrailingCommas: None +#JavaImportGroups +#JavaScriptQuotes +#JavaScriptWrapImports +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: OuterScope +MacroBlockBegin: '' #? +MacroBlockEnd: '' #? +MaxEmptyLinesToKeep: 3 +NamespaceIndentation: None +NamespaceMacros: [] #? +#ObjCBinPackProtocolList +#ObjCBlockIndentWidth +#ObjCBreakBeforeNestedBlockParam +#ObjCSpaceAfterProperty +#ObjCSpaceBeforeProtocolList +PackConstructorInitializers: Never +#PenaltyBreakAssignment +#PenaltyBreakBeforeFirstCallParameter +#PenaltyBreakComment +#PenaltyBreakFirstLessLess +#PenaltyBreakOpenParenthesis +#PenaltyBreakString +#PenaltyBreakTemplateDeclaration +#PenaltyExcessCharacter +#PenaltyIndentedWhitespace +#PenaltyReturnTypeOnItsOwnLine +PointerAlignment: Middle +PPIndentWidth: -1 +#RawStringFormats +QualifierAlignment: Leave +#QualifierOrder: ['static', 'inline', 'constexpr', 'volatile', 'const', 'restrict', 'type'] +ReferenceAlignment: Pointer +ReflowComments: false +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: false +#SortJavaStaticImport +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDeclarationName: false + AfterFunctionDefinitionName: false + AfterIfMacros: true + AfterOverloadedOperator: false + #AfterRequiresInClause: false + #AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: true +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: true +SpacesInSquareBrackets: false +StatementAttributeLikeMacros: [] +StatementMacros: [ 'MPT_WARNING', 'MPT_TEST_GROUP_INLINE_IDENTIFIER', 'MPT_TEST_GROUP_INLINE', 'MPT_TEST_GROUP_STATIC' ] #? +TabWidth: 2 +TypenameMacros: [] #? +UseCRLF: false +UseTab: ForContinuationAndIndentation +WhitespaceSensitiveMacros: + - MPT_PP_STRINGIFY diff --git a/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123.cpp b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123.cpp new file mode 100644 index 00000000..065225f6 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123.cpp @@ -0,0 +1,2544 @@ +/* + * 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 = +"Copyright (c) 2004-2022, OpenMPT Project Developers and 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 COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"" "\n" +"AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE" "\n" +"IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE" "\n" +"DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE" "\n" +"FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL" "\n" +"DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR" "\n" +"SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER" "\n" +"CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY," "\n" +"OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE" "\n" +"OF THIS 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 <locale> +#include <map> +#include <memory> +#include <random> +#include <set> +#include <sstream> +#include <stdexcept> +#include <string> +#include <vector> + +#include <cassert> +#include <cmath> +#include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ctime> + +#if defined(__DJGPP__) +#include <conio.h> +#include <dpmi.h> +#include <fcntl.h> +#include <io.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <termios.h> +#include <unistd.h> +#elif defined(WIN32) +#include <conio.h> +#include <fcntl.h> +#include <io.h> +#include <stdio.h> +#if defined(__MINGW32__) && !defined(__MINGW64__) +#include <string.h> +#endif +#include <sys/stat.h> +#include <sys/types.h> +#include <windows.h> +#include <mmsystem.h> +#include <mmreg.h> +#else +#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_allegro42.hpp" +#include "openmpt123_portaudio.hpp" +#include "openmpt123_pulseaudio.hpp" +#include "openmpt123_sdl2.hpp" +#include "openmpt123_waveout.hpp" + +namespace openmpt123 { + +struct silent_exit_exception : public std::exception { +}; + +struct show_license_exception : public std::exception { +}; + +struct show_credits_exception : public std::exception { +}; + +struct show_man_version_exception : public std::exception { +}; + +struct show_man_help_exception : public std::exception { +}; + +struct show_short_version_number_exception : public std::exception { +}; + +struct show_version_number_exception : public std::exception { +}; + +struct show_long_version_number_exception : public std::exception { +}; + +#if defined( WIN32 ) +bool IsConsole( DWORD stdHandle ) { + HANDLE hStd = GetStdHandle( stdHandle ); + if ( ( hStd != NULL ) && ( hStd != INVALID_HANDLE_VALUE ) ) { + DWORD mode = 0; + if ( GetConsoleMode( hStd, &mode ) != FALSE ) { + return true; + } + } + return false; +} +#endif + +bool IsTerminal( int fd ) { +#if defined( WIN32 ) + if ( !_isatty( fd ) ) { + return false; + } + DWORD stdHandle = 0; + if ( fd == 0 ) { + stdHandle = STD_INPUT_HANDLE; + } else if ( fd == 1 ) { + stdHandle = STD_OUTPUT_HANDLE; + } else if ( fd == 2 ) { + stdHandle = STD_ERROR_HANDLE; + } + return IsConsole( stdHandle ); +#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: + std::unique_ptr<file_audio_stream_base> impl; +public: + file_audio_stream_raii( const commandlineflags & flags, const std::string & filename, std::ostream & log ) + : impl(nullptr) + { + 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 = std::make_unique<raw_stream_raii>( filename, flags, log ); +#ifdef MPT_WITH_MMIO + } else if ( flags.output_extension == "wav" ) { + impl = std::make_unique<mmio_stream_raii>( filename, flags, log ); +#endif +#ifdef MPT_WITH_FLAC + } else if ( flags.output_extension == "flac" ) { + impl = std::make_unique<flac_stream_raii>( filename, flags, log ); +#endif +#ifdef MPT_WITH_SNDFILE + } else { + impl = std::make_unique<sndfile_stream_raii>( filename, flags, log ); +#endif + } + if ( !impl ) { + throw exception( "file format handler '" + flags.output_extension + "' not found" ); + } + } + virtual ~file_audio_stream_raii() { + return; + } + void write_metadata( std::map<std::string,std::string> metadata ) override { + impl->write_metadata( metadata ); + } + void write_updated_metadata( std::map<std::string,std::string> metadata ) override { + impl->write_updated_metadata( metadata ); + } + void write( const std::vector<float*> buffers, std::size_t frames ) override { + impl->write( buffers, frames ); + } + void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override { + impl->write( buffers, frames ); + } +}; + +static std::string ctls_to_string( const std::map<std::string, std::string> & ctls ) { + std::string result; + for ( const auto & ctl : ctls ) { + if ( !result.empty() ) { + result += "; "; + } + result += ctl.first + "=" + ctl.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 ( const auto & filename : flags.filenames ) { + 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-2022 OpenMPT Project Developers and Contributors <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 ( const auto & field : fields ) { + if ( first ) { + first = false; + } else { + log << ", "; + } + log << field; + } + } + 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 ); + 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_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() : "unknown" ) << ") <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-2022 OpenMPT Project Developers and Contributors <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 individual PCM data files" << 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 individual PCM files (only applies to --render mode) [default: " << commandlineflags().output_extension << "]" << std::endl; + log << " -o, --output f Write PCM output to file f instead of streaming to audio device (only applies to --ui and --batch modes) [default: " << commandlineflags().output_filename << "]" << std::endl; + 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 ( const auto & extension : extensions ) { + if ( first ) { + first = false; + } else { + log << ", "; + } + log << extension; + } + 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 Tmod > +static void apply_mod_settings( commandlineflags & flags, Tmod & mod ) { + flags.separation = std::max( flags.separation, std::int32_t( 0 ) ); + flags.filtertaps = std::max( flags.filtertaps, std::int32_t( 1 ) ); + flags.filtertaps = std::min( flags.filtertaps, std::int32_t( 8 ) ); + flags.ramping = std::max( flags.ramping, std::int32_t( -1 ) ); + flags.ramping = std::min( flags.ramping, std::int32_t( 10 ) ); + flags.tempo = std::max( flags.tempo, std::int32_t( -48 ) ); + flags.tempo = std::min( flags.tempo, std::int32_t( 48 ) ); + flags.pitch = std::max( flags.pitch, std::int32_t( -48 ) ); + flags.pitch = std::min( flags.pitch, std::int32_t( 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 ); + try { + mod.ctl_set_floatingpoint( "play.tempo_factor", tempo_flag_to_double( flags.tempo ) ); + } catch ( const openmpt::exception & ) { + // ignore + } + try { + mod.ctl_set_floatingpoint( "play.pitch_factor", pitch_flag_to_double( flags.pitch ) ); + } catch ( const openmpt::exception & ) { + // ignore + } + mod.ctl_set_integer( "dither", flags.dither ); +} + +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 ) { + int val = std::numeric_limits<int>::min(); + int hold_pos = std::numeric_limits<int>::min(); + if ( meter.peak > 0.0f ) { + float db = 20.0f * std::log10( meter.peak ); + val = static_cast<int>( db + 48.0f ); + } + if ( meter.hold > 0.0f ) { + float db_hold = 20.0f * std::log10( meter.hold ); + 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 || meter.peak >= 1.0f ) { + return "#"; + } else if ( meter.peak > std::pow( 10.0f, -6.0f / 20.0f ) ) { + return "O"; + } else if ( meter.peak > std::pow( 10.0f, -12.0f / 20.0f ) ) { + return "o"; + } else if ( meter.peak > std::pow( 10.0f, -18.0f / 20.0f ) ) { + return "."; + } else { + return " "; + } + } else { + std::ostringstream res1; + std::ostringstream res2; + res1 + << " " + << channel_tags[channels-1][channel] + << " : " + ; + res2 + << std::string(val,'>') << std::string(std::size_t{48}-val,' ') + << ( ( meter.clip != 0.0f ) ? "#" : ":" ) + << std::string(headroom,'>') << std::string(std::size_t{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 == Mode::UI ) { + bufsize = std::min( flags.ui_redraw_interval, flags.period ) * flags.samplerate / 1000; + } else if ( flags.mode == Mode::Batch ) { + bufsize = flags.period * flags.samplerate / 1000; + } else { + bufsize = 1024; + } + + std::int64_t last_redraw_frame = std::int64_t{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; + // cppcheck-suppress identicalInnerCondition + 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(); + + double cpu_smooth = 0.0; + + while ( true ) { + + if ( flags.mode == Mode::UI ) { + +#if defined( __DJGPP__ ) + + while ( kbhit() ) { + int c = getch(); + if ( !handle_keypress( c, flags, mod, audio_stream ) ) { + return; + } + } + +#elif defined( WIN32 ) && defined( UNICODE ) + + while ( _kbhit() ) { + wint_t c = _getwch(); + if ( !handle_keypress( c, flags, mod, audio_stream ) ) { + return; + } + } + +#elif 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; + std::snprintf( cpu_str, 64, "%.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 ) { + log.cursor_up( lines ); + 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 ( const auto & key : metadata_keys ) { + 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 ( const auto & field :fields ) { + std::string key = field.key; + std::string val = field.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_default2, 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 != Mode::Probe && flags.mode != Mode::Info ) { + 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_metadata( "originaltype" ).empty() ) { + set_field( fields, "Orig. Type" ).ostream() << mod.get_metadata( "originaltype" ) << " (" << mod.get_metadata( "originaltype_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 == Mode::Render ) { + audio_stream.write_metadata( get_metadata( mod ) ); + } else { + audio_stream.write_updated_metadata( get_metadata( mod ) ); + } + + if ( flags.mode == Mode::Probe || flags.mode == Mode::Info ) { + 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( mpt::transcode<std::wstring>( mpt::common_encoding::utf8, 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( mpt::transcode<std::wstring>( mpt::common_encoding::utf8, 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( mpt::transcode<std::wstring>( mpt::common_encoding::utf8, 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( mpt::transcode<std::wstring>( mpt::common_encoding::utf8, 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, std::default_random_engine & prng ) { + std::size_t index = std::uniform_int_distribution<std::size_t>( 0, filenames.size() - 1 )( prng ); + 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, std::default_random_engine & prng ) { + if ( flags.randomize ) { + std::shuffle( flags.filenames.begin(), flags.filenames.end(), prng ); + } + 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, prng ); + 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( mpt::transcode<std::wstring>( mpt::common_encoding::utf8, 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( mpt::transcode<std::wstring>( mpt::common_encoding::utf8, 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 = mpt::transcode<std::string>( mpt::common_encoding::utf8, mpt::logical_encoding::locale, line ); +#else + newfile = line; +#endif + } + } else { + if ( m3u8 ) { + newfile = line; + } else { +#if defined(WIN32) + newfile = mpt::transcode<std::string>( mpt::common_encoding::utf8, mpt::logical_encoding::locale, 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; + // cppcheck false-positive + // cppcheck-suppress StlMissingComparison + for ( auto 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 = Mode::Probe; + } else if ( arg == "--info" ) { + flags.mode = Mode::Info; + } else if ( arg == "--ui" ) { + flags.mode = Mode::UI; + } else if ( arg == "--batch" ) { + flags.mode = Mode::Batch; + } else if ( arg == "--render" ) { + flags.mode = Mode::Render; + } 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_PORTAUDIO ) + drivers << " " << "portaudio" << std::endl; +#endif +#if defined( WIN32 ) + drivers << " " << "waveout" << std::endl; +#endif +#if defined( MPT_WITH_ALLEGRO42 ) + drivers << " " << "allegro42" << 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 +#if defined( MPT_WITH_ALLEGRO42 ) + devices << show_allegro42_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 FD_utf8_raii { +private: + FILE * file; + int old_mode; +public: + FD_utf8_raii( FILE * file, bool set_utf8 ) + : file(file) + , old_mode(-1) + { + if ( set_utf8 ) { + fflush( file ); + #if defined(UNICODE) + old_mode = _setmode( _fileno( file ), _O_U8TEXT ); + #else + old_mode = _setmode( _fileno( file ), _O_TEXT ); + #endif + if ( old_mode == -1 ) { + throw exception( "failed to set TEXT mode on file descriptor" ); + } + } + } + ~FD_utf8_raii() + { + if ( old_mode != -1 ) { + fflush( file ); + old_mode = _setmode( _fileno( file ), old_mode ); + } + } +}; + +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( mpt::transcode<std::string>( mpt::common_encoding::utf8, wargv[arg] ) ); + } + #else + args = std::vector<std::string>( argv, argv + argc ); + #endif + +#if defined(WIN32) + FD_utf8_raii stdin_utf8_guard( stdin, true ); + FD_utf8_raii stdout_utf8_guard( stdout, true ); + FD_utf8_raii stderr_utf8_guard( stderr, true ); +#endif + textout_dummy dummy_log; +#if defined(WIN32) +#if defined(UNICODE) + textout_ostream_console std_out( std::wcout, STD_OUTPUT_HANDLE ); + textout_ostream_console std_err( std::wclog, STD_ERROR_HANDLE ); +#else + textout_ostream_console std_out( std::cout, STD_OUTPUT_HANDLE ); + textout_ostream_console std_err( std::clog, STD_ERROR_HANDLE ); +#endif +#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 ( const auto & filename : flags.filenames ) { + 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 == Mode::UI ) { + 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.warnings.empty() ) { + log << flags.warnings << std::endl; + } + + if ( flags.verbose ) { + log << flags; + } + + log.writeout(); + + std::default_random_engine prng; + try { + std::random_device rd; + std::seed_seq seq{ rd(), static_cast<unsigned int>( std::time( NULL ) ) }; + prng = std::default_random_engine{ seq }; + } catch ( const std::exception & ) { + std::seed_seq seq{ static_cast<unsigned int>( std::time( NULL ) ) }; + prng = std::default_random_engine{ seq }; + } + std::srand( std::uniform_int_distribution<unsigned int>()( prng ) ); + + switch ( flags.mode ) { + case Mode::Probe: { + for ( const auto & filename : flags.filenames ) { + probe_file( flags, filename, log ); + flags.playlist_index++; + } + } break; + case Mode::Info: { + void_audio_stream dummy; + render_files( flags, log, dummy, prng ); + } break; + case Mode::UI: + case Mode::Batch: { + if ( flags.use_stdout ) { + flags.apply_default_buffer_sizes(); + stdout_stream_raii stdout_audio_stream; + render_files( flags, log, stdout_audio_stream, prng ); + } 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, prng ); +#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, prng ); +#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, prng ); +#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, prng ); +#endif +#if defined( WIN32 ) + } else if ( flags.driver == "waveout" || flags.driver.empty() ) { + waveout_stream_raii waveout_stream( flags ); + render_files( flags, log, waveout_stream, prng ); +#endif +#if defined( MPT_WITH_ALLEGRO42 ) + } else if ( flags.driver == "allegro42" || flags.driver.empty() ) { + allegro42_stream_raii allegro42_stream( flags, log ); + render_files( flags, log, allegro42_stream, prng ); +#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 Mode::Render: { + for ( const auto & filename : flags.filenames ) { + 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 Mode::None: + break; + } + + } catch ( args_error_exception & ) { + show_help( std_out ); + return 1; +#ifdef MPT_WITH_ALLEGRO42 + } catch ( allegro42_exception & e ) { + std_err << "Allegro-4.2 error: " << e.what() << std::endl; + std_err.writeout(); + return 1; +#endif +#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_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__) || (defined(__clang__) && !defined(_MSC_VER)) +// mingw64 does only default to special C linkage for "main", but not for "wmain". +extern "C" int wmain( int wargc, wchar_t * wargv [] ); +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/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123.h2m b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123.h2m new file mode 100644 index 00000000..b354975c --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123.h2m @@ -0,0 +1,2 @@ +[Name] +openmpt123 - command line module music player based on libopenmpt diff --git a/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123.hpp b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123.hpp new file mode 100644 index 00000000..878359aa --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123.hpp @@ -0,0 +1,726 @@ +/* + * openmpt123.hpp + * -------------- + * Purpose: libopenmpt command line player + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#ifndef OPENMPT123_HPP +#define OPENMPT123_HPP + +#include "openmpt123_config.hpp" + +#include "mpt/base/compiletime_warning.hpp" +#include "mpt/base/floatingpoint.hpp" +#include "mpt/base/preprocessor.hpp" +#include "mpt/string_transcode/transcode.hpp" + +#include <string> + +namespace openmpt123 { + +struct exception : public openmpt::exception { + exception( const std::string & text ) : openmpt::exception(text) { } +}; + +struct show_help_exception { + std::string message; + bool longhelp; + show_help_exception( const std::string & msg = "", bool longhelp_ = true ) : message(msg), longhelp(longhelp_) { } +}; + +struct args_error_exception { + args_error_exception() { } +}; + +struct show_help_keyboard_exception { }; + +#if defined(WIN32) +bool IsConsole( DWORD stdHandle ); +#endif +bool IsTerminal( int fd ); + + + +struct field { + std::string key; + std::string val; + field( const std::string & key ) + : key(key) + { + return; + } +}; + +class textout : public std::ostringstream { +public: + textout() { + return; + } + virtual ~textout() { + return; + } +protected: + std::string pop() { + std::string text = str(); + str(std::string()); + return text; + } +public: + virtual void writeout() = 0; + virtual void cursor_up( std::size_t lines ) { + static_cast<void>( lines ); + } +}; + +class textout_dummy : public textout { +public: + textout_dummy() { + return; + } + virtual ~textout_dummy() { + return; + } +public: + void writeout() override { + static_cast<void>( pop() ); + } +}; + +class textout_ostream : public textout { +private: + std::ostream & s; +#if defined(__DJGPP__) + mpt::common_encoding codepage; +#endif +public: + textout_ostream( std::ostream & s_ ) + : s(s_) +#if defined(__DJGPP__) + , codepage(mpt::common_encoding::cp437) +#endif + { + #if defined(__DJGPP__) + codepage = mpt::djgpp_get_locale_encoding(); + #endif + return; + } + virtual ~textout_ostream() { + writeout_impl(); + } +private: + void writeout_impl() { + std::string text = pop(); + if ( text.length() > 0 ) { + #if defined(__DJGPP__) + s << mpt::transcode<std::string>( codepage, mpt::common_encoding::utf8, text ); + #elif defined(__EMSCRIPTEN__) + s << text; + #else + s << mpt::transcode<std::string>( mpt::logical_encoding::locale, mpt::common_encoding::utf8, text ); + #endif + s.flush(); + } + } +public: + void writeout() override { + writeout_impl(); + } + void cursor_up( std::size_t lines ) override { + s.flush(); + for ( std::size_t line = 0; line < lines; ++line ) { + *this << "\x1b[1A"; + } + } +}; + +#if defined(WIN32) + +class textout_ostream_console : public textout { +private: +#if defined(UNICODE) + std::wostream & s; +#else + std::ostream & s; +#endif + HANDLE handle; + bool console; +public: +#if defined(UNICODE) + textout_ostream_console( std::wostream & s_, DWORD stdHandle_ ) +#else + textout_ostream_console( std::ostream & s_, DWORD stdHandle_ ) +#endif + : s(s_) + , handle(GetStdHandle( stdHandle_ )) + , console(IsConsole( stdHandle_ )) + { + return; + } + virtual ~textout_ostream_console() { + writeout_impl(); + } +private: + void writeout_impl() { + std::string text = pop(); + if ( text.length() > 0 ) { + if ( console ) { + #if defined(UNICODE) + std::wstring wtext = mpt::transcode<std::wstring>( mpt::common_encoding::utf8, text ); + WriteConsole( handle, wtext.data(), static_cast<DWORD>( wtext.size() ), NULL, NULL ); + #else + std::string ltext = mpt::transcode<std::string>( mpt::logical_encoding::locale, mpt::common_encoding::utf8, text ); + WriteConsole( handle, ltext.data(), static_cast<DWORD>( ltext.size() ), NULL, NULL ); + #endif + } else { + #if defined(UNICODE) + s << mpt::transcode<std::wstring>( mpt::common_encoding::utf8, text ); + #else + s << mpt::transcode<std::string>( mpt::logical_encoding::locale, mpt::common_encoding::utf8, text ); + #endif + s.flush(); + } + } + } +public: + void writeout() override { + writeout_impl(); + } + void cursor_up( std::size_t lines ) override { + if ( console ) { + s.flush(); + CONSOLE_SCREEN_BUFFER_INFO csbi; + ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) ); + COORD coord_cursor = COORD(); + if ( GetConsoleScreenBufferInfo( handle, &csbi ) != FALSE ) { + coord_cursor = csbi.dwCursorPosition; + coord_cursor.X = 1; + coord_cursor.Y -= static_cast<SHORT>( lines ); + SetConsoleCursorPosition( handle, coord_cursor ); + } + } + } +}; + +#endif // WIN32 + +static inline float mpt_round( float val ) { + if ( val >= 0.0f ) { + return std::floor( val + 0.5f ); + } else { + return std::ceil( val - 0.5f ); + } +} + +static inline long mpt_lround( float val ) { + return static_cast< long >( mpt_round( val ) ); +} + +static inline std::string append_software_tag( std::string software ) { + std::string openmpt123 = std::string() + "openmpt123 " + OPENMPT123_VERSION_STRING + " (libopenmpt " + openmpt::string::get( "library_version" ) + ", OpenMPT " + openmpt::string::get( "core_version" ) + ")"; + if ( software.empty() ) { + software = openmpt123; + } else { + software += " (via " + openmpt123 + ")"; + } + return software; +} + +static inline std::string get_encoder_tag() { + return std::string() + "openmpt123 " + OPENMPT123_VERSION_STRING + " (libopenmpt " + openmpt::string::get( "library_version" ) + ", OpenMPT " + openmpt::string::get( "core_version" ) + ")"; +} + +static inline std::string get_extension( std::string filename ) { + if ( filename.find_last_of( "." ) != std::string::npos ) { + return filename.substr( filename.find_last_of( "." ) + 1 ); + } + return ""; +} + +enum class Mode { + None, + Probe, + Info, + UI, + Batch, + Render +}; + +static inline std::string mode_to_string( Mode mode ) { + switch ( mode ) { + case Mode::None: return "none"; break; + case Mode::Probe: return "probe"; break; + case Mode::Info: return "info"; break; + case Mode::UI: return "ui"; break; + case Mode::Batch: return "batch"; break; + case Mode::Render: return "render"; break; + } + return ""; +} + +static const std::int32_t default_low = -2; +static const std::int32_t default_high = -1; + +struct commandlineflags { + Mode mode; + bool canUI; + std::int32_t ui_redraw_interval; + bool canProgress; + std::string driver; + std::string device; + std::int32_t buffer; + std::int32_t period; + std::int32_t samplerate; + std::int32_t channels; + std::int32_t gain; + std::int32_t separation; + std::int32_t filtertaps; + std::int32_t ramping; // ramping strength : -1:default 0:off 1 2 3 4 5 // roughly milliseconds + std::int32_t tempo; + std::int32_t pitch; + std::int32_t dither; + std::int32_t repeatcount; + std::int32_t subsong; + std::map<std::string, std::string> ctls; + double seek_target; + double end_time; + bool quiet; + bool verbose; + int terminal_width; + int terminal_height; + bool show_details; + bool show_message; + bool show_ui; + bool show_progress; + bool show_meters; + bool show_channel_meters; + bool show_pattern; + bool use_float; + bool use_stdout; + bool randomize; + bool shuffle; + bool restart; + std::size_t playlist_index; + std::vector<std::string> filenames; + std::string output_filename; + std::string output_extension; + bool force_overwrite; + bool paused; + std::string warnings; + void apply_default_buffer_sizes() { + if ( ui_redraw_interval == default_high ) { + ui_redraw_interval = 50; + } else if ( ui_redraw_interval == default_low ) { + ui_redraw_interval = 10; + } + if ( buffer == default_high ) { + buffer = 250; + } else if ( buffer == default_low ) { + buffer = 50; + } + if ( period == default_high ) { + period = 50; + } else if ( period == default_low ) { + period = 10; + } + } + commandlineflags() { + mode = Mode::UI; + ui_redraw_interval = default_high; + driver = ""; + device = ""; + buffer = default_high; + period = default_high; +#if defined(__DJGPP__) + samplerate = 44100; + channels = 2; + use_float = false; +#else + samplerate = 48000; + channels = 2; + use_float = mpt::float_traits<float>::is_hard && mpt::float_traits<float>::is_ieee754_binary; +#endif + gain = 0; + separation = 100; + filtertaps = 8; + ramping = -1; + tempo = 0; + pitch = 0; + dither = 1; + repeatcount = 0; + subsong = -1; + seek_target = 0.0; + end_time = 0.0; + quiet = false; + verbose = false; +#if defined(__DJGPP__) + terminal_width = 80; + terminal_height = 25; +#else + terminal_width = 72; + terminal_height = 23; +#endif +#if defined(WIN32) + terminal_width = 72; + terminal_height = 23; + HANDLE hStdOutput = GetStdHandle( STD_OUTPUT_HANDLE ); + if ( ( hStdOutput != NULL ) && ( hStdOutput != INVALID_HANDLE_VALUE ) ) { + CONSOLE_SCREEN_BUFFER_INFO csbi; + ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) ); + if ( GetConsoleScreenBufferInfo( hStdOutput, &csbi ) != FALSE ) { + terminal_width = std::min( static_cast<int>( 1 + csbi.srWindow.Right - csbi.srWindow.Left ), static_cast<int>( csbi.dwSize.X ) ); + terminal_height = std::min( static_cast<int>( 1 + csbi.srWindow.Bottom - csbi.srWindow.Top ), static_cast<int>( csbi.dwSize.Y ) ); + } + } +#else // WIN32 + if ( isatty( STDERR_FILENO ) ) { + const char * env_columns = std::getenv( "COLUMNS" ); + if ( env_columns ) { + std::istringstream istr( env_columns ); + int tmp = 0; + istr >> tmp; + if ( tmp > 0 ) { + terminal_width = tmp; + } + } + const char * env_rows = std::getenv( "ROWS" ); + if ( env_rows ) { + std::istringstream istr( env_rows ); + int tmp = 0; + istr >> tmp; + if ( tmp > 0 ) { + terminal_height = tmp; + } + } + #if defined(TIOCGWINSZ) + struct winsize ts; + if ( ioctl( STDERR_FILENO, TIOCGWINSZ, &ts ) >= 0 ) { + terminal_width = ts.ws_col; + terminal_height = ts.ws_row; + } + #elif defined(TIOCGSIZE) + struct ttysize ts; + if ( ioctl( STDERR_FILENO, TIOCGSIZE, &ts ) >= 0 ) { + terminal_width = ts.ts_cols; + terminal_height = ts.ts_rows; + } + #endif + } +#endif + show_details = true; + show_message = false; +#if defined(WIN32) + canUI = IsTerminal( 0 ) ? true : false; + canProgress = IsTerminal( 2 ) ? true : false; +#else // !WIN32 + canUI = isatty( STDIN_FILENO ) ? true : false; + canProgress = isatty( STDERR_FILENO ) ? true : false; +#endif // WIN32 + show_ui = canUI; + show_progress = canProgress; + show_meters = canUI && canProgress; + show_channel_meters = false; + show_pattern = false; + use_stdout = false; + randomize = false; + shuffle = false; + restart = false; + playlist_index = 0; + output_extension = "auto"; + force_overwrite = false; + paused = false; + } + void check_and_sanitize() { + if ( filenames.size() == 0 ) { + throw args_error_exception(); + } + if ( use_stdout && ( device != commandlineflags().device || !output_filename.empty() ) ) { + throw args_error_exception(); + } + if ( !output_filename.empty() && ( device != commandlineflags().device || use_stdout ) ) { + throw args_error_exception(); + } + for ( const auto & filename : filenames ) { + if ( filename == "-" ) { + canUI = false; + } + } + show_ui = canUI; + if ( mode == Mode::None ) { + if ( canUI ) { + mode = Mode::UI; + } else { + mode = Mode::Batch; + } + } + if ( mode == Mode::UI && !canUI ) { + throw args_error_exception(); + } + if ( show_progress && !canProgress ) { + throw args_error_exception(); + } + switch ( mode ) { + case Mode::None: + throw args_error_exception(); + break; + case Mode::Probe: + show_ui = false; + show_progress = false; + show_meters = false; + show_channel_meters = false; + show_pattern = false; + break; + case Mode::Info: + show_ui = false; + show_progress = false; + show_meters = false; + show_channel_meters = false; + show_pattern = false; + break; + case Mode::UI: + break; + case Mode::Batch: + show_meters = false; + show_channel_meters = false; + show_pattern = false; + break; + case Mode::Render: + show_meters = false; + show_channel_meters = false; + show_pattern = false; + show_ui = false; + break; + } + if ( quiet ) { + verbose = false; + show_ui = false; + show_details = false; + show_progress = false; + show_channel_meters = false; + } + if ( verbose ) { + show_details = true; + } + if ( channels != 1 && channels != 2 && channels != 4 ) { + channels = commandlineflags().channels; + } + if ( samplerate < 0 ) { + samplerate = commandlineflags().samplerate; + } + if ( output_extension == "auto" ) { + output_extension = ""; + } + if ( mode != Mode::Render && !output_extension.empty() ) { + throw args_error_exception(); + } + if ( mode == Mode::Render && !output_filename.empty() ) { + throw args_error_exception(); + } + if ( mode != Mode::Render && !output_filename.empty() ) { + output_extension = get_extension( output_filename ); + } + if ( output_extension.empty() ) { + output_extension = "wav"; + } + } +}; + +template < typename Tsample > Tsample convert_sample_to( float val ); +template < > float convert_sample_to( float val ) { + return val; +} +template < > std::int16_t convert_sample_to( float val ) { + std::int32_t tmp = static_cast<std::int32_t>( val * 32768.0f ); + tmp = std::min( tmp, std::int32_t( 32767 ) ); + tmp = std::max( tmp, std::int32_t( -32768 ) ); + return static_cast<std::int16_t>( tmp ); +} + +class write_buffers_interface { +protected: + virtual ~write_buffers_interface() { + return; + } +public: + virtual void write_metadata( std::map<std::string,std::string> metadata ) { + (void)metadata; + return; + } + virtual void write_updated_metadata( std::map<std::string,std::string> metadata ) { + (void)metadata; + return; + } + virtual void write( const std::vector<float*> buffers, std::size_t frames ) = 0; + virtual void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) = 0; + virtual bool pause() { + return false; + } + virtual bool unpause() { + return false; + } + virtual bool sleep( int /*ms*/ ) { + return false; + } + virtual bool is_dummy() const { + return false; + } +}; + +class write_buffers_polling_wrapper : public write_buffers_interface { +protected: + std::size_t channels; + std::size_t sampleQueueMaxFrames; + std::deque<float> sampleQueue; +protected: + virtual ~write_buffers_polling_wrapper() { + return; + } +protected: + write_buffers_polling_wrapper( const commandlineflags & flags ) + : channels(flags.channels) + , sampleQueueMaxFrames(0) + { + return; + } + void set_queue_size_frames( std::size_t frames ) { + sampleQueueMaxFrames = frames; + } + template < typename Tsample > + Tsample pop_queue() { + float val = 0.0f; + if ( !sampleQueue.empty() ) { + val = sampleQueue.front(); + sampleQueue.pop_front(); + } + return convert_sample_to<Tsample>( val ); + } +public: + void write( const std::vector<float*> buffers, std::size_t frames ) override { + for ( std::size_t frame = 0; frame < frames; ++frame ) { + for ( std::size_t channel = 0; channel < channels; ++channel ) { + sampleQueue.push_back( buffers[channel][frame] ); + } + while ( sampleQueue.size() >= sampleQueueMaxFrames * channels ) { + while ( !forward_queue() ) { + sleep( 1 ); + } + } + } + } + void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override { + for ( std::size_t frame = 0; frame < frames; ++frame ) { + for ( std::size_t channel = 0; channel < channels; ++channel ) { + sampleQueue.push_back( buffers[channel][frame] * (1.0f/32768.0f) ); + } + while ( sampleQueue.size() >= sampleQueueMaxFrames * channels ) { + while ( !forward_queue() ) { + sleep( 1 ); + } + } + } + } + virtual bool forward_queue() = 0; + bool sleep( int ms ) override = 0; +}; + +class write_buffers_polling_wrapper_int : public write_buffers_interface { +protected: + std::size_t channels; + std::size_t sampleQueueMaxFrames; + std::deque<std::int16_t> sampleQueue; +protected: + virtual ~write_buffers_polling_wrapper_int() { + return; + } +protected: + write_buffers_polling_wrapper_int( const commandlineflags & flags ) + : channels(flags.channels) + , sampleQueueMaxFrames(0) + { + return; + } + void set_queue_size_frames( std::size_t frames ) { + sampleQueueMaxFrames = frames; + } + std::int16_t pop_queue() { + std::int16_t val = 0; + if ( !sampleQueue.empty() ) { + val = sampleQueue.front(); + sampleQueue.pop_front(); + } + return val; + } +public: + void write( const std::vector<float*> buffers, std::size_t frames ) override { + for ( std::size_t frame = 0; frame < frames; ++frame ) { + for ( std::size_t channel = 0; channel < channels; ++channel ) { + sampleQueue.push_back( convert_sample_to<std::int16_t>( buffers[channel][frame] ) ); + } + while ( sampleQueue.size() >= sampleQueueMaxFrames * channels ) { + while ( !forward_queue() ) { + sleep( 1 ); + } + } + } + } + void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override { + for ( std::size_t frame = 0; frame < frames; ++frame ) { + for ( std::size_t channel = 0; channel < channels; ++channel ) { + sampleQueue.push_back( buffers[channel][frame] ); + } + while ( sampleQueue.size() >= sampleQueueMaxFrames * channels ) { + while ( !forward_queue() ) { + sleep( 1 ); + } + } + } + } + virtual bool forward_queue() = 0; + bool sleep( int ms ) override = 0; +}; + +class void_audio_stream : public write_buffers_interface { +public: + virtual ~void_audio_stream() { + return; + } +public: + void write( const std::vector<float*> buffers, std::size_t frames ) override { + (void)buffers; + (void)frames; + } + void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override { + (void)buffers; + (void)frames; + } + bool is_dummy() const override { + return true; + } +}; + +class file_audio_stream_base : public write_buffers_interface { +protected: + file_audio_stream_base() { + return; + } +public: + void write_metadata( std::map<std::string,std::string> metadata ) override { + (void)metadata; + return; + } + void write_updated_metadata( std::map<std::string,std::string> metadata ) override { + (void)metadata; + return; + } + void write( const std::vector<float*> buffers, std::size_t frames ) override = 0; + void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override = 0; + virtual ~file_audio_stream_base() { + return; + } +}; + +} // namespace openmpt123 + +#endif // OPENMPT123_HPP diff --git a/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_allegro42.hpp b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_allegro42.hpp new file mode 100644 index 00000000..a0979eaa --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_allegro42.hpp @@ -0,0 +1,185 @@ +/* + * openmpt123_allegro42.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_ALLEGRO42_HPP +#define OPENMPT123_ALLEGRO42_HPP + +#include "openmpt123_config.hpp" +#include "openmpt123.hpp" + +#if defined(MPT_WITH_ALLEGRO42) + +#if defined(__GNUC__) && !defined(__clang__) && !defined(_MSC_VER) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +#pragma GCC diagnostic ignored "-Wfloat-conversion" +#endif +#include <allegro.h> +#if defined(__GNUC__) && !defined(__clang__) && !defined(_MSC_VER) +#pragma GCC diagnostic pop +#endif + +namespace openmpt123 { + +struct allegro42_exception : public exception { + static std::string error_to_string() { + try { + return std::string( allegro_error ); + } catch ( const std::bad_alloc & ) { + return std::string(); + } + } + allegro42_exception() + : exception( error_to_string() ) + { + } +}; + +class allegro42_raii { +public: + allegro42_raii() { + if ( allegro_init() != 0 ) { + throw allegro42_exception(); + } + } + ~allegro42_raii() { + allegro_exit(); + } +}; + +class allegro42_sound_raii { +public: + allegro42_sound_raii() { + if ( install_sound( DIGI_AUTODETECT, MIDI_NONE, NULL ) != 0 ) { + throw allegro42_exception(); + } + if ( digi_card == DIGI_NONE ) { + remove_sound(); + throw exception( "no audio device found" ); + } + } + ~allegro42_sound_raii() { + remove_sound(); + } +}; + +class allegro42_stream_raii : public write_buffers_polling_wrapper_int { +private: + allegro42_raii allegro; + allegro42_sound_raii allegro_sound; + AUDIOSTREAM * stream; + std::size_t bits; + std::size_t channels; + std::uint32_t period_frames; +private: + std::uint32_t round_up_power2(std::uint32_t x) + { + std::uint32_t result = 1; + while ( result < x ) { + result *= 2; + } + return result; + } +public: + allegro42_stream_raii( commandlineflags & flags, std::ostream & log ) + : write_buffers_polling_wrapper_int(flags) + , stream(NULL) + , bits(16) + , channels(flags.channels) + , period_frames(1024) + { + if ( flags.use_float ) { + throw exception( "floating point is unsupported" ); + } + if ( ( flags.channels != 1 ) && ( flags.channels != 2 ) ) { + throw exception( "only mono or stereo supported" ); + } + if ( flags.buffer == default_high ) { + flags.buffer = 1024 * 2 * 1000 / flags.samplerate; + } else if ( flags.buffer == default_low ) { + flags.buffer = 512 * 2 * 1000 / flags.samplerate; + } + if ( flags.period == default_high ) { + flags.period = 1024 / 2 * 1000 / flags.samplerate; + } else if ( flags.period == default_low ) { + flags.period = 512 / 2 * 1000 / flags.samplerate; + } + flags.apply_default_buffer_sizes(); + period_frames = round_up_power2( ( flags.buffer * flags.samplerate ) / ( 1000 * 2 ) ); + set_queue_size_frames( period_frames ); + if ( flags.verbose ) { + log << "Allegro-4.2:" << std::endl; + log << " allegro samplerate: " << get_mixer_frequency() << std::endl; + log << " latency: " << flags.buffer << std::endl; + log << " period: " << flags.period << std::endl; + log << " frames per buffer: " << period_frames << std::endl; + log << " ui redraw: " << flags.ui_redraw_interval << std::endl; + } + stream = play_audio_stream( period_frames, 16, ( flags.channels > 1 ) ? TRUE : FALSE, flags.samplerate, 255, 128 ); + if ( !stream ) { + bits = 8; + stream = play_audio_stream( period_frames, 8, ( flags.channels > 1 ) ? TRUE : FALSE, flags.samplerate, 255, 128 ); + if ( !stream ) { + throw allegro42_exception(); + } + } + } + ~allegro42_stream_raii() { + if ( stream ) { + stop_audio_stream( stream ); + stream = NULL; + } + } +public: + bool forward_queue() override { + void * p = get_audio_stream_buffer( stream ); + if ( !p ) { + return false; + } + for ( std::size_t frame = 0; frame < period_frames; ++frame ) { + for ( std::size_t channel = 0; channel < channels; ++channel ) { + std::int16_t sample = pop_queue(); + if ( bits == 8 ) { + std::uint8_t u8sample = ( static_cast<std::uint16_t>( sample ) + 0x8000u ) >> 8; + std::memcpy( reinterpret_cast<unsigned char *>( p ) + ( ( ( frame * channels ) + channel ) * sizeof( std::uint8_t ) ), &u8sample, sizeof( std::uint8_t ) ); + } else { + std::uint16_t u16sample = static_cast<std::uint16_t>( sample ) + 0x8000u; + std::memcpy( reinterpret_cast<unsigned char *>( p ) + ( ( ( frame * channels ) + channel ) * sizeof( std::uint16_t ) ), &u16sample, sizeof( std::uint16_t ) ); + } + } + } + free_audio_stream_buffer( stream ); + return true; + } + bool unpause() override { + voice_start( stream->voice ); + return true; + } + bool pause() override { + voice_stop( stream->voice ); + return true; + } + bool sleep( int ms ) override { + rest( ms ); + return true; + } +}; + +static std::string show_allegro42_devices( std::ostream & /* log */ ) { + std::ostringstream devices; + devices << " allegro42:" << std::endl; + devices << " " << "0" << ": Default Device" << std::endl; + return devices.str(); +} + +} // namespace openmpt123 + +#endif // MPT_WITH_ALLEGRO42 + +#endif // OPENMPT123_ALLEGRO42_HPP diff --git a/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_config.hpp b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_config.hpp new file mode 100644 index 00000000..fd242f1d --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_config.hpp @@ -0,0 +1,64 @@ +/* + * 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 + +#define MPT_INLINE_NS mpt_openmpt123 + +#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 +#ifdef _UNICODE +#ifndef UNICODE +#define UNICODE +#endif +#endif +#ifdef UNICODE +#ifndef _UNICODE +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#endif +#define _UNICODE +#if defined(__clang__) +#pragma clang diagnostic pop +#endif +#endif +#endif +#endif // WIN32 + +#if defined(WIN32) +#define MPT_WITH_MMIO +#endif // WIN32 + +#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 + +#define OPENMPT123_VERSION_STRING OPENMPT_API_VERSION_STRING + +#endif // OPENMPT123_CONFIG_HPP diff --git a/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_flac.hpp b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_flac.hpp new file mode 100644 index 00000000..969c6530 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_flac.hpp @@ -0,0 +1,142 @@ +/* + * 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 +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#endif +#include <FLAC/metadata.h> +#include <FLAC/format.h> +#include <FLAC/stream_encoder.h> +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +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 ) override { + 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 ) override { + 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 ) override { + 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/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_mmio.hpp b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_mmio.hpp new file mode 100644 index 00000000..24cd4784 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_mmio.hpp @@ -0,0 +1,133 @@ +/* + * 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 { + +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; +private: + void CHECKED( HRESULT err ) { + if ( err != 0 ) { + throw exception( "error writing wave file" ); + } + } + void UNCHECKED( HRESULT err ) { + if ( err != 0 ) { + log << "error writing wave file" << std::endl; + } + } +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 = static_cast<WORD>( flags.channels ); + waveformatex.nSamplesPerSec = flags.samplerate; + waveformatex.wBitsPerSample = flags.use_float ? 32 : 16; + waveformatex.nBlockAlign = static_cast<WORD>( flags.channels * ( waveformatex.wBitsPerSample / 8 ) ); + waveformatex.nAvgBytesPerSec = waveformatex.nSamplesPerSec * waveformatex.nBlockAlign; + + #if defined(WIN32) && defined(UNICODE) + wchar_t * tmp = _wcsdup( mpt::transcode<std::wstring>( mpt::common_encoding::utf8, 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 ) override { + 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 ) override { + 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; + + } +}; + +} // namespace openmpt123 + +#endif // MPT_WITH_MMIO + +#endif // OPENMPT123_MMIO_HPP diff --git a/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_portaudio.hpp b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_portaudio.hpp new file mode 100644 index 00000000..4444c3c0 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/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 ) : 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 != Mode::UI ) { + framesperbuffer = paFramesPerBufferUnspecified; + flags.period = 50; + flags.period = std::min( flags.period, flags.buffer / 3 ); + } else if ( flags.period == default_high ) { + framesperbuffer = paFramesPerBufferUnspecified; + flags.period = 50; + flags.period = std::min( flags.period, flags.buffer / 3 ); + } else if ( flags.period == default_low ) { + framesperbuffer = paFramesPerBufferUnspecified; + flags.period = 10; + flags.period = std::min( 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( static_cast<std::uint64_t>( frames ), static_cast<std::uint64_t>( 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( static_cast<std::uint64_t>( frames ), static_cast<std::uint64_t>( 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 ) override { + 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 ) override { + 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() override { + check_portaudio_error( Pa_StartStream( stream ) ); + return true; + } + bool pause() override { + check_portaudio_error( Pa_StopStream( stream ) ); + return true; + } + bool sleep( int ms ) override { + 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/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_pulseaudio.hpp b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_pulseaudio.hpp new file mode 100644 index 00000000..9650005f --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/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 ) : 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 ) override { + 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 ) override { + 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() override { + return true; + } + bool pause() override { + int error = 0; + error = 0; + if ( pa_simple_drain( stream, &error ) < 0 ) { + throw pulseaudio_exception( error ); + } + return true; + } + bool sleep( int ms ) override { + 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/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_raw.hpp b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_raw.hpp new file mode 100644 index 00000000..cb7d8d5f --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/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 */ ) override { + return; + } + void write( const std::vector<float*> buffers, std::size_t frames ) override { + 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 ) override { + 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/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_sdl2.hpp b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_sdl2.hpp new file mode 100644 index 00000000..5acab163 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_sdl2.hpp @@ -0,0 +1,227 @@ +/* + * 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) + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#endif // __clang__ +#include <SDL.h> +#if defined(__clang__) +#pragma clang diagnostic pop +#endif // __clang__ +#ifdef main +#undef main +#endif +#ifdef SDL_main +#undef SDL_main +#endif +#if (SDL_COMPILEDVERSION < SDL_VERSIONNUM(2, 0, 4)) +MPT_WARNING("Support for SDL2 < 2.0.4 has been deprecated and will be removed in a future openmpt123 version.") +#endif + +namespace openmpt123 { + +struct sdl2_exception : public exception { +private: + static std::string text_from_code( int code ) { + std::ostringstream s; + s << code; + return s.str(); + } +public: + sdl2_exception( int code, const char * error ) : exception( text_from_code( code ) + " (" + ( error ? std::string(error) : std::string("NULL") ) + ")" ) { } +}; + +static void check_sdl2_error( int e ) { + if ( e < 0 ) { + throw sdl2_exception( e, SDL_GetError() ); + } +} + +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_interface { +private: + std::ostream & log; + sdl2_raii sdl2; + int dev; + std::size_t channels; + bool use_float; + std::size_t sampleQueueMaxFrames; + std::vector<float> sampleBufFloat; + std::vector<std::int16_t> sampleBufInt; +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_ ) + : log(log_) + , sdl2( SDL_INIT_NOPARACHUTE | SDL_INIT_TIMER | SDL_INIT_AUDIO ) + , dev(-1) + , channels(flags.channels) + , use_float(flags.use_float) + , sampleQueueMaxFrames(0) + { + 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 = NULL; + audiospec.userdata = NULL; + if ( flags.verbose ) { + log << "SDL2:" << std::endl; + log << " latency: " << ( audiospec.samples * 2.0 / flags.samplerate ) << " (2 * " << audiospec.samples << ")" << std::endl; + log << std::endl; + } + sampleQueueMaxFrames = 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: + std::size_t get_num_writeable_frames() { + std::size_t num_queued_frames = SDL_GetQueuedAudioSize( dev ) / ( use_float ? sizeof( float ) : sizeof( std::int16_t ) ) / channels; + if ( num_queued_frames > sampleQueueMaxFrames ) { + return 0; + } + return sampleQueueMaxFrames - num_queued_frames; + } + template<typename Tsample> + void write_frames( const Tsample * buffer, std::size_t frames ) { + while ( frames > 0 ) { + std::size_t chunk_frames = std::min( frames, get_num_writeable_frames() ); + if ( chunk_frames > 0 ) { + check_sdl2_error( SDL_QueueAudio( dev, buffer, chunk_frames * channels * ( use_float ? sizeof( float ) : sizeof( std::int16_t ) ) ) ); + frames -= chunk_frames; + buffer += chunk_frames * channels; + } else { + SDL_Delay( 1 ); + } + } + } +public: + void write( const std::vector<float*> buffers, std::size_t frames ) override { + 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 ) override { + 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 pause() override { + SDL_PauseAudioDevice( dev, 1 ); + return true; + } + bool unpause() override { + SDL_PauseAudioDevice( dev, 0 ); + return true; + } + bool sleep( int ms ) override { + 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/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_sndfile.hpp b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_sndfile.hpp new file mode 100644 index 00000000..8fc3156e --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/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 ) override { + 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 ) override { + 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 ) override { + 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/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_stdout.hpp b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_stdout.hpp new file mode 100644 index 00000000..585ce91c --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/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 ) override { + 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 ) override { + 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/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_waveout.hpp b/Src/external_dependencies/openmpt-trunk/openmpt123/openmpt123_waveout.hpp new file mode 100644 index 00000000..11c60bfe --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/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() : 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 = static_cast<WORD>( 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 ) override { + write_buffers( buffers, frames ); + } + void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override { + write_buffers( buffers, frames ); + } + bool pause() override { + waveOutPause( waveout ); + return true; + } + bool unpause() override { + waveOutRestart( waveout ); + return true; + } + bool sleep( int ms ) override { + 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 << mpt::transcode<std::string>( mpt::common_encoding::utf8, caps.szPname ); + devices << std::endl; + } + return devices.str(); +} + +} // namespace openmpt123 + +#endif // WIN32 + +#endif // OPENMPT123_WAVEOUT_HPP |