Створення аудіоплагіна, частина 8

    Всі пости серії:
 Частина 1. Введення та налаштування
 Частина 2. Вивчення коду
 Частина 3. VST і AU
 Частина 4. Цифровий дисторшн
 Частина 5. Пресети і GUI
 Частина 6. Синтез сигналів
 Частина 7. Отримання MIDI повідомлень
 Частина 8. Віртуальна клавіатура
 

 
Налаштування віртуальної клавіатури в REAPER не так очевидна, до того ж хост користувача може взагалі не мати такої функціональності. Давайте додамо свою маленьку екранну клавіатуру в GUI.
 
 
 Елемент GUI
 
У WDL-OL елементи GUI називаються controls . У цій бібліотеці є клас
IkeyboardControl
, що володіє всією необхідною функціональністю для цього завдання.
Він використовує одну фонову картинку і два додаткових спрайта: в одному зображення натиснутою чорної клавіші, в іншому кілька натиснутих білих. Логічно: всі чорні клавіші мають однакову форму, тоді як білі бувають різними. При натисканні на клавіші ці спрайт будуть відображатися поверх фонового зображення клавіатури, яке буде видно завжди.
Якщо ви хочете намалювати свої чудові кастомниє клавіші, пробіжіться по цьому керівництву . Ну а ті, що йдуть з бібліотекою, виглядають так:
 
 
 
 
 
 
 
Скачайте ці файли і закиньте в папку проекту / resources / img / . Якщо користуєтеся Xcode, перетяните їх у вікно для додавання в проект. Як звичайно, робота з графікою починається з додавання імен файлів в resource.h . Заодно, поки ви там, видаліть посилання на knob.png і background.png , і видаліть самі файли з проекту.
 
 
// 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

 
На Windows для включення . Png файлів в збірку також необхідно відредагувати заголовок Synthesis.rc :
 
 
#include "resource.h"

BG_ID       PNG BG_FN
WHITE_KEY_ID       PNG WHITE_KEY_FN
BLACK_KEY_ID       PNG BLACK_KEY_FN

 
Тепер в секції
public
файлу Synthesis.h додамо кілька членів класу Synthesis :
 
 
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.cpp нужно добавить lastVirtualKeyboardNoteNumber:
Synthesis::Synthesis(IPlugInstanceInfo instanceInfo)
    :   IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo),
    lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1) {
    // ...
}

 
Коли джерелом програються нот є хост, вони повинні відображатися як натиснуті на клавіатурі плагіна. Клавіатура викликатиме
getNumKeys
і
getKeyStatus
, щоб дізнатися, які клавіші натискати. Ми вже імплементували ці функції в
MIDIReceiver
в минулий раз, так що їдемо далі.
 
У секцію private теж треба додати пару рядків:
 
 
IControl* mVirtualKeyboard;
void processVirtualKeyboard();

 
Клас
IControl
є базовим для всіх елементів управління GUI. Ми не можемо оголосити тут об'єкт
IkeyboardControl
, т. к. він «не відомий» . H файлам. Тому нам доведеться використовувати покажчики. У IKeyboardControl.h є коменти, в яких сказано: «слід додавати (# include) цей хедер після оголошення класу вашого плагіна, так що краще всього додати його в головний. Cpp файл плагіна».
Щоб прояснити ситуацію, давайте подивимося на Synthesis.cpp . Додайте
#include "IKeyboardControl.h"
перед
#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
. Третій аргумент функції
LoadIBitmap
(
6
) повідомляє графічній системі що в whitekeys.png міститься шість кадрів:
 
 
За замовчуванням pRegularKeys повинен містити 6 зображень (C / F, D, E / B, G, A, верхня C), в той час як pSharpKey містить тільки 1 картинку (для всіх бемолів / діезів).
— IKeyboardControl.h
 
Масив
keyCoordinates
повідомляє систему зміщення кожної клавіші щодо лівої межі. Ця дія потрібно проробити тільки з однією октавою, а
IkeyboardControl
вирахує зміщення для всіх інших октав.
У наступному рядку ми, грубо кажучи, инициализируем новий об'єкт new
IkeyboardControl
і призначаємо йому ім'я
mVirtualKeyboard
. Ми передаємо масу інформації:
 
     
Покажчик на екземпляр плагіна. Це приклад шаблону делегування : віртуальна клавіатура викликатиме
getNumKeys
і
getKeyStatus
для цього примірника (
this
);
 Координати клавіатури в GUI;
 Номер найнижчою ноти. При кліці на крайню зліва клавішу саме ця нота програватиметься;
 Кількість октав;
 Посилання на картинки натиснутих клавіш;
 Відносну координату X всередині однієї октави;
 
Цікаво, що об'єкт віртуальної клавіатури навіть не знає про існування файлу bg.png . Він йому просто не потрібен, все і так буде працювати. Це плюс, так як зображення клавіатури може бути частиною фонової картинки, і тоді довелося б вирізати цей шматок тільки щоб передати його конструктору
IkeyboardControl
.
 
Якщо у вас є досвід програмування в C + +, то повинен виникнути умовний рефлекс: конструктор містить
new
, значить в деструкції необхідно написати
delete mVirtualKeyboard
. Але якщо ми це зробимо, а потім видалимо плагін з треку, то вискочить виняток runtime exception . Причина в тому, що коли здійснюється виклик
 
 
pGraphics->AttachControl(mVirtualKeyboard);

ми передаємо управління пам'яттю графічній системі, і керування цією областю пам'яті — більше не наш обов'язок. Використовуючи
delete
ми будемо намагатися відкріпити вже вільну область пам'яті.
 
Тепер видаліть тіло функції
CreatePresets
:
 
 
void Synthesis::CreatePresets() {
}
И добавьте kKeybX и kKeybY в ELayout: 
enum ELayout
{
    kWidth = GUI_WIDTH,
    kHeight = GUI_HEIGHT,
    kKeybX = 1,
    kKeybY = 0
};

 
З міркувань продуктивності
IkeyboardControl
НЕ перемальовує сам себе. Це часта практика в програмуванні графіки: помітити елемент GUI як «брудний», тобто Його зображення буде оновлено тільки в наступному циклі перемальовування. Якщо ви поглянете на IKeyboardControl.h , зокрема на
OnMouseDown
і
OnMouseUp
, ви побачите, що
mKey
присвоєно деяке значення і що викликається функція
SetDirty
(на противагу
Draw
).
SetDirty
це функція член класу
IControl
(імплементацію якого можна знайти в IControl.cpp , відповідно). Вона встановлює значення параметра
mDirty
даного елемента управління рівним
true
. Кожен цикл перемальовування графічна система перемальовує всі елементи GUI, чий
mDirty
дорівнює
true
. Я заглибився в такі деталі, так як важливо розуміти цей аспект роботи графічної системи.
 
 Реакція на зовнішні MIDI повідомлення
 
Поки що клавіатура стає «брудної», коли на неї натискають. Від
mMIDIReceiver
вона отримує дані про натиснутих клавішах, але вона також повинна отримувати й зовнішні MIDI дані.
mVirtualKeyboard
і
mMIDIReceiver
нічого один про одного не відомо, так що давайте в Synthesis.cpp відредагуємо
ProcessMidiMsg
:
 
 
void Synthesis::ProcessMidiMsg(IMidiMsg* pMsg) {
    mMIDIReceiver.onMessageReceived(pMsg);
    mVirtualKeyboard->SetDirty();
}

 
Спочатку
mMIDIReceiver
оновлює члени
mLast...
відповідно до отриманих MIDI даними. Потім
mVirtualKeyboard
позначається як «брудний». Тоді в наступному циклі перемальовування буде викликана
Draw
для
mVirtualKeyboard
, який, у свою чергу, викличе
getNumKeys
і
getKeyStatus
. Спочатку це може здатися хитромудрою, але насправді це прозорий чітко структурований дизайн, що дозволяє уникнути надмірності і зайвих рухів.
Наша віртуальна клавіатура тепер реагує на зовнішні MIDI повідомлення і правильно малює натиснуті клавіші.
 
 Реакція на натискання на віртуальній клавіатурі
 
Залишилося змусити клавіатуру реагувати на натискання по вбудованої в хост віртуальній клавіатурі, генерувати MIDI повідомлення і посилати їх одержувачу
mMIDIReceiver
.
Додайте цей виклик
ProcessDoubleReplacing
безпосередньо перед циклом
for
:
 
 
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
дає нам номер ноти, відповідний натиснутій клавіші.
IkeyboardControl
не підтримує мультитач, так що тільки одна клавіша може бути натиснута за раз. Перший
if
відпускає клавішу, на яку більше не тиснуть (якщо така є). Так як ця функція викликається кожні
mBlockSize
семплів, другий if гарантує, що буде згенеровано тільки одне повідомлення note on для цього кліка (а не через кожні
mBlockSize
семплів). Ми запам'ятовуємо значення
lastVirtualKeyboardNoteNumber
щоб уникнути цих «повітряних натискань» при кожному виклику функції.
 
 Поїхали!
 
Ми знову готові запустити наш сінтюк! Якщо все зробили правильно, можна грати на його клавіатурі. А використання віртуальної клавіатури хоста або будь-якого іншого підключеного джерела MIDI повинно відображатися на клавіатурі плагіна (по черзі, показуючи останню натиснуту клавішу). Правда, звук теж буде відповідати тільки цієї однієї останньої клавіші. Ми розберемося з поліфонією трохи пізніше.
Можете поки похвалитися перед друзями і зіграти свого улюбленого Бетховена на класичному синтезаторного звуці. Тільки ось звук якийсь «дерев'яний», і чути клацання, коли натискаєш і відпускаєш клавішу. Особливо це помітно, якщо генерується синус. Значить, треба додати огинають. Зробимо це в наступному пості.
 
Файли проекту на даному етапі можна скачати звідси .
    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.