IkeyboardControl
class that has all the necessary functionality for this task. // Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 // 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"
// GUI default dimensions #define GUI_WIDTH 434 #define GUI_HEIGHT 66
#include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN
public
section of the Synthesis.h file, add several members of the Synthesis class: public: // ... // Needed for the GUI keyboard: // Should return non-zero if one or more keys are playing. inline int GetNumKeys() const { return mMIDIReceiver.getNumKeys(); }; // Should return true if the specified key is playing. inline bool GetKeyStatus(int key) const { return mMIDIReceiver.getKeyStatus(key); }; static const int virtualKeyboardMinimumNoteNumber = 48; int lastVirtualKeyboardNoteNumber;
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1) { // ... }
getNumKeys
and getKeyStatus
to find out which keys are pressed. We have already implemented these functions in the MIDIReceiver
last time, so we go further. IControl* mVirtualKeyboard; void processVirtualKeyboard();
IControl
class is the base class for all GUI controls. We cannot declare an IkeyboardControl
object IkeyboardControl
, IkeyboardControl
it is “unknown” to the .h files. Therefore, we will have to use pointers. IKeyboardControl.h has comments that say: “this header should be added (#include) after your plugin’s class is declared, so it’s best to add it to the main plug-in .cpp file”.#include "IKeyboardControl.h"
before #include resource.h
. Synthesis::Synthesis(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1) { TRACE; 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, 7, 12, 20, 24, 36, 43, 48, 56, 60, 69, 72 }; mVirtualKeyboard = new IKeyboardControl(this, kKeybX, kKeybY, virtualKeyboardMinimumNoteNumber, /* octaves: */ 5, &whiteKeyImage, &blackKeyImage, keyCoordinates); pGraphics->AttachControl(mVirtualKeyboard); AttachGraphics(pGraphics); CreatePresets(); }
Ibitmap
objects. The third argument of the LoadIBitmap
( 6
) function tells the graphics system that there are six frames in whitekeys.png :By default, pRegularKeys should contain 6 images (C / F, D, E / B, G, A, upper C), while pSharpKey contains only 1 image (for all flat / sharps).- IKeyboardControl.h
keyCoordinates
array tells the system the offset of each key relative to the left border. This action needs to be done with only one octave, and IKeyboardControl
calculate the offsets for all other octaves.IKeyboardControl
new object and assign it the name mVirtualKeyboard
. We pass a lot of information:GetNumKeys
and GetKeyStatus
for this instance ( this
);IkeyboardControl
.new
, it means that you must write delete mVirtualKeyboard
in the destructor. But if we do this, and then remove the plugin from the track, the runtime exception will pop up. The reason is that when a call is made pGraphics->AttachControl(mVirtualKeyboard);
delete
we will try to detach an already free area of ​​memory.CreatePresets
function: void Synthesis::CreatePresets() { }
enum ELayout { kWidth = GUI_WIDTH, kHeight = GUI_HEIGHT, kKeybX = 1, kKeybY = 0 };
IKeyboardControl
does not redraw itself. This is a common practice in graphics programming: mark the GUI element as “dirty”, that is, its image will be updated only in the next cycle of redrawing. If you look at IKeyboardControl.h , in particular on OnMouseUp
and OnMouseUp
, you will see that mKey
assigned a value and that the SetDirty
function is SetDirty
(as opposed to Draw
). SetDirty
is a member function of the IControl
class (the implementation of which can be found in IControl.cpp , respectively). It sets the value of the mDirty
parameter to true
. Each redraw cycle the graphics system redraws all GUI elements whose mDirty
is true
. I went into such details, as it is important to understand this aspect of the graphics system.mMIDIReceiver
it receives data about mMIDIReceiver
pressed, but it must also receive external MIDI data. mVirtualKeyboard
and mMIDIReceiver
do not know anything about each other, so let's edit ProcessMidiMsg
in Synthesis.cpp : void Synthesis::ProcessMidiMsg(IMidiMsg* pMsg) { mMIDIReceiver.onMessageReceived(pMsg); mVirtualKeyboard->SetDirty(); }
mMIDIReceiver
updates the mLast...
members mLast...
according to the received MIDI data. Then mVirtualKeyboard
is marked as dirty. Then in the next redraw cycle, Draw
will be called for mVirtualKeyboard
, which, in turn, will call GetNumKeys
and GetKeyStatus
. At first, this may seem crappy, but in reality it is a transparent, well-structured design that allows you to avoid redundancy and unnecessary movements.mMIDIReceiver
.ProcessDoubleReplacing
call immediately before the for loop: processVirtualKeyboard();
void Synthesis::processVirtualKeyboard() { IKeyboardControl* virtualKeyboard = (IKeyboardControl*) mVirtualKeyboard; int virtualKeyboardNoteNumber = virtualKeyboard->GetKey() + virtualKeyboardMinimumNoteNumber; if(lastVirtualKeyboardNoteNumber >= virtualKeyboardMinimumNoteNumber && virtualKeyboardNoteNumber != lastVirtualKeyboardNoteNumber) { // The note number has changed from a valid key to something else (valid key or nothing). Release the valid key: IMidiMsg midiMessage; midiMessage.MakeNoteOffMsg(lastVirtualKeyboardNoteNumber, 0); mMIDIReceiver.onMessageReceived(&midiMessage); } if (virtualKeyboardNoteNumber >= virtualKeyboardMinimumNoteNumber && virtualKeyboardNoteNumber != lastVirtualKeyboardNoteNumber) { // A valid key is pressed that wasn't pressed the previous call. Send a "note on" message to the MIDI receiver: IMidiMsg midiMessage; midiMessage.MakeNoteOnMsg(virtualKeyboardNoteNumber, virtualKeyboard->GetVelocity(), 0); mMIDIReceiver.onMessageReceived(&midiMessage); } lastVirtualKeyboardNoteNumber = virtualKeyboardNoteNumber; }
GetKey
gives us the note number corresponding to the pressed key. IKeyboardControl
does not support multitouch, so only one key can be pressed at a time. The first if
releases the key, which is no longer pressed (if any). Since this function is called every mBlockSize
samples, the second if ensures that only one note on message is generated for this click (and not every mBlockSize
samples). We memorize the lastVirtualKeyboardNoteNumber
value to avoid these “repeated clicks” each time the function is called.Source: https://habr.com/ru/post/226823/
All Articles