1079 lines
37 KiB
C++
1079 lines
37 KiB
C++
#include "PluginProcessor.h"
|
|
#include "PluginEditor.h"
|
|
#include <cmath>
|
|
|
|
//==============================================================
|
|
// Factory presets (Category + Name + full parameter snapshot)
|
|
//==============================================================
|
|
namespace
|
|
{
|
|
struct Patch
|
|
{
|
|
juce::String category;
|
|
juce::String name;
|
|
|
|
// Core
|
|
int oscA, oscB;
|
|
float mix, detune, cutoff, reso, envAmt;
|
|
|
|
// Amp ADSR
|
|
float aA, dA, sA, rA;
|
|
// Filter ADSR
|
|
float aF, dF, sF, rF;
|
|
|
|
float master; // dB
|
|
|
|
// NEW: LFO (sine) stored in presets
|
|
float lfoRate; // Hz
|
|
float lfoToCut; // octaves
|
|
float lfoToPitch; // cents
|
|
};
|
|
|
|
// Factory helper with defaulted LFO so existing calls remain valid
|
|
Patch P (juce::String cat, juce::String nm,
|
|
int oscA, int oscB, float mix, float detune,
|
|
float cutoff, float reso, float envAmt,
|
|
float aA, float dA, float sA, float rA,
|
|
float aF, float dF, float sF, float rF,
|
|
float master,
|
|
float lfoRate = 0.0f, float lfoToCut = 0.0f, float lfoToPitch = 0.0f)
|
|
{
|
|
return Patch { std::move (cat), std::move (nm),
|
|
oscA, oscB, mix, detune, cutoff, reso, envAmt,
|
|
aA, dA, sA, rA, aF, dF, sF, rF, master,
|
|
lfoRate, lfoToCut, lfoToPitch };
|
|
}
|
|
|
|
const std::vector<Patch>& getFactoryPatches()
|
|
{
|
|
static const std::vector<Patch> patches = {
|
|
// ===================== Bass =====================
|
|
P("Bass","Deep Bass",
|
|
1,2, 0.35f,-5.f,
|
|
180.f,0.18f,0.25f,
|
|
0.01f,0.10f,0.70f,0.12f,
|
|
0.015f,0.18f,0.10f,0.18f, -18.f,
|
|
0.7f, 0.20f, 0.0f),
|
|
|
|
P("Bass","Rubber Bass",
|
|
1,3, 0.45f,+2.f,
|
|
600.f,0.24f,0.22f,
|
|
0.005f,0.12f,0.60f,0.12f,
|
|
0.008f,0.15f,0.10f,0.12f, -17.f,
|
|
1.2f, 0.15f, 0.0f),
|
|
|
|
P("Bass","Analog Growl",
|
|
2,1, 0.50f,+4.f,
|
|
1200.f,0.40f,0.35f,
|
|
0.008f,0.18f,0.60f,0.14f,
|
|
0.010f,0.20f,0.20f,0.25f, -16.f,
|
|
0.8f, 0.25f, 3.0f),
|
|
|
|
P("Bass","Punch Bass",
|
|
1,1, 0.50f,+4.f,
|
|
1000.f,0.22f,0.40f,
|
|
0.003f,0.10f,0.55f,0.12f,
|
|
0.010f,0.12f,0.20f,0.18f, -16.f,
|
|
1.5f, 0.10f, 0.0f),
|
|
|
|
P("Bass","Acid Bite",
|
|
1,2, 0.50f,+3.f,
|
|
800.f,0.70f,0.45f,
|
|
0.002f,0.10f,0.55f,0.10f,
|
|
0.008f,0.14f,0.15f,0.18f, -16.f,
|
|
6.5f, 0.35f, 0.0f),
|
|
|
|
P("Bass","Wobble Bass",
|
|
2,1, 0.48f, 0.f,
|
|
220.f,0.35f,0.18f,
|
|
0.004f,0.12f,0.60f,0.12f,
|
|
0.010f,0.18f,0.12f,0.18f, -16.f,
|
|
1.6f, 0.80f, 2.0f),
|
|
|
|
P("Bass","Pluck Bass",
|
|
1,3, 0.42f,-3.f,
|
|
420.f,0.22f,0.25f,
|
|
0.001f,0.08f,0.30f,0.10f,
|
|
0.006f,0.12f,0.10f,0.12f, -17.f,
|
|
5.5f, 0.10f, 5.0f),
|
|
|
|
// ===================== Pad ======================
|
|
P("Pad","Dream Pad",
|
|
3,1, 0.50f,+7.f,
|
|
1200.f,0.22f,0.35f,
|
|
0.20f,0.60f,0.85f,0.90f,
|
|
0.18f,0.70f,0.60f,0.80f, -18.f,
|
|
0.35f, 0.45f, 0.0f),
|
|
|
|
P("Pad","Analog Pad",
|
|
1,1, 0.50f,+4.f,
|
|
1500.f,0.26f,0.28f,
|
|
0.12f,0.45f,0.80f,0.70f,
|
|
0.10f,0.50f,0.55f,0.65f, -18.f,
|
|
0.30f, 0.35f, 0.0f),
|
|
|
|
P("Pad","Glass Sweep",
|
|
3,2, 0.45f,+2.f,
|
|
1800.f,0.18f,0.42f,
|
|
0.30f,0.70f,0.80f,0.85f,
|
|
0.20f,0.65f,0.55f,0.75f, -18.f,
|
|
0.25f, 0.40f, 0.0f),
|
|
|
|
P("Pad","Glass Pad",
|
|
0,1, 0.35f,+3.f,
|
|
2000.f,0.18f,0.28f,
|
|
0.25f,0.65f,0.85f,0.95f,
|
|
0.18f,0.65f,0.50f,0.75f, -18.f,
|
|
0.28f, 0.30f, 0.0f),
|
|
|
|
P("Pad","Dreamscape",
|
|
1,1, 0.50f,+9.f,
|
|
4000.f,0.22f,0.40f,
|
|
0.30f,0.80f,0.85f,1.00f,
|
|
0.20f,0.75f,0.60f,0.85f, -18.f,
|
|
0.20f, 0.50f, 0.0f),
|
|
|
|
P("Pad","Slow Motion",
|
|
3,3, 0.50f, 0.f,
|
|
2000.f,0.30f,0.20f,
|
|
0.50f,0.90f,0.90f,1.20f,
|
|
0.30f,0.90f,0.70f,1.10f, -18.f,
|
|
0.18f, 0.55f, 0.0f),
|
|
|
|
P("Pad","Lunar Pad",
|
|
1,2, 0.55f,+5.f,
|
|
2500.f,0.24f,0.35f,
|
|
0.22f,0.70f,0.85f,1.00f,
|
|
0.20f,0.70f,0.60f,0.90f, -18.f,
|
|
0.32f, 0.35f, 0.0f),
|
|
|
|
// ===================== Lead =====================
|
|
P("Lead","Bright Lead",
|
|
2,1, 0.40f,+6.f,
|
|
2400.f,0.30f,0.45f,
|
|
0.01f,0.20f,0.60f,0.20f,
|
|
0.02f,0.28f,0.20f,0.30f, -18.f,
|
|
5.8f, 0.05f, 12.0f),
|
|
|
|
P("Lead","Sync Pulse",
|
|
2,2, 0.50f,+5.f,
|
|
2200.f,0.24f,0.30f,
|
|
0.01f,0.15f,0.55f,0.18f,
|
|
0.02f,0.20f,0.20f,0.25f, -16.f,
|
|
5.5f, 0.04f, 9.0f),
|
|
|
|
P("Lead","Solo Glide",
|
|
1,1, 0.50f,+6.f,
|
|
3500.f,0.22f,0.25f,
|
|
0.005f,0.18f,0.60f,0.20f,
|
|
0.010f,0.20f,0.20f,0.22f, -18.f,
|
|
5.2f, 0.03f, 10.0f),
|
|
|
|
P("Lead","Vintage Lead",
|
|
2,3, 0.50f,+3.f,
|
|
3200.f,0.28f,0.20f,
|
|
0.010f,0.22f,0.65f,0.25f,
|
|
0.020f,0.24f,0.22f,0.28f, -18.f,
|
|
5.0f, 0.04f, 8.0f),
|
|
|
|
P("Lead","PWM Lead",
|
|
2,1, 0.55f,+5.f,
|
|
3000.f,0.26f,0.30f,
|
|
0.010f,0.20f,0.60f,0.22f,
|
|
0.020f,0.22f,0.22f,0.26f, -18.f,
|
|
5.6f, 0.03f, 10.0f),
|
|
|
|
P("Lead","Expressive Sync",
|
|
1,3, 0.50f,+2.f,
|
|
2800.f,0.30f,0.35f,
|
|
0.008f,0.20f,0.60f,0.22f,
|
|
0.020f,0.26f,0.25f,0.30f, -18.f,
|
|
5.4f, 0.06f, 12.0f),
|
|
|
|
// ===================== Strings ==================
|
|
P("Strings","Warm Ensemble",
|
|
1,1, 0.50f,+6.f,
|
|
1800.f,0.22f,0.25f,
|
|
0.18f,0.60f,0.85f,0.90f,
|
|
0.12f,0.65f,0.55f,0.75f, -18.f,
|
|
5.0f, 0.00f, 6.0f),
|
|
|
|
P("Strings","Silky Strings",
|
|
3,1, 0.45f,+4.f,
|
|
1500.f,0.20f,0.30f,
|
|
0.20f,0.70f,0.85f,0.95f,
|
|
0.14f,0.70f,0.60f,0.80f, -18.f,
|
|
5.3f, 0.00f, 5.0f),
|
|
|
|
P("Strings","Octa Strings",
|
|
1,1, 0.50f,+12.f,
|
|
2200.f,0.26f,0.22f,
|
|
0.12f,0.50f,0.80f,0.80f,
|
|
0.10f,0.50f,0.50f,0.65f, -18.f,
|
|
5.0f, 0.00f, 7.0f),
|
|
|
|
P("Strings","Mellow Chamber",
|
|
3,2, 0.40f,+2.f,
|
|
1200.f,0.18f,0.18f,
|
|
0.15f,0.55f,0.75f,0.85f,
|
|
0.10f,0.45f,0.45f,0.60f, -18.f,
|
|
4.8f, 0.00f, 5.0f),
|
|
|
|
P("Strings","Glass Ensemble",
|
|
3,1, 0.48f,+6.f,
|
|
1700.f,0.22f,0.28f,
|
|
0.20f,0.65f,0.85f,0.95f,
|
|
0.14f,0.60f,0.60f,0.85f, -18.f,
|
|
5.2f, 0.00f, 6.0f),
|
|
|
|
P("Strings","Warm Section",
|
|
1,1, 0.52f,+5.f,
|
|
1400.f,0.20f,0.25f,
|
|
0.18f,0.60f,0.85f,0.90f,
|
|
0.12f,0.58f,0.55f,0.78f, -18.f,
|
|
5.0f, 0.00f, 5.0f),
|
|
|
|
P("Strings","Silk Legato",
|
|
0,3, 0.55f,+4.f,
|
|
1300.f,0.18f,0.22f,
|
|
0.18f,0.65f,0.90f,1.10f,
|
|
0.12f,0.60f,0.60f,0.85f, -18.f,
|
|
5.4f, 0.00f, 4.0f),
|
|
|
|
P("Strings","Octave Ensemble 2",
|
|
1,3, 0.50f,+12.f,
|
|
2100.f,0.24f,0.20f,
|
|
0.16f,0.55f,0.80f,0.90f,
|
|
0.10f,0.52f,0.50f,0.70f, -18.f,
|
|
5.0f, 0.00f, 7.0f),
|
|
|
|
// ===================== Polysynth ================
|
|
P("Polysynth","Poly Warm",
|
|
1,3, 0.50f,+5.f,
|
|
1000.f,0.24f,0.30f,
|
|
0.04f,0.25f,0.85f,0.40f,
|
|
0.05f,0.30f,0.40f,0.40f, -18.f,
|
|
0.30f, 0.25f, 0.0f),
|
|
|
|
P("Polysynth","Stab Classic",
|
|
2,1, 0.45f,+3.f,
|
|
1600.f,0.20f,0.25f,
|
|
0.01f,0.10f,0.60f,0.15f,
|
|
0.02f,0.12f,0.20f,0.18f, -16.f),
|
|
|
|
P("Polysynth","PolyStack",
|
|
1,1, 0.50f,+7.f,
|
|
1200.f,0.22f,0.30f,
|
|
0.03f,0.22f,0.80f,0.35f,
|
|
0.05f,0.28f,0.40f,0.42f, -18.f,
|
|
0.35f, 0.30f, 0.0f),
|
|
|
|
P("Polysynth","Soft Poly Pluck",
|
|
2,1, 0.45f,+3.f,
|
|
1600.f,0.24f,0.35f,
|
|
0.001f,0.10f,0.25f,0.16f,
|
|
0.02f,0.14f,0.20f,0.18f, -16.f,
|
|
4.5f, 0.00f, 0.0f),
|
|
|
|
P("Polysynth","Warm Chords",
|
|
3,1, 0.50f,+5.f,
|
|
1800.f,0.22f,0.28f,
|
|
0.05f,0.30f,0.85f,0.45f,
|
|
0.06f,0.32f,0.42f,0.45f, -18.f,
|
|
0.30f, 0.25f, 0.0f),
|
|
|
|
P("Polysynth","Airy Poly",
|
|
3,3, 0.50f, 0.f,
|
|
2400.f,0.26f,0.20f,
|
|
0.20f,0.70f,0.88f,0.95f,
|
|
0.16f,0.68f,0.60f,0.90f, -18.f,
|
|
0.22f, 0.35f, 0.0f),
|
|
|
|
// ===================== Organ ====================
|
|
P("Organ","Soft Organ",
|
|
2,2, 0.50f, 0.f,
|
|
1800.f,0.12f,0.00f,
|
|
0.01f,0.05f,0.75f,0.20f,
|
|
0.01f,0.05f,0.00f,0.05f, -18.f,
|
|
5.8f, 0.00f, 7.0f),
|
|
|
|
P("Organ","Full Drawbars",
|
|
2,1, 0.55f, 0.f,
|
|
2200.f,0.12f,0.00f,
|
|
0.01f,0.06f,0.80f,0.22f,
|
|
0.01f,0.05f,0.00f,0.05f, -16.f,
|
|
5.8f, 0.00f, 6.0f),
|
|
|
|
P("Organ","Jazz 888000000",
|
|
2,1, 0.55f, 0.f,
|
|
20000.f,0.12f,0.00f,
|
|
0.005f,0.06f,0.85f,0.18f,
|
|
0.005f,0.05f,0.00f,0.06f, -16.f,
|
|
5.8f, 0.00f, 7.0f),
|
|
|
|
P("Organ","Gospel Perc",
|
|
2,2, 0.52f, 0.f,
|
|
5000.f,0.14f,0.00f,
|
|
0.001f,0.20f,0.60f,0.25f,
|
|
0.006f,0.05f,0.00f,0.06f, -16.f,
|
|
6.0f, 0.00f, 5.0f),
|
|
|
|
P("Organ","Rock Drawbars",
|
|
1,2, 0.58f,+1.f,
|
|
8000.f,0.16f,0.00f,
|
|
0.004f,0.08f,0.80f,0.22f,
|
|
0.006f,0.06f,0.00f,0.06f, -15.f,
|
|
6.2f, 0.00f, 6.0f),
|
|
|
|
P("Organ","Chorus Organ",
|
|
3,1, 0.50f, 0.f,
|
|
6000.f,0.15f,0.00f,
|
|
0.004f,0.10f,0.80f,0.24f,
|
|
0.006f,0.06f,0.00f,0.06f, -17.f,
|
|
0.6f, 0.15f, 4.0f),
|
|
|
|
// ===================== Keys =====================
|
|
P("Keys","Clav Bite",
|
|
2,2, 0.40f,-2.f,
|
|
1800.f,0.22f,0.22f,
|
|
0.01f,0.08f,0.55f,0.18f,
|
|
0.02f,0.18f,0.15f,0.20f, -16.f),
|
|
// (no LFO to keep it percussive)
|
|
|
|
P("Keys","Poly Warm",
|
|
3,1, 0.50f,+5.f,
|
|
3000.f,0.24f,0.30f,
|
|
0.04f,0.25f,0.85f,0.40f,
|
|
0.05f,0.30f,0.40f,0.40f, -18.f,
|
|
0.30f, 0.22f, 0.0f),
|
|
|
|
P("Keys","Bell Keys",
|
|
0,3, 0.40f,+2.f,
|
|
4500.f,0.20f,0.15f,
|
|
0.005f,0.20f,0.30f,0.35f,
|
|
0.010f,0.22f,0.10f,0.22f, -16.f,
|
|
5.2f, 0.00f, 4.0f),
|
|
|
|
P("Keys","MK1 Soft",
|
|
3,0, 0.52f, 0.f,
|
|
1500.f,0.18f,0.12f,
|
|
0.006f,0.22f,0.75f,0.28f,
|
|
0.010f,0.22f,0.10f,0.20f, -18.f,
|
|
5.0f, 0.00f, 3.0f),
|
|
|
|
P("Keys","MK1 Bark",
|
|
2,1, 0.50f,+2.f,
|
|
2500.f,0.22f,0.18f,
|
|
0.003f,0.22f,0.50f,0.25f,
|
|
0.012f,0.20f,0.12f,0.20f, -16.f,
|
|
5.2f, 0.00f, 4.0f),
|
|
|
|
P("Keys","Wurly Bite",
|
|
2,2, 0.45f, 0.f,
|
|
1800.f,0.24f,0.20f,
|
|
0.004f,0.18f,0.55f,0.22f,
|
|
0.012f,0.18f,0.12f,0.18f, -16.f,
|
|
5.6f, 0.10f, 3.0f),
|
|
|
|
P("Keys","Trem EP",
|
|
3,1, 0.50f, 0.f,
|
|
2200.f,0.20f,0.16f,
|
|
0.004f,0.24f,0.70f,0.30f,
|
|
0.012f,0.22f,0.10f,0.22f, -18.f,
|
|
4.8f, 0.25f, 0.0f),
|
|
// ===================== Brass ====================
|
|
P("Brass","Soft Brass",
|
|
1,2, 0.60f,+4.f,
|
|
1800.f,0.22f,0.35f,
|
|
0.01f,0.20f,0.55f,0.30f,
|
|
0.02f,0.30f,0.25f,0.40f, -18.f,
|
|
5.0f, 0.06f, 6.0f),
|
|
|
|
P("Brass","Stacked Brass",
|
|
1,1, 0.55f,+5.f,
|
|
1600.f,0.24f,0.30f,
|
|
0.01f,0.18f,0.60f,0.25f,
|
|
0.02f,0.28f,0.22f,0.35f, -16.f,
|
|
5.2f, 0.05f, 8.0f),
|
|
P("Brass","Soft Trumpet",
|
|
1,2, 0.55f,+6.f,
|
|
1800.f,0.32f,0.40f,
|
|
0.010f,0.25f,0.60f,0.35f,
|
|
0.020f,0.30f,0.28f,0.42f, -18.f,
|
|
5.3f, 0.06f, 6.0f),
|
|
|
|
P("Brass","Brass Stabs",
|
|
2,1, 0.52f,+4.f,
|
|
1600.f,0.24f,0.45f,
|
|
0.004f,0.12f,0.40f,0.18f,
|
|
0.012f,0.20f,0.20f,0.28f, -16.f,
|
|
5.0f, 0.00f, 4.0f),
|
|
|
|
P("Brass","Warm Horns",
|
|
1,3, 0.50f,+3.f,
|
|
1400.f,0.22f,0.32f,
|
|
0.020f,0.40f,0.75f,0.50f,
|
|
0.020f,0.35f,0.30f,0.45f, -18.f,
|
|
4.8f, 0.05f, 5.0f),
|
|
|
|
P("Brass","Sync Brass",
|
|
2,2, 0.58f,+7.f,
|
|
2200.f,0.34f,0.35f,
|
|
0.006f,0.18f,0.55f,0.22f,
|
|
0.016f,0.26f,0.24f,0.32f, -16.f,
|
|
5.6f, 0.05f, 7.0f),
|
|
|
|
// ===================== Choir ====================
|
|
P("Choir","Ooh Pad",
|
|
3,3, 0.60f,+3.f,
|
|
1200.f,0.16f,0.20f,
|
|
0.12f,0.60f,0.85f,0.90f,
|
|
0.10f,0.60f,0.60f,0.70f, -18.f,
|
|
0.28f, 0.40f, 0.0f),
|
|
|
|
P("Choir","Ahh Warm",
|
|
3,1, 0.58f,+2.f,
|
|
1100.f,0.18f,0.22f,
|
|
0.14f,0.58f,0.80f,0.88f,
|
|
0.10f,0.55f,0.55f,0.68f, -18.f,
|
|
0.26f, 0.35f, 0.0f),
|
|
|
|
// ===================== Drums ====================
|
|
P("Drums","Click Kick",
|
|
2,2, 0.50f, 0.f,
|
|
220.f,0.15f,0.00f,
|
|
0.00f,0.06f,0.35f,0.18f,
|
|
0.00f,0.05f,0.00f,0.06f, -16.f),
|
|
|
|
P("Drums","Deep Kick",
|
|
0,0, 0.35f, 0.f,
|
|
180.f,0.16f,0.00f,
|
|
0.001f,0.12f,0.20f,0.18f,
|
|
0.001f,0.08f,0.00f,0.08f, -12.f,
|
|
0.0f, 0.00f, 0.0f),
|
|
|
|
P("Drums","Snappy Snare",
|
|
2,1, 0.50f, 0.f,
|
|
2200.f,0.30f,0.20f,
|
|
0.001f,0.10f,0.00f,0.15f,
|
|
0.004f,0.10f,0.05f,0.10f, -14.f,
|
|
0.0f, 0.00f, 0.0f),
|
|
|
|
P("Drums","Closed Hat",
|
|
2,2, 0.50f, 0.f,
|
|
8000.f,0.18f,0.00f,
|
|
0.001f,0.05f,0.00f,0.05f,
|
|
0.002f,0.05f,0.00f,0.05f, -18.f,
|
|
0.0f, 0.00f, 0.0f),
|
|
|
|
P("Drums","Low Tom",
|
|
0,0, 0.30f, 0.f,
|
|
220.f,0.16f,0.00f,
|
|
0.001f,0.14f,0.25f,0.18f,
|
|
0.002f,0.08f,0.00f,0.08f, -16.f,
|
|
0.0f, 0.00f, 0.0f),
|
|
|
|
// ===================== SFX ======================
|
|
P("SFX","Meteor Rise",
|
|
3,1, 0.50f, +4.f,
|
|
900.f,0.28f,0.85f,
|
|
0.05f,0.70f,0.10f,0.80f,
|
|
0.08f,0.75f,0.10f,0.85f, -18.f,
|
|
0.15f, 0.80f, 0.0f),
|
|
|
|
P("SFX","Noise Drone",
|
|
1,1, 0.50f,+12.f,
|
|
1200.f,0.30f,0.50f,
|
|
0.30f,0.90f,0.90f,1.20f,
|
|
0.30f,0.90f,0.70f,1.10f, -14.f,
|
|
0.22f, 0.65f, 0.0f),
|
|
|
|
P("SFX","Reso Sweep",
|
|
3,1, 0.50f,+2.f,
|
|
700.f,0.60f,0.80f,
|
|
0.02f,0.50f,0.30f,0.60f,
|
|
0.05f,0.70f,0.20f,0.70f, -18.f,
|
|
0.20f, 0.70f, 0.0f),
|
|
|
|
P("SFX","Dark Pad Sweep",
|
|
1,1, 0.50f,+5.f,
|
|
500.f,0.35f,0.85f,
|
|
0.40f,0.90f,0.90f,1.20f,
|
|
0.80f,1.20f,0.60f,1.00f, -18.f,
|
|
0.18f, 0.90f, 0.0f),
|
|
};
|
|
return patches;
|
|
}
|
|
|
|
// Flattened labels for the stored preset index
|
|
juce::StringArray getFlatPresetLabels()
|
|
{
|
|
juce::StringArray a;
|
|
for (auto& p : getFactoryPatches())
|
|
a.add (p.category + " - " + p.name);
|
|
return a;
|
|
}
|
|
|
|
inline void setFloatParam (juce::AudioProcessorValueTreeState& apvts,
|
|
const juce::String& id, float value)
|
|
{
|
|
if (auto* param = apvts.getParameter (id))
|
|
if (auto* rp = dynamic_cast<juce::RangedAudioParameter*> (param))
|
|
{
|
|
const float norm = rp->convertTo0to1 (value);
|
|
param->beginChangeGesture();
|
|
param->setValueNotifyingHost (juce::jlimit (0.0f, 1.0f, norm));
|
|
param->endChangeGesture();
|
|
}
|
|
}
|
|
|
|
inline void setChoiceParam (juce::AudioProcessorValueTreeState& apvts,
|
|
const juce::String& id, int index)
|
|
{
|
|
if (auto* param = apvts.getParameter (id))
|
|
if (auto* cp = dynamic_cast<juce::AudioParameterChoice*> (param))
|
|
{
|
|
const int n = cp->choices.size();
|
|
index = juce::jlimit (0, juce::jmax (0, n - 1), index);
|
|
const float norm = (n > 1 ? (float) index / (float) (n - 1) : 0.0f);
|
|
param->beginChangeGesture();
|
|
param->setValueNotifyingHost (juce::jlimit (0.0f, 1.0f, norm));
|
|
param->endChangeGesture();
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
//==============================================================
|
|
// SimpleSound / SimpleVoice
|
|
//==============================================================
|
|
bool SimpleSound::appliesToNote (int) { return true; }
|
|
bool SimpleSound::appliesToChannel (int) { return true; }
|
|
|
|
bool SimpleVoice::canPlaySound (juce::SynthesiserSound* s)
|
|
{
|
|
return dynamic_cast<SimpleSound*> (s) != nullptr;
|
|
}
|
|
|
|
void SimpleVoice::setParameters (juce::AudioProcessorValueTreeState& apvts)
|
|
{
|
|
oscAChoice = (int) apvts.getRawParameterValue ("oscA")->load();
|
|
oscBChoice = (int) apvts.getRawParameterValue ("oscB")->load();
|
|
mix = (float) apvts.getRawParameterValue ("mix")->load();
|
|
detune = (float) apvts.getRawParameterValue ("detune")->load();
|
|
cutoff = (float) apvts.getRawParameterValue ("cutoff")->load();
|
|
reso = (float) apvts.getRawParameterValue ("reso")->load();
|
|
envAmt = (float) apvts.getRawParameterValue ("envAmt")->load();
|
|
filterBypass = apvts.getRawParameterValue ("filterBypass")->load() > 0.5f;
|
|
|
|
// LFO pulls
|
|
lfoRateHz = (float) apvts.getRawParameterValue ("lfoRate")->load();
|
|
lfoToCutOct = (float) apvts.getRawParameterValue ("lfoToCut")->load();
|
|
lfoToPitchCt = (float) apvts.getRawParameterValue ("lfoToPitch")->load();
|
|
|
|
ampParams.attack = (float) apvts.getRawParameterValue ("aA")->load();
|
|
ampParams.decay = (float) apvts.getRawParameterValue ("dA")->load();
|
|
ampParams.sustain = (float) apvts.getRawParameterValue ("sA")->load();
|
|
ampParams.release = (float) apvts.getRawParameterValue ("rA")->load();
|
|
|
|
filterParams.attack = (float) apvts.getRawParameterValue ("aF")->load();
|
|
filterParams.decay = (float) apvts.getRawParameterValue ("dF")->load();
|
|
filterParams.sustain = (float) apvts.getRawParameterValue ("sF")->load();
|
|
filterParams.release = (float) apvts.getRawParameterValue ("rF")->load();
|
|
|
|
adsrAmp.setParameters (ampParams);
|
|
adsrFilter.setParameters (filterParams);
|
|
|
|
// Smooth only targets (no skip)
|
|
detuneSmoothed.setTargetValue (detune);
|
|
cutoffSmoothed.setTargetValue (cutoff);
|
|
resoSmoothed.setTargetValue (reso);
|
|
envAmtSmoothed.setTargetValue (envAmt);
|
|
|
|
lfoCutSmoothed.setTargetValue (lfoToCutOct);
|
|
lfoPitchSmoothed.setTargetValue (lfoToPitchCt);
|
|
}
|
|
|
|
void SimpleVoice::startNote (int midiNoteNumber, float velocity, juce::SynthesiserSound*, int)
|
|
{
|
|
currentNoteHz = (float) juce::MidiMessage::getMidiNoteInHertz (midiNoteNumber);
|
|
velocityGain = juce::jlimit (0.0f, 1.0f, velocity);
|
|
|
|
// Ensure minimum attack so starting ramps are never instant
|
|
const float minA = 0.003f;
|
|
auto p = adsrAmp.getParameters();
|
|
p.attack = juce::jmax (p.attack, minA);
|
|
adsrAmp.setParameters (p);
|
|
|
|
// Reset per-note
|
|
triState = 0.0f;
|
|
forcedOffActive = false;
|
|
forcedOffCounter = forcedOffSamples = 0;
|
|
|
|
// Reset LFO phase on key-on
|
|
lfoPhase = 0.0f;
|
|
|
|
// Per-voice SVF
|
|
const double sr = getSampleRate();
|
|
juce::dsp::ProcessSpec spec { sr, 1u, 1u };
|
|
lpFilter.reset(); lpFilter.prepare (spec);
|
|
lpFilter.setType (juce::dsp::StateVariableTPTFilterType::lowpass);
|
|
|
|
// SR-aware triangle leak: tau ≈ 40 ms
|
|
const float Tc = 0.040f;
|
|
triLeakCoeff = 1.0f - std::exp (-1.0f / (float) (sr * Tc));
|
|
|
|
// Smoothers
|
|
detuneSmoothed.reset (sr, 0.020);
|
|
cutoffSmoothed.reset (sr, 0.020);
|
|
resoSmoothed.reset (sr, 0.020);
|
|
envAmtSmoothed.reset (sr, 0.020);
|
|
cutoffModSmooth.reset (sr, 0.005);
|
|
|
|
lfoCutSmoothed.reset (sr, 0.020);
|
|
lfoPitchSmoothed.reset (sr, 0.020);
|
|
|
|
detuneSmoothed.setCurrentAndTargetValue (detune);
|
|
cutoffSmoothed.setCurrentAndTargetValue (cutoff);
|
|
resoSmoothed.setCurrentAndTargetValue (reso);
|
|
envAmtSmoothed.setCurrentAndTargetValue (envAmt);
|
|
cutoffModSmooth.setCurrentAndTargetValue (cutoff);
|
|
|
|
lfoCutSmoothed.setCurrentAndTargetValue (lfoToCutOct);
|
|
lfoPitchSmoothed.setCurrentAndTargetValue (lfoToPitchCt);
|
|
|
|
adsrAmp.noteOn();
|
|
adsrFilter.noteOn();
|
|
|
|
// ~2.5 ms fade-in
|
|
rampSamples = (int) std::ceil (sr * 0.0025);
|
|
rampCounter = 0;
|
|
}
|
|
|
|
void SimpleVoice::stopNote (float /*velocity*/, bool allowTailOff)
|
|
{
|
|
adsrAmp.noteOff();
|
|
adsrFilter.noteOff();
|
|
|
|
if (! allowTailOff)
|
|
{
|
|
const double sr = getSampleRate();
|
|
forcedOffSamples = (int) std::ceil (sr * 0.003);
|
|
forcedOffCounter = forcedOffSamples;
|
|
forcedOffActive = true;
|
|
return;
|
|
}
|
|
|
|
if (! adsrAmp.isActive())
|
|
clearCurrentNote();
|
|
}
|
|
|
|
void SimpleVoice::renderNextBlock (juce::AudioBuffer<float>& out, int start, int num)
|
|
{
|
|
const float sr = (float) getSampleRate();
|
|
if (sr <= 0.0f) return;
|
|
|
|
for (int i = 0; i < num; ++i)
|
|
{
|
|
// --- LFO (sine) ---
|
|
const float lfoDt = juce::jmax (0.0f, lfoRateHz) / sr;
|
|
const float lfoVal = (lfoRateHz > 0.0f ? std::sin (juce::MathConstants<float>::twoPi * lfoPhase) : 0.0f);
|
|
lfoPhase = wrap01 (lfoPhase + lfoDt);
|
|
|
|
const float vibCents = lfoPitchSmoothed.getNextValue() * lfoVal;
|
|
|
|
// Freq increments with vibrato applied to both oscillators
|
|
const float hzBaseA = currentNoteHz * std::pow (2.0f, (vibCents) / 1200.0f);
|
|
const float hzBaseB = currentNoteHz * std::pow (2.0f, (detuneSmoothed.getNextValue() + vibCents) / 1200.0f);
|
|
|
|
const float dtA = juce::jmin (0.5f, hzBaseA / sr); // clamp dt to be safe
|
|
const float dtB = juce::jmin (0.5f, hzBaseB / sr);
|
|
|
|
// Band-limited oscillators (free-running phase)
|
|
const float sA = oscBLEP (oscAChoice, phaseA, dtA);
|
|
const float sB = oscBLEP (oscBChoice, phaseB, dtB);
|
|
|
|
phaseA = wrap01 (phaseA + dtA);
|
|
phaseB = wrap01 (phaseB + dtB);
|
|
|
|
// Voice mix + headroom
|
|
float raw = (1.0f - mix) * sA + mix * sB;
|
|
raw *= 0.65f; // voice-level headroom
|
|
|
|
// Filter env + LFO cutoff modulation if active
|
|
float y = raw;
|
|
if (! filterBypass)
|
|
{
|
|
const float fEnv = juce::jlimit (0.0f, 1.0f, adsrFilter.getNextSample());
|
|
|
|
// Exponent adds: envAmt*env + lfoCut*LFO (both in octaves)
|
|
const float expo = envAmtSmoothed.getNextValue() * fEnv
|
|
+ lfoCutSmoothed.getNextValue() * lfoVal;
|
|
|
|
float modCut = cutoffSmoothed.getNextValue() * std::pow (2.0f, expo);
|
|
modCut = juce::jlimit (20.0f, sr * 0.45f, modCut);
|
|
cutoffModSmooth.setTargetValue (modCut);
|
|
|
|
lpFilter.setCutoffFrequency (cutoffModSmooth.getNextValue());
|
|
lpFilter.setResonance (resoSmoothed.getNextValue());
|
|
y = lpFilter.processSample (0, raw);
|
|
}
|
|
|
|
// Amp env
|
|
float amp = adsrAmp.getNextSample() * velocityGain;
|
|
|
|
// Start ramp
|
|
if (rampCounter < rampSamples)
|
|
{
|
|
const float t = (float) rampCounter / (float) rampSamples;
|
|
amp *= t * t;
|
|
++rampCounter;
|
|
}
|
|
|
|
// Forced stop ramp
|
|
if (forcedOffActive && forcedOffSamples > 0)
|
|
{
|
|
const float t = (float) forcedOffCounter / (float) forcedOffSamples;
|
|
amp *= juce::jlimit (0.0f, 1.0f, t);
|
|
if (--forcedOffCounter <= 0) { forcedOffActive = false; clearCurrentNote(); }
|
|
}
|
|
|
|
const float s = y * amp;
|
|
|
|
for (int ch = 0; ch < out.getNumChannels(); ++ch)
|
|
out.addSample (ch, start + i, s);
|
|
}
|
|
|
|
if (! adsrAmp.isActive() && ! forcedOffActive)
|
|
clearCurrentNote();
|
|
}
|
|
|
|
void SimpleVoice::reset()
|
|
{
|
|
lpFilter.reset();
|
|
triState = 0.0f;
|
|
lfoPhase = 0.0f;
|
|
forcedOffActive = false;
|
|
forcedOffSamples = forcedOffCounter = 0;
|
|
}
|
|
|
|
|
|
//==============================================================
|
|
// Processor
|
|
//==============================================================
|
|
TwoOscAudioProcessor::TwoOscAudioProcessor()
|
|
: juce::AudioProcessor (BusesProperties()
|
|
#if ! JucePlugin_IsMidiEffect
|
|
#if ! JucePlugin_IsSynth
|
|
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
|
|
#endif
|
|
.withOutput ("Output", juce::AudioChannelSet::stereo(), true)
|
|
#endif
|
|
),
|
|
apvts (*this, nullptr, "PARAMS", createParameterLayout())
|
|
{
|
|
apvts.addParameterListener ("presetIndex", this);
|
|
const int preset = (int) apvts.getRawParameterValue ("presetIndex")->load();
|
|
applyPresetByIndex (preset, false);
|
|
}
|
|
|
|
TwoOscAudioProcessor::~TwoOscAudioProcessor()
|
|
{
|
|
apvts.removeParameterListener ("presetIndex", this);
|
|
}
|
|
|
|
// Basic info overrides (linker was missing these)
|
|
const juce::String TwoOscAudioProcessor::getName() const { return JucePlugin_Name; }
|
|
bool TwoOscAudioProcessor::acceptsMidi() const { return true; }
|
|
bool TwoOscAudioProcessor::producesMidi() const { return false; }
|
|
bool TwoOscAudioProcessor::isMidiEffect() const { return false; }
|
|
double TwoOscAudioProcessor::getTailLengthSeconds() const { return 0.0; }
|
|
|
|
// Prepare / process
|
|
void TwoOscAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
|
|
{
|
|
lastSampleRate = sampleRate;
|
|
|
|
// Kill denormals globally (prevents random CPU spikes / noise)
|
|
juce::FloatVectorOperations::disableDenormalisedNumberSupport();
|
|
|
|
// Synth setup
|
|
synth.clearVoices();
|
|
synth.clearSounds();
|
|
|
|
// Choose the polyphony here
|
|
for (int i = 0; i < 32; ++i)
|
|
synth.addVoice (new SimpleVoice());
|
|
|
|
synth.addSound (new SimpleSound());
|
|
synth.setCurrentPlaybackSampleRate (sampleRate);
|
|
synth.setNoteStealingEnabled (true);
|
|
|
|
// Optional hidden HPF (prepared even if bypassed)
|
|
juce::dsp::ProcessSpec spec { sampleRate, (juce::uint32) samplesPerBlock, 2 };
|
|
hpFilter.reset();
|
|
hpFilter.prepare (spec);
|
|
*hpFilter.state = *juce::dsp::IIR::Coefficients<float>::makeHighPass ((float) sampleRate, 40.0f);
|
|
|
|
// Global smoothers (if you keep them)
|
|
smoothedCutoff.reset (sampleRate, 0.01);
|
|
smoothedReso.reset (sampleRate, 0.01);
|
|
smoothedEnvAmt.reset (sampleRate, 0.01);
|
|
|
|
// Make sure all voices start in a clean state
|
|
for (int i = 0; i < synth.getNumVoices(); ++i)
|
|
if (auto* v = dynamic_cast<SimpleVoice*> (synth.getVoice (i)))
|
|
v->reset();
|
|
}
|
|
|
|
#ifndef JucePlugin_PreferredChannelConfigurations
|
|
bool TwoOscAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
|
|
{
|
|
#if JucePlugin_IsSynth
|
|
juce::ignoreUnused (layouts);
|
|
return true;
|
|
#else
|
|
if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono()
|
|
&& layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
|
|
return false;
|
|
|
|
#if ! JucePlugin_IsSynth
|
|
if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
|
|
return false;
|
|
#endif
|
|
return true;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
void TwoOscAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer,
|
|
juce::MidiBuffer& midiMessages)
|
|
{
|
|
juce::ScopedNoDenormals noDenormals;
|
|
buffer.clear();
|
|
|
|
// --- Push current parameter values into each voice ---
|
|
for (int i = 0; i < synth.getNumVoices(); ++i)
|
|
if (auto* voice = dynamic_cast<SimpleVoice*> (synth.getVoice (i)))
|
|
voice->setParameters (apvts);
|
|
|
|
// --- Render voices ---
|
|
synth.renderNextBlock (buffer, midiMessages, 0, buffer.getNumSamples());
|
|
|
|
// --- Optional high-pass (normally bypassed) ---
|
|
// juce::dsp::AudioBlock<float> block (buffer);
|
|
// hpFilter.process (juce::dsp::ProcessContextReplacing<float> (block));
|
|
|
|
// --- Stage 1: inter-voice safety headroom ---
|
|
buffer.applyGain (0.80f);
|
|
|
|
// --- Stage 2: ultra-soft output saturation (very gentle) ---
|
|
constexpr float k = 0.12f; // smaller = softer
|
|
for (int ch = 0; ch < buffer.getNumChannels(); ++ch)
|
|
{
|
|
float* d = buffer.getWritePointer (ch);
|
|
const int n = buffer.getNumSamples();
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
const float x = d[i];
|
|
d[i] = x - k * x * x * x;
|
|
}
|
|
}
|
|
|
|
// --- Stage 3: master output gain (dB) ---
|
|
const float masterDb = (float) apvts.getRawParameterValue ("masterGain")->load();
|
|
buffer.applyGain (juce::Decibels::decibelsToGain (masterDb));
|
|
|
|
// --- Stage 4: anti-denormal / NaN cleanup ---
|
|
for (int ch = 0; ch < buffer.getNumChannels(); ++ch)
|
|
{
|
|
float* d = buffer.getWritePointer (ch);
|
|
for (int i = 0; i < buffer.getNumSamples(); ++i)
|
|
if (! std::isfinite (d[i]))
|
|
d[i] = 0.0f;
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
// Editor
|
|
bool TwoOscAudioProcessor::hasEditor() const { return true; }
|
|
juce::AudioProcessorEditor* TwoOscAudioProcessor::createEditor()
|
|
{
|
|
return new TwoOscAudioProcessorEditor (*this);
|
|
}
|
|
|
|
//==============================================================================
|
|
// State
|
|
void TwoOscAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
|
|
{
|
|
auto state = apvts.copyState();
|
|
std::unique_ptr<juce::XmlElement> xml (state.createXml());
|
|
copyXmlToBinary (*xml, destData);
|
|
}
|
|
|
|
void TwoOscAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
|
|
{
|
|
std::unique_ptr<juce::XmlElement> xml (getXmlFromBinary (data, sizeInBytes));
|
|
if (xml != nullptr && xml->hasTagName (apvts.state.getType()))
|
|
apvts.replaceState (juce::ValueTree::fromXml (*xml));
|
|
}
|
|
|
|
//==============================================================================
|
|
// APVTS listener (for preset changes)
|
|
void TwoOscAudioProcessor::parameterChanged (const juce::String& paramID, float newValue)
|
|
{
|
|
if (paramID == "presetIndex")
|
|
applyPresetByIndex ((int) newValue, false);
|
|
}
|
|
|
|
//==============================================================================
|
|
// Preset helpers (for the editor)
|
|
juce::StringArray TwoOscAudioProcessor::getPresetCategories() const
|
|
{
|
|
juce::StringArray cats;
|
|
for (auto& p : getFactoryPatches())
|
|
if (! cats.contains (p.category))
|
|
cats.add (p.category);
|
|
return cats;
|
|
}
|
|
|
|
juce::Array<int> TwoOscAudioProcessor::getPresetIndicesForCategory (const juce::String& category) const
|
|
{
|
|
juce::Array<int> idx;
|
|
const auto& bank = getFactoryPatches();
|
|
for (int i = 0; i < (int) bank.size(); ++i)
|
|
if (bank[(size_t) i].category == category)
|
|
idx.add (i);
|
|
return idx;
|
|
}
|
|
|
|
juce::String TwoOscAudioProcessor::getPresetLabel (int index) const
|
|
{
|
|
const auto& bank = getFactoryPatches();
|
|
if ((size_t) index < bank.size())
|
|
return bank[(size_t) index].name;
|
|
return {};
|
|
}
|
|
|
|
juce::String TwoOscAudioProcessor::getCurrentPresetLabel() const
|
|
{
|
|
const int idx = (int) apvts.getRawParameterValue ("presetIndex")->load();
|
|
auto labels = getFlatPresetLabels();
|
|
if (idx >= 0 && idx < labels.size())
|
|
return labels[idx];
|
|
return {};
|
|
}
|
|
|
|
//==============================================================================
|
|
// Apply preset (writes into APVTS)
|
|
void TwoOscAudioProcessor::applyPresetByIndex (int index, bool setParamToo)
|
|
{
|
|
const auto& bank = getFactoryPatches();
|
|
if (bank.empty()) return;
|
|
|
|
index = juce::jlimit (0, (int) bank.size() - 1, index);
|
|
const auto& p = bank[(size_t) index];
|
|
|
|
if (isApplyingPreset.exchange (true))
|
|
return;
|
|
|
|
setChoiceParam (apvts, "oscA", p.oscA);
|
|
setChoiceParam (apvts, "oscB", p.oscB);
|
|
setFloatParam (apvts, "mix", p.mix);
|
|
setFloatParam (apvts, "detune", p.detune);
|
|
setFloatParam (apvts, "cutoff", p.cutoff);
|
|
setFloatParam (apvts, "reso", p.reso);
|
|
setFloatParam (apvts, "envAmt", p.envAmt);
|
|
|
|
setFloatParam (apvts, "aA", p.aA); setFloatParam (apvts, "dA", p.dA);
|
|
setFloatParam (apvts, "sA", p.sA); setFloatParam (apvts, "rA", p.rA);
|
|
setFloatParam (apvts, "aF", p.aF); setFloatParam (apvts, "dF", p.dF);
|
|
setFloatParam (apvts, "sF", p.sF); setFloatParam (apvts, "rF", p.rF);
|
|
|
|
setFloatParam (apvts, "masterGain", p.master);
|
|
|
|
// NEW: LFO params from preset
|
|
setFloatParam (apvts, "lfoRate", p.lfoRate);
|
|
setFloatParam (apvts, "lfoToCut", p.lfoToCut);
|
|
setFloatParam (apvts, "lfoToPitch", p.lfoToPitch);
|
|
|
|
if (setParamToo)
|
|
if (auto* prm = dynamic_cast<juce::AudioParameterChoice*> (apvts.getParameter ("presetIndex")))
|
|
{
|
|
const int n = prm->choices.size();
|
|
const float norm = (n > 1 ? (float) index / (float) (n - 1) : 0.0f);
|
|
prm->beginChangeGesture();
|
|
prm->setValueNotifyingHost (juce::jlimit (0.0f, 1.0f, norm));
|
|
prm->endChangeGesture();
|
|
}
|
|
|
|
isApplyingPreset = false;
|
|
}
|
|
|
|
//==============================================================================
|
|
// Parameter layout
|
|
juce::AudioProcessorValueTreeState::ParameterLayout TwoOscAudioProcessor::createParameterLayout()
|
|
{
|
|
std::vector<std::unique_ptr<juce::RangedAudioParameter>> p;
|
|
|
|
p.push_back (std::make_unique<juce::AudioParameterChoice>(
|
|
"presetIndex","Preset", getFlatPresetLabels(), 0));
|
|
|
|
p.push_back (std::make_unique<juce::AudioParameterChoice>(
|
|
"oscA","Osc A", juce::StringArray{ "Sine","Saw","Square","Tri" }, 1));
|
|
p.push_back (std::make_unique<juce::AudioParameterChoice>(
|
|
"oscB","Osc B", juce::StringArray{ "Sine","Saw","Square","Tri" }, 2));
|
|
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("mix","A/B Mix",0.0f,1.0f,0.35f));
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("detune","Detune (c)",-24.0f,24.0f,-5.0f));
|
|
|
|
juce::NormalisableRange<float> cutoffRange (20.0f, 20000.0f);
|
|
cutoffRange.setSkewForCentre (500.0f);
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("cutoff","Cutoff",cutoffRange,180.0f));
|
|
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("reso","Resonance",0.1f,1.0f,0.18f));
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("envAmt","Filter Env",-1.0f,1.0f,0.25f));
|
|
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("aA","Amp A",0.001f,2.0f,0.01f));
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("dA","Amp D",0.001f,2.0f,0.10f));
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("sA","Amp S",0.0f,1.0f,0.70f));
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("rA","Amp R",0.001f,4.0f,0.12f));
|
|
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("aF","Filt A",0.001f,2.0f,0.015f));
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("dF","Filt D",0.001f,2.0f,0.18f));
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("sF","Filt S",0.0f,1.0f,0.10f));
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("rF","Filt R",0.001f,4.0f,0.18f));
|
|
|
|
p.push_back (std::make_unique<juce::AudioParameterBool>("filterBypass","Deactivate Filter", false));
|
|
|
|
// ------- NEW: LFO parameters -------
|
|
// Rate: 0..20 Hz (0 = off)
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("lfoRate","LFO Rate (Hz)", 0.0f, 20.0f, 0.0f));
|
|
|
|
// Depth to cutoff in octaves (-4..+4 for full range if you wish; keep 0..4 positive here)
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("lfoToCut","LFO → Cutoff (oct)", -4.0f, 4.0f, 0.0f));
|
|
|
|
// Depth to pitch in cents
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("lfoToPitch","LFO → Pitch (c)", 0.0f, 100.0f, 0.0f));
|
|
|
|
p.push_back (std::make_unique<juce::AudioParameterFloat>("masterGain","Master",-36.0f,12.0f,-18.0f));
|
|
|
|
return { p.begin(), p.end() };
|
|
}
|
|
|
|
//==============================================================================
|
|
// Misc helpers (kept for completeness)
|
|
void TwoOscAudioProcessor::updateFilter()
|
|
{
|
|
smoothedCutoff.setTargetValue ((float) apvts.getRawParameterValue ("cutoff")->load());
|
|
smoothedReso.setTargetValue ((float) apvts.getRawParameterValue ("reso")->load());
|
|
smoothedEnvAmt.setTargetValue ((float) apvts.getRawParameterValue ("envAmt")->load());
|
|
}
|
|
|
|
//==============================================================================
|
|
// Factory
|
|
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
|
|
{
|
|
return new TwoOscAudioProcessor();
|
|
}
|
|
|