Розробка додатку MIDI синтезатора для Android з використанням Kotlin та C++

Як створити Android-додаток за допомогою Kotlin і C++, який відповідає на MIDI-команди та відтворює синтезовані звуки з файлів SoundFont

Спочатку опубліковано португальською на Embarcados.

pic

Зображення, згенероване за допомогою штучного інтелекту

Вступ

Міжнародний цифровий інтерфейс музичних інструментів (MIDI) — це широко поширений протокол у музичній сфері, який полегшує комунікацію між електронними інструментами, комп'ютерами та іншими сумісними пристроями. За допомогою цифрових повідомлень MIDI дозволяє передавати команди, такі як музичні ноти, зміни швидкості та керування параметрами, що забезпечує надійну інтеграцію музичного обладнання. В рамках контексту MIDI важливу роль відіграють контролери та синтезатори. MIDI-контролер — це пристрій, який генерує MIDI-команди, але не має можливості створювати звук самостійно. Натомість синтезатори відповідають за інтерпретацію цих MIDI-повідомлень і генерують відповідні звуки, як через спеціалізоване апаратне забезпечення, так і через програмне.

Завдяки виникненню програмних синтезаторів музична сцена зазнала революції, пропонуючи більшу доступність і гнучкість. Серед популярних рішень для програмного синтезу виділяється FluidSynth — бібліотека з відкритим кодом, здатна інтерпретувати файли SoundFont, які є колекціями аудіо зразків, організованих для відтворення звуків музичних інструментів.

Реалізація Android-додатку, що інтегрує ці аудіо технології, може надати значну практичність для кінцевих користувачів, адже смартфони та планшети є легкодоступними пристроями. Однак для розробників це вимагає розуміння архітектури Android. Додатки Android в основному працюють на Java Virtual Machine (JVM), що дозволяє використовувати мови, такі як Java та Kotlin. Хоча Java є зрілою та широко використовуваною мовою, Kotlin здобув популярність завдяки своїй компактній синтаксису, безпеці від помилок null та безперешкодній інтеграції з екосистемою Android. Проте додатки, що вимагають високої продуктивності, такі як обробка аудіо в реальному часі, потребують використання рідних компонентів, реалізованих на C або C++. Java Native Interface (JNI) дозволяє розробникам інтегрувати ці мови в середовище Android, поєднуючи ефективність рідного коду з гнучкістю розробки на основі JVM.

Ця стаття представляє, окрім необхідних концептуальних основ, практичний посібник із реалізації MIDI-синтезатора для Android. Запропонований проект інтегрує Kotlin, C і C++ для створення функціонального рішення. Додаток може отримувати MIDI-команди від контролера, підключеного через USB-OTG-кабель, і відтворювати звуки на основі SoundFont, використовуючи бібліотеку FluidSynth. За допомогою цієї реалізації будуть розглянуті теоретичні концепції, практики розробки та технічні труднощі, притаманні такому типу додатків.

Приклад проекту додатку доступний на GitHub під ліцензією MIT за наступним посиланням: https://github.com/robsonsmartins/android-midi-synth.

MIDI (Міжнародний цифровий інтерфейс музичних інструментів)

MIDI (Musical Instrument Digital Interface) — це стандартний цифровий комунікаційний протокол, встановлений у 1983 році для з'єднання електронних інструментів, комп'ютерів та інших пристроїв. На відміну від аудіофайлів, MIDI не несе сам звук, а лише цифрові інструкції, які визначають, як має бути створений звук. Протокол MIDI є важливим у музичній сфері, оскільки дозволяє різним пристроям взаємодіяти стандартним чином.
Наприклад, клавішник може використовувати MIDI-контролер для виклику звуків, збережених у синтезаторі, навіть якщо пристрої походять від різних виробників.

MIDI працює через цифрові повідомлення, що передаються від пристрою-відправника (контролера) до пристрою-приймача (синтезатора). Ці повідомлення можуть передавати таку інформацію, як:

  • Нота, яку потрібно відтворити (наприклад, C, D, E);
  • Інтенсивність ноти (швидкість);
  • Початок і кінець відтворення ноти;
  • Модифікації параметрів, таких як тембр, гучність або ефекти.

MIDI-контролер — це пристрій, який відправляє MIDI-команди, але не виробляє звук безпосередньо. Прикладом можуть бути клавіатурні контролери, пади та контрольні панелі. Натомість MIDI-синтезатор — це пристрій, який інтерпретує MIDI-повідомлення та генерує відповідні звуки. Синтезатори можуть бути фізичними інструментами (апаратне забезпечення) або комп'ютерними програмами (програмне забезпечення).

pic

Рисунок 1 — MIDI-контролер і синтезатор — Джерело: https://nektartech.com/article/blog/all-you-need-to-know-about-midi/

MIDI-повідомлення — це пакети даних, що містять інформацію про музичні події. Вони поділяються на два основні типи:

  • Канальні повідомлення: Пов'язані з відтворенням нот і керуванням параметрами.
  • Системні повідомлення: Використовуються для синхронізації та глобальних налаштувань.

pic

Таблиця 1 — Основні MIDI канальні повідомлення — Джерело: https://midi.org/summary-of-midi-1-0-messages

pic

Таблиця 2 — Основні MIDI системні повідомлення — Джерело: https://midi.org/summary-of-midi-1-0-messages

Більшість пристроїв, доступних сьогодні, сумісні з протоколом MIDI 1.0, як описано вище. Проте в 2020 році MIDI Manufacturers Association (MMA) офіційно представила нову версію оригінального протоколу, відому як MIDI 2.0. MIDI 2.0 зберігає зворотну сумісність з MIDI 1.0, але пропонує численні вдосконалення та нові функції, спрямовані на розширення творчих і технічних можливостей для музикантів і розробників. Серед найбільш очікуваних нововведень:

  • Збільшена роздільна здатність: MIDI 2.0 забезпечує значно вищу роздільну здатність для команд керування, що дозволяє точніше налаштовувати параметри, такі як бенди висоти тону, гучність та інші безперервні керування.
  • Двостороння комунікація: Пристрої тепер можуть комунікувати більш інтелектуально, автоматично узгоджуючи свої можливості та налаштовуючи параметри без необхідності ручного втручання користувача.
  • Розширена виразність: Завдяки рідній підтримці MIDI Polyphonic Expression (MPE), MIDI 2.0 дозволяє кожній ноті мати окремі керування для таких параметрів, як вібрато або aftertouch, що забезпечує більшу виразність.
  • Протокол на основі JSON: Окрім традиційного формату повідомлень, MIDI 2.0 включає схему конфігурації на основі JSON (JavaScript Object Notation), що спрощує впровадження динамічних і сучасних конфігурацій для розробників.
  • Масштабованість: Новий протокол розроблений так, щоб бути гнучким, дозволяючи розширення в майбутньому без компромісів щодо сумісності з поточним стандартом.

MIDI 2.0 є важливим кроком уперед у розвитку музичних технологій, надаючи музикантам і продюсерам потужніші інструменти для створення та управління цифровою музикою. Хоча його впровадження триває, вже є сумісні пристрої та програмне забезпечення, які обіцяють революціонізувати робочі процеси в музиці.

MIDI-з'єднання

На даний момент існують два типи з'єднань для обладнання, сумісного з протоколом MIDI. Перший і більш традиційний варіант включає з'єднувачі DIN-5, які зазвичай називають MIDI-інтерфейсом.
Цей тип з'єднання забезпечує асинхронну серійну комунікацію з електричною ізоляцією (через оптокоплер) між пристроями.

pic

Рисунок 2 — MIDI Інтерфейс з'єднувачів (DIN-5) — Джерело: https://nektartech.com/article/blog/all-you-need-to-know-about-midi/

Однак більш сучасне обладнання використовує роз'єми Universal Serial Bus (USB) типу B (USB-B) та реалізує протокол USB-MIDI класу. Цей тип з'єднання пропонує кращу сумісність з комп'ютерами. Для підключення обладнання до комп'ютера достатньо звичайного кабелю USB-A до USB-B.

pic

Рисунок 3 — Роз'єм USB-B — Джерело: https://www.mozartproject.org/what-is-the-usb-port-in-piano-keyboard-for/

Для підключення більш традиційного обладнання, оснащеного стандартними роз'ємами MIDI DIN-5, до комп'ютерів або інших пристроїв, сумісних з USB, на ринку доступні адаптерні кабелі, відомі як "MIDI кабелі". Ці адаптери мають різні формати, якості та цінові категорії. Одна частина цих кабелів оснащена роз'ємом USB-A, сумісним з комп'ютерами, а інша частина має роз'єми DIN-5, які відповідають стандарту MIDI для музичних інструментів.

pic

Рисунок 4 — MIDI до USB адаптерний кабель — Джерело: https://cscltf.en.made-in-china.com/product/WFiTLItluAGm/China-USB-MIDI-Cable-Converter-to-PC-Music-Studio-Adapter-2-in-1-for-Piano-Keyboard-Interface-Wire-Plug-High-Quality-Music-Editing-Cord-Cable.html

Якщо мета полягає в підключенні MIDI-пристрою з USB-роз'ємом до смартфона або планшета, оснащеного портом USB-C (як у випадку з пристроями на Android), необхідно використовувати кабель або адаптер, відомий як USB-OTG (On-The-Go).

pic

Рисунок 5 — Адаптер USB-OTG — Джерело: https://www.samsung.com/au/support/mobile-devices/what-is-an-otg-on-the-go-cable

Адаптер OTG (також відомий як OTG кабель або OTG роз'єм) дозволяє підключати стандартний USB-пристрій або кабель USB-A до смартфона або планшета через порт USB-C. Це забезпечує передавання даних та підключення до різних периферійних пристроїв, ефективно перетворюючи смартфон або планшет на хост-пристрій, тобто він функціонує так, як якщо б це був комп'ютер.

Програмні синтезатори та SoundFonts

Програмні синтезатори

Програмні синтезатори є основними інструментами в сучасному музичному виробництві. На відміну від апаратних синтезаторів, які є фізичними пристроями, призначеними для генерації звуків, програмні синтезатори — це програми, що симулюють або створюють звуки цифровим способом. Вони пропонують широкий спектр творчих можливостей, від емуляцій традиційних інструментів до створення абсолютно нових звуків. Серед їхніх основних переваг:

  • Доступність: Зазвичай вони є більш економічними, ніж їхні апаратні аналоги.
  • Гнучкість: Дозволяють детально налаштовувати і персоналізувати звуки.
  • Інтеграція з DAW: Можуть безпосередньо використовуватися в цифрових аудіо робочих станціях (Digital Audio Workstation, DAW), таких як Ableton Live, Logic Pro та FL Studio, серед інших.
  • Портативність: Працюють на комп'ютерах або мобільних пристроях, що виключає необхідність транспортувати додаткове обладнання.

Ці синтезатори часто використовують SoundFonts, що є файлами, що містять аудіозразки та інформацію про те, як ці зразки мають відтворюватися для створення реалістичних або абстрактних звуків. Найбільш поширеними форматами є SF2 і SF3, кожен з яких має свої особливості.

Існує кілька популярних програмних синтезаторів, які задовольняють специфічні потреби музичних або розробницьких проєктів. Деякі з найбільш відомих:

  • Kontakt: Потужний семплер, широко використовуваний в індустрії, розроблений компанією Native Instruments.
    Він підтримує величезну бібліотеку віртуальних інструментів, які зазвичай називаються VST (Virtual Studio Technology).
  • Serum: Синтезатор, орієнтований на електронну музику, відомий своєю інтуїтивно зрозумілою інтерфейсом та здатністю створювати складні звуки.
  • Dexed: Безкоштовний синтезатор, заснований на Yamaha DX7, ідеально підходить для відтворення класичних звуків FM (Frequency Modulation) синтезу.
  • ZynAddSubFX: Відкритий синтезатор, що пропонує кілька технік синтезу і широкий спектр звукових можливостей.

Кожен з цих синтезаторів має свої сильні сторони та спеціалізації, що робить їх підходящими для різних типів користувачів і застосувань.

FluidSynth

FluidSynth є широко використовуваною програмною бібліотекою для реального часу синтезу аудіо. Розроблений мовою програмування C, це проєкт з відкритим вихідним кодом, ліцензований під LGPL (GNU Lesser General Public License), версія 2.1. Ця ліцензія дозволяє вбудовувати бібліотеку як в комерційні, так і в некомерційні застосунки. Однак, якщо в межах застосунку вносяться зміни в саму бібліотеку FluidSynth, вихідний код повинен бути зроблений загальнодоступним відповідно до умов LGPL.

FluidSynth призначений для відтворення звуків з SoundFonts у форматах SF2 та SF3, забезпечуючи високу якість звуку та низьку затримку. Його можна використовувати як самостійну програму або інтегрувати в інші програми як бібліотеку. Його гнучкість та продуктивність роблять його популярним вибором серед розробників аудіо та музикантів. Основні характеристики включають:

  • Синтез в реальному часі: Можливість обробки та відтворення аудіо з низькою затримкою.
  • Сумісність з SoundFont: Підтримує широко використовувані формати SF2 та SF3 у музичному виробництві.
  • Міцний API: Забезпечує детальне управління над створеними звуками.
  • Відкритий код: Код доступний публічно, що дозволяє модифікації та адаптації.
  • Портативність: Сумісність з різними платформами, включаючи Linux, Windows та Android. Може бути інтегрований як бібліотека в застосунки, написані на різних мовах програмування, таких як C/C++, Python, Java/Kotlin.

Нижче наведено простий приклад на C, що демонструє, як використовувати API бібліотеки FluidSynth для відтворення музичної ноти:

#include   

int main() {  
 // Ініціалізація FluidSynth  
 fluid_settings_t* settings = new_fluid_settings();  
 fluid_synth_t* synth = new_fluid_synth(settings);  
 // Завантаження SoundFont  
 if (fluid_synth_sfload(synth, "soundfont.sf2", 1) == FLUID_FAILED) {  
 printf("Помилка завантаження SoundFont.\n");  
 return 1;  
 }  
 // Ініціалізація аудіо драйвера  
 fluid_audio_driver_t* adriver = new_fluid_audio_driver(settings, synth);  
 // Відтворення ноти (канал: 0, нота: C4, швидкість: 100)  
 fluid_synth_noteon(synth, 0, 60, 100);  
 // Зачекайте 1 секунду  
 sleep(1);  
 // Зупинка ноти  
 fluid_synth_noteoff(synth, 0, 60);  
 // Очищення ресурсів  
 delete_fluid_audio_driver(adriver);  
 delete_fluid_synth(synth);  
 delete_fluid_settings(settings);  
 // Вихід  
 return 0;  
}

Цей приклад ілюструє наступні основні кроки:

  1. Створення конфігурації та ініціалізація синтезатора.
  2. Завантаження файлу SoundFont.
  3. Відтворення та зупинка музичної ноти.
  4. Очищення ресурсів.

Завдяки своїй універсальності та широкій підтримці, FluidSynth є відмінною бібліотекою для розробників у музичній сфері. Його можливість інтегруватися в застосунки, такі як MIDI-синтезатори, дозволяє створювати багаті та чуйні музичні досвіди на різних платформах.

SoundFonts

Файли SoundFont грають важливу роль у цифровому синтезі аудіо, служачи колекцією звукових зразків та інформацією про те, як ці зразки мають відтворюватися.
Спочатку представлені компанією Creative Labs у 1990-х роках, SoundFont були розроблені для використання у звукових картах, таких як Sound Blaster AWE32, що дозволяло музикантам і розробникам включати реалістичні звуки у свої композиції та застосунки.

SoundFont еволюціонували з часом, виникли різні формати, кожен з яких має свої переваги та недоліки:

SF2 (SoundFont 2.0)

  • Опис: Найбільш підтримуваний і поширений формат.
  • Структура: Організовано так, щоб включати кілька інструментів і детальні налаштування.
  • Аудіоформат: Зберігає звукові зразки у форматі PCM (Pulse Code Modulation), без стиснення.
  • Переваги: Сумісність з широким спектром програмного та апаратного синтезаторів.
  • Недоліки: Великі розміри файлів, що робить його менш ефективним для великих проєктів.

SF3 (SoundFont 3.0)

  • Опис: Розширення формату SF2, з підтримкою стиснення зразків.
  • Аудіо стиснення: Використовує OGG Vorbis, формат стиснення, що втратює якість, але зберігає хорошу якість звуку при меншому розмірі файлу.
  • Переваги:
    Зменшений розмір файлу: Стиснуті зразки займають значно менше місця, ніж SF2. Ідеально підходить для мобільних застосунків або середовищ з обмеженим сховищем.
    Налаштовувана якість: Ступінь стиснення можна налаштувати для балансування якості звуку та розміру файлу.
  • Недоліки:
    Втрата якості: Як формат зі стисненням з втратами, може спостерігатись незначна деградація якості звуку, особливо при високих налаштуваннях стиснення.
    Обмежена сумісність: Не всі синтезатори підтримують SF3, тому перед використанням потрібно перевірити сумісність.

SFZ

  • Опис: Текстовий формат, що визначає звукові зразки та їх конфігурацію.
  • Переваги:
    Гнучкість налаштування.
    Підтримка високоякісних зразків і детальних налаштувань.
  • Недоліки:
    Більша складність: Потрібні спеціалізовані редактори для ручного налаштування.
    Обмежена підтримка синтезаторами.

Файл SoundFont може містити різні інструменти та банки звуків, що дозволяє організувати різні тембри та інструменти в одному файлі, спрощуючи вибір звуку для синтезаторів або музичних застосунків.

Кожен інструмент у SoundFont створюється з одного або кількох звукових зразків (записаного аудіо), поєднаних з конфігураційними даними, такими як:

  • Регіони нот: Вказує, які ноти використовують кожен зразок.
  • Амплітудні огранки: Визначає, як звук розвивається з часом.
  • LFO (Low-Frequency Oscillators): Додає ефекти, такі як вібрато.

Інструменти для створення та редагування SoundFont

SoundFont можна створювати або редагувати за допомогою спеціальних інструментів, серед яких:

  • Polyphone: Безкоштовний і інтуїтивно зрозумілий редактор для створення та модифікації файлів SF2 та SF3.
  • Vienna SoundFont Studio: Офіційний пропрієтарний інструмент від Creative Labs для редагування SoundFont.
  • Awave Studio: Пропрієтарний інструмент, що підтримує кілька аудіоформатів, включаючи SoundFont.

pic

Figure 6 — Інтерфейс Polyphone — Джерело: https://www.polyphone.io/en/documentation/manual/index

Ці програми дозволяють користувачам записувати власні зразки або налаштовувати параметри інструментів, створюючи індивідуальні файли для використання в синтезаторах або музичних застосунках.
Крім того, в інтернеті доступні кілька публічних репозиторіїв, що надають файли SoundFont для завантаження, деякі з яких мають ліцензії, що дозволяють безкоштовне використання як у комерційних, так і в некомерційних застосунках. Одним з таких репозиторіїв є Polyphone SoundFonts.

Операційна система Android та її застосунки

Операційна система Android, розроблена компанією Google, широко використовується на мобільних пристроях, таких як смартфони та планшети. Її архітектура розроблена для забезпечення гнучкості, продуктивності та безпеки, поєднуючи кілька рівнів, які ефективно взаємодіють.
Ці шари включають ядро, рідне середовище виконання та Java Virtual Machine (JVM).

Шарова архітектура Android спеціально розроблена для покращення безпеки системи. Кожен застосунок працює в ізольованому середовищі, відомому як пісочниця (sandbox), що запобігає шкідливим застосункам впливати на інші процеси або отримувати доступ до даних без дозволу. Ці шари працюють разом, щоб захистити як пристрій, так і дані користувача.

pic

Figure 7 — Шари системи Android — Джерело: https://developer.android.com/guide/platform

Ядро

Ядро виступає як бар'єр між апаратним забезпеченням і програмним забезпеченням, забезпечуючи взаємодію з системними ресурсами лише авторизованого коду. Android використовує ядро Linux як основу своєї операційної системи, яке надає такі основні функції:

  • Управління пам'яттю: Сприяє ефективному використанню обмежених ресурсів мобільних пристроїв.
  • Управління процесами: Координує виконання кількох застосунків безпечно та ефективно.
  • Драйвери апаратного забезпечення: Дозволяють пряме спілкування з фізичними компонентами, такими як камери, датчики та аудіоінтерфейси.
  • Безпека: Реалізує механізми, як-от SELinux, що покращують контроль доступу і запобігають атакам зловмисного ПЗ.

Розробка програмного забезпечення на рівні ядра операційної системи Android рідко зустрічається, якщо не розробляються апаратні компоненти для смартфонів.

Android Runtime (ART)

В Android застосунки зазвичай пишуться на Java та/або Kotlin, які компілюються в байт-код і виконуються на віртуальній машині (Java Virtual Machine — JVM). Спочатку Android використовував Dalvik VM, але з Android 5.0 ART (Android Runtime) став стандартною віртуальною машиною. Виконання застосунків у віртуальній машині додає абстракційний рівень, що ускладнює прямі атаки на код застосунків.

Порівняно з Dalvik, ART пропонує значні покращення, зокрема:

  • Ahead-of-Time (AOT) компіляція: Компільує застосунки до виконання, зменшуючи час запуску.
  • Ефективність використання пам'яті: Покращене управління ресурсами.
  • Швидше виконання: Краща продуктивність порівняно з Dalvik.

Як Java, так і Kotlin можна використовувати для написання застосунків Android, причому їхні основні відмінності:

Java:

  • Історія та популярність: Java була оригінальною мовою для розробки Android-застосунків ще з 1995 року. Вона досі широко використовується на різних платформах з величезними спадковими кодовими базами, що робить її актуальною для підтримки та розширення існуючих Android проєктів.
  • Синтаксис та багатослівність: Java має більш багатослівний синтаксис, що часто потребує більше коду для виконання звичних завдань, що може сповільнювати розробку складних проєктів.
  • Безпека від нулів: В Java немає вбудованої безпеки від нульових значень, що вимагає ручного оброблення винятків з null-покажчиками, збільшуючи ризик помилок, пов'язаних з нулями.
  • Сумісність: Як оригінальна мова Android, Java повністю сумісна з усіма версіями Android і легко інтегрується в будь-який Android проєкт.
  • Сучасні можливості: Хоча вона підтримує такі функції, як лямбда-вирази та функціональне програмування, ці можливості були введені пізніше і в обмеженій формі, тому Java є менш сучасною порівняно з новішими мовами.
  • Інструменти та підтримка: Потужні інструменти з повною інтеграцією в Android Studio і великою документацією.
    Її велика розробницька спільнота пропонує безліч ресурсів.
  • Продуктивність: Застосунки на Java стабільно працюють на ART, демонструючи добре відому стабільність і ефективність.
  • Офіційна підтримка: Хоча Java і досі підтримується Google, вона більше не є мовою, рекомендованою для нових Android проєктів, здебільшого використовуються для спадкових застосунків.

Kotlin:

  • Історія та популярність: Введена у 2011 році компанією JetBrains і офіційно визнана для Android у 2017 році, Kotlin швидко здобула популярність для нових проєктів, ставши сучасною мовою для розробки Android застосунків.
  • Синтаксис та лаконічність: Розроблена для скорочення коду і сучасності, вимагає менше коду для виконання звичних завдань, що покращує продуктивність і зменшує кількість помилок.
  • Безпека від null: Включає вбудовану безпеку від null, примушуючи до явного оброблення випадків з null, що зменшує кількість помилок, пов'язаних з null.
  • Сумісність: Повністю сумісна з Java, дозволяючи їй співіснувати в одному проєкті, полегшуючи перехід від спадкового коду на Java.
  • Сучасні функції: Пропонує розширені функції, як-от розширення функцій, корутини для асинхронних операцій і класи даних, що дозволяє більш ефективно і сучасно розробляти застосунки.
  • Інструменти та підтримка: Повністю інтегрована в Android Studio, підтримується компанією JetBrains і Google. Зростаюча спільнота розробників забезпечує широке покриття документацією та ресурсами.
  • Продуктивність: Застосунки на Kotlin працюють так само ефективно, як і на Java, обидва виконуються на ART, а лаконічний синтаксис зменшує потенційні проблеми під час розробки.
  • Офіційна підтримка: Google офіційно рекомендує Kotlin для нових Android проєктів, підкреслюючи його відповідність сучасним практикам розробки.

Java залишається придатною для спадкових проєктів і розробників, знайомих з нею, але Kotlin випереджає Java за продуктивністю, безпекою та сучасними функціями, що робить її мовою, яку вибирають для нових розробок Android.

Рідне середовище виконання

Хоча більшість Android застосунків розробляються за допомогою JVM мов, таких як Java та/або Kotlin, Android також підтримує рідне виконання. Код, написаний мовами, такими як C та/або C++, може виконуватись безпосередньо, використовуючи рідні бібліотеки та ресурси системи. Цей рівень в основному використовується для:

  • Завдань, що вимагають високої продуктивності: Як-от ігри чи обробка реального часу аудіо.
  • Інтероперабельність з апаратним забезпеченням: Пряма взаємодія з апаратними компонентами, такими як камери або підключені пристрої.

Для інтеграції рідного коду з Java та/або Kotlin застосунками використовується Java Native Interface (JNI). JNI служить мостом, що дозволяє здійснювати комунікацію між рідним кодом і JVM, забезпечуючи безшовну взаємодію між шарами виконання.

Щодо мов C та/або C++, обидві можуть використовуватися для написання рідного коду на Android. Основні відмінності між ними:

C:

  • Парадигма програмування: C — це процедурна мова, що зосереджена на викликах функцій і структурах управління. Код організований у процедури або функції, які працюють з даними.
  • Типізація та безпека типів: C надає гнучку типізацію, що може призвести до проблем з безпекою типів, таких як помилки неявного перетворення типів.
  • Управління пам'яттю: Використовує malloc() та free() для динамічного виділення та звільнення пам'яті. Ручне управління збільшує ризик витоків пам'яті та звислих вказівників.
  • Стандартні бібліотеки: Стандартна бібліотека C обмежена, пропонуючи базовий функціонал для роботи зі строками, введенням/виведенням та управлінням пам'яттю.
  • Підтримка класів та об'єктів: Відсутня вбудована підтримка об'єктно-орієнтованого програмування (OOP).
    Моделювання ООП часто призводить до складного і менш читабельного коду (наприклад, використання структур і вказівників).
  • Структури управління та дані: Забезпечує базові структури управління, як-от if, for, while, та структури даних, як-от масиви та структури, без інкапсуляції чи наслідування.
  • Сумісність: Повністю сумісний з JNI для взаємодії з Android застосунками, написаними на Java та/або Kotlin.

C++:

  • Парадигма програмування: C++ — це мова з мультипарадигмами, переважно відома своїми можливостями для об'єктно-орієнтованого програмування (ООП). Вона полегшує моделювання задач за допомогою колекцій взаємодіючих об'єктів, що спрощує управління складними системами.
  • Типізація та безпека типів: Вводить надійну і безпечну типізацію за допомогою шаблонів та перевантаження операторів/функцій, що покращує гнучкість і безпеку типів.
  • Управління пам'яттю: Має оператори new та delete для управління пам'яттю, а також конструктори і деструктори, які покращують управління ресурсами та безпеку пам'яті.
  • Стандартні бібліотеки: Пропонує багатшу стандартну бібліотеку (STL), яка підтримує узагальнені колекції, алгоритми та утиліти для ООП.
  • Підтримка класів та об'єктів: Підтримує інкапсуляцію, наслідування та поліморфізм, що дозволяє ефективно моделювати та повторно використовувати код.
  • Структури управління та дані: Розвиває структури C з додаванням можливостей ООП і додає обробку винятків (try-catch), спрощуючи управління помилками.
  • Сумісність: Повністю сумісний з JNI, використовуючи extern "C", і може безперешкодно інтегруватися як з C, так і з структурованими парадигмами.

Для рідного коду в Android застосунках необхідно використовувати Android NDK для безпосереднього доступу до рідних бібліотек платформи.

Android Native MIDI API

Android MIDI застосунки зазвичай використовують MIDI API для комунікації з Android MIDI сервісом. Ці застосунки покладаються на клас MidiManager для виявлення, відкриття та закриття об'єктів MidiDevice, а також для передачі даних до та від MIDI вхідних і вихідних портів.

pic

Figure 8 — Android MIDI — Source: https://developer.android.com/ndk/guides/audio/midi

Цей підхід, однак, передбачає, що весь застосунок працює на JVM (написаний на Java та/або Kotlin), включаючи реалізацію процесора MIDI повідомлень та синтезатор, якщо метою застосунку є MIDI синтез.

У випадку застосунку, який працює з рідним кодом, наприклад, синтезатор FluidSynth, написаний на C, вбудований в застосунок і взаємодіючий з кодом Java/Kotlin через JNI, цей підхід вводить надмірну затримку. Ось як виглядає цей процес:

  1. MIDI команди, ініційовані контролером, надходять як USB-MIDI повідомлення на Android пристрій.
  2. MIDI повідомлення перехоплюються JVM і передаються застосунку.
  3. Застосунок використовує MIDI API для отримання та обробки повідомлень, аналізуючи кожне з них.
  4. Застосунок викликає функції з бібліотеки FluidSynth, яка працює як рідний код, через JNI.
  5. Бібліотека FluidSynth відповідає на ці виклики і генерує запитуваний звуковий синтез.
  6. Бібліотека повертається до застосунку, що працює на JVM через JNI для продовження виконання програми.

Для мінімізації затримки та покращення продуктивності аудіо застосунків Android представив Native MIDI API. Ця API доступна починаючи з Android NDK r20b та наступних версій.
Це дозволяє розробникам застосунків надсилати та отримувати MIDI дані за допомогою рідного коду на C/C++.

При використанні Native MIDI API потрібно передати дескриптор MidiDevice до рідного шару коду через виклик JNI. Після цього API створює посилання на AMidiDevice, який містить більшість функцій MidiDevice. Рідний код використовує функції з Native MIDI API для безпосереднього зв'язку з AMidiDevice. AMidiDevice напряму підключається до MIDI сервісу.

pic

Figure 9 — Android Native MIDI API — Source: https://developer.android.com/ndk/guides/audio/midi

Використовуючи виклики Native MIDI API, аудіо/контрольна логіка застосунку на C/C++ може бути тісніше інтегрована з MIDI передачею. Це зменшує необхідність у викликах JNI або зворотних викликах до Java/Kotlin частини застосунку. Наприклад, програмний синтезатор, реалізований на C коді (такий як FluidSynth), може безпосередньо отримувати події MIDI клавіатури від AMidiDevice, замість того, щоб чекати на виклик JNI для передачі подій із Java/Kotlin шару. Або, якщо застосунок функціонує як MIDI контролер, алгоритмічний процес композиції може бути написаний на C/C++ та безпосередньо надсилати MIDI команди до AMidiDevice, без потреби у зворотньому виклику до Java/Kotlin частини для передачі подій на апаратний синтезатор.

Хоча Native MIDI API покращує безпосереднє підключення до MIDI пристроїв, застосунки все одно повинні використовувати MidiManager для виявлення та відкриття об'єктів MidiDevice. З цього моменту AMidiDevice відповідає за комунікацію.

Іноді може виникнути необхідність передавати інформацію з шару інтерфейсу користувача (UI) до рідного коду. Наприклад, коли MIDI події відправляються у відповідь на натискання кнопок на екрані. Для цього необхідно створювати власні виклики JNI для рідної логіки. Якщо потрібно надіслати дані назад для оновлення UI, можна використовувати зворотний виклик із рідного шару.

Додаткову інформацію про Android Native MIDI API можна знайти в офіційній документації для розробників Google Android: Android Native MIDI API Guide.

Аудіо затримка на Android

Затримка відноситься до часу, необхідного для того, щоб сигнал пройшов через систему. Поширені типи затримки в аудіо застосунках включають:

  • Затримка вихідного аудіо: Час між генерацією аудіо зразка застосунком і відтворенням цього зразка через навушники або вбудовані колонки.
  • Затримка вхідного аудіо: Час між отриманням аудіо сигналу через вхід пристрою (наприклад, мікрофон) і доступністю цих даних для застосунку.
  • Загальна затримка: Сума затримки входу, часу обробки застосунком і затримки виходу.
  • Затримка дотику: Час між доторком користувача до екрану і отриманням події дотику в застосунку.
  • Затримка прогріву: Час, необхідний для ініціалізації аудіоканалу, коли дані вперше ставляться в буфер.

Вимірювати затримку вхідного та вихідного аудіо окремо складно, оскільки це вимагає точних знань про момент, коли перший зразок був надісланий в аудіо шлях (наприклад, використовуючи тестову схему і осцилограф). Однак, якщо відома загальна затримка аудіо, можна застосувати таке правило: затримка вхідного (та вихідного) аудіо приблизно дорівнює половині загальної затримки в шляхах без обробки сигналу.

Загальна затримка аудіо значно варіюється в залежності від моделі пристрою та версії Android. Її можна виміряти, створивши застосунок, який генерує аудіосигнал, відслідковує цей сигнал і вимірює час між його передачею і прийомом.
Найнижчу затримку можна досягти, використовуючи аудіо шляхи з мінімальним обробленням сигналу.

Визначити або точно вказати затримку для конкретного Android застосунку складно. Проте наступні практики допоможуть досягти найменшої можливої аудіо затримки:

  • Додайте одну з наступних директив uses-feature у файл AndroidManifest.xml:

android.hardware.audio.low_latency вказує на безперервну затримку вихідного аудіо 45 мс або менше.


android.hardware.audio.pro вказує на безперервну загальну затримку 20 мс або менше.

  • Використовуйте оптимальну частоту дискретизації та уникайте перетворень частоти дискретизації.
  • Налаштуйте оптимальний розмір для аудіо буферів.
  • Уникайте вихідних інтерфейсів (наприклад, аудіо ефектів), які обробляють сигнали і можуть викликати затримки.

Додаткову інформацію про зменшення аудіо затримки в Android застосунках можна знайти в офіційній документації для розробників Google Android: Android Audio Latency Guide.

Аудіо затримка у FluidSynth

Бібліотека FluidSynth також включає налаштування, які можуть впливати на аудіо затримку при компіляції для Android:

  • audio.oboe.performance-mode: [Тільки для Android] Встановлює режим продуктивності, як зазначено в документації Obue (наприклад, None, PowerSaving, LowLatency).
  • audio.oboe.sharing-mode: [Тільки для Android] Встановлює режим спільного доступу, як зазначено в документації Obue (наприклад, Shared, Exclusive).
  • audio.periods: Кількість аудіо буферів, які використовуються драйвером. Кількість буферів, помножена на розмір буфера, визначає максимальну затримку драйвера (діапазон: 2–64, за замовчуванням: 16).
  • audio.period-size: Вказує кількість аудіо зразків, які драйвер запитує у синтезатора за один раз. Надмірно великі значення можуть призвести до поганої квантизації MIDI подій (діапазон: 64–8192, за замовчуванням: 64).
  • synth.cpu-cores: Визначає кількість ядер процесора для синтезу. Більше значення дозволяє використовувати додаткові потоки для аудіо рендерингу, розподіляючи навантаження і зменшуючи час рендерингу (діапазон: 1–256).
  • synth.sample-rate: Частота дискретизації аудіо, яку генерує синтезатор. Для оптимальної роботи цей параметр має співпадати з рідною частотою дискретизації вихідного сигналу драйвера аудіо (діапазон: 8000–96000, за замовчуванням: 44100).

Практичний проект: Android застосунок для MIDI синтезатора

Ця стаття представляє практичний проект: застосунок для MIDI синтезатора на Android. Повний вихідний код доступний на GitHub:
https://github.com/robsonsmartins/android-midi-synth

Ліцензування

Вихідний код ліцензований за MIT Ліцензією, ліберальною безкоштовною та відкритою ліцензією.
Однак деякі сторонні компоненти в проекті мають специфічні ліцензійні умови:

  • Бібліотека FluidSynth: Ліцензована за LGPL 2.1.
  • KawaiStereoGrand SoundFont: Ліцензована за CC0 1.0 Universal.

У будь-якому випадку, при повторному використанні цього коду для будь-яких цілей, рекомендується зазначити оригінальних авторів і надати їм належне визнання.

Якщо вихідний код бібліотеки FluidSynth змінюється, вихідний код проекту також має бути наданий відповідно до вимог LGPL 2.1.

Інструкції з компіляції

Для компіляції та запуску проекту необхідна система з Git, Android Studio (включаючи Java SDK, Android SDK та Android NDK) та Android емулатор (або фізичний пристрій з увімкненим режимом розробника). Виконайте такі кроки:

  1. Клонуйте репозиторій проекту за допомогою git clone.
  2. Відкрийте каталог проекту в Android Studio.
  3. Зачекайте, поки завершиться синхронізація Gradle.
  4. Побудуйте проект, використовуючи Build > Rebuild Project або запустіть застосунок безпосередньо на емуляторі або підключеному пристрої.

Щоб протестувати застосунок:

  1. Використовуйте смартфон або планшет з увімкненим режимом розробника для передачі застосунку через Android Studio.
  2. Підключіть вихід для навушників пристрою до колонки або зовнішньої аудіосистеми для підсилення сигналу.
  3. Використовуйте OTG-кабель для підключення USB MIDI контролера або MIDI-USB адаптера до пристрою.
  4. Перевірте, чи застосунок виявляє MIDI пристрій, і протестуйте, натискаючи клавіші або педаль утримання. Застосунок повинен генерувати звук, відображати натиснуті клавіші (note on), їх швидкість та події відпускання (note off).

pic

Figure 10 — MIDI Synthesizer Application for Android — Source: Author

Деталі вихідного коду

Проект Android MIDI синтезатора організований таким чином:

  • assets — Ця папка містить файл SoundFont, який буде програватися синтезатором. Зауважте, що в коді реалізовано метод для копіювання файлу з папки assets до тимчасового каталогу, а потім викликається відповідна функція FluidSynth для завантаження файлу SoundFont.
  • cpp — Ця папка містить весь вихідний код на C++, що реалізує синтезатор і менеджер MIDI повідомлень, а також заголовні файли і попередньо скомпільовані C бібліотеки для FluidSynth.
  • java — Ця папка містить весь вихідний код застосунку, написаний на Kotlin. Окрім класів, що інкапсулюють нативні функції (через JNI), є клас для основної активності застосунку (інтерфейс користувача).

Файл Manifest
Файл AndroidManifest.xml містить основні налаштування застосунку. Значущі пункти для цього застосунку включають:

  • android.hardware.audio.pro - Вказує на те, що буде використовуватися найменша можлива затримка аудіо, доступна в системі Android, оскільки це "професійний" аудіо застосунок.
  • android.software.midi - Вказує на те, що в цьому застосунку будуть використовуватися ресурси MIDI API.

Бібліотека FluidSynth
Бібліотека FluidSynth була інтегрована в застосунок за допомогою попередньо скомпільованої версії для Android, доступної у FluidSynth Releases. Для цього проекту була використана версія 2.4.0, випущена 31 жовтня 2024 року. Рекомендовані кроки для цього процесу виглядають так:

  1. Завантажте zip файл, що містить попередньо скомпільований реліз для Android.
  2. Розпакуйте файл.
  3. Скопіюйте всю папку lib до проекту, отримавши шлях /app/src/main/cpp/fluidsynth/lib.
    4.
    Скопіюйте всю папку include до проекту, що призведе до шляху /app/src/main/cpp/fluidsynth/include.

Ця процедура детально описана в офіційній документації FluidSynth: Використання попередньо скомпільованих бібліотек на Android.

Альтернативно, можна клонувати репозиторій FluidSynth і скомпілювати вихідний код для генерації бінарників для Android, використовуючи NDK. Цей процес описано в офіційній документації: Компіляція для Android.

Щоб застосунок використовував нативні бібліотеки FluidSynth, необхідно налаштувати CMake через файл /app/src/main/cpp/CMakeLists.txt.

Нативні класи C++

Для маніпуляції з нативним кодом було створено кілька класів C++:

  • SynthManager — Реалізований у файлах SynthManager.h та SynthManager.cpp, цей сінглтон-клас гарантує єдину інстанцію протягом виконання. Він виступає як обгортка для FluidSynth, реалізуючи програмний синтезатор.
  • MidiManager — Реалізований у файлах MidiManager.h та MidiManager.cpp, цей сінглтон-клас обробляє MIDI повідомлення та інкапсулює низькорівневі виклики до Android Native MIDI API. Він взаємодіє з інстанцією SynthManager для генерації синтезованого аудіо на основі отриманих та розпізнаних MIDI повідомлень. Для допомоги в ідентифікації загальних MIDI повідомлень був доданий заголовний файл MidiSpec.h.

Інтерфейс JNI
Для забезпечення комунікації між інстанціями нативних класів C++ та класами на основі JVM, був побудований JNI інтерфейс. В цьому проекті оголошення JNI додаються в кінці файлів SynthManager.cpp та MidiManager.cpp. Використовуючи клаузу extern "C", JNI функції обгортають відповідні інстанції та методи C++ класів, що дозволяє забезпечити сумісність з JNI моделлю, яка є чисто C.

Класи Kotlin
Для взаємодії з нативним кодом через JNI було реалізовано наступні класи Kotlin:

  • SynthManager — Реалізований у файлі SynthManager.kt, цей клас інкапсулює всі JNI виклики до свого нативного аналога. Він виступає як обгортка для FluidSynth. Зауважте, що не всі методи були реалізовані, оскільки застосунок їх не потребує.
  • MidiManager — Реалізований у файлі MidiManager.kt, цей клас інкапсулює всі JNI виклики до свого нативного аналога, займається виявленням підключених MIDI пристроїв на Android і передає посилання на нативний код. Крім того, він реалізує зворотний виклик для відображення отриманих повідомлень у користувацькому інтерфейсі.

Також основна активність застосунку (користувацький інтерфейс) реалізована на Kotlin:

  • MainActivity — Реалізована у файлі MainActivity.kt, цей клас служить як основна активність, створюючи інстанції SynthManager та MidiManager, налаштовуючи необхідні елементи і ініціалізуючи нативну бібліотеку.

Висновок

Ця стаття надавала огляд розробки Android MIDI синтезатора, поєднуючи Kotlin, C, C++ та бібліотеку FluidSynth. Обговорювалися основні концепції та технології, необхідні для реалізації функціонального рішення, від основ MIDI протоколу до інтеграції SoundFonts для відтворення звуку.

Архітектура Android з підтримкою нативного виконання через JNI та обробки аудіо в реальному часі виявилася привабливою платформою для такого типу застосунку. Комбінація MIDI API, оптимізованих налаштувань низької латентності та гнучкості бібліотеки FluidSynth дозволила створити практичний приклад проекту, що продемонстрував як технічні аспекти, так і виклики обраного підходу.

Протягом тексту були обговорені переваги та обмеження використаних інструментів, а також налаштування та кроки, необхідні для підготовки середовища розробки та компіляції застосунку.
Наведенний практичний приклад чітко ілюструє, як підключати MIDI контролери до Android пристроїв, інтегрувати нативні бібліотеки та використовувати SoundFonts для синтезу звуку.

Повністю доступний репозиторій на GitHub служить відправною точкою для розробників, які бажають дослідити можливості Android для аудіо- та цифрових музичних застосунків. Ця стаття має на меті не лише передати технічні знання, але й надихнути на нові реалізації та інновації в музичній сфері.

Джерела

FluidSynth. A SoundFont Synthesizer. 2017. Доступно на: https://www.fluidsynth.org/. Дата доступу: 14 грудня 2024 року.

FluidSynth. Документація розробника FluidSynth 2.4. 2024. Доступно на: https://www.fluidsynth.org/api/. Дата доступу: 14 грудня 2024 року.

FluidSynth. Репозиторій FluidSynth на GitHub. 2017. Доступно на: https://github.com/FluidSynth/fluidsynth. Дата доступу: 14 грудня 2024 року.

FluidSynth. Wiki: Компіляція для Android. 2023. Доступно на: https://github.com/FluidSynth/fluidsynth/wiki/BuildingForAndroid. Дата доступу: 14 грудня 2024 року.

FluidSynth. Wiki: Особливості Fluid. 2023. Доступно на: https://github.com/FluidSynth/fluidsynth/wiki/FluidFeatures. Дата доступу: 14 грудня 2024 року.

FluidSynth. Wiki: Використання попередньо скомпільованих бібліотек на Android. 2023. Доступно на: https://github.com/FluidSynth/fluidsynth/wiki/Using-prebuilt-libraries-on-Android. Дата доступу: 14 грудня 2024 року.

Google. Android Developers. 2008. Доступно на: https://developer.android.com. Дата доступу: 14 грудня 2024 року.

Google. Android Developers: Посібник з латентності аудіо. 2015. Доступно на: https://developer.android.com/ndk/guides/audio/audio-latency. Дата доступу: 14 грудня 2024 року.

Google. Android Developers: Посібник з Native MIDI API. 2019. Доступно на: https://developer.android.com/ndk/guides/audio/midi. Дата доступу: 14 грудня 2024 року.

Google. Android Developers: Приклад Native MIDI. 2019. Доступно на: https://github.com/android/ndk-samples/tree/main/native-midi. Дата доступу: 14 грудня 2024 року.

Google. Android Developers: Архітектура платформи. 2024. Доступно на: https://developer.android.com/guide/platform. Дата доступу: 14 грудня 2024 року.

Google. Android Open Source Project. 2008. Доступно на: https://source.android.com. Дата доступу: 14 грудня 2024 року.

Martins, R. Android MIDI Synth Project. 2024. Доступно на: https://github.com/robsonsmartins/android-midi-synth. Дата доступу: 14 грудня 2024 року.

Martins, R. MIDI — PC Adapter. 2008. Доступно на: https://robsonmartins.com/content/eletr/projects/midi/. Дата доступу: 14 грудня 2024 року.

Martins, R. MIDI — USB Adapter. 2012. Доступно на: https://robsonmartins.com/content/eletr/projects/midiusb/. Дата доступу: 14 грудня 2024 року.

Polyphone. Polyphone SoundFont Editor. 2013. Доступно на: https://www.polyphone.io/. Дата доступу: 14 грудня 2024 року.

Polyphone. Polyphone SoundFonts. 2013. Доступно на: https://www.polyphone.io/en/soundfonts. Дата доступу: 14 грудня 2024 року.

Polyphone. Різні формати SoundFont. 2013.
Доступно на: https://www.polyphone.io/en/documentation/manual/annexes/the-different-soundfont-formats. Дата доступу: 14 грудня 2024 року.

Ricardo, H. Створення додатку FluidSynth Hello World для Android. 2020. Доступно на: https://medium.com/swlh/creating-a-fluidsynth-hello-world-app-for-android-5e112454a8eb. Дата доступу: 14 грудня 2024 року.

SFZ Format. Огляд формату SFZ. 2020. Доступно на: https://sfzformat.com/. Дата доступу: 14 грудня 2024 року.

The MIDI Association. Специфікації MIDI. 2024. Доступно на: https://midi.org/specs. Дата доступу: 14 грудня 2024 року.

Перекладено з: Developing a MIDI Synthesizer App for Android Using Kotlin and C++

Leave a Reply

Your email address will not be published. Required fields are marked *