Here is the code for RenderMan and the FM Synth for those curious few (note you’ll need to include Maximilian files to build both projects):
RenderMan:
Main.cpp
/*
==============================================================================
______ _ ___ ___
| ___ \ | | | \/ |
| |_/ /___ _ __ __| | ___ _ __| . . | __ _ _ __
| // _ \ '_ \ / _` |/ _ \ '__| |\/| |/ _` | '_ \
| |\ \ __/ | | | (_| | __/ | | | | | (_| | | | |
\_| \_\___|_| |_|\__,_|\___|_| \_| |_/\__,_|_| |_|
* * Command Line VST Audio, Features and Parameter Renderer * *
Copyright (C) 2017 Leon Fedden - All Rights Reserved
==============================================================================
*/
#include "Utils.h"
#include "ParameterGenerator.h"
using namespace juce;
//=============================================================================
int main (int argc, char* argv[])
{
// Convenient class to parse arguments.
ArgumentsParser parser(argc, argv);
String arg;
// If user wants help.
if (parser.flagExists("-h"))
{
printHelp();
exit(0);
}
#ifdef __APPLE__
String defaultVstPath = "/Users/admin/Library/Audio/Plug-Ins/VST/IncrediblySimpleFMSynth.vst";
#else
String defaultVstPath = "/home/tollie/Development/FINAL_PROJECT/plugins/amsynth";
#endif
// Variable declaration and assignment.
double sampleRate = ((arg = parser.getArgument("-s")) != "") ? arg.getDoubleValue() : 44100;
int bufferSize = ((arg = parser.getArgument("-b")) != "") ? arg.getIntValue() : 512;
String vstPath = ((arg = parser.getArgument("-v")) != "") ? arg : defaultVstPath;
String name = getName(vstPath);
String audioPath = "../../../../Renders/";
String audioFile = name + "_audio_";
String featuresPath = "../../../../Features/";
String featuresFileName = name + "_features_";
String parametersPath = "../../../../Parameters/";
String parametersFileName = name + "_parameters_";
double noteLength = ((arg = parser.getArgument("-n")) != "") ? arg.getDoubleValue() : 4;
uint8 midiChannel = 2;
uint8 midiPitch = ((arg = parser.getArgument("-p")) != "") ? uint8(arg.getIntValue()) : 60;
uint8 midiVelocity = 127;
int bitsPerSample = 16;
double renderLength = ((arg = parser.getArgument("-r")) != "") ? arg.getDoubleValue() : noteLength * 2;
int numberOfBuffers = int(std::ceil(renderLength * sampleRate / bufferSize));
long amountOfPresets = ((arg = parser.getArgument("-a")) != "") ? arg.getIntValue() : 20;
int fftSize = 16384;
bool listParameters = (parser.flagExists("-l")) ? true : false;
bool iterateAllPatches = (parser.flagExists("-i")) ? true : false;
// Our vst instance.
auto vst = ScopedPointer<AudioPluginInstance>(createSynthInstance(int(sampleRate),
bufferSize,
vstPath));
const int numberChannels = vst->getTotalNumOutputChannels();
// Add parameters indices to the initialiser list to force them to stay
// at the supplied float. Could be useful for sustain or gain parameters.
std::vector<std::pair<int, float>> overridenParameters;
getOverridenParameters(overridenParameters,
vst->getNumParameters(),
parser);
// Maximilian setup.
maxiSettings::setup(sampleRate, numberChannels, bufferSize);
maxiFFT fft;
maxiMFCC mfcc;
fft.setup(fftSize, fftSize / 2, fftSize / 4);
mfcc.setup(512, 42, 13, 20, int(sampleRate / 2), sampleRate);
// The container for the features pulled from the synth's output audio.
std::vector<std::array<double, 13>> features;
// Data structure to hold multi-channel audio data.
AudioSampleBuffer audioBuffer(numberChannels, bufferSize);
// The midi note that goes into the vst.
auto masterNote = getMidiNote(midiChannel,
midiPitch,
midiVelocity,
noteLength,
sampleRate);
// The indices of the parameter's that are used. They are stored as pairs
// with a float intended to be the parameters value.
std::vector<std::pair<int, float>> parameterIndexValuePairs;
parameterIndexValuePairs.reserve(vst->getNumParameters());
// This loop gets all the parameters that are ACTUALLY being used.
int usedParameterAmount = 0;
for (int i = 0; i < vst->getNumParameters(); ++i)
{
if (!isUnusedParameter(vst->getParameterName(i)))
{
++usedParameterAmount;
parameterIndexValuePairs.push_back(std::make_pair(i, 0.0f));
}
}
parameterIndexValuePairs.shrink_to_fit();
// List all available parameters
String parameterListString("");
if (listParameters)
{
for (auto& pair : parameterIndexValuePairs)
{
String leadingSpace = (pair.first < 10) ? " " : "";
parameterListString = parameterListString +
"Index: " + leadingSpace + String(pair.first) +
", Name: " + vst->getParameterName(pair.first) + "\n";
}
std::cout << parameterListString.toStdString() << std::endl;
exit(0);
}
// Used to generate random (or every single) sample of parameters from the
// parameter space.
ParameterGenerator parameterGenerator;
// If we are generating all possible parameters, then fill the
// ParameterMatrix (a vector of arrays of pairs) with all the combinations
// of parameter values. Reset amount of presets too so we can loop through
// every one too.
ParameterMatrix allParameters;
if (iterateAllPatches)
{
parameterGenerator.fillAllCombinationsOfParameters(128, allParameters);
amountOfPresets = allParameters.size();
}
const long totalPatchesSampled = amountOfPresets;
// Sample all of the desired presets.
while (--amountOfPresets >= 0)
{
// Access the elements from the start, not the end.
const std::size_t indexFromStart = totalPatchesSampled - 1 - amountOfPresets;
// Print progress
printProgress(indexFromStart, totalPatchesSampled);
if (iterateAllPatches)
{
// Get the current set of parameters.
const auto currentPatch = allParameters[indexFromStart];
for (int i = 0; i < currentPatch.size(); ++i)
{
// Set the vst parameter with the parameter given.
const auto currentParameter = currentPatch[i];
vst->setParameter(currentParameter.first, currentParameter.second);
}
}
else
{
// Generate a random synth patch from a uniform distribution. Keep
// overriden parameters the way they were set in the initialiser
// list.
std::pair<int, float> copy;
for (auto& pair : parameterIndexValuePairs)
{
// Should we have overriden this parameter's index...
if (std::any_of(overridenParameters.begin(), overridenParameters.end(),
[pair, ©] (std::pair<int, float> p)
{
copy = p;
return p.first == pair.first;
}))
{
// Assign the current pair the 'copy' pair so the set of
// parameters are consistent when it comes to writing the
// parameters to file.
pair = copy;
vst->setParameter(copy.first, copy.second);
}
else
{
// Else create random value for the parameter.
pair = parameterGenerator.getRandomParameter(pair.first);
vst->setParameter(pair.first, pair.second);
}
}
}
// Open a unique .wav in the given path with the given name.
// There must be a nicer way of writing this..
const File wavFile((File(audioPath)).getNonexistentChildFile(audioFile,
".wav",
false));
// The objects used to stream the channel data in the audio buffer to
// the aforementioned unique file.
ScopedPointer<FileOutputStream> fileStream(wavFile.createOutputStream());
WavAudioFormat wavFormat;
AudioFormatWriter* wavWriter = wavFormat.createWriterFor(fileStream,
sampleRate,
numberChannels,
bitsPerSample,
StringPairArray(),
0);
// Get the note on midiBuffer.
auto midiNote = getInitialNoteOn(midiChannel,
midiPitch,
midiVelocity);
// Loop through all the buffers and create the audio from the midi data.
for (int i = 0; i < numberOfBuffers; ++i)
{
// Trigger note off if in the correct audio buffer.
ifTimeSetNoteOff(noteLength,
sampleRate,
bufferSize,
midiChannel,
midiPitch,
midiVelocity,
i,
midiNote);
// Turn Midi to audio via the vst.
vst->processBlock(audioBuffer, midiNote);
// Get audio features and fill the datastructure.
fillAudioFeatures(audioBuffer, fft, mfcc, features);
// Write the audio data to a buffer to be flushed.
wavWriter->writeFromAudioSampleBuffer(audioBuffer, 0, bufferSize);
}
// Write the .wav, features and parameters files to disk.
wavWriter->flush();
writeFeaturesToCSV(features, featuresPath, featuresFileName);
writeParametersToCSV(parameterIndexValuePairs, parametersPath, parametersFileName);
}
std::cout << std::endl;
vst->releaseResources();
return 0;
}
ParameterGenerator.h
/*
==============================================================================
ParameterGenerator.h
Created: 1 Feb 2017 11:17:22pm
Author: admin
==============================================================================
*/
#ifndef PARAMETERGENERATOR_H_INCLUDED
#define PARAMETERGENERATOR_H_INCLUDED
#include <random>
#include <array>
#include "../JuceLibraryCode/JuceHeader.h"
typedef std::vector<std::array<std::pair<int, float>, 2>> ParameterMatrix;
class ParameterGenerator
{
public:
ParameterGenerator()
{
distribution = new std::uniform_real_distribution<>(0, 1);
}
~ParameterGenerator()
{
delete distribution;
}
std::pair<int, float> getRandomParameter(int index)
{
float randomValue = (*distribution)(generator);
return std::make_pair(index, randomValue);
}
void fillAllCombinationsOfParameters(int stepSize,
ParameterMatrix& matrixToFill)
{
// The amount of parameter sets we are making.
std::size_t amountToMake = std::size_t(std::pow(stepSize, 2));
// Reserve the room to prevent unessessary allocations
matrixToFill.reserve(amountToMake);
for (int i = 0; i < stepSize; ++i)
{
// Our set of parameters.
std::array<std::pair<int, float>, 2> parameters;
// The first parameter.
parameters[0] = std::make_pair(int(0), float(i) / float(stepSize));
// Loop through again to get every possible combination of the
// second parameter with the first.
for (int j = 0; j < stepSize; ++j)
{
// The second parameter
parameters[1] = std::make_pair(int(1), float(j) / float(stepSize));
matrixToFill.push_back(parameters);
}
}
// Shrink the vector to the correct size.
matrixToFill.shrink_to_fit();
// This shouldn't fire unless we are getting the combinations wrong.
jassert(amountToMake == matrixToFill.size());
}
private:
// This will be used to create random parameter samples.
std::random_device random;
std::mt19937_64 generator;
std::uniform_real_distribution<>* distribution;
};
#endif // PARAMETERGENERATOR_H_INCLUDED
Utils.cpp
/*
==============================================================================
Utils.cpp
Created: 29 Jan 2017 5:45:03pm
Author: Leon Fedden
==============================================================================
*/
#include "Utils.h"
//=============================================================================
void printHelp()
{
String helpString = "\nRenderMan Options:\n\n";
helpString += "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
helpString += " -s <arg> : Sample rate. Double\n";
helpString += " argument which\n";
helpString += " shouldn't need setting.\n";
helpString += "\n";
helpString += " Default: 44100\n";
helpString += "\n";
helpString += "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
helpString += " -b <arg> : Buffer size. Integer\n";
helpString += " argument and should\n";
helpString += " be power of two.\n";
helpString += "\n";
helpString += " Default: 512\n";
helpString += "\n";
helpString += "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
helpString += " -v <arg> : VST name. Supply the\n";
helpString += " full file path and\n";
helpString += " extension. String\n";
helpString += " argument e.g:\n";
helpString += " '/Path/To/My/VstName.vst'\n";
helpString += "\n";
helpString += "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
helpString += " -n <arg> : Note length in seconds\n";
helpString += " of how long the midi\n";
helpString += " note should play.\n";
helpString += " Integer argument.\n";
helpString += "\n";
helpString += " Default: 4\n";
helpString += "\n";
helpString += "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
helpString += " -p <arg> : Pitch of the midi note\n";
helpString += " that is passed to the\n";
helpString += " vst. Integer argument\n";
helpString += " of the midi note.\n";
helpString += "\n";
helpString += " Default: 60 (Middle C)\n";
helpString += "\n";
helpString += "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
helpString += " -r <arg> : Render length in\n";
helpString += " seconds of how long\n";
helpString += " the .wav file will be.\n";
helpString += "\n";
helpString += " Default: Twice the midi\n";
helpString += " note length (8)\n";
helpString += "\n";
helpString += "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
helpString += " -a <arg> : Amount of presets to\n";
helpString += " generate. Integer\n";
helpString += " argument.\n";
helpString += "\n";
helpString += " Default: 1\n";
helpString += "\n";
helpString += "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
helpString += " -i : Iterate over every\n";
helpString += " possible combination of\n";
helpString += " set of parameters.\n";
helpString += " Warning, this is only.\n";
helpString += " working for synths with.\n";
helpString += " two parameters currently.\n";
helpString += "\n";
helpString += "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
helpString += " -p1 <arg> : You can override\n";
helpString += " -p2 : any parameter the synth\n";
helpString += " ... : offers to the supplied\n";
helpString += " -pN : value which is a float\n";
helpString += " between 0 and 1 for min\n";
helpString += " and max. To override\n";
helpString += " parameter 35 to 0.6, you\n";
helpString += " would type -p35 0.6.\n";
helpString += "\n";
helpString += " Default: No parameters\n";
helpString += " are overriden!\n";
helpString += "\n";
helpString += "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
helpString += " -l : List all parameters\n";
helpString += " used in the VST with\n";
helpString += " with indices. No\n";
helpString += " argument needed.\n\n";
helpString += "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
std::cout << helpString.toStdString() << std::endl;
}
//=============================================================================
AudioPluginInstance* createSynthInstance(int samplerate,
int buffersize,
String pluginPath)
{
OwnedArray<PluginDescription> pluginDescriptions;
KnownPluginList pluginList;
AudioPluginFormatManager pluginFormatManager;
pluginFormatManager.addDefaultFormats();
for (int i = pluginFormatManager.getNumFormats(); --i >= 0;)
{
pluginList.scanAndAddFile(pluginPath,
true,
pluginDescriptions,
*pluginFormatManager.getFormat(i));
}
jassert(pluginDescriptions.size() > 0);
String errorMessage;
auto plugin = pluginFormatManager.createPluginInstance(*pluginDescriptions[0],
samplerate,
buffersize,
errorMessage);
if (plugin == nullptr)
{
std::cout << "createSynthInstance - Error: "
<< errorMessage.toStdString()
<< std::endl;
exit(1);
}
plugin->prepareToPlay(samplerate, buffersize);
plugin->setNonRealtime(true);
return plugin;
}
//=============================================================================
MidiBuffer getMidiNote(int midiChannel,
uint8 midiPitch,
uint8 midiVelocity,
double noteSeconds,
double sampleRate)
{
MidiMessage onMessage = MidiMessage::noteOn (midiChannel,
midiPitch,
midiVelocity);
MidiMessage offMessage = MidiMessage::noteOff(onMessage.getChannel(),
onMessage.getNoteNumber(),
onMessage.getVelocity());
double noteSampleLength = noteSeconds * sampleRate;
onMessage.setTimeStamp(0);
offMessage.setTimeStamp(onMessage.getTimeStamp() + noteSampleLength);
MidiBuffer midiBuffer;
midiBuffer.addEvent(onMessage, onMessage.getTimeStamp());
midiBuffer.addEvent(offMessage, offMessage.getTimeStamp());
return midiBuffer;
}
//=============================================================================
void ifTimeSetNoteOff(const double& noteLength,
const double& sampleRate,
const int& bufferSize,
const uint8& midiChannel,
const uint8& midiPitch,
const uint8& midiVelocity,
const int& currentBufferIndex,
MidiBuffer& bufferToNoteOff)
{
double eventFrame = noteLength * sampleRate;
bool bufferBeginIsBeforeEvent = currentBufferIndex * bufferSize < eventFrame;
bool bufferEndIsAfterEvent = (currentBufferIndex + 1) * bufferSize >= eventFrame;
bool noteOffEvent = bufferBeginIsBeforeEvent && bufferEndIsAfterEvent;
if (noteOffEvent)
{
MidiBuffer midiOffBuffer;
MidiMessage offMessage = MidiMessage::noteOff(midiChannel,
midiPitch,
midiVelocity);
offMessage.setTimeStamp(eventFrame);
midiOffBuffer.addEvent(offMessage, offMessage.getTimeStamp());
bufferToNoteOff = midiOffBuffer;
}
}
//=============================================================================
MidiBuffer getInitialNoteOn(const uint8& midiChannel,
const uint8& midiPitch,
const uint8& midiVelocity)
{
MidiBuffer midiOnBuffer;
MidiMessage onMessage = MidiMessage::noteOn(midiChannel,
midiPitch,
midiVelocity);
onMessage.setTimeStamp(0);
midiOnBuffer.addEvent(onMessage, onMessage.getTimeStamp());
return midiOnBuffer;
}
//=============================================================================
bool isUnusedParameter(const String& parameterName)
{
return parameterName == "Param";
}
//=============================================================================
void fillAudioFeatures(const AudioSampleBuffer& data,
maxiFFT& fft,
maxiMFCC& mfcc,
std::vector<std::array<double, 13>>& features)
{
// Keep it auto as it may or may not be double precision.
auto readptrs = data.getArrayOfReadPointers();
for (int i = 0; i < data.getNumSamples(); ++i)
{
// Mono the frame.
int channel = 0;
auto currentFrame = readptrs[channel][i];
while (++channel < data.getNumChannels())
{
currentFrame += readptrs[channel][i];
}
currentFrame /= data.getNumChannels();
// Extract features.
if (fft.process(currentFrame))
{
// This isn't real-time so I can take the luxuary of allocating
// heap memory here.
double* mfccs = new double[13];
mfcc.mfcc(fft.magnitudes, mfccs);
std::array<double, 13> mfccsFrame;
std::memcpy(mfccsFrame.data(), mfccs, sizeof(double) * 13);
features.push_back(mfccsFrame);
delete[] mfccs;
}
}
}
//=============================================================================
void writeFeaturesToCSV(std::vector<std::array<double, 13>>& features,
const String& csvPath,
const String& csvName)
{
File csvFile((File(csvPath)).getNonexistentChildFile(csvName,
".csv",
false));
String fileText = "";
for (size_t i = 0; i < features.size(); ++i)
{
for (size_t j = 0; j < features[i].size() - 1; ++j)
{
fileText += String(features[i][j]) + ',';
}
fileText += String(features[i][features[i].size() - 1]) + '\n';
}
csvFile.replaceWithText(fileText);
features.clear();
}
//=============================================================================
void writeParametersToCSV(std::vector<std::pair<int, float>> parameters,
const String& csvPath,
const String& csvName)
{
File csvFile((File(csvPath)).getNonexistentChildFile(csvName,
".csv",
false));
String fileText = "";
for (size_t i = 0; i < parameters.size(); ++i)
{
fileText += String(parameters[i].first) + ',' + String(parameters[i].second) + '\n';
}
csvFile.replaceWithText(fileText);
}
//=============================================================================
String getName(const String& vstPath)
{
String noExtension;
if (vstPath.containsWholeWord(".vst"))
noExtension = vstPath.dropLastCharacters(4);
else
noExtension = vstPath.dropLastCharacters(3);
int i = noExtension.length();
String name("");
while (noExtension[--i] != '/')
name = noExtension[i] + name;
return name;
}
//=============================================================================
void getOverridenParameters(std::vector<std::pair<int, float>>& overridenParameters,
int vstAmountOfParameters,
ArgumentsParser& parser)
{
String arg;
for (int i = 0; i < vstAmountOfParameters; ++i)
{
String parameterFlagName = "-p" + String(i);
if ((arg = parser.getArgument(parameterFlagName)) != "")
{
overridenParameters.push_back(std::make_pair(i, arg.getFloatValue()));
}
}
}
//=============================================================================
// Stack overflow - search for how to display a progress indictor in pure C/C++
void printProgress(long currentPatch, long amountOfPatches)
{
int barWidth = 50;
float progress = float(currentPatch) / float(amountOfPatches);
std::cout << "[";
int position = int(progress * barWidth);
for (int i = 0; i < barWidth; ++i)
{
if (i < position) std::cout << "=";
else if (i == position) std::cout << ">";
else std::cout << " ";
}
std::cout << "] " << int(progress * 100.0) << " %\r";
std::cout.flush();
}
Utils.h
/*
==============================================================================
Utils.h
Created: 29 Jan 2017 5:47:49pm
Author: Leon Fedden
==============================================================================
*/
#ifndef UTILS_H_INCLUDED
#define UTILS_H_INCLUDED
#include <array>
#include <thread>
#include <chrono>
#include <random>
#include "Maximilian/maximilian.h"
#include "Maximilian/libs/maxiFFT.h"
#include "Maximilian/libs/maxiMFCC.h"
#include "../JuceLibraryCode/JuceHeader.h"
//=============================================================================
// http://stackoverflow.com/questions/865668/how-to-parse-command-line-arguments-in-c
// Minimal changes to the work from the above link. Praise the lord for Stack.
class ArgumentsParser
{
public:
// Store all the command line arguments.
ArgumentsParser(int &argc, char *argv[]) : emptyString("")
{
for (int i = 1; i < argc; ++i)
arguments.push_back(String(argv[i]));
}
// Get the argument supplied after a given flag.
const String& getArgument(const String& flag)
{
auto it = std::find(arguments.begin(), arguments.end(), flag);
if (it != arguments.end() && ++it != arguments.end())
return *it;
return emptyString;
}
// Has a given flag been supplied as an argument?
bool flagExists(const String &flag)
{
return std::find(arguments.begin(), arguments.end(), flag) != arguments.end();
}
private:
std::vector<String> arguments;
String emptyString;
};
//=============================================================================
void printHelp();
//=============================================================================
AudioPluginInstance* createSynthInstance(int samplerate,
int buffersize,
String pluginPath = "");
//=============================================================================
MidiBuffer getMidiNote(int midiChannel,
uint8 midiPitch,
uint8 midiVelocity,
double noteSeconds,
double sampleRate);
//=============================================================================
void ifTimeSetNoteOff(const double& noteLength,
const double& sampleRate,
const int& bufferSize,
const uint8& midiChannel,
const uint8& midiPitch,
const uint8& midiVelocity,
const int& currentBufferIndex,
MidiBuffer& bufferToNoteOff);
//=============================================================================
MidiBuffer getInitialNoteOn(const uint8& midiChannel,
const uint8& midiPitch,
const uint8& midiVelocity);
//=============================================================================
bool isUnusedParameter(const String& parameterName);
//=============================================================================
void fillAudioFeatures(const AudioSampleBuffer& data,
maxiFFT& fft,
maxiMFCC& mfcc,
std::vector<std::array<double, 13>>& features);
//=============================================================================
void writeFeaturesToCSV(std::vector<std::array<double, 13>>& features,
const String& csvPath,
const String& csvName);
//=============================================================================
void writeParametersToCSV(std::vector<std::pair<int, float>> parameters,
const String& csvPath,
const String& csvName);
//=============================================================================
String getName(const String& vstPath);
//=============================================================================
void getOverridenParameters(std::vector<std::pair<int, float>>& overridenParameters,
int vstAmountOfParameters,
ArgumentsParser& parser);
//=============================================================================
void printProgress(long currentPatch, long amountOfPatches);
#endif // UTILS_H_INCLUDED