Картина Владимира Матвецева 'Сон'

 

'... І оркестр не заграв, і навіть не гримнув, і навіть не вхопив, а саме, за огиднім висловом кота, урізав якийсь неймовірний, ні на що не схожий по розбещеності своєї марш.'

М.А. Булгаков "Майстер і Маргарита"

Запитайте у хорошого кравця, що є головним елементом чоловічого одягу, і напевно, ви почуєте - ґудзик. Так і в разі дверного дзвінка, про розробку якого я продовжую розмову, його звучання є однією з головних характеристик. У той же час, після ознайомлення в інтернеті з описами дзвінків, що відтворюють до 30 приємних мелодій, у мене виникла асоціація з фразою, яку і поставив епіграфом до цієї статті. Не проблема, хоча і блюзнірство, "навчити" дверний дзвінок заспівати що-небудь гламурне чарівним голосом Сари Брайтман, але о 3 годині ночі ця мелодія швидше за все змусить нас глибше заритися в ковдру в надії побачити продовження настільки ж чарівного сну, який бачить персонаж однойменної картини Володимира Матвейцева. А адже саме в цей момент сусіди, що живуть поверхом нижче, до непристойності наполегливо тиснуть кнопку вашого мелодійного дзвінка, бажаючи поговорити на тему "Для чого призначені водопровідні крани у ванній кімнаті?". Напевно у вас є свої історії на цей випадок, але думаю ми зійдемося в думці, що добрий марш, виконаний на хрипкий бойової трубі, незамінний в даному випадку.

Висновок: в даному додатку цілком прийнятний звук низької якості.

Коротко про історію музичних синтезаторів

Як ви побачите в наступній статті, де ми займемося схемотехнікою "заліза" і його програмуванням, відтворення звуку стандартними засобами мікрокомп'ютера (MCU), заняття не складне. Але тому що цей цикл статей призначений для опису повного процесу виробництва, то поговоримо про головне, а саме про створення мелодій. Адже не з неба ж вони впали прямо в електронні мізки нашого дзвінка. А так як в своєму інтернет-магазині ми хочемо надати покупцеві можливість вибирати мелодії, то бажано навчитися самостійно створювати їх опісі, зрозумілі для MCU. Отож, не будемо ми безсовісно вирізати шматки з концертів Pink Floyd і запихати їх в програмовані звукові чіпи. Думаю важко знайти людину, яка б не чула концерт цієї групи "Темна сторона Місяця" або "Тварини", де яскраво представлений електронний синтезатор. Перед тим як заглибитися в роботу згадаємо батька музичних синтезаторів, людини з дивовижною біографією, артилерійського полковника Євгена Мурзіна, незаслужено забутого нині. У далекому 1941 року, ще будучи студентом він розробив перші креслення фотоелектронного оптичного синтезатора звуку, ідея виробництва якого по-зрозумілих причин не отримала підтримки в ті важкі воєнні роки. Тільки в 1958 р. був виготовлений перший зразок, який він назвав АНС на честь композитора О.М. Скрябіна. Для порівняння і усвідомлення наскільки просте наше завдання зазначу, що синтезатор Мурзіна мав 72 звукі в октаві. Ми ж будемо працювати зі стандартними сім'ю нотами в октаві.

Завдання і інструменти

Оскільки ми вже ось-ось почнемо програмувати, то настав час вибору мови і засобів розробки. Навіть побіжний погляд на цю тему відкриє вам безліч мов і інструментів, призначених для написання і перекладу текстів програм в масиви бітів, зрозумілі заданому MCU. Серед них "бабусі і дідусі" типу Ada і Fortran, вже досвідчені Java, Пітон, С і С++, а також молодий Rust. Звичайно справа смаку, але портами MCU можна управляти навіть з PHP, наприклад, за допомогою PiPHP в разі Raspberry PI.

Ймовірно багато хто не погодиться, але насмілюся стверджувати, що професія програміста в теперішньому її вигляді кардинально зміниться в найближчі 10-15 років. Наприклад, ще зовсім недавно, створення Графічного Інтерфейсу Користувача (GUI) було серйозним трудомістким випробуванням для програміста, а зараз деякі інструменти дозволяють легко описати його простими текстовими конструкціями на XML, QML, або графічними засобами, які надають численні IDE. Але справа навіть не в цьому. Просто стандартний цикл зміни людських поколінь, що обчислюється 25-ма роками навряд-чи може значно змінитися, так як знаходиться в прямій залежності від фізіології людини. З іншого боку, цикл зміни технологій вже зараз коротше циклу зміни поколінь, і ймовірно скоро досягне 5 років. У зв'язку з цим, щоб залишатися затребуваним, програміст зіткнеться з необхідністю постійного вивчення нових мов і засобів роботи з ними. При цьому, він зобов'язаний встигати якісно виконувати поточну роботу. Задайте собі питання про тривалість найбільш продуктивного періоду роботи середньо-статистичного програміста і чи не буде він зменшуватися, хоча б через постійне збільшення стресу? На мій погляд ми вже близькі до насичення і програмування перейде на більш високий системний рівень, тривалість активності якого буде ще коротше. У статті 'Олійня' ви також знайдете цікавий діалог щодо 'необхідності' мати настільки велику кількість мов програмування.

Тому, я пропоную просте правило - виберіть відносно довготривалий напрям роботи для виконання якої необхідне знання трьох-п'яти мов, а вільний час присвятіть більше важливої ​​справи, як наприклад, вивчення "Книги змін". У більшості випадків кінцевому користувачеві не важливо вибрав ти Erlang для вирішення завдання, або BASIC. Важливо щоб виріб мав реальну споживчу цінність і надійно працював роками без ремонту і втручання розробника. Хоча і ця, здавалося б логічна вимога, вже зазнала змін, підкоряючись іншим законам, не пов'язаним безпосередньо з працею інженерів. По-всякому разі холодильники і пральні машини раніше проектувалися для більш тривалої роботи.

Отже, потрібно створити програму, що дозволяє програвати прості одноголосні мелодії на спікері Персонального Комп'ютера (PC). Текстове представлення музичних фрагментів повинно легко читатися людиною, що не знайома з нотною грамотою, і не вимагати спеціалізованих музичних редакторів для написання. Програма повинна мати наочні засоби для оперативної модифікації малюнка мелодії і можливість його збереження в вихідному файлі опису. Основним призначенням програми є трансформація текстового подання остаточно відредагованого мелодії в стандартний файл налаштувань (в разі мови С++ це заголовний файл з розширенням ".hh"), який буде підключатися на етапі компіляції програми для обраного типу MCU, з урахуванням частоти його тактування. При цьому, такі параметри кожної ноти, як октава, висота звуку, тривалість звучання і тривалість паузи, повинні разом поміщатися в один байт (8 біт).

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

В якості мови програмування обраний С++, а в якості середовища розробки Code :: Blocks, тому що цей безкоштовний крос-платформенне інструмент дозволяє створювати широкий спектр програм, включаючи внутрешні (для різних типів MCU):

CodeBlocks GUI
Таким чином, оволодіння практичними навичками роботи з цією програмою дозволить в подальшому в багатьох випадках відмовитися від покупки або витрати часу на вивчення інших інструментів подібного призначення. Операційна система як і раніше Linux, але не проблема використовувати його, наприклад, в Windows.

Створення первинного файлу мелодії

Допитливий інженер звичайно по-початку вивчить хоча б деякі стандарти щодо синтезу музики, наприклад, MIDI і SoundFont. Потім відкриє в Audacity, що-небудь типу "Fly me to the Moon" у виконанні Оскара Пітерсона і дослідіть мелодію на предмет невдалих, на його погляд пасажів і формант. І вже потім видалить всі "зайве" і "пошиє" з неї щось підбадьорливе, користуючись тільки однотональний патернами.

Melody Edit Audacity
За вже неодноразово згаданої причини дуже малих ресурсів MCU, ми максимально спростимо задачу і не станемо шукати готові рішення або створювати OCR-сканер стандартного віду музичних творів для нашої програми обробки первинного описателя мелодії:

Tea for two notes
З тієї ж причини, ми не станемо навантажувати MCU обробкою складних MIDI-структур, значить немає сенсу ускладнювати і згадану програму, що обробляє текст музичної композиції. Тим більше, що реальна мелодія, призначена для виконання навіть на найпростішому музичному синтезаторі, зазвичай розбита на партії для різних інструментів. Нам потрібен простий формат запису мелодій, достатній для виконання завдання відповідно до поставлених умов. Тому, якщо ви як і я не володієте нотною грамотою, то озбройтеся своїм 100% слухом і награйте одним пальцем фрагмент необхідної мелодії, наприклад, в програмі Virtual MIDI Piano Keyboard:

Virtual MIDI Piano Keyboard
В результаті отримаємо, що-небудь подібне згаданому "Tea for two", показане в наступній парі рядків, де кожна нота представлена ​​в Американської Стандартної Системі нотації (STN), яку ще називають Наукова Нотація:

B3,G3#,A3#,G3#,B3,G3#,A3#,A3#
A3#,F3#,G3#,F3#,A3#,A3#,G3#

Як бачимо, тут є вказівка ​​на октаву (3) і висоту звуку ноти (B, G, ...), але немає даних про тривалості і паузи. Тривалість зазвичай не сильно варіюється в музичному творі, тим більше в разі мелодій для нашого застосування. А ось пауза ... Це великий винахід у взаєминах людей! На наступній картинці показаний фрагмент інтермедії, де протягом декількох хвилин статуси персонажів відносно один одного змінюються в діапазоні від залежного до лідируючого і навпаки. Прекрасні актори настільки вміло показують ці метаморфози, що не потрібно вникати в сенс слів і навіть дивитися на екран - досить просто слухати паузи, щоб визначити поточний статус мовця.

Boss and Staff
І якщо вже музика покликана відображати життя, то ми розширимо діапазон опису паузи хоча б до чотирьох станів. Втім, в деяких випадках це правило можна було б спростити - в однорічної дитини пауза завжди однаково доброзичлива.

Отже, створимо нотну граматику для нашої простої програми:

  • уявлення кожної ноти завжди складається не менше, ніж з двох символів в науковій нотації, з можливим доповненням третього символу альтернаціі '#'
  • підтримується 2 типу тривалості ноти - звичайна і довга. Тривала нота має тип шрифту 'жирний'
  • підтримується 4 типу паузи - які відрізняються кольором шрифту: відсутня, коротка, звичайна, тривала.
  • описатели нот можуть відділятися один від одного символом ','

Таким чином, наша спеціалізована нотна граматика дозволяє представляти прості мелодії в більш-менш зручному вигляді для читання людиною. Тепер треба визначитися з інструментом, за допомогою якого ми будемо створювати ці первинні описатели. Можна скористатися широкими можливостями wxWidgets і wxSmith, що дозволяють розширити розроблювану програму полем редагування тексту, що підтримує управління шрифтами, відповідно вимог нашої граматики. З іншого боку, тому що це буде не комерційним і вкрай рідко використовуваним додатком, то ймовірно ви погодитеся, що простіше надати користувачеві можливість використовувати якийсь знайомий йому текстовий редактор.

Тобто пора визначитися з форматом файлу первинного описателя мелодії, з якого наша програма повинна сформувати заголовки, "зрозумілий" компілятору для обраного MCU. Скопіюйте вищевказаний приклад описателя мелодії в редактор 'Microsoft Word' або 'LibreOffice Writer' і збережіть його в різних форматах. Спробуйте, наприклад, RTF, XML, HTML і подивіться їх вміст найпростішим текстовим редактором. Ймовірно ви будете дуже здивовані обсягом різноманітної службової інформації і, можливо, вважаєте HTML найкращим для обробки парсером, який вам належить створити в розроблюваної програмі. Можна також спробувати використовувати який-небудь Markdown Editor для створення первинного описателя мелодії. Загалом, виберіть відповідний формат і, звичайно-ж, ознайомтеся з описуючим його стандартом.

Цікаво, яку спрощену граматику ви б запропонували для підтримки 72-х звуков в октаві у синтезатора Мурзіна?

Опис програми

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

B3,G3#,A3#,G3#,B3,G3#,A3#,A3#
A3#,F3#,G3#,F3#,A3#,A3#,G3#

На наступному малюнку показаний GUI програми в середовищі розробки Code :: Blocks:

Melody Header File Gen Software
Як бачимо він підтримує настройку пауз і тривалісті, оголошених в нашій граматиці. Додатково можна вказати тип MCU і частоту його тактирования. Оскільки звучання мелодії на спікері відрізняється від того, яке ми чули, награючи її в Virtual MIDI Piano Keyboard, то може стати в нагоді можливість зсуву оригінальної мелодії на потрібне число октав. При цьому, програма повинна враховувати зазначену частоту MCU і повідомляти, при необхідності, про неможливість програвання мелодії з заданими конкретними параметрами. І нарешті, можна вказати кількість відтворених нот в разі, якщо дзвінок запитуеться від батареї (радіо-кнопка). Мелодія все ж повинна бути пізнавана, тому не можна грубо вказати, наприклад, число 5 для всіх випадків.

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

  • намагайтеся робити обробники переривань в програмі MCU максимально короткими. У більшості випадків в ньому досить лише встановити певний 'прапор', який буде оброблений в основному циклі програми, або в коді обробки виходу MCU зі стану сну. Якщо бажано все ж виробляти певні дії в обробнику переривання, то вони повинні бути максимально прості.

У зв'язку з цим ще раз звернемо увагу на наведене раніше формулювання кінцевої задачі:

При цьому, такі параметри кожної ноти, як октава, висота звуку, тривалість звучання і тривалість паузи, повинні разом поміщатися в один байт (8 біт).

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

Управління спікером PC - варіант №1

У наведеному нижче прикладі генерується звук частотою 2 кГц і тривалістю 1 сек.

int freq = 2000; // Note frequency in Hz
int dur = 1000; // Note duration in ms

int fd = open("/dev/console", O_WRONLY);
ioctl(fd, KIOCSOUND, (int)(1193180/freq));
usleep(1000 * dur);
ioctl(fd, KIOCSOUND, 0);
close(fd);

Але, ми почуємо його тільки в тому випадку, якщо запустимо програму від імені sudo. Тому, даний варіант не розглядаємо в якості відповідного для нашої мети.

Управління спікером PC - варіант №2

Використовуємо функцію "beep", повний аналог якої є в Windows. Її можливості в повній мірі відповідають нашій граматиці і дозволяють встановити частоту звучання ноти, її тривалість, а також тривалість паузи. У середовищі Linux її підтримка цілком можливо відключена за замовчуванням. Встановити і змусити працювати можна в такій послідовності:

  • Встановити 'beep':
    sudo apt-get install beep
  • У файлі '/etc/modprobe.d/blacklist.conf' закомментувать рядок:
    blacklist pcspkr
    і перезавантажити комп'ютер.
  • Перевірити роботу команди 'beep', наприклад:
    beep -f 1000 -r 2 -d 1000 -l 400
  • Якщо звуку немає, то в терміналі ввести команду:
    alsamixer
    Відкриється діалог, показаний на наступному рисунку, де треба відкрити канал 'Beep':

ALSAmixer
ПРИМІТКА:
 якщо комп'ютер не має спікера, то для виведення на зовнішні динаміки можна застосувати команду 'speaker-test'. Наприклад:

speaker-test -t sine -b 1000 -p 1000 -f 1000

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

Функції елементів GUI програми

На представленому вище малюнку GUI показано стан програми, після того як користувач вибрав пункт "File" в меню і відкрив файл з дескриптором необхідної мелодії. Якщо структура його вмісту відповідає правилам нашої граматики, то буде дозволено активувати кнопку "Generate". При натисканні на неї сформується масив, що містить частоти, що відповідають кожному символу з описателя мелодії, тривалості і паузи, а також будуть проведені деякі додаткові перевірки. Дані про відповідність частоти символу ноти легко знайти в інтернеті і, наприклад, для 3-ої октави їх можна представити у вигляді такої таблиці:

C3 = 130.813
C3# = 138.591
D3 = 146.832
D3# = 155.563
E3 = 164.814
F3 = 174.614
F3# = 184.997
G3 = 195.998
G3# = 207.652
A3 = 220.000
A3# = 233.082
B3 = 246.942

Якщо проблем немає, то програма заборонить кнопку “Generate”, але дозволить кнопки "Play" і "Save". При натисканні на кнопку "Save", оновлюється вихідний файл описателя мелодії, відповідно до змін параметрів, які користувач вніс за допомогою GUI, а також створюється або оновлюється змист файлу заголовка з розширенням ".hh", що відповідає обраному MCU і частоті його тактирования. Кнопка "Generate" дозволяється при будь-якої оперативної зміні параметрів, представлених в GUI програми. При цьому програвання мелодії зупиняється і збереження забороняється.

Реалізація описаних функцій не представляє особливого інтересу, тому зупинимося на кнопці "Play". Хоч в GUI, побудованих на основі wxWidgets можна використовувати функцію wxExecute, що дозволяє виконувати синхронні і асинхронні виклики, ми розглянемо інші варіанти програвання мелодії.

Синхронне відтворення мелодії

Схематичний обробник натискання кнопки "Play":

for (i = 0; i < melody_notes_cntr; i++) {
   // Create command to play each note
   cur_note_cmd = 'beep ';
   ......
   cur_note_cmd += '-D ' + cur_duration_value;

   // Example of command: 'beep -f 1000 -l 400 -D 1000'
   system(cur_note_cmd); // Execute command
}

Це робочий варіант, але нам доведеться чекати програвання всієї мелодії, тому що GUI з натиснутою кнопкою "Play" застигне в очікуванні її закінчення. Нам же потрібно мати можливість зупинити програвання в момент зміни будь-якого параметра, і тут же спробувати програти модифікований варіант.

Асинхронне програвання мелодії - додаємо Процеси

Створимо наступні змінні:

pid_t child_pid, first_child_pid;
volatile sig_atomic_t player_ready = 1;

Схематичний обробник натискання кнопки "Play":

first_child_pid = fork(); // Create child process to scan notes

if (first_child_pid == 0) {
   player_ready = 1;

   for (i = 0; i < melody_notes_cntr; i++) {
      // Fill in array 'arg_list' of current note parameters
      ......

      // Example of array content:
      // arg_list[1] = '-f 1000';
      // arg_list[2] = '-l 400;
      // arg_list[3] = '-D 1000';

      // Play note
      if (player_ready == 1) {
         player_ready = 0;

         // Create child process for command 'beep'
         if ((child_pid = fork()) == 0) {
            execvp ("beep", arg_list);
            return;
          }

          // Wait to the end of current note play
          while (player_ready == 0) {}
      }
   }
}

Для того, щоб відстежити момент завершення звучання поточної ноти і оповістити про це батьківський процес циклу сканування мелодії, необхідно створити обробник сигналу від дочірнього процесу:

void player_signal_handler(int sig_num) {
   switch(sig_num) {
   case SIGCHLD:
      player_ready = 1;
      break;
   default:
      break;
   }
}

Щоб перервати програвання мелодії, треба завершити відповідні процеси, тому додамо, наприклад, наступну функцію (показаний фрагмент):

void stop_player(bool play_only = false) {
   if (child_pid > 0) {
      kill(child_pid, SIGTERM);
      child_pid = -1;
   }

   if (first_child_pid > 0) {
      kill(first_child_pid, SIGTERM);
      first_child_pid = -1;
   }
}

Ну і нарешті, в обробник кожного параметра GUI необхідно додати код виклику функції 'stop_player'. Наприклад, для списку вибору типу MCU код може бути таким:

void MelodyHeaderFileGenFrame::OnchoMCUSelect(wxCommandEvent& event){
   stop_player();
}

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

Асинхронне програвання мелодії - Нитки

Нитки можуть бути створені всередині процесу, а також простіше в управлінні і синхронізації.
Створимо наступну змінну:

pthread_t thread_player_id;

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

void* thread_player(void *args) {
   for (i = 0; i < melody_notes_cntr; i++) {
      // Create command to play each note
      cur_note_cmd = 'beep ';
      ......
      cur_note_cmd += '-D ' + cur_duration_value;

      // Example of command: 'beep -f 1000 -l 400 -D 1000'
      system(cur_note_cmd); // Execute command
   }
}

Функцію 'stop_player' відредагуємо наступним чином:

void stop_player(bool play_only = false) {
   pthread_cancel(thread_player_id);
}

Схематичний обробник натискання кнопки "Play" також модифікуємо:

pthread_create(&thread_player_id, NULL, thread_player, NULL);

І звичайно додамо виклик функції 'stop_player' в оброблювачі параметрів мелодії. Наприклад:

void MelodyHeaderFileGenFrame::OnspnNotesOnBatteryChange(wxSpinEvent& event) {
   melody_len_on_battery = spnNotesOnBattery->GetValue();
   stop_player(true);
}

Файл заголовка для MCU

На завершення кілька слів про файл з розширенням '.hh', заради якого затівалася вся ця історія і який, як згадувалося раніше, програма автоматично створить і заповнить, відповідно до первинного файлу мелодії і поточним параметрам, встановленим в GUI. Ось його вміст:

#define MELODY_BASE_PRESCALER 4
#define MELODY_LEN_ON_BATTERY 8
const unsigned char melody[15] PROGMEM = {123,8,74,8,27,72,10,106,106,6,72,6,10,10,104};

Я не прихильник невиправданих надмірностей, але при бажанні навіть в MCU, що має всього 1000 байт пам'яті програм, можна виділити 150-200 байт на десяток простих мелодій.

ПРИМІТКА: зверніть увагу, що всі визначення в даному файлі не мають унікальних ознак, однозначно ідентифікують мелодію. Тут також немає визначень тривалості нот і пауз, які уніфіковані для цього додатка і винесені в окремий заголовковій файл. Це зроблено з метою економії ресурсів MCU і накладає обмеження на вибір типу мелодій, які повинні мати однаковий, як ми визначили на початку статті, бадьорий ритм. Інакше зазначені параметри повинні визначатися в кожному подібному заголовковому файли. Таким чином, якщо дзвінок повинен підтримувати більше однієї мелодії, то автоматичний сканер замовлення обробить всі необхідні музичні файли і створить єдиний, який буде підключений до інших файлів проекту для обраного MCU.

Инструменты и документы

Языки програмування: С++ (https://isocpp.org/get-started)
Програми: Code::Blocks (http://www.codeblocks.org/)
Virtual MIDI Piano Keyboard (https://sourceforge.net/projects/vmpk/)
Додаткове читання:

MIDI (https://www.midi.org/specifications)
SoundFont (http://freepats.zenvoid.org/sf2/sfspec24.pdf)

JavaScript (https://www.javascript.com/)
MIDI.js (https://mudcu.be/midi-js/)
Python (https://www.python.org/)
XML (https://www.w3.org/XML/)
JSON (http://www.json.org/)

Qt (https://www.qt.io/)
PyCharm (https://www.jetbrains.com/pycharm/)
Kivy (https://kivy.org/#home)
Git (https://git-scm.com/)
GitKraken (https://www.gitkraken.com/)

 

Далі буде ...