Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add export to WAV command-line functionality #372

Open
wants to merge 77 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
1eacee8
Set C++ standard to C++11.
techanvil Dec 30, 2024
ece27b5
Add export-to-wav utility.
techanvil Dec 27, 2024
b4fa593
Add debug logging to export-to-wav.
techanvil Dec 27, 2024
aa8a6d0
Add debug build target.
techanvil Dec 27, 2024
1e6b9a9
Handle return value correctly.
techanvil Dec 28, 2024
da98993
Create trackerlib library for shared functionality.
techanvil Dec 28, 2024
c4ed563
Use ModuleServices::exportToWAV instead of PlayerGeneric::exportToWAV.
techanvil Dec 28, 2024
89dd087
Use export params from tracker config,
techanvil Dec 28, 2024
6589283
Add explicit cast to fix build error.
techanvil Dec 28, 2024
c69d448
Add command line args to export-to-wav.
techanvil Dec 28, 2024
194f63b
Allow DMG build to be skipped.
techanvil Dec 28, 2024
569c9a3
Allow multi track output.
techanvil Dec 28, 2024
e5e1b74
Rename CLI args.
techanvil Dec 28, 2024
c76fa8c
Remove silent wavs in multi-track mode.
techanvil Dec 28, 2024
ff348fb
Add build-debug to .gitignore.
techanvil Dec 30, 2024
ceee915
Move export-to-wav to its own directory.
techanvil Dec 30, 2024
fc53709
Lift isWavSilent into separate utility.
techanvil Dec 30, 2024
3935e34
Create Parameters class with RAII handling for automatic cleanup.
techanvil Dec 30, 2024
0809549
Fix build errors.
techanvil Dec 30, 2024
77790c1
Rename Wav -> WAV for consistency.
techanvil Dec 30, 2024
d502771
Use platform specific osinterface include.
techanvil Dec 30, 2024
8254e04
Fix build errors.
techanvil Dec 30, 2024
1dbebec
Add comment.
techanvil Dec 31, 2024
c9cba14
Add a verbose flag to export-to-wav.
techanvil Dec 31, 2024
5db46fe
Remove unnecessary changes.
techanvil Dec 31, 2024
ca5d63f
Merge branch 'master' into trh/export-to-wav
techanvil Jan 24, 2025
25cc081
Refactor WAV export functionality: Introduce WAV export parameters an…
techanvil Jan 14, 2025
01636e8
Update export-to-wav include paths and CMake configuration
techanvil Jan 23, 2025
27152d1
Enhance WAV export command-line parsing with improved input validatio…
techanvil Jan 23, 2025
4472ee3
Rename WAVExportParams to WAVExportArgs and update related files
techanvil Jan 23, 2025
b649c6f
Refactor export-to-wav to improve error handling and configuration lo…
techanvil Jan 23, 2025
1f923a6
Improve WAV export arguments initialization and memory management
techanvil Jan 24, 2025
802d385
Add input file existence check in export-to-wav tool
techanvil Jan 24, 2025
78a3ce4
Update SDL_Main.cpp to use WAVExportArgs header
techanvil Jan 24, 2025
18bde8a
Refactor export-to-wav tool: Extract WAV export logic into WAVExporte…
techanvil Jan 24, 2025
d5b3a04
Unify WAV export implementation across platforms using WAVExporter
techanvil Jan 24, 2025
c800ee0
Refactor WAVExporter to support modular export process and improve er…
techanvil Jan 24, 2025
ead9830
Implement deep copy and memory management for WAVExportArgs
techanvil Jan 24, 2025
98b4d34
Fix memory management in WAVExportArgs command-line parsing
techanvil Jan 24, 2025
a6392e1
Add channel count support to WAV export process
techanvil Jan 24, 2025
d044e8d
Improve WAV export error handling and muting array management
techanvil Jan 24, 2025
3ec0bc2
Refactor WAVExporter error handling and argument parsing
techanvil Jan 24, 2025
8f882f7
Add CLI argument parsing library and refactor WAV export argument han…
techanvil Jan 24, 2025
ace6765
Add built-in help functionality to CLIParser
techanvil Jan 24, 2025
45e4715
Enhance CLIParser with additional help text and refactor WAV export a…
techanvil Jan 24, 2025
a20edf8
Improve CLIParser option value validation
techanvil Jan 24, 2025
b146115
Integrate CLI parsing with AppDelegate and main for macOS tracker
techanvil Jan 24, 2025
cd61af7
Refactor WAVExporter to support new CLI parser integration
techanvil Jan 24, 2025
584d869
Refactor WAVExporter to improve parser-based export workflow
techanvil Jan 24, 2025
dd25df0
Update CLI parsing in macOS tracker main and WAV export arguments
techanvil Jan 24, 2025
8fa0013
Add method to set positional argument values in CLIParser
techanvil Jan 24, 2025
a0fcace
Improve file loading during macOS tracker startup
techanvil Jan 24, 2025
081c5a1
Enhance CLIParser to support required positional arguments
techanvil Jan 24, 2025
21a876f
Refactor WAVExporter and CLIParser to improve export workflow
techanvil Jan 24, 2025
8771821
Improve error handling and input validation in WAV export workflow
techanvil Jan 24, 2025
2dc3038
Refactor WAV export main workflow and error handling
techanvil Jan 24, 2025
90d4110
Remove unused CLI option handling in AppDelegate
techanvil Jan 24, 2025
ac1eb56
Standardize code formatting across WAV export and CLI parsing modules
techanvil Jan 25, 2025
1045326
Simplify include paths and remove commented-out code
techanvil Jan 25, 2025
ce63f77
Enhance CLIParser with option value validation and improved usage dis…
techanvil Jan 25, 2025
f476336
Refactor CLIParser to support template constructor and improve memory…
techanvil Jan 25, 2025
2e2b3a6
Enhance CLIParser option detection to support single-letter options
techanvil Jan 25, 2025
ad6c5e7
Enhance CLIParser help flag handling and support custom help flags
techanvil Jan 25, 2025
9b3e2e2
Refactor SDL_Main argument parsing and help handling
techanvil Jan 25, 2025
273c1b7
Simplify SDL_Main argument handling and add debug logging
techanvil Jan 25, 2025
a812242
Improve SDL_Main argument handling with safer memory management
techanvil Jan 25, 2025
af1aa6d
Rename positional argument and remove debug print statements
techanvil Jan 25, 2025
1e74da8
Add support for single-dash CLI option formats in WAV export
techanvil Jan 25, 2025
5ab613f
Update WAVExporter to pass dash format to WAVExportArgs registration
techanvil Jan 25, 2025
481d471
Remove redundant -output option from SDL_Main
techanvil Jan 25, 2025
492e6b3
Add debug method to dump parsed CLI options and arguments
techanvil Jan 25, 2025
3f54daf
Refactor dumpParsedOptions() to use range-based for loop
techanvil Jan 25, 2025
5f086a2
Refactor WAVExporter error handling
techanvil Jan 25, 2025
652f3e8
Remove debug print statements for loadFile and outputWAVFile
techanvil Jan 25, 2025
fbf7350
Consolidate CLI and WAV export functionality into commandlib
techanvil Jan 25, 2025
a61ee5d
Merge pull request #4 from trh-x/trh/export-to-wav-integration
trh-x Jan 25, 2025
5e19d9f
Update input file argument description for module file
techanvil Jan 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# The build output folder
# The build output folders
build/
build-debug/

# Editor config files
.vscode/
.vs/

# Audio files
*.wav
*.wav.asd
9 changes: 6 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
cmake_minimum_required(VERSION 3.10)
project(MilkyTracker)

# Set C++ standard to C++98
set(CMAKE_CXX_STANDARD 98)
# Set C++ standard to C++11
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)

# Enable IDE solution folders
Expand Down Expand Up @@ -63,7 +63,9 @@ configure_file(
)

# Packaging
if(APPLE)
option(BUILD_DMG "Build DMG package on macOS" ON)

if(APPLE AND BUILD_DMG)
set(CPACK_GENERATOR DragNDrop)
set(CPACK_DMG_VOLUME_NAME "${PROJECT_NAME} ${VER_FULL}")
set(
Expand Down Expand Up @@ -225,6 +227,7 @@ add_subdirectory(src/fx)
add_subdirectory(src/milkyplay)
add_subdirectory(src/ppui)
add_subdirectory(src/tracker)
add_subdirectory(src/tools/export-to-wav)

# Set MilkyTracker target as startup project in Visual Studio
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT tracker)
45 changes: 41 additions & 4 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,46 @@
#!/bin/bash
# https://crascit.com/2016/04/03/scripting-cmake-builds/

cmake -E make_directory build
pushd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build . --config Release
# Set defaults
BUILD_TYPE="Release"
BUILD_DMG=ON

# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
Release|Debug)
BUILD_TYPE="$1"
;;
--no-dmg)
BUILD_DMG=OFF
;;
*)
echo "Unknown argument: $1"
echo "Usage: $0 [Release|Debug] [--no-dmg]"
exit 1
;;
esac
shift
done

# Set build directory based on build type
BUILD_DIR="build"
CMAKE_FLAGS="-DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_DMG=$BUILD_DMG"

if [[ "$BUILD_TYPE" == "Debug" ]]; then
BUILD_DIR="build-debug"
# Add debug-specific flags
CMAKE_FLAGS="$CMAKE_FLAGS -DCMAKE_CXX_FLAGS_DEBUG='-g3'"
fi

echo "Building MilkyTracker in $BUILD_TYPE mode in $BUILD_DIR..."
if [ "$BUILD_DMG" = "OFF" ]; then
echo "DMG generation: OFF"
fi

cmake -E make_directory $BUILD_DIR
pushd $BUILD_DIR
cmake $CMAKE_FLAGS ..
cmake --build . --config $BUILD_TYPE
cpack .
popd
250 changes: 250 additions & 0 deletions src/cli/CLIParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
#include "CLIParser.h"
#include <cstring>
#include <cstdio>

// Constructor is now a template in the header

void CLIParser::addOption(const char* name, bool requiresValue, const char* description, const std::vector<std::string>& allowedValues, bool isHelpFlag)
{
options.emplace_back(name, requiresValue, description, allowedValues, isHelpFlag);
}

void CLIParser::addPositionalArg(const char* name, const char* description, bool required)
{
positionalArgs.emplace_back(name, description, required);
}

bool CLIParser::isValidOptionValue(const Option* opt, const char* value) const
{
if (opt->allowedValues.empty()) {
return true; // No restrictions
}

for (const auto& allowed : opt->allowedValues) {
if (allowed == value) {
return true;
}
}
return false;
}

bool CLIParser::parse()
{
parsedOptions.clear();
parsedPositionalArgs.clear();
errorMessage.clear();
helpRequested = false;

size_t positionalIndex = 0;

for (int i = 1; i < argc; i++) {
const char* arg = argv[i];

if (isOption(arg)) {
const Option* opt = findOption(arg);
if (!opt) {
errorMessage = "Unknown option: ";
errorMessage += arg;
return false;
}

// Check if this is a help flag
if (opt->isHelpFlag) {
helpRequested = true;
return true;
}

if (opt->requiresValue) {
if (i + 1 >= argc) {
errorMessage = "Option requires value: ";
errorMessage += arg;
return false;
}
// Check that next argument isn't an option
const char* value = argv[i + 1];
if (isOption(value)) {
errorMessage = "Option requires value, but got another option: ";
errorMessage += arg;
errorMessage += " ";
errorMessage += value;
return false;
}

// Validate value against allowed values if any
if (!isValidOptionValue(opt, value)) {
errorMessage = "Invalid value for option ";
errorMessage += arg;
errorMessage += ": ";
errorMessage += value;
errorMessage += "\nAllowed values are: ";
for (size_t j = 0; j < opt->allowedValues.size(); j++) {
if (j > 0) errorMessage += ", ";
errorMessage += opt->allowedValues[j];
}
return false;
}

parsedOptions[opt->name] = value;
i++; // Skip the value we just processed
} else {
parsedOptions[opt->name] = "true";
}
} else {
if (positionalIndex >= positionalArgs.size()) {
errorMessage = "Too many positional arguments";
return false;
}
parsedPositionalArgs.push_back(arg);
positionalIndex++;
}
}

// If help was requested, no need to validate other args
if (helpRequested) {
return true;
}

// Check each position to ensure required args are satisfied
for (size_t i = 0; i < positionalArgs.size(); i++) {
bool isRequired = std::get<2>(positionalArgs[i]);
if (isRequired && i >= parsedPositionalArgs.size()) {
errorMessage = "Missing required argument: ";
errorMessage += std::get<0>(positionalArgs[i]);
return false;
}
}

return true;
}

bool CLIParser::hasOption(const char* name) const
{
return parsedOptions.find(name) != parsedOptions.end();
}

const char* CLIParser::getOptionValue(const char* name) const
{
auto it = parsedOptions.find(name);
return it != parsedOptions.end() ? it->second.c_str() : nullptr;
}

int CLIParser::getIntOptionValue(const char* name, int defaultValue) const
{
const char* value = getOptionValue(name);
return value ? atoi(value) : defaultValue;
}

const char* CLIParser::getPositionalArg(size_t index) const
{
return index < parsedPositionalArgs.size() ? parsedPositionalArgs[index].c_str() : nullptr;
}

size_t CLIParser::getPositionalArgCount() const
{
return parsedPositionalArgs.size();
}

void CLIParser::printUsage() const
{
fprintf(stderr, "Usage: %s", argv[0]);

// Print positional args in order
for (const auto& arg : positionalArgs) {
if (std::get<2>(arg)) {
fprintf(stderr, " <%s>", std::get<0>(arg).c_str());
} else {
fprintf(stderr, " [%s]", std::get<0>(arg).c_str());
}
}

fprintf(stderr, " [options]\n\nPositional arguments:\n");

// Print positional arg descriptions
for (const auto& arg : positionalArgs) {
fprintf(stderr, " %-20s %s%s\n",
std::get<0>(arg).c_str(),
std::get<1>(arg).c_str(),
std::get<2>(arg) ? "" : " (optional)");
}

fprintf(stderr, "\nOptions:\n");

// Print option descriptions with allowed values
for (const auto& opt : options) {
if (opt.requiresValue) {
fprintf(stderr, " %s <value>", opt.name.c_str());
fprintf(stderr, "%*s%s",
static_cast<int>(20 - strlen(opt.name.c_str()) - 8),
"", opt.description.c_str());

// Print allowed values if any
if (!opt.allowedValues.empty()) {
fprintf(stderr, " (allowed values: ");
for (size_t i = 0; i < opt.allowedValues.size(); i++) {
if (i > 0) fprintf(stderr, ", ");
fprintf(stderr, "%s", opt.allowedValues[i].c_str());
}
fprintf(stderr, ")");
}
fprintf(stderr, "\n");
} else {
fprintf(stderr, " %s", opt.name.c_str());
fprintf(stderr, "%*s%s\n",
static_cast<int>(20 - strlen(opt.name.c_str())),
"", opt.description.c_str());
}
}

// Print additional help text if present
if (!additionalHelpText.empty()) {
fprintf(stderr, "\n%s", additionalHelpText.c_str());
}
}

const char* CLIParser::getError() const
{
return errorMessage.c_str();
}

bool CLIParser::isOption(const char* arg) const
{
return arg[0] == '-' && (arg[1] == '-' || isalpha(arg[1])); // Accept both -- and -alpha
}

const CLIParser::Option* CLIParser::findOption(const char* name) const
{
for (const auto& opt : options) {
if (opt.name == name) {
return &opt;
}
}
return nullptr;
}

void CLIParser::setPositionalArgValue(size_t index, const char* value) {
if (index < parsedPositionalArgs.size()) {
parsedPositionalArgs[index] = value;
}
}

bool CLIParser::hasPositionalArg(const char* name) const {
for (const auto& arg : positionalArgs) {
if (std::get<0>(arg) == name) {
return true;
}
}
return false;
}

void CLIParser::dumpParsedOptions() const {
fprintf(stderr, "\n=== Parsed Options ===\n");
for (const auto& opt : parsedOptions) {
fprintf(stderr, "Option: %s = %s\n", opt.first.c_str(), opt.second.c_str());
}

fprintf(stderr, "\n=== Positional Arguments ===\n");
for (size_t i = 0; i < parsedPositionalArgs.size(); i++) {
fprintf(stderr, "Arg[%zu]: %s\n", i, parsedPositionalArgs[i].c_str());
}
fprintf(stderr, "\n");
}
Loading