spectralyze/src/main.cpp

167 lines
4.6 KiB
C++
Raw Normal View History

2021-04-27 11:19:52 +00:00
#include <iostream>
#include <fstream>
#include <iomanip>
#include <filesystem>
#include "AudioFile.h"
#include "json.hpp"
2021-04-27 15:15:32 +00:00
#include "cxxopts.hpp"
2021-04-27 11:19:52 +00:00
#include "FFT.hpp"
2021-04-27 15:15:32 +00:00
#define PRINTER(s, x) if(!s.quiet) { std::cout << x; }
struct Settings {
std::vector<std::filesystem::path> files;
bool quiet;
float splitInterval;
2021-04-28 18:10:52 +00:00
double minFreq, maxFreq;
2021-04-27 15:15:32 +00:00
};
Settings Parse(int argc, char** argv);
2021-04-27 11:19:52 +00:00
int main(int argc, char** argv)
{
2021-04-27 15:15:32 +00:00
Settings setts;
setts = Parse(argc, argv);
2021-04-27 11:19:52 +00:00
2021-04-27 15:15:32 +00:00
int numFiles = setts.files.size();
for (auto& file : setts.files) {
2021-04-27 11:19:52 +00:00
AudioFile<double> audioFile;
2021-04-27 15:15:32 +00:00
if (!audioFile.load(file.string()))
{
continue;
}
std::string filename = file.filename().string();
2021-04-27 11:19:52 +00:00
int sampleRate = audioFile.getSampleRate();
int numChannels = audioFile.getNumChannels();
nlohmann::json output;
for (int c = 1; c <= numChannels; c++) {
2021-04-27 15:15:32 +00:00
PRINTER(setts, "\rAnalyzing " << filename << "... Channel " << c << "/" << numChannels << " 0% ");
2021-04-27 11:19:52 +00:00
std::string chName = "channel_" + std::to_string(c);
output[chName] = nlohmann::json::array();
2021-04-27 15:15:32 +00:00
if (setts.splitInterval == 0.0f)
{
2021-04-28 18:10:52 +00:00
std::vector<std::pair<double, double>> spectrum =
FFT(
audioFile.samples[c-1].cbegin(),
audioFile.samples[c-1].cend(),
sampleRate,
setts.minFreq, setts.maxFreq);
2021-04-27 15:15:32 +00:00
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,
2021-04-28 18:10:52 +00:00
audioFile.samples[c-1].cend()
),
sampleRate,
setts.minFreq, setts.maxFreq
2021-04-27 15:15:32 +00:00
);
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) << "% ");
}
2021-04-27 11:19:52 +00:00
}
}
2021-04-27 15:15:32 +00:00
std::ofstream ofs(file.replace_extension("json"));
2021-04-27 11:19:52 +00:00
ofs << std::setw(4) << output << std::endl;
ofs.close();
2021-04-27 15:15:32 +00:00
PRINTER(setts, "\rAnalyzing " << filename << "... 100% " << std::endl);
2021-04-27 11:19:52 +00:00
}
return 0;
}
2021-04-27 15:15:32 +00:00
Settings Parse(int argc, char** argv)
2021-04-27 11:19:52 +00:00
{
2021-04-27 15:15:32 +00:00
Settings setts;
try
{
cxxopts::Options options("spectralyze", "Fourier transforms audio files");
options
.set_width(70)
2021-04-28 18:10:52 +00:00
.positional_help("FILE1 [FILE2...]")
2021-04-27 15:15:32 +00:00
.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>())
2021-04-28 18:10:52 +00:00
("f,frequency", "Defines the frequency range of the output spectrum (Default: all the frequencies)", cxxopts::value<std::vector<double>>())
2021-04-27 15:15:32 +00:00
("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);
}
2021-04-28 18:10:52 +00:00
if (!result.count("frequency"))
{
setts.minFreq = 0.0f;
setts.maxFreq = 0.0f;
}
else
{
setts.minFreq = result["frequency"].as<std::vector<double>>()[0];
setts.maxFreq = result["frequency"].as<std::vector<double>>()[1];
}
2021-04-27 15:15:32 +00:00
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);
2021-04-28 18:10:52 +00:00
if (setts.maxFreq <= setts.minFreq && (setts.maxFreq != 0))
{
std::cerr << "Maximum frequency cannot be smaller than minimum frequency" << std::endl;
exit(1);
}
2021-04-27 15:15:32 +00:00
}
catch (const cxxopts::OptionException& e)
{
std::cout << "Invalid parameters: " << e.what() << std::endl;
exit(1);
}
return setts;
2021-04-27 11:19:52 +00:00
}