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