sfizz/demos/PlotLFO.cpp

202 lines
5.7 KiB
C++
Raw Normal View History

2020-08-05 01:00:01 +02:00
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
/**
This program generates the data file of a LFO output recorded for a fixed
duration. The file contains columns for each LFO in the SFZ region.
The columns are: Time, Lfo1, ... LfoN
One can use Gnuplot to display this data.
Example:
sfizz_plot_lfo file.sfz > lfo.dat
gnuplot
plot "lfo.dat" using 1:2 with lines
*/
#include "sfizz/Synth.h"
#include "sfizz/LFO.h"
2020-11-01 09:00:22 +01:00
#include "sfizz/Region.h"
2020-08-05 01:00:01 +02:00
#include "sfizz/LFODescription.h"
#include "sfizz/MathHelpers.h"
#include "cxxopts.hpp"
2020-08-05 01:00:01 +02:00
#include <absl/types/span.h>
#include <vector>
#include <iostream>
#include <cmath>
#ifdef _WIN32
#define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1
#endif
#include <sndfile.hh>
2020-08-05 01:00:01 +02:00
//==============================================================================
static double sampleRate = 1000.0; // sample rate used to compute
static double duration = 5.0; // length in seconds
static std::string outputFilename;
static bool saveFlac = false;
2020-08-05 01:00:01 +02:00
static std::vector<sfz::LFODescription> lfoDescriptionFromSfzFile(const fs::path &sfzPath, bool &success)
{
sfz::Synth synth;
if (!synth.loadSfzFile(sfzPath)) {
std::cerr << "Cannot load the SFZ file.\n";
success = false;
return {};
}
if (synth.getNumRegions() != 1) {
std::cerr << "The SFZ file must contain exactly one region.\n";
success = false;
return {};
}
success = true;
return synth.getRegionView(0)->lfos;
}
/**
Program which loads LFO configuration and generates plot data for the given duration.
*/
int main(int argc, char* argv[])
{
cxxopts::Options options("sfizz_plot_lfo", "Compute LFO and generate plot data");
options.add_options()
("s,samplerate", "Sample rate", cxxopts::value(sampleRate))
("d,duration", "Duration", cxxopts::value(duration))
("o,output", "Output file", cxxopts::value(outputFilename))
("F,flac", "Save output as FLAC", cxxopts::value(saveFlac))
("h,help", "Print usage")
;
options.positional_help("sfz-file");
fs::path sfzPath;
try {
cxxopts::ParseResult result = options.parse(argc, argv);
options.parse_positional({ "sfz-file" });
if (result.count("help")) {
std::cout << options.help() << std::endl;
return 0;
}
if (argc != 2) {
std::cerr << "Please indicate the SFZ file to process.\n";
return 1;
}
sfzPath = argv[1];
}
catch (cxxopts::OptionException& ex) {
std::cerr << ex.what() << "\n";
2020-08-05 01:00:01 +02:00
return 1;
}
bool success = false;
const std::vector<sfz::LFODescription> desc = lfoDescriptionFromSfzFile(sfzPath, success);
if (!success){
std::cerr << "Could not extract LFO descriptions from SFZ file.\n";
2020-08-05 01:00:01 +02:00
return 1;
}
if (sampleRate <= 0) {
std::cerr << "The sample rate provided is invalid.\n";
return 1;
}
2020-08-05 01:00:01 +02:00
size_t numLfos = desc.size();
std::vector<sfz::LFO> lfos(numLfos);
for (size_t l = 0; l < numLfos; ++l) {
lfos[l].setSampleRate(sampleRate);
lfos[l].configure(&desc[l]);
}
size_t numFrames = (size_t)std::ceil(sampleRate * duration);
std::vector<float> outputMemory(numLfos * numFrames);
2020-08-11 23:46:08 +02:00
for (size_t l = 0; l < numLfos; ++l) {
lfos[l].start(0);
2020-08-11 23:46:08 +02:00
}
2020-08-05 01:00:01 +02:00
std::vector<absl::Span<float>> lfoOutputs(numLfos);
for (size_t l = 0; l < numLfos; ++l) {
lfoOutputs[l] = absl::MakeSpan(&outputMemory[l * numFrames], numFrames);
lfos[l].process(lfoOutputs[l]);
}
if (saveFlac) {
if (outputFilename.empty()) {
std::cerr << "Please indicate the audio file to save.\n";
return 1;
}
fs::path outputPath = fs::u8path(outputFilename);
SndfileHandle snd(
#ifndef _WIN32
outputPath.c_str(),
#else
outputPath.wstring().c_str(),
#endif
SFM_WRITE, SF_FORMAT_FLAC|SF_FORMAT_PCM_16, numLfos, sampleRate);
std::unique_ptr<float[]> frame(new float[numLfos]);
size_t numClips = 0;
for (size_t i = 0; i < numFrames; ++i) {
for (size_t l = 0; l < numLfos; ++l) {
float orig = lfoOutputs[l][i];
float clamped = clamp(orig, -1.0f, 1.0f);
numClips += clamped != orig;
frame[l] = clamped;
}
snd.writef(frame.get(), 1);
}
snd.writeSync();
if (snd.error()) {
std::error_code ec;
fs::remove(outputPath, ec);
std::cerr << "Could not save audio to the output file.\n";
return 1;
}
if (numClips > 0)
std::cerr << "Warning: the audio output has been clipped on " << numClips << " frames.\n";
}
else {
std::ostream* os = &std::cout;
fs::ofstream of;
fs::path outputPath;
if (!outputFilename.empty()) {
outputPath = fs::u8path(outputFilename);
of.open(outputPath);
os = &of;
}
for (size_t i = 0; i < numFrames; ++i) {
*os << (i / sampleRate);
for (size_t l = 0; l < numLfos; ++l)
*os << ' ' << lfoOutputs[l][i];
*os << '\n';
}
if (os == &of) {
of.flush();
if (!of) {
std::error_code ec;
fs::remove(outputPath, ec);
std::cerr << "Could not save data to the output file.\n";
return 1;
}
}
2020-08-05 01:00:01 +02:00
}
return 0;
}