Added commandline parsing
This commit is contained in:
parent
7ae946f4c7
commit
c279d783bc
|
@ -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"
|
||||
)
|
|
@ -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
2385
lib/cxxopts/cxxopts.hpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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))
|
||||
{
|
||||
|
|
120
src/main.cpp
120
src/main.cpp
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue