3434
3535#include " almalloc.h"
3636#include " alnumeric.h"
37+ #include " alstring.h"
3738#include " common/alhelpers.hpp"
3839#include " fmt/core.h"
3940#include " fmt/format.h"
@@ -99,6 +100,7 @@ using std::chrono::duration_cast;
99100
100101constexpr auto AppName = std::to_array(" alffplay" );
101102
103+ auto PlaybackGain = 1 .0f ;
102104auto DirectOutMode = ALenum{AL_FALSE };
103105auto EnableWideStereo = false ;
104106auto EnableUhj = false ;
@@ -1259,6 +1261,31 @@ void AudioState::handler()
12591261 alGenBuffers (gsl::narrow_cast<ALsizei>(mBuffers .size ()), mBuffers .data ());
12601262 alGenSources (1 , &mSource );
12611263
1264+ if (PlaybackGain > 1 .0f )
1265+ {
1266+ const auto maxgain = alIsExtensionPresent (" AL_SOFT_gain_clamp_ex" )
1267+ ? alGetFloat (AL_GAIN_LIMIT_SOFT ) : 1 .0f ;
1268+
1269+ auto gain = PlaybackGain;
1270+ if (gain > maxgain)
1271+ {
1272+ fmt::println (stderr, " Limiting requested gain {:+}db ({}) to max {:+}db ({})" ,
1273+ std::round (std::log10 (double {gain})*2000.0 ) / 100.0 , gain,
1274+ std::round (std::log10 (double {maxgain})*2000.0 ) / 100.0 , maxgain);
1275+ gain = maxgain;
1276+ }
1277+ else
1278+ fmt::println (" Setting gain {:+}dB ({})" ,
1279+ std::round (std::log10 (double {gain})*2000.0 ) / 100.0 , gain);
1280+ alSourcef (mSource , AL_MAX_GAIN , gain);
1281+ alSourcef (mSource , AL_GAIN , gain);
1282+ }
1283+ else if (PlaybackGain < 1 .0f )
1284+ {
1285+ fmt::println (" Setting gain {:+}dB ({})" ,
1286+ std::round (std::log10 (double {PlaybackGain})*2000.0 ) / 100.0 , PlaybackGain);
1287+ alSourcef (mSource , AL_GAIN , PlaybackGain);
1288+ }
12621289 if (DirectOutMode)
12631290 alSourcei (mSource , AL_DIRECT_CHANNELS_SOFT , DirectOutMode);
12641291 if (EnableWideStereo)
@@ -2060,6 +2087,8 @@ auto main(std::span<std::string_view> args) -> int
20602087 {
20612088 fmt::println (stderr, " Usage: {} [-device <device name>] [options] <files...>" , args[0 ]);
20622089 fmt::println (stderr, " \n Options:\n "
2090+ " -gain <g> Set audio playback gain (prepend +/- or append \" dB\" to \n "
2091+ " indicate decibels, otherwise it's linear amplitude)\n "
20632092 " -novideo Disable video playback\n "
20642093 " -direct Play audio directly on the output, bypassing virtualization\n "
20652094 " -superstereo Apply Super Stereo processing to stereo tracks\n "
@@ -2124,8 +2153,10 @@ auto main(std::span<std::string_view> args) -> int
21242153 }
21252154 /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */
21262155
2127- auto curarg = std::ranges::find_if (args, [](const std::string_view argval)
2156+ auto curarg = args.begin ();
2157+ for (auto args_end=args.end ();curarg != args_end;++curarg)
21282158 {
2159+ const auto argval = *curarg;
21292160 if (argval == " -direct" )
21302161 {
21312162 if (alIsExtensionPresent (" AL_SOFT_direct_channels_remix" ))
@@ -2140,7 +2171,7 @@ auto main(std::span<std::string_view> args) -> int
21402171 }
21412172 else
21422173 fmt::println (stderr, " AL_SOFT_direct_channels not supported for direct output" );
2143- return false ;
2174+ continue ;
21442175 }
21452176 if (argval == " -wide" )
21462177 {
@@ -2151,7 +2182,7 @@ auto main(std::span<std::string_view> args) -> int
21512182 fmt::println (" Found AL_EXT_STEREO_ANGLES" );
21522183 EnableWideStereo = true ;
21532184 }
2154- return false ;
2185+ continue ;
21552186 }
21562187 if (argval == " -uhj" )
21572188 {
@@ -2162,7 +2193,7 @@ auto main(std::span<std::string_view> args) -> int
21622193 fmt::println (" Found AL_SOFT_UHJ" );
21632194 EnableUhj = true ;
21642195 }
2165- return false ;
2196+ continue ;
21662197 }
21672198 if (argval == " -superstereo" )
21682199 {
@@ -2173,15 +2204,51 @@ auto main(std::span<std::string_view> args) -> int
21732204 fmt::println (" Found AL_SOFT_UHJ (Super Stereo)" );
21742205 EnableSuperStereo = true ;
21752206 }
2176- return false ;
2207+ continue ;
21772208 }
21782209 if (argval == " -novideo" )
21792210 {
21802211 DisableVideo = true ;
2181- return false ;
2212+ continue ;
21822213 }
2183- return true ;
2184- });
2214+ if (argval == " -gain" )
2215+ {
2216+ if (curarg+1 == args_end)
2217+ fmt::println (stderr, " Missing argument for -gain" );
2218+ else
2219+ {
2220+ const auto optarg = *++curarg;
2221+
2222+ auto endpos = size_t {};
2223+ const auto gainval = std::invoke ([optarg,&endpos]
2224+ {
2225+ try { return std::stof (std::string{optarg}, &endpos); }
2226+ catch (std::exception &e) {
2227+ fmt::println (stderr, " Exception reading gain value: {}" , e.what ());
2228+ }
2229+ return std::numeric_limits<float >::quiet_NaN ();
2230+ });
2231+ if (optarg.starts_with (" +" ) || optarg.starts_with (" -" )
2232+ || al::case_compare (optarg.substr (endpos), " db" ) == 0 )
2233+ {
2234+ if (!std::isfinite (gainval) || (endpos != optarg.size ()
2235+ && al::case_compare (optarg.substr (endpos), " db" ) != 0 ))
2236+ fmt::println (stderr, " Invalid dB gain value: {}" , optarg);
2237+ else
2238+ PlaybackGain = std::pow (10 .0f , gainval/20 .0f );
2239+ }
2240+ else
2241+ {
2242+ if (endpos != optarg.size () || !(gainval >= 0 .0f ) || !std::isfinite (gainval))
2243+ fmt::println (stderr, " Invalid linear gain value: {}" , optarg);
2244+ else
2245+ PlaybackGain = gainval;
2246+ }
2247+ }
2248+ continue ;
2249+ }
2250+ break ;
2251+ }
21852252
21862253 auto movState = std::unique_ptr<MovieState>{};
21872254 curarg = std::ranges::find_if (curarg, args.end (), [&movState](const std::string_view argval)
0 commit comments