Added commandline parsing

This commit is contained in:
Robert 2021-04-27 17:15:32 +02:00
parent 7ae946f4c7
commit c279d783bc
5 changed files with 2497 additions and 19 deletions

View file

@ -6,9 +6,10 @@ project(spectralyze)
add_executable(spectralyze
"src/main.cpp"
"src/FFT.hpp")
"src/FFT.hpp" "lib/cxxopts/cxxopts.hpp")
target_include_directories(spectralyze PRIVATE
"lib/AudioFile"
"lib/json"
"lib/cxxopts"
)

View file

@ -24,3 +24,8 @@ Each audio channel in the file is transformed separately. The resulting JSON has
}
```
Every supplied audio file will result in one JSON file. The magnitude is the absolute value of the real and imaginary part of the Fourier transformation.
## Used libraries
* [AudioFile](https://github.com/adamstark/AudioFile) for loading audio files
* [JSON for Modern C++](https://github.com/nlohmann/json) for writing JSON data
* [cxxopts](https://github.com/jarro2783/cxxopts) for parsing commandline arguments

2385
lib/cxxopts/cxxopts.hpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -49,8 +49,9 @@ std::vector<std::complex<double>> radix2dit(const std::vector<double>::const_ite
return output;
}
std::vector<std::pair<double, double>> FFT(std::vector<double> signal, size_t sampleRate)
std::vector<std::pair<double, double>> FFT(const std::vector<double>::const_iterator& begin, const std::vector<double>::const_iterator& end, size_t sampleRate)
{
std::vector<double> signal(begin, end);
size_t N = signal.size();
while (!POW_OF_TWO(N))
{

View file

@ -5,49 +5,135 @@
#include "AudioFile.h"
#include "json.hpp"
#include "cxxopts.hpp"
#include "FFT.hpp"
void PrintUsage();
#define PRINTER(s, x) if(!s.quiet) { std::cout << x; }
struct Settings {
std::vector<std::filesystem::path> files;
bool quiet;
float splitInterval;
};
Settings Parse(int argc, char** argv);
int main(int argc, char** argv)
{
if (argc < 2) {
PrintUsage();
return 1;
}
Settings setts;
setts = Parse(argc, argv);
int numFiles = argc - 1;
for (int i = 1; i < argc; i++) {
int numFiles = setts.files.size();
for (auto& file : setts.files) {
AudioFile<double> audioFile;
audioFile.load(argv[i]);
std::string filename = std::filesystem::path(argv[i]).filename().string();
if (!audioFile.load(file.string()))
{
continue;
}
std::string filename = file.filename().string();
int sampleRate = audioFile.getSampleRate();
int numChannels = audioFile.getNumChannels();
nlohmann::json output;
for (int c = 1; c <= numChannels; c++) {
std::cout << "\rAnalyzing " << filename << "... Channel " << c << "/" << numChannels << " ";
std::vector<std::pair<double, double>> spectrum = FFT(audioFile.samples[c-1], sampleRate);
PRINTER(setts, "\rAnalyzing " << filename << "... Channel " << c << "/" << numChannels << " 0% ");
std::string chName = "channel_" + std::to_string(c);
output[chName] = nlohmann::json::array();
for (const std::pair<double, double>& pair : spectrum) {
output[chName].push_back({ {"freq", pair.first}, {"mag", pair.second } });
if (setts.splitInterval == 0.0f)
{
std::vector<std::pair<double, double>> spectrum = FFT(audioFile.samples[c-1].cbegin(), audioFile.samples[c-1].cend(), sampleRate);
output[chName] = nlohmann::json::array();
for (const std::pair<double, double>& pair : spectrum) {
output[chName].push_back({ {"freq", pair.first}, {"mag", pair.second } });
}
}
else
{
int sampleInterval = sampleRate * setts.splitInterval / 1000;
int currentSample;
for (currentSample = 0; currentSample < audioFile.samples[c - 1].size(); currentSample += sampleInterval)
{
std::vector<std::pair<double, double>> spectrum =
FFT(
audioFile.samples[c-1].cbegin() + currentSample,
std::min(
audioFile.samples[c-1].cbegin() + currentSample + sampleInterval,
audioFile.samples[c-1].cend()),
sampleRate
);
output[chName].push_back({
{"begin", currentSample},
{"end", currentSample + sampleInterval},
{"spectrum", nlohmann::json::array()}
});
for (const std::pair<double, double>& pair : spectrum) {
output[chName].back()["spectrum"].push_back({ {"freq", pair.first}, {"mag", pair.second } });
}
PRINTER(setts, "\rAnalyzing " << filename << "... Channel " << c << "/" << numChannels << " " << (int)std::floor((float)currentSample / (float)audioFile.samples[c-1].size() * 100.0f) << "% ");
}
}
}
std::ofstream ofs(std::filesystem::path(argv[i]).replace_extension("json"));
std::ofstream ofs(file.replace_extension("json"));
ofs << std::setw(4) << output << std::endl;
ofs.close();
std::cout << "\rAnalyzing " << filename << "... Done! " << std::endl;
PRINTER(setts, "\rAnalyzing " << filename << "... 100% " << std::endl);
}
return 0;
}
void PrintUsage()
Settings Parse(int argc, char** argv)
{
std::cerr << "Usage: spectralyze file1 [file2...]" << std::endl;
Settings setts;
try
{
cxxopts::Options options("spectralyze", "Fourier transforms audio files");
options
.set_width(70)
.positional_help("file1 [file2...]")
.add_options()
("q,quiet", "Suppress text output", cxxopts::value<bool>()->default_value("false"))
("i,interval", "Splits audio file into intervals of length i milliseconds and transforms them individually (0 to not split file)", cxxopts::value<float>())
("files", "Files to fourier transform", cxxopts::value<std::vector<std::filesystem::path>>())
("h,help", "Print usage")
;
options.parse_positional("files");
auto result = options.parse(argc, argv);
if (result.count("help"))
{
std::cout << options.help() << std::endl;
exit(0);
}
if (!result.count("files"))
{
std::cerr << "At least one positional argument is required." << std::endl;
exit(1);
}
setts.files = result["files"].as<std::vector<std::filesystem::path>>();
setts.quiet = (result.count("quiet") ? result["quiet"].as<bool>() : false);
setts.splitInterval = (result.count("interval") ? result["interval"].as<float>() : 0.0f);
}
catch (const cxxopts::OptionException& e)
{
std::cout << "Invalid parameters: " << e.what() << std::endl;
exit(1);
}
return setts;
}