feat(plugin): add plugin processor and editor
This commit is contained in:
@@ -0,0 +1,381 @@
|
||||
#include "PluginProcessor.h"
|
||||
#include "PluginEditor.h"
|
||||
#include "Presets/FactoryPresets.h"
|
||||
|
||||
namespace serum
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
std::unique_ptr<juce::AudioParameterFloat> f (const char* id, const juce::String& name, float def)
|
||||
{
|
||||
return std::make_unique<juce::AudioParameterFloat> (juce::ParameterID { id, 1 }, name,
|
||||
juce::NormalisableRange<float> (0.0f, 1.0f), def);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
SerumAltAudioProcessor::SerumAltAudioProcessor()
|
||||
: AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo(), false)
|
||||
.withOutput ("Output", juce::AudioChannelSet::stereo(), true)),
|
||||
parameters (*this, nullptr, juce::Identifier ("SerumAlt"), createParameterLayout())
|
||||
{
|
||||
}
|
||||
|
||||
SerumAltAudioProcessor::~SerumAltAudioProcessor() = default;
|
||||
|
||||
juce::AudioProcessorValueTreeState::ParameterLayout SerumAltAudioProcessor::createParameterLayout()
|
||||
{
|
||||
std::vector<std::unique_ptr<juce::RangedAudioParameter>> params;
|
||||
|
||||
// Global
|
||||
params.push_back (f (ids::master, "Master", 0.8f));
|
||||
params.push_back (f (ids::uiScale, "UI Scale", 0.25f));
|
||||
params.push_back (f (ids::rave, "RAVE", 0.0f));
|
||||
|
||||
// Oscillator A
|
||||
params.push_back (f (ids::oscAOn, "Osc A On", 1.0f));
|
||||
params.push_back (f (ids::oscAWave, "Osc A Wave", 0.0f));
|
||||
params.push_back (f (ids::oscAWtPos, "Osc A WT Pos", 0.0f));
|
||||
params.push_back (f (ids::oscAWarp, "Osc A Warp", 0.0f));
|
||||
params.push_back (f (ids::oscAWarpAmt, "Osc A Warp Amt", 0.0f));
|
||||
params.push_back (f (ids::oscACoarse, "Osc A Coarse", 0.5f));
|
||||
params.push_back (f (ids::oscAFine, "Osc A Fine", 0.5f));
|
||||
params.push_back (f (ids::oscALevel, "Osc A Level", 0.8f));
|
||||
params.push_back (f (ids::oscAPan, "Osc A Pan", 0.5f));
|
||||
params.push_back (f (ids::oscAUnison, "Osc A Unison", 0.0f));
|
||||
params.push_back (f (ids::oscADetune, "Osc A Detune", 0.0f));
|
||||
params.push_back (f (ids::oscASpread, "Osc A Spread", 0.0f));
|
||||
params.push_back (f (ids::oscAPhase, "Osc A Phase", 0.0f));
|
||||
params.push_back (f (ids::oscARandPh, "Osc A Rand Phase", 0.0f));
|
||||
|
||||
// Oscillator B
|
||||
params.push_back (f (ids::oscBOn, "Osc B On", 0.0f));
|
||||
params.push_back (f (ids::oscBWave, "Osc B Wave", 1.0f / 9.0f));
|
||||
params.push_back (f (ids::oscBWtPos, "Osc B WT Pos", 0.0f));
|
||||
params.push_back (f (ids::oscBWarp, "Osc B Warp", 0.0f));
|
||||
params.push_back (f (ids::oscBWarpAmt, "Osc B Warp Amt", 0.0f));
|
||||
params.push_back (f (ids::oscBCoarse, "Osc B Coarse", 0.5f));
|
||||
params.push_back (f (ids::oscBFine, "Osc B Fine", 0.5f));
|
||||
params.push_back (f (ids::oscBLevel, "Osc B Level", 0.5f));
|
||||
params.push_back (f (ids::oscBPan, "Osc B Pan", 0.5f));
|
||||
params.push_back (f (ids::oscBUnison, "Osc B Unison", 0.0f));
|
||||
params.push_back (f (ids::oscBDetune, "Osc B Detune", 0.0f));
|
||||
params.push_back (f (ids::oscBSpread, "Osc B Spread", 0.0f));
|
||||
params.push_back (f (ids::oscBPhase, "Osc B Phase", 0.0f));
|
||||
params.push_back (f (ids::oscBRandPh, "Osc B Rand Phase", 0.0f));
|
||||
|
||||
// Sub
|
||||
params.push_back (f (ids::subOn, "Sub On", 0.0f));
|
||||
params.push_back (f (ids::subShape, "Sub Shape", 0.0f));
|
||||
params.push_back (f (ids::subOct, "Sub Octave", 1.0f / 2.0f));
|
||||
params.push_back (f (ids::subLevel, "Sub Level", 0.5f));
|
||||
|
||||
// Noise
|
||||
params.push_back (f (ids::noiseOn, "Noise On", 0.0f));
|
||||
params.push_back (f (ids::noiseType, "Noise Type", 0.0f));
|
||||
params.push_back (f (ids::noiseLevel, "Noise Level", 0.5f));
|
||||
|
||||
// Filter 1
|
||||
params.push_back (f (ids::f1On, "Filter 1 On", 1.0f));
|
||||
params.push_back (f (ids::f1Type, "Filter 1 Type", 0.0f));
|
||||
params.push_back (f (ids::f1Cutoff, "Filter 1 Cutoff", 0.65f));
|
||||
params.push_back (f (ids::f1Res, "Filter 1 Res", 0.05f));
|
||||
params.push_back (f (ids::f1Drive, "Filter 1 Drive", 0.0f));
|
||||
params.push_back (f (ids::f1Key, "Filter 1 Keytrack", 0.0f));
|
||||
params.push_back (f (ids::f1Slope, "Filter 1 Slope", 1.0f));
|
||||
|
||||
// Filter 2
|
||||
params.push_back (f (ids::f2On, "Filter 2 On", 0.0f));
|
||||
params.push_back (f (ids::f2Type, "Filter 2 Type", 0.0f));
|
||||
params.push_back (f (ids::f2Cutoff, "Filter 2 Cutoff", 0.5f));
|
||||
params.push_back (f (ids::f2Res, "Filter 2 Res", 0.0f));
|
||||
params.push_back (f (ids::f2Drive, "Filter 2 Drive", 0.0f));
|
||||
params.push_back (f (ids::f2Key, "Filter 2 Keytrack", 0.0f));
|
||||
params.push_back (f (ids::f2Slope, "Filter 2 Slope", 1.0f));
|
||||
|
||||
params.push_back (f (ids::fRoute, "Filter Route", 0.0f));
|
||||
params.push_back (f (ids::fMix, "Filter Mix", 0.5f));
|
||||
params.push_back (f (ids::fOut, "Filter Out", 0.667f));
|
||||
|
||||
// Envelopes 1..4
|
||||
const char* envA[4] = { ids::env1A, ids::env2A, ids::env3A, ids::env4A };
|
||||
const char* envD[4] = { ids::env1D, ids::env2D, ids::env3D, ids::env4D };
|
||||
const char* envS[4] = { ids::env1S, ids::env2S, ids::env3S, ids::env4S };
|
||||
const char* envR[4] = { ids::env1R, ids::env2R, ids::env3R, ids::env4R };
|
||||
const char* envC[4] = { ids::env1Curve, ids::env2Curve, ids::env3Curve, ids::env4Curve };
|
||||
const float envADef[4] = { 0.05f, 0.1f, 0.2f, 0.2f };
|
||||
const float envDDef[4] = { 0.25f, 0.3f, 0.3f, 0.3f };
|
||||
const float envSDef[4] = { 0.8f, 0.5f, 0.5f, 0.5f };
|
||||
const float envRDef[4] = { 0.3f, 0.3f, 0.4f, 0.4f };
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
params.push_back (f (envA[i], juce::String ("Env ") + juce::String (i + 1) + " Attack", envADef[i]));
|
||||
params.push_back (f (envD[i], juce::String ("Env ") + juce::String (i + 1) + " Decay", envDDef[i]));
|
||||
params.push_back (f (envS[i], juce::String ("Env ") + juce::String (i + 1) + " Sustain", envSDef[i]));
|
||||
params.push_back (f (envR[i], juce::String ("Env ") + juce::String (i + 1) + " Release", envRDef[i]));
|
||||
params.push_back (f (envC[i], juce::String ("Env ") + juce::String (i + 1) + " Curve", 0.5f));
|
||||
}
|
||||
|
||||
// LFOs 1..4
|
||||
const char* lfoRate[4] = { ids::lfo1Rate, ids::lfo2Rate, ids::lfo3Rate, ids::lfo4Rate };
|
||||
const char* lfoSync[4] = { ids::lfo1Sync, ids::lfo2Sync, ids::lfo3Sync, ids::lfo4Sync };
|
||||
const char* lfoBeat[4] = { ids::lfo1Beat, ids::lfo2Beat, ids::lfo3Beat, ids::lfo4Beat };
|
||||
const char* lfoShape[4] = { ids::lfo1Shape, ids::lfo2Shape, ids::lfo3Shape, ids::lfo4Shape };
|
||||
const char* lfoPhase[4] = { ids::lfo1Phase, ids::lfo2Phase, ids::lfo3Phase, ids::lfo4Phase };
|
||||
const char* lfoFade[4] = { ids::lfo1Fade, ids::lfo2Fade, ids::lfo3Fade, ids::lfo4Fade };
|
||||
const char* lfoDelay[4] = { ids::lfo1Delay, ids::lfo2Delay, ids::lfo3Delay, ids::lfo4Delay };
|
||||
const float lfoShapeDef[4] = { 0.0f, 1.0f / 6.0f, 2.0f / 6.0f, 3.0f / 6.0f };
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
const juce::String n = juce::String ("LFO ") + juce::String (i + 1);
|
||||
params.push_back (f (lfoRate[i], n + " Rate", 0.5f));
|
||||
params.push_back (f (lfoSync[i], n + " Sync", 0.0f));
|
||||
params.push_back (f (lfoBeat[i], n + " Beat", 0.5f));
|
||||
params.push_back (f (lfoShape[i], n + " Shape", lfoShapeDef[i]));
|
||||
params.push_back (f (lfoPhase[i], n + " Phase", 0.0f));
|
||||
params.push_back (f (lfoFade[i], n + " Fade", 0.0f));
|
||||
params.push_back (f (lfoDelay[i], n + " Delay", 0.0f));
|
||||
}
|
||||
|
||||
// FX slots 1..8
|
||||
const char* fxType[8] = { ids::fx1Type, ids::fx2Type, ids::fx3Type, ids::fx4Type,
|
||||
ids::fx5Type, ids::fx6Type, ids::fx7Type, ids::fx8Type };
|
||||
const char* fxMix[8] = { ids::fx1Mix, ids::fx2Mix, ids::fx3Mix, ids::fx4Mix,
|
||||
ids::fx5Mix, ids::fx6Mix, ids::fx7Mix, ids::fx8Mix };
|
||||
const char* fxP[8][4] = {
|
||||
{ ids::fx1P1, ids::fx1P2, ids::fx1P3, ids::fx1P4 },
|
||||
{ ids::fx2P1, ids::fx2P2, ids::fx2P3, ids::fx2P4 },
|
||||
{ ids::fx3P1, ids::fx3P2, ids::fx3P3, ids::fx3P4 },
|
||||
{ ids::fx4P1, ids::fx4P2, ids::fx4P3, ids::fx4P4 },
|
||||
{ ids::fx5P1, ids::fx5P2, ids::fx5P3, ids::fx5P4 },
|
||||
{ ids::fx6P1, ids::fx6P2, ids::fx6P3, ids::fx6P4 },
|
||||
{ ids::fx7P1, ids::fx7P2, ids::fx7P3, ids::fx7P4 },
|
||||
{ ids::fx8P1, ids::fx8P2, ids::fx8P3, ids::fx8P4 }
|
||||
};
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
const juce::String n = juce::String ("FX ") + juce::String (i + 1);
|
||||
params.push_back (f (fxType[i], n + " Type", 0.0f));
|
||||
params.push_back (f (fxMix[i], n + " Mix", 0.5f));
|
||||
params.push_back (f (fxP[i][0], n + " P1", 0.5f));
|
||||
params.push_back (f (fxP[i][1], n + " P2", 0.5f));
|
||||
params.push_back (f (fxP[i][2], n + " P3", 0.5f));
|
||||
params.push_back (f (fxP[i][3], n + " P4", 0.5f));
|
||||
}
|
||||
|
||||
// Macros
|
||||
params.push_back (f (ids::macro1, "Macro 1", 0.0f));
|
||||
params.push_back (f (ids::macro2, "Macro 2", 0.0f));
|
||||
params.push_back (f (ids::macro3, "Macro 3", 0.0f));
|
||||
params.push_back (f (ids::macro4, "Macro 4", 0.0f));
|
||||
|
||||
return { params.begin(), params.end() };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
void SerumAltAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
|
||||
{
|
||||
engine.prepare (sampleRate, samplesPerBlock);
|
||||
}
|
||||
|
||||
void SerumAltAudioProcessor::releaseResources()
|
||||
{
|
||||
engine.reset();
|
||||
}
|
||||
|
||||
void SerumAltAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midi)
|
||||
{
|
||||
juce::ScopedNoDenormals noDenormals;
|
||||
engine.processBlock (buffer, midi, parameters, getPlayHead());
|
||||
}
|
||||
|
||||
juce::AudioProcessorEditor* SerumAltAudioProcessor::createEditor()
|
||||
{
|
||||
return new PluginEditor (*this);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Programs / presets
|
||||
// ---------------------------------------------------------------------------
|
||||
int SerumAltAudioProcessor::getNumPrograms()
|
||||
{
|
||||
return (int) getFactoryPresets().size();
|
||||
}
|
||||
|
||||
int SerumAltAudioProcessor::getCurrentProgram()
|
||||
{
|
||||
return currentProgram;
|
||||
}
|
||||
|
||||
void SerumAltAudioProcessor::setCurrentProgram (int index)
|
||||
{
|
||||
index = juce::jlimit (0, getNumPrograms() - 1, index);
|
||||
loadFactoryPreset (index);
|
||||
currentProgram = index;
|
||||
}
|
||||
|
||||
const juce::String SerumAltAudioProcessor::getProgramName (int index)
|
||||
{
|
||||
const auto& presets = getFactoryPresets();
|
||||
if (index >= 0 && index < (int) presets.size())
|
||||
return presets[(size_t) index].name;
|
||||
return {};
|
||||
}
|
||||
|
||||
void SerumAltAudioProcessor::changeProgramName (int, const juce::String&)
|
||||
{
|
||||
}
|
||||
|
||||
int SerumAltAudioProcessor::getNumFactoryPresets() const
|
||||
{
|
||||
return (int) getFactoryPresets().size();
|
||||
}
|
||||
|
||||
void SerumAltAudioProcessor::loadFactoryPreset (int index)
|
||||
{
|
||||
const auto& presets = getFactoryPresets();
|
||||
if (index < 0 || index >= (int) presets.size())
|
||||
return;
|
||||
|
||||
const FactoryPreset& preset = presets[(size_t) index];
|
||||
|
||||
// RAVE should start off for a freshly loaded preset.
|
||||
rave.resetSnapshot();
|
||||
if (auto* raveParam = parameters.getParameter (ids::rave))
|
||||
raveParam->setValueNotifyingHost (0.0f);
|
||||
|
||||
for (const auto& kv : preset.params)
|
||||
if (auto* param = parameters.getParameter (kv.first))
|
||||
param->setValueNotifyingHost (kv.second);
|
||||
|
||||
engine.getMatrix().clear();
|
||||
for (const auto& mod : preset.mods)
|
||||
engine.getMatrix().addConnection (mod.source, mod.target, mod.depth, mod.bipolar);
|
||||
|
||||
engine.getMacros().clear();
|
||||
for (const auto& ma : preset.macroAssigns)
|
||||
engine.getMacros().addAssignment (ma.macro, ma.target, ma.depth);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RAVE / UI scale
|
||||
// ---------------------------------------------------------------------------
|
||||
void SerumAltAudioProcessor::setRaveEnabled (bool enabled)
|
||||
{
|
||||
rave.setEnabled (enabled, parameters);
|
||||
if (auto* raveParam = parameters.getParameter (ids::rave))
|
||||
raveParam->setValueNotifyingHost (enabled ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
bool SerumAltAudioProcessor::isRaveEnabled() const
|
||||
{
|
||||
return rave.isEnabled();
|
||||
}
|
||||
|
||||
int SerumAltAudioProcessor::getUiScaleIndex() const
|
||||
{
|
||||
if (auto* p = parameters.getRawParameterValue (ids::uiScale))
|
||||
return juce::jlimit (0, 4, (int) std::llround (p->load() * 4.0f));
|
||||
return 1;
|
||||
}
|
||||
|
||||
void SerumAltAudioProcessor::setUiScaleIndex (int index)
|
||||
{
|
||||
index = juce::jlimit (0, 4, index);
|
||||
if (auto* p = parameters.getParameter (ids::uiScale))
|
||||
p->setValueNotifyingHost ((float) index / 4.0f);
|
||||
}
|
||||
|
||||
float SerumAltAudioProcessor::getUiScale() const
|
||||
{
|
||||
static constexpr float scales[5] = { 0.75f, 1.0f, 1.25f, 1.5f, 2.0f };
|
||||
return scales[getUiScaleIndex()];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// State
|
||||
// ---------------------------------------------------------------------------
|
||||
void SerumAltAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
|
||||
{
|
||||
auto state = parameters.copyState();
|
||||
state.appendChild (engine.getMatrix().toValueTree(), nullptr);
|
||||
state.appendChild (engine.getMacros().toValueTree(), nullptr);
|
||||
saveLfoShapesToState (state);
|
||||
|
||||
std::unique_ptr<juce::XmlElement> xml (state.createXml());
|
||||
if (xml != nullptr)
|
||||
copyXmlToBinary (*xml, destData);
|
||||
}
|
||||
|
||||
void SerumAltAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
|
||||
{
|
||||
std::unique_ptr<juce::XmlElement> xml (getXmlFromBinary (data, sizeInBytes));
|
||||
if (xml == nullptr)
|
||||
return;
|
||||
|
||||
juce::ValueTree state = juce::ValueTree::fromXml (*xml);
|
||||
if (! state.isValid())
|
||||
return;
|
||||
|
||||
parameters.replaceState (state);
|
||||
engine.getMatrix().fromValueTree (state.getChildWithName ("MODMATRIX"));
|
||||
engine.getMacros().fromValueTree (state.getChildWithName ("MACROS"));
|
||||
restoreLfoShapesFromState (state);
|
||||
|
||||
// A persisted RAVE toggle has no live snapshot, so start it off.
|
||||
rave.resetSnapshot();
|
||||
if (auto* raveParam = parameters.getParameter (ids::rave))
|
||||
raveParam->setValueNotifyingHost (0.0f);
|
||||
}
|
||||
|
||||
void SerumAltAudioProcessor::saveLfoShapesToState (juce::ValueTree& state) const
|
||||
{
|
||||
juce::ValueTree tree ("LFOSHAPES");
|
||||
for (int i = 0; i < kNumLfos; ++i)
|
||||
{
|
||||
const auto& data = engine.getLfos()[(size_t) i].getShapeData();
|
||||
juce::ValueTree lfo ("LFO");
|
||||
lfo.setProperty ("index", i, nullptr);
|
||||
lfo.setProperty ("steps", engine.getLfos()[(size_t) i].getShapeSteps(), nullptr);
|
||||
|
||||
juce::Array<juce::var> arr;
|
||||
for (float vv : data)
|
||||
arr.add (vv);
|
||||
lfo.setProperty ("data", juce::var (arr), nullptr);
|
||||
tree.appendChild (lfo, nullptr);
|
||||
}
|
||||
state.appendChild (tree, nullptr);
|
||||
}
|
||||
|
||||
void SerumAltAudioProcessor::restoreLfoShapesFromState (const juce::ValueTree& state)
|
||||
{
|
||||
const juce::ValueTree tree = state.getChildWithName ("LFOSHAPES");
|
||||
if (! tree.isValid())
|
||||
return;
|
||||
|
||||
for (const auto& lfo : tree)
|
||||
{
|
||||
if (! lfo.hasType ("LFO"))
|
||||
continue;
|
||||
const int index = juce::jlimit (0, kNumLfos - 1, (int) lfo.getProperty ("index", 0));
|
||||
const int steps = (int) lfo.getProperty ("steps", 16);
|
||||
|
||||
std::vector<float> data;
|
||||
if (auto* arr = lfo.getProperty ("data").getArray())
|
||||
for (const auto& vv : *arr)
|
||||
data.push_back ((float) vv);
|
||||
|
||||
engine.setLfoShapeData (index, data, steps);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace serum
|
||||
|
||||
// ===========================================================================
|
||||
// Plugin entry point (required by the JUCE plugin clients).
|
||||
// ===========================================================================
|
||||
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
|
||||
{
|
||||
return new serum::SerumAltAudioProcessor();
|
||||
}
|
||||
Reference in New Issue
Block a user