IKeyboardControl
will not work correctly.IKeyboardControl
is Up. So you need a white key with no black on top. If you work non-destructively, it is not difficult.IKeyboardControl
cd ~/plugin-development/wdl-ol/IPlugExamples/
./duplicate.py Synthesis/ SpaceBass YourName
duplicate
script.KNOB_SMALL_ID
and KNOB_SMALL_FN
. The file header should look like this: // Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define FILTERMODE_ID 106 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define FILTERMODE_FN "resources/img/filtermode.png"
// GUI default dimensions #define GUI_WIDTH 571 #define GUI_HEIGHT 500
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN FILTERMODE_ID PNG FILTERMODE_FN
Oscillator
class a bit. Let's enum OscillatorMode
inside the class so that it can be accessed from the outside as Oscillator::OscillatorMode
. We did the same in Filter
and EnvelopeGenerator
, and we will do it here, “for symmetry”.public
and private
to make the public
go first. And move the enum OscillatorMode
up to this section: class Oscillator { public: enum OscillatorMode { OSCILLATOR_MODE_SINE = 0, OSCILLATOR_MODE_SAW, OSCILLATOR_MODE_SQUARE, OSCILLATOR_MODE_TRIANGLE, kNumOscillatorModes }; void setMode(OscillatorMode mode); void setFrequency(double frequency); void setSampleRate(double sampleRate); void generate(double* buffer, int nFrames); inline void setMuted(bool muted) { isMuted = muted; } double nextSample(); Oscillator() : mOscillatorMode(OSCILLATOR_MODE_SINE), mPI(2*acos(0.0)), twoPI(2 * mPI), isMuted(true), mFrequency(440.0), mPhase(0.0), mSampleRate(44100.0) { updateIncrement(); }; private: OscillatorMode mOscillatorMode; const double mPI; const double twoPI; bool isMuted; double mFrequency; double mPhase; double mSampleRate; double mPhaseIncrement; void updateIncrement(); };
private
functions: void CreateParams(); void CreateGraphics();
double mFrequency
we don’t double mFrequency
.enum EParams
add a constant: const double parameterStep = 0.001;
EParams
: enum EParams { // Oscillator Section: mOsc1Waveform = 0, mOsc1PitchMod, mOsc2Waveform, mOsc2PitchMod, mOscMix, // Filter Section: mFilterMode, mFilterCutoff, mFilterResonance, mFilterLfoAmount, mFilterEnvAmount, // LFO: mLFOWaveform, mLFOFrequency, // Volume Envelope: mVolumeEnvAttack, mVolumeEnvDecay, mVolumeEnvSustain, mVolumeEnvRelease, // Filter Envelope: mFilterEnvAttack, mFilterEnvDecay, mFilterEnvSustain, mFilterEnvRelease, kNumParams };
ELayout
too, ELayout
layout of the virtual keyboard has changed: enum ELayout { kWidth = GUI_WIDTH, kHeight = GUI_HEIGHT, kKeybX = 62, kKeybY = 425 };
InitDouble()
and new IKnobMultiControl
, it is better to create a special data structure for storing information about the GUI.struct
under EParams
: typedef struct { const char* name; const int x; const int y; const double defaultVal; const double minVal; const double maxVal; } parameterProperties_struct;
double
). For the switches, we will not need the default/min/maxVal
. Because of static typing, this would be superfluous.parameterProperties_struct
, which means we need an array of size kNumParams
: const parameterProperties_struct parameterProperties[kNumParams] =
default/min/maxVals
remain uninitialized for parameters of type enum
, such as Filter Mode : { {.name="Osc 1 Waveform", .x=30, .y=75}, {.name="Osc 1 Pitch Mod", .x=69, .y=61, .defaultVal=0.0, .minVal=0.0, .maxVal=1.0}, {.name="Osc 2 Waveform", .x=203, .y=75}, {.name="Osc 2 Pitch Mod", .x=242, .y=61, .defaultVal=0.0, .minVal=0.0, .maxVal=1.0}, {.name="Osc Mix", .x=130, .y=61, .defaultVal=0.5, .minVal=0.0, .maxVal=1.0}, {.name="Filter Mode", .x=30, .y=188}, {.name="Filter Cutoff", .x=69, .y=174, .defaultVal=0.99, .minVal=0.0, .maxVal=0.99}, {.name="Filter Resonance", .x=124, .y=174, .defaultVal=0.0, .minVal=0.0, .maxVal=1.0}, {.name="Filter LFO Amount", .x=179, .y=174, .defaultVal=0.0, .minVal=0.0, .maxVal=1.0}, {.name="Filter Envelope Amount", .x=234, .y=174, .defaultVal=0.0, .minVal=-1.0, .maxVal=1.0}, {.name="LFO Waveform", .x=30, .y=298}, {.name="LFO Frequency", .x=69, .y=284, .defaultVal=6.0, .minVal=0.01, .maxVal=30.0}, {.name="Volume Env Attack", .x=323, .y=61, .defaultVal=0.01, .minVal=0.01, .maxVal=10.0}, {.name="Volume Env Decay", .x=378, .y=61, .defaultVal=0.5, .minVal=0.01, .maxVal=15.0}, {.name="Volume Env Sustain", .x=433, .y=61, .defaultVal=0.1, .minVal=0.001, .maxVal=1.0}, {.name="Volume Env Release", .x=488, .y=61, .defaultVal=1.0, .minVal=0.01, .maxVal=15.0}, {.name="Filter Env Attack", .x=323, .y=174, .defaultVal=0.01, .minVal=0.01, .maxVal=10.0}, {.name="Filter Env Decay", .x=378, .y=174, .defaultVal=0.5, .minVal=0.01, .maxVal=15.0}, {.name="Filter Env Sustain", .x=433, .y=174, .defaultVal=0.1, .minVal=0.001, .maxVal=1.0}, {.name="Filter Env Release", .x=488, .y=174, .defaultVal=1.0, .minVal=0.01, .maxVal=15.0} };
{}
is a relatively new technique in C / C ++, which is called “ composite literals ”. The basic idea is that you can initialize structures and arrays in this way. The external brackets initialize the parameterProperties[]
array, they contain a comma-separated list of compound literals, each of which initializes one parameterProperties_struct
. Let's break it down using the first literal as an example: {.name="Osc 1 Waveform", .x=30, .y=75}
parameterProperties_struct* osc1Waveform_prop = parameterProperties[mOsc1Waveform]; osc1Waveform_prop->name = "Osc 1 Waveform"; osc1Waveform_prop->x = 30; osc1Waveform_prop->y = 75;
struct
is: {"Osc 1 Waveform", 30, 75}
struct
or change the order of the elements, problems will arise. It is better to use designated initializers , although you will have to type more. This monstrous phrase simply means that you can access the elements of a struct
using the syntax .membername=
. In the final form, this is a bit like JSON or hashes in Ruby : {.name="Osc 1 Waveform", .x=30, .y=75}
CreateParams
, CreateGraphics
and CreateGraphics
. Now the constructor looks very simple: SpaceBass::SpaceBass(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1) { TRACE; CreateParams(); CreateGraphics(); CreatePresets(); mMIDIReceiver.noteOn.Connect(this, &SpaceBass::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &SpaceBass::onNoteOff); mEnvelopeGenerator.beganEnvelopeCycle.Connect(this, &SpaceBass::onBeganEnvelopeCycle); mEnvelopeGenerator.finishedEnvelopeCycle.Connect(this, &SpaceBass::onFinishedEnvelopeCycle); }
GetParam()
and pGraphics
here, we took it all out.CreateParams
! void SpaceBass::CreateParams() { for (int i = 0; i < kNumParams; i++) { IParam* param = GetParam(i); const parameterProperties_struct& properties = parameterProperties[i]; switch (i) { // Enum Parameters: case mOsc1Waveform: case mOsc2Waveform: param->InitEnum(properties.name, Oscillator::OSCILLATOR_MODE_SAW, Oscillator::kNumOscillatorModes); // For VST3: param->SetDisplayText(0, properties.name); break; case mLFOWaveform: param->InitEnum(properties.name, Oscillator::OSCILLATOR_MODE_TRIANGLE, Oscillator::kNumOscillatorModes); // For VST3: param->SetDisplayText(0, properties.name); break; case mFilterMode: param->InitEnum(properties.name, Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); break; // Double Parameters: default: param->InitDouble(properties.name, properties.defaultVal, properties.minVal, properties.maxVal, parameterStep); break; } }
switch
initialize different enum
. For the LFO, the default waveform is triangular, simply because it is the one that is most often used. Please note that for all sixteen pens we use only one expression!SetShape
calls to the end of CreateParams
: GetParam(mFilterCutoff)->SetShape(2); GetParam(mVolumeEnvAttack)->SetShape(3); GetParam(mFilterEnvAttack)->SetShape(3); GetParam(mVolumeEnvDecay)->SetShape(3); GetParam(mFilterEnvDecay)->SetShape(3); GetParam(mVolumeEnvSustain)->SetShape(2); GetParam(mFilterEnvSustain)->SetShape(2); GetParam(mVolumeEnvRelease)->SetShape(3); GetParam(mFilterEnvRelease)->SetShape(3);
OnParamChange
, so that when you first call the plug-in, the internal variables have the correct values: for (int i = 0; i < kNumParams; i++) { OnParamChange(i); } }
CreateGraphics
. First, add a background image: void SpaceBass::CreateGraphics() { IGraphics* pGraphics = MakeGraphics(this, kWidth, kHeight); pGraphics->AttachBackground(BG_ID, BG_FN);
IBitmap whiteKeyImage = pGraphics->LoadIBitmap(WHITE_KEY_ID, WHITE_KEY_FN, 6); IBitmap blackKeyImage = pGraphics->LoadIBitmap(BLACK_KEY_ID, BLACK_KEY_FN); // C# D# F# G# A# int keyCoordinates[12] = { 0, 10, 17, 30, 35, 52, 61, 68, 79, 85, 97, 102 }; mVirtualKeyboard = new IKeyboardControl(this, kKeybX, kKeybY, virtualKeyboardMinimumNoteNumber, /* octaves: */ 4, &whiteKeyImage, &blackKeyImage, keyCoordinates); pGraphics->AttachControl(mVirtualKeyboard);
keyCoordinates
. New keys are wider, so you need to adjust the step between them so that keystrokes appear on adequate coordinates. IBitmap waveformBitmap = pGraphics->LoadIBitmap(WAVEFORM_ID, WAVEFORM_FN, 4); IBitmap filterModeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); IBitmap knobBitmap = pGraphics->LoadIBitmap(KNOB_ID, KNOB_FN, 64);
for (int i = 0; i < kNumParams; i++) { const parameterProperties_struct& properties = parameterProperties[i]; IControl* control; IBitmap* graphic; switch (i) { // Switches: case mOsc1Waveform: case mOsc2Waveform: case mLFOWaveform: graphic = &waveformBitmap; control = new ISwitchControl(this, properties.x, properties.y, i, graphic); break; case mFilterMode: graphic = &filterModeBitmap; control = new ISwitchControl(this, properties.x, properties.y, i, graphic); break; // Knobs: default: graphic = &knobBitmap; control = new IKnobMultiControl(this, properties.x, properties.y, i, graphic); break; } pGraphics->AttachControl(control); }
switch
for specific cases. Also, instead of waveform.png , filtermode.png is used for the mFilterMode parameter. Again, default is the code for the handle, because the handle is most often found among controls.AttachGraphics
function body with the AttachGraphics
call: AttachGraphics(pGraphics); }
switch
from OnParamChange()
in SpaceBass.cpp . We will rewrite it next time.Source: https://habr.com/ru/post/228267/
All Articles