Skip to content

Commit

Permalink
Fully support std::format and std::print
Browse files Browse the repository at this point in the history
  • Loading branch information
complexlogic committed Jul 21, 2024
1 parent 96f153d commit acc0599
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 113 deletions.
7 changes: 4 additions & 3 deletions docs/BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ The Windows version uses C++23, and the Unix versions use either C++20 or C++23

### fmt library

The features that rsgain uses from the fmt library have been integrated into the C++20 and C++23 standards, specifically the `<format>` and `<print>` headers. Currently, neither GCC nor Clang have full support those features, so the fmt libary is required. When they gain support, the fmt dependency will be dropped in favor of the C++ standard library.

The features have been incorporated into Microsoft's C++ standard library implementation, so the fmt library is no longer required for the Windows version.
The features that rsgain uses from the fmt library have been integrated into the C++20 and C++23 standards, specifically the `<format>` and `<print>` headers. The standard library implementations are planned to completely replace rsgain's fmt dependency in the future. To support the transition, rsgain can use either the fmt library or standard library, depending on platform:
- MSVC has full support in the standard library since version 19.37. The fmt library has been dropped from the Windows version in favor of the standard library implementation
- GCC (libstdc++) has full support in the standard library since version 14. Pass `-DUSE_STD_FORMAT=ON` to `cmake` to use the standard library instead of the fmt library
- Clang (libc++) has full support in the standard library since version 18. Enable it using the configuration switch described above for GCC

## Unix

Expand Down
44 changes: 22 additions & 22 deletions src/easymode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
#include "scan.hpp"

#define MAX_THREAD_SLEEP 30
#define HELP_STATS(title, format, ...) print(COLOR_YELLOW "{:<18} " COLOR_OFF format "\n", title ":" __VA_OPT__(,) __VA_ARGS__)
#define HELP_STATS(title, format, ...) rsgain::print(COLOR_YELLOW "{:<18} " COLOR_OFF format "\n", title ":" __VA_OPT__(,) __VA_ARGS__)

extern "C" {
int format_handler(void *user, const char *section, const char *name, const char *value);
Expand Down Expand Up @@ -776,7 +776,7 @@ void scan_easy(const std::filesystem::path &path, const std::filesystem::path &p
break;
cv.wait_for(lock, std::chrono::milliseconds(200));
}
print("\33[2K\n");
rsgain::print("\33[2K\n");
}

// Single threaded scanning
Expand All @@ -787,69 +787,69 @@ void scan_easy(const std::filesystem::path &path, const std::filesystem::path &p
job->update_data(data);
jobs.pop();
}
print("\n");
rsgain::print("\n");
}

// Output statistics at the end
auto duration = std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now() - start_time);
if (!data.files) {
if (data.skipped)
print("Skipped {:L} file{} with existing ReplayGain information\n",
rsgain::print("Skipped {:L} file{} with existing ReplayGain information\n",
data.skipped,
data.skipped > 1 ? "s" : ""
);
print("No files were scanned\n");
rsgain::print("No files were scanned\n");
return;
}

print(COLOR_GREEN "Scanning Complete" COLOR_OFF "\n");
rsgain::print(COLOR_GREEN "Scanning Complete" COLOR_OFF "\n");
HELP_STATS("Time Elapsed", "{:%H:%M:%S}", duration);
HELP_STATS("Files Scanned", "{:L}", data.files);
if (data.skipped)
HELP_STATS("Files Skipped", "{:L}", data.skipped);
HELP_STATS("Clip Adjustments", "{:L} ({:.1f}% of files)", data.clipping_adjustments, 100.f * (float) data.clipping_adjustments / (float) data.files);
HELP_STATS("Average Gain", "{:.2f} dB", data.total_gain / (double) data.files);
double average_peak = data.total_peak / (double) data.files;
HELP_STATS("Average Peak", "{:.6f}{}", average_peak, average_peak != 0.0 ? format(" ({:.2f} dB)", 20.0 * log10(average_peak)) : "");
HELP_STATS("Average Peak", "{:.6f}{}", average_peak, average_peak != 0.0 ? rsgain::format(" ({:.2f} dB)", 20.0 * log10(average_peak)) : "");
HELP_STATS("Negative Gains", "{:L} ({:.1f}% of files)", data.total_negative, 100.f * (float) data.total_negative / (float) data.files);
HELP_STATS("Positive Gains", "{:L} ({:.1f}% of files)", data.total_positive, 100.f * (float) data.total_positive / (float) data.files);
print("\n");
rsgain::print("\n");

// Inform user of errors
if (!data.error_directories.empty()) {
print(COLOR_RED "There were errors while scanning the following directories:" COLOR_OFF "\n");
rsgain::print(COLOR_RED "There were errors while scanning the following directories:" COLOR_OFF "\n");
for (const std::string &s : data.error_directories)
print("{}\n", s);
print("\n");
rsgain::print("{}\n", s);
rsgain::print("\n");
}
}

static inline void help_easy() {
print(COLOR_RED "Usage: " COLOR_OFF "{}{}{} easy [OPTIONS] DIRECTORY\n", COLOR_GREEN, EXECUTABLE_TITLE, COLOR_OFF);
rsgain::print(COLOR_RED "Usage: " COLOR_OFF "{}{}{} easy [OPTIONS] DIRECTORY\n", COLOR_GREEN, EXECUTABLE_TITLE, COLOR_OFF);

print(" Easy Mode recursively scans a directory using the recommended settings for each\n");
print(" file type. Easy Mode assumes that you have your music library organized with each album\n");
print(" in its own folder.\n");
rsgain::print(" Easy Mode recursively scans a directory using the recommended settings for each\n");
rsgain::print(" file type. Easy Mode assumes that you have your music library organized with each album\n");
rsgain::print(" in its own folder.\n");

print("\n");
print(COLOR_RED "Options:\n" COLOR_OFF);
rsgain::print("\n");
rsgain::print(COLOR_RED "Options:\n" COLOR_OFF);

CMD_HELP("--help", "-h", "Show this help");
CMD_HELP("--quiet", "-q", "Don't print scanning status messages");
print("\n");
rsgain::print("\n");

CMD_HELP("--skip-existing", "-S", "Don't scan files with existing ReplayGain information");
CMD_HELP("--multithread=n", "-m n", "Scan files with n parallel threads");
CMD_HELP("--preset=s", "-p s", "Load scan preset s");

print("\n");
rsgain::print("\n");

CMD_HELP("--output", "-O", "Output tab-delimited scan data to CSV file per directory");
CMD_HELP("--output=s", "-O s", "Output with sep header (needed for Microsoft Excel compatibility)");
CMD_HELP("--output=a", "-O a", "Output with files sorted in alphanumeric order");

print("\n");
rsgain::print("\n");

print("Please report any issues to " PROJECT_URL "/issues\n");
print("\n");
rsgain::print("Please report any issues to " PROJECT_URL "/issues\n");
rsgain::print("\n");
}
6 changes: 3 additions & 3 deletions src/output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ void ProgressBar::update(int pos)

// Only output if we've actually made progress since last the call, or the console width changed
if (c != c_prev || w != w_prev) {
print(" {:3.0f}% [", percent * 100.f);
rsgain::print(" {:3.0f}% [", percent * 100.f);
memset(buffer, '=', (size_t) c);
memset(buffer + c, ' ', (size_t) (w - c));
buffer[w] = ']';
Expand All @@ -117,7 +117,7 @@ void ProgressBar::complete()

delete buffer;
buffer = nullptr;
print("\n");
rsgain::print("\n");
}

inline int ProgressBar::get_console_width()
Expand All @@ -144,7 +144,7 @@ void MTProgress::update(const std::string &path)
if (w_path + w_message >= w_console)
w_path = w_console - w_message;

print("\33[2K " COLOR_GREEN "{:5.1f}%" COLOR_OFF MT_MESSAGE "{:.{}}\r",
rsgain::print("\33[2K " COLOR_GREEN "{:5.1f}%" COLOR_OFF MT_MESSAGE "{:.{}}\r",
100.f * ((float) (cur) / (float) (total)),
path,
w_path < 0 ? 0 : w_path
Expand Down
28 changes: 20 additions & 8 deletions src/output.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,25 @@
#ifdef USE_STD_FORMAT
#include <format>
#include <print>
#define format std::format
#define print std::print
namespace rsgain {
template<class... Args>
constexpr auto format(std::format_string<Args...> fmt, Args&&... args) { return std::format(fmt, std::forward<Args>(args)...); }
template<class... Args>
constexpr auto print(std::format_string<Args...> fmt, Args&&... args) { std::print(fmt, std::forward<Args>(args)...); }
template<class... Args>
constexpr auto print(std::FILE* stream, std::format_string<Args...> fmt, Args&&... args) { std::print(stream, fmt, std::forward<Args>(args)...); }
}
#else
#include <fmt/core.h>
#include <fmt/chrono.h>
#define format fmt::format
#define print fmt::print
namespace rsgain {
template<class... Args>
constexpr auto format(fmt::format_string<Args...> fmt, Args&&... args) { return fmt::format(fmt, std::forward<Args>(args)...); }
template<class... Args>
constexpr auto print(fmt::format_string<Args...> fmt, Args&&... args) { fmt::print(fmt, std::forward<Args>(args)...); }
template<class... Args>
constexpr auto print(std::FILE* stream, fmt::format_string<Args...> fmt, Args&&... args) { fmt::print(stream, fmt, std::forward<Args>(args)...); }
}
#endif

#define COLOR_GREEN ""
Expand Down Expand Up @@ -87,10 +99,10 @@
#define FAIL_PREFIX "[" COLOR_RED FAIL_CHAR COLOR_OFF "] "

extern int quiet;
#define output_ok(format, ...) if (!quiet) print(OK_PREFIX format "\n" __VA_OPT__(,) __VA_ARGS__)
#define output_warn(format, ...) if (!quiet) print(WARN_PREFIX format "\n" __VA_OPT__(,) __VA_ARGS__)
#define output_error(format, ...) print(stderr, ERROR_PREFIX format "\n" __VA_OPT__(,) __VA_ARGS__)
#define output_fail(format, ...) print(stderr, FAIL_PREFIX format "\n" __VA_OPT__(,) __VA_ARGS__)
#define output_ok(format, ...) if (!quiet) rsgain::print(OK_PREFIX format "\n" __VA_OPT__(,) __VA_ARGS__)
#define output_warn(format, ...) if (!quiet) rsgain::print(WARN_PREFIX format "\n" __VA_OPT__(,) __VA_ARGS__)
#define output_error(format, ...) rsgain::print(stderr, ERROR_PREFIX format "\n" __VA_OPT__(,) __VA_ARGS__)
#define output_fail(format, ...) rsgain::print(stderr, FAIL_PREFIX format "\n" __VA_OPT__(,) __VA_ARGS__)

class ProgressBar {
private:
Expand Down
82 changes: 41 additions & 41 deletions src/rsgain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ extern "C" {
#include "output.hpp"
#include "easymode.hpp"

#define PRINT_LIB(lib, version) print(" " COLOR_YELLOW " {:<14}" COLOR_OFF " {}\n", lib, version)
#define PRINT_LIB(lib, version) rsgain::print(" " COLOR_YELLOW " {:<14}" COLOR_OFF " {}\n", lib, version)
#define PRINT_LIB_FFMPEG(name, fn) \
ffver = fn(); \
PRINT_LIB(name, format("{}.{}.{}", AV_VERSION_MAJOR(ffver), AV_VERSION_MINOR(ffver), AV_VERSION_MICRO(ffver)))
PRINT_LIB(name, rsgain::format("{}.{}.{}", AV_VERSION_MAJOR(ffver), AV_VERSION_MINOR(ffver), AV_VERSION_MICRO(ffver)))

#ifdef _WIN32
#include <windows.h>
Expand Down Expand Up @@ -386,77 +386,77 @@ int main(int argc, char *argv[]) {
}

static void help_main() {
print(COLOR_RED "Usage: " COLOR_OFF "{}{}{} [OPTIONS] <command> ...\n", COLOR_GREEN, EXECUTABLE_TITLE, COLOR_OFF);
rsgain::print(COLOR_RED "Usage: " COLOR_OFF "{}{}{} [OPTIONS] <command> ...\n", COLOR_GREEN, EXECUTABLE_TITLE, COLOR_OFF);

print("{} {} supports writing tags to the following file types:\n", PROJECT_NAME, PROJECT_VERSION);
print(" FLAC (.flac), Ogg (.ogg, .oga, .spx), Opus (.opus), MP2 (.mp2),\n");
print(" MP3 (.mp3), MP4 (.m4a), WMA (.wma), WavPack (.wv), APE (.ape),\n");
print(" WAV (.wav), AIFF (.aiff, .aif, .snd), and TAK (.tak).\n");
rsgain::print("{} {} supports writing tags to the following file types:\n", PROJECT_NAME, PROJECT_VERSION);
rsgain::print(" FLAC (.flac), Ogg (.ogg, .oga, .spx), Opus (.opus), MP2 (.mp2),\n");
rsgain::print(" MP3 (.mp3), MP4 (.m4a), WMA (.wma), WavPack (.wv), APE (.ape),\n");
rsgain::print(" WAV (.wav), AIFF (.aiff, .aif, .snd), and TAK (.tak).\n");

print("\n");
print(COLOR_RED "Options:\n" COLOR_OFF);
rsgain::print("\n");
rsgain::print(COLOR_RED "Options:\n" COLOR_OFF);

CMD_HELP("--help", "-h", "Show this help");
CMD_HELP("--version", "-v", "Show version number");

print("\n");
print(COLOR_RED "Commands:\n" COLOR_OFF);
rsgain::print("\n");
rsgain::print(COLOR_RED "Commands:\n" COLOR_OFF);

CMD_CMD("easy", "Easy Mode: Recursively scan a directory with recommended settings");
CMD_CMD("custom", "Custom Mode: Scan individual files with custom settings");
print("\n");
print("Run '{} easy --help' or '{} custom --help' for more information.", EXECUTABLE_TITLE, EXECUTABLE_TITLE);
rsgain::print("\n");
rsgain::print("Run '{} easy --help' or '{} custom --help' for more information.", EXECUTABLE_TITLE, EXECUTABLE_TITLE);

print("\n\n");
print("Please report any issues to " PROJECT_URL "/issues\n\n");
rsgain::print("\n\n");
rsgain::print("Please report any issues to " PROJECT_URL "/issues\n\n");
}

static inline void help_custom() {
print(COLOR_RED "Usage: " COLOR_OFF "{}{}{} custom [OPTIONS] FILES...\n", COLOR_GREEN, EXECUTABLE_TITLE, COLOR_OFF);
rsgain::print(COLOR_RED "Usage: " COLOR_OFF "{}{}{} custom [OPTIONS] FILES...\n", COLOR_GREEN, EXECUTABLE_TITLE, COLOR_OFF);

print(" Custom Mode allows the user to specify the options to scan the files with. The\n");
print(" list of files to scan must be listed explicitly after the options.\n");
print("\n");
rsgain::print(" Custom Mode allows the user to specify the options to scan the files with. The\n");
rsgain::print(" list of files to scan must be listed explicitly after the options.\n");
rsgain::print("\n");

print(COLOR_RED "Options:\n" COLOR_OFF);
rsgain::print(COLOR_RED "Options:\n" COLOR_OFF);
CMD_HELP("--help", "-h", "Show this help");
print("\n");
rsgain::print("\n");

CMD_HELP("--album", "-a", "Calculate album gain and peak");
CMD_HELP("--skip-existing", "-S", "Don't scan files with existing ReplayGain information");
print("\n");
rsgain::print("\n");

CMD_HELP("--tagmode=s", "-s s", "Scan files but don't write ReplayGain tags (default)");
CMD_HELP("--tagmode=d", "-s d", "Delete ReplayGain tags from files");
CMD_HELP("--tagmode=i", "-s i", "Scan and write ReplayGain 2.0 tags to files");
print("\n");
rsgain::print("\n");

CMD_HELP("--loudness=n", "-l n", "Use n LUFS as target loudness (" STR(MIN_TARGET_LOUDNESS) " ≤ n ≤ " STR(MAX_TARGET_LOUDNESS) ")");
print("\n");
rsgain::print("\n");

CMD_HELP("--clip-mode=n", "-c n", "No clipping protection (default)");
CMD_HELP("--clip-mode=p", "-c p", "Clipping protection enabled for positive gain values only");
CMD_HELP("--clip-mode=a", "-c a", "Clipping protection always enabled");
CMD_HELP("--max-peak=n", "-m n", "Use max peak level n dB for clipping protection");
CMD_HELP("--true-peak", "-t", "Use true peak for peak calculations");

print("\n");
rsgain::print("\n");

CMD_HELP("--lowercase", "-L", "Write lowercase tags (MP2/MP3/MP4/WMA/WAV/AIFF)");
CMD_CONT("This is non-standard but sometimes needed");
CMD_HELP("--id3v2-version=keep", "-I keep", "Keep file's existing ID3v2 version, 3 if none exists (default)");
CMD_HELP("--id3v2-version=3", "-I 3", "Write ID3v2.3 tags to MP2/MP3/WAV/AIFF");
CMD_HELP("--id3v2-version=4", "-I 4", "Write ID3v2.4 tags to MP2/MP3/WAV/AIFF");

print("\n");
rsgain::print("\n");

CMD_HELP("--opus-mode=d", "-o d", "Write standard ReplayGain tags, clear header output gain (default)");
CMD_HELP("--opus-mode=r", "-o r", "Write R128_*_GAIN tags, clear header output gain");
CMD_HELP("--opus-mode=s", "-o s", "Same as 'r', plus override target loudness to -23 LUFS");
CMD_HELP("--opus-mode=t", "-o t", "Write track gain to header output gain");
CMD_HELP("--opus-mode=a", "-o a", "Write album gain to header output gain");

print("\n");
rsgain::print("\n");

CMD_HELP("--output", "-O", "Output tab-delimited scan data to stdout");
CMD_HELP("--output=s", "-O s", "Output with sep header (needed for Microsoft Excel compatibility)");
Expand All @@ -465,18 +465,18 @@ static inline void help_custom() {
CMD_HELP("--preserve-mtimes", "-p", "Preserve file mtimes");
CMD_HELP("--quiet", "-q", "Don't print scanning status messages");

print("\n");
rsgain::print("\n");

print("Please report any issues to " PROJECT_URL "/issues\n");
print("\n");
rsgain::print("Please report any issues to " PROJECT_URL "/issues\n");
rsgain::print("\n");
}

static void version() {
unsigned int ffver;
int ebur128_v_major = 0;
int ebur128_v_minor = 0;
int ebur128_v_patch = 0;
print(COLOR_GREEN PROJECT_NAME COLOR_OFF " " PROJECT_VERSION
rsgain::print(COLOR_GREEN PROJECT_NAME COLOR_OFF " " PROJECT_VERSION
#if defined COMMITS_SINCE_TAG && defined COMMIT_HASH
"-r" COMMITS_SINCE_TAG "-" COMMIT_HASH
#endif
Expand All @@ -485,36 +485,36 @@ static void version() {

// Library versions
ebur128_get_version(&ebur128_v_major, &ebur128_v_minor, &ebur128_v_patch);
PRINT_LIB("libebur128", format("{}.{}.{}", ebur128_v_major, ebur128_v_minor, ebur128_v_patch));
PRINT_LIB("libebur128", rsgain::format("{}.{}.{}", ebur128_v_major, ebur128_v_minor, ebur128_v_patch));
PRINT_LIB_FFMPEG("libavformat", avformat_version);
PRINT_LIB_FFMPEG("libavcodec", avcodec_version);
PRINT_LIB_FFMPEG("libavutil", avutil_version);
PRINT_LIB_FFMPEG("libswresample", swresample_version);
#if HAS_TAGLIB2
TagLib::VersionNumber tver = TagLib::runtimeVersion();
PRINT_LIB("TagLib", format("{}.{}{}", tver.majorVersion(), tver.minorVersion(), tver.patchVersion() ? format(".{}", tver.patchVersion()) : ""));
PRINT_LIB("TagLib", rsgain::format("{}.{}{}", tver.majorVersion(), tver.minorVersion(), tver.patchVersion() ? rsgain::format(".{}", tver.patchVersion()) : ""));
#else
print("\n");
print("Built with:\n");
PRINT_LIB("TagLib", format("{}.{}{}", TAGLIB_MAJOR_VERSION, TAGLIB_MINOR_VERSION, TAGLIB_PATCH_VERSION ? format(".{}", TAGLIB_PATCH_VERSION) : ""));
rsgain::print("\n");
rsgain::print("Built with:\n");
PRINT_LIB("TagLib", rsgain::format("{}.{}{}", TAGLIB_MAJOR_VERSION, TAGLIB_MINOR_VERSION, TAGLIB_PATCH_VERSION ? rsgain::format(".{}", TAGLIB_PATCH_VERSION) : ""));
#endif
print("\n");
rsgain::print("\n");

#if defined(__GNUC__) && !defined(__clang__)
print(COLOR_YELLOW "{:<17}" COLOR_OFF " GCC {}.{}\n", "Compiler:", __GNUC__, __GNUC_MINOR__);
rsgain::print(COLOR_YELLOW "{:<17}" COLOR_OFF " GCC {}.{}\n", "Compiler:", __GNUC__, __GNUC_MINOR__);
#endif

#ifdef __clang__
print(COLOR_YELLOW "{:<17}" COLOR_OFF " "
rsgain::print(COLOR_YELLOW "{:<17}" COLOR_OFF " "
#ifdef __apple_build_version__
"Apple "
#endif
"Clang {}.{}.{}\n", "Compiler:", __clang_major__, __clang_minor__, __clang_patchlevel__);
#endif

#ifdef _MSC_VER
print(COLOR_YELLOW "{:<17}" COLOR_OFF " Microsoft C/C++ {:.2f}\n", "Compiler:", (float) _MSC_VER / 100.0f);
rsgain::print(COLOR_YELLOW "{:<17}" COLOR_OFF " Microsoft C/C++ {:.2f}\n", "Compiler:", (float) _MSC_VER / 100.0f);
#endif

print(COLOR_YELLOW "{:<17}" COLOR_OFF " " BUILD_DATE "\n", "Build Date:");
rsgain::print(COLOR_YELLOW "{:<17}" COLOR_OFF " " BUILD_DATE "\n", "Build Date:");
}
6 changes: 3 additions & 3 deletions src/rsgain.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#pragma once

#define CMD_HELP(CMDL, CMDS, MSG) print(" {}{:<8} {:<20}{} {}.\n", COLOR_YELLOW, CMDS ",", CMDL, COLOR_OFF, MSG);
#define CMD_CMD(CMD, MSG) print(" {}{:<22}{} {}.\n", COLOR_YELLOW, CMD, COLOR_OFF, MSG);
#define CMD_CONT(MSG) print(" {}{:<8} {:<20}{} {}.\n", COLOR_YELLOW, "", "", COLOR_OFF, MSG);
#define CMD_HELP(CMDL, CMDS, MSG) rsgain::print(" {}{:<8} {:<20}{} {}.\n", COLOR_YELLOW, CMDS ",", CMDL, COLOR_OFF, MSG);
#define CMD_CMD(CMD, MSG) rsgain::print(" {}{:<22}{} {}.\n", COLOR_YELLOW, CMD, COLOR_OFF, MSG);
#define CMD_CONT(MSG) rsgain::print(" {}{:<8} {:<20}{} {}.\n", COLOR_YELLOW, "", "", COLOR_OFF, MSG);
#define MATCH(x,y) !strcmp(x,y)
#define STR_CAT(a) #a
#define STR(a) STR_CAT(a)
Expand Down
Loading

0 comments on commit acc0599

Please sign in to comment.