#include #include #include #include #include "AudioFile.h" #include "json.hpp" #include "cxxopts.hpp" #include "FFT.hpp" #define PRINTER(s, x) if(!s.quiet) { std::cout << x; } struct Settings { std::vector files; bool quiet; float splitInterval; double minFreq, maxFreq; unsigned int analyzeChannel; unsigned int zeropadding; }; Settings Parse(int argc, char** argv); int main(int argc, char** argv) { Settings setts; setts = Parse(argc, argv); int numFiles = setts.files.size(); for (auto& file : setts.files) { AudioFile audioFile; if (!audioFile.load(file.string())) { continue; } std::string filename = file.filename().string(); int sampleRate = audioFile.getSampleRate(); int numChannels = audioFile.getNumChannels(); nlohmann::json output; int c = setts.analyzeChannel; if (c == 0) c = 1; else numChannels = c; for (int c = 1; c <= numChannels; c++) { PRINTER(setts, "\rAnalyzing " << filename << "... Channel " << c << "/" << numChannels << " 0% "); std::string chName = "channel_" + std::to_string(c); output[chName] = nlohmann::json::array(); if (setts.splitInterval == 0.0f) { std::vector> spectrum = FFT( audioFile.samples[c-1].cbegin(), audioFile.samples[c-1].cend(), sampleRate, setts.minFreq, setts.maxFreq, setts.zeropadding ); output[chName] = nlohmann::json::array(); for (const std::pair& 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> spectrum = FFT( audioFile.samples[c-1].cbegin() + currentSample, std::min( audioFile.samples[c-1].cbegin() + currentSample + sampleInterval, audioFile.samples[c-1].cend() ), sampleRate, setts.minFreq, setts.maxFreq, setts.zeropadding ); output[chName].push_back({ {"begin", currentSample}, {"end", currentSample + sampleInterval}, {"spectrum", nlohmann::json::array()} }); for (const std::pair& 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(file.replace_extension("json")); ofs << std::setw(4) << output << std::endl; ofs.close(); PRINTER(setts, "\rAnalyzing " << filename << "... 100% " << std::endl); } return 0; } Settings Parse(int argc, char** argv) { 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()->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()) ("f,frequency", "Defines the frequency range of the output spectrum (Default: all the frequencies)", cxxopts::value>()) ("p,pad", "Add extra zero-padding. By default, the program will pad the signals with 0s until the number of samples is a power of 2 (this would be equivalent to -p 1). With this option you can tell the program to instead pad until the power of 2 after the next one (-p 2) etc. This increases frequency resolution", cxxopts::value()) ("m,mono", "Analyze only the given channel", cxxopts::value()->default_value("0")) ("files", "Files to fourier transform", cxxopts::value>()) ("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("frequency")) { setts.minFreq = 0.0f; setts.maxFreq = 0.0f; } else { setts.minFreq = result["frequency"].as>()[0]; setts.maxFreq = result["frequency"].as>()[1]; } if (!result.count("files")) { std::cerr << "At least one positional argument is required." << std::endl; exit(1); } setts.files = result["files"].as>(); setts.quiet = (result.count("quiet") ? result["quiet"].as() : false); setts.splitInterval = (result.count("interval") ? result["interval"].as() : 0.0f); setts.analyzeChannel = (result.count("mono") ? result["mono"].as() : 0); setts.zeropadding = (result.count("pad") ? result["pad"].as() : 1); if (setts.maxFreq <= setts.minFreq && (setts.maxFreq != 0)) { std::cerr << "Maximum frequency cannot be smaller than minimum frequency" << std::endl; exit(1); } } catch (const cxxopts::OptionException& e) { std::cout << "Invalid parameters: " << e.what() << std::endl; exit(1); } return setts; }