Пишем музыку с помощью PHP

в 22:25, , рубрики: php, Алгоритмы, цепи маркова

Я не специалист по теории вероятностей, искусственного интеллекта и машинного обучения, кроме того уроки начальной музыкальной школы давно забыты. Но если вы уделите мне 10 минут, вы обнаружите, что даже небольшой запас знаний может дать впечатляющие результаты при их творческом применении. Я хочу поделиться с вами, как научить PHP сочинять музыку.

Например, такую:

generated melody

Вы видите мелодию, сгенерированную на PHP. Да, конечно, эта мелодия не из самых запоминающихся, но она не безобразна. И что удивительно, код, генерирующий эту последовательность нот довольно короткий.

Итак, что происходит? Если коротко, музыкальные данные анализируются скриптом чтобы «научиться» понимать, какие интервалы составляют приятные мелодии. Затем скрипт создаёт новую композицию, выбирая высоты, основываясь на своих знаниях. Говоря технически, скрипт вычисляет карту вероятностей мелодичных интервалов и применяет Марковский процесс для генерации новой последовательности.

Стоя на плечах гигантов

Сочинение музыки не происходит из ничего. Бах был впечатлён Букстехуде и Вивальди. Шопен был под влиянием Листа и Вагнера. Моцарт и Гайдн учили Бетховена. Одна и та же фраза мелодии может быть найдена в различных частях работы. Орфей и Эвридика Глюка и мотив гимна Non Dignus содержат общие музыкальные фразы.

общие музыкальные фразы в Орфее и Эвридике Глюка и мотиве гимна Non Dignus

Если вы попросите PHP сочинять вслепую, результаты получатся не очень. В качестве доказательства приведу мелодию, составленную путём сопоставления случайных значений успешных вызовов функции rand() с нотами.

случайная мелодия

Если вы не владеете техникой додекафонии, то для вдохновения и понимания лучше отталкивайтесь от классических композиций.

Для этого я сперва переписал мелодию нескольких частей музыкальных произведений с помощью научной системы обозначения высоты звука. Я не заморачивался с длительностью нот. Вместо этого я сосредоточился на самой высоте звука. «До» первой октавы из привычных нот вводилось как C4 (где C — название ноты, 4 — октава), нота на пол тона выше 'C#4', ещё на пол тона выше D4, и так далее, в результате мелодия (здесь показаны первые 8 тактов Tantum Ergo) записывалась так:

первые 8 тактов из Tantum Ergo, Bottazzo

A4 C5 G4 A4 G4 A#4 D5 A4 B4 A4 C5 D5 C5 G4 B4 B4 C5

Теперь у меня появилась последовательность нот, которая легко парсилась и которую я мог также легко использовать для анализа. Например, какая нота вероятнее всего следует за A4?

A4 C5 G4 A4 G4 A#4 D5 A4 B4 A4 C5 D5 C5 G4 B4 B4 C5

что в итоге даёт:

C5 G4 B4 C5

Видим, что в 50% случаев следом идёт нота C5, в 25% случаев это C4, и ещё в 25% случаев это B4.

Этот процесс делает из тёплой и живой музыки что-то понятное только компьютеру с его холодной и бесчувственной математической сущностью.

Взывая к Доктору Маркову

Мы знакомы с детерминистическими системами — системами, где одни и те же входные данные всегда возвращают неизменный результат. Сложение — это детерминистическая функция, в которой 2 и 4 на входе всегда дадут 6. Стохастические системы, напротив, ведут себя с определенным уровнем случайности. Одни и те же входные данные могут привести к совершенно разным результатам, например с функцией array_rand(). В сочинении музыки присутствует элемент случайности. Будь это не так, любой композитор, встретив ноту F4 одинаково её продолжит, создавая очередной хит для ненасытной династии эстрадных певцов. Но случайность всё же вносит свои коррективы, несмотря на то что на подсознательном уровне композитор понимает, какие сочетания приятны слуху.

Ярким примером стохастической системы, которая также имеет отношение к скрипту, сочиняющему музыку — Марковский процесс (названный в честь математика Андрея Маркова который не только изучил его, но и отростил замечательную бороду). Вот как поясняет Ник Дидковский:

Анализ Маркова рассматривает последовательность событий и анализирует тенденции единичного события, следующего за ним. С помощью этого анализа, можно создать новую последовательность случайных и при этом связанных событий, которые выглядят похожими на оригинал. Марковский процесс полезен при анализе связанных случайных событий, т.е. событий, которые взаимосвязаны с произошедшими в прошлом.

Традиционный пример использующийся для иллюстрации концепции — граф прогнозирования погоды. Предположим, следующий день после солнечного имеет 90% вероятность также быть солнечным, а тот, который следует после дождливого, имеет 50% вероятность быть дождливым. Граф будет выглядеть следующим образом:

Граф Маркова Солнечно/дождливо

В зависимости от механизма, используемого для нахождения вероятности, пройдясь по графу 5 итераций мы могли бы обнаружить себя переходящими в Солнечный первый день, Дождливый следующий, Солнечный после него, затем Солнечный и опять Солнечный, или же обнаружить себя переходящими в Солнечный, Солнечный, Солнечный, Дождливый, Дождливый в другом случае.

Надеюсь, теперь ясно, к чему я клоню — имеется возможность для ограничения случайного процесса «выбора следующей ноты», используя взвешенные вероятности определённые анализом мелодий, для лучшего звучания.

График ноты A4

Этот простой процесс позволяет нам создавать сносные мелодии, затратив намного меньше времени, чем потребовалось бы обезьяне, случайно стучащей по клавишам органа чтобы сыграть полное собрание сочинений Мессиана.

Роботы композиторы (сингулярность уже близко)

Ну вот, надеюсь, в общих чертах вы разобрались в ключевых принципах, которые я использовал для создания музыки. Даже если нет, вы по крайней мере, вытерпели мой юмор и заслужили увидеть немного кода.

<?php
namespace Zaemis;

class Composer
{
    private $pitchProb;

    public function __construct() {
        $this->pitchProb = [];
    }

    public function train($noteData) {
       $numNotes = count($noteData);
       for ($i = 0; $i < $numNotes - 1; $i++) {
           $current = $noteData[$i];
           $next = $noteData[$i + 1];
           $this->pitchProb[$current][] = $next;
       }
   }

   public function compose($note, $numNotes) {
       $melody = [$note];
       while (--$numNotes) {
           $i = array_rand($this->pitchProb[$note], 1);
           $note = $this->pitchProb[$note][$i];
           $melody[] = $note;
       }
       return $melody;
   }
}
<?php
require_once '../include/Zaemis/Composer.php';
use ZaemisComposer;

$noteData = trim(file_get_contents('../data.txt'));
$noteData = explode(' ', $noteData);

$c = new Composer();
$c->train($noteData);
$melody = $c->compose($_GET['note'], $_GET['count']);

echo '<img src="img/notes/clef.png" alt="Treble Clef">';
foreach ($melody as $note) {
    echo '<img src="img/notes/' . urlencode($note) . '.png" alt="' .
        $note . '">';
}

Процесс обучения описан в методе train(), принимающем на вход массив нот для обучения (закодированная мелодия разделена пробелами). Код прост, быстр и выглядит топорно; Ноты хранятся в двумерном массиве с вероятностью косвенно подразумевающейся самим количеством элементов.

В заполненном состоянии, массив будет выглядеть примерно так:

array(9) {
  ["A4"]=> array(13) {
    [0]=> string(2) "C5"
    [1]=> string(2) "G4"
    [2]=> string(3) "A#4"
    [3]=> string(2) "C5"
    [4]=> string(2) "B4"
    [5]=> string(3) "A#4"
    [6]=> string(2) "G4"
    [7]=> string(2) "A4"
    [8]=> string(2) "D5"
    [9]=> string(2) "G4"
    [10]=> string(2) "C5"
    [11]=> string(2) "C5"
    [12]=> string(2) "G4"
  }
  ["C5"]=> array(11) {
...

Глядя на данные, мы видим, что случайно выбранная нота, следующая после A4 имеет примерно 31% вероятность стать С5 т.к. 4 из 13 элементов списка содержат это значение. (Да, я знаю, лучше оперировать вероятностями, чем держать в памяти список всех возможных нот в композиции, но я сделал это намеренно, чтобы лучше проиллюстрировать идею.)

Метод compose() инкапсклирует логику генерации мелодичной последовательности. Получая на входе желаемую продолжительность последовательности нот, следующих за начальной, метод случайно выбирает значение нот из массива, пока не получит их столько, сколько нужно.

Естественно, мы люди и хотим видеть результат в привычной нам нотной записи. Поэтому я создал набор изображений, помогающих скрипту. Каждое изображение отражает ноту на соответствующем месте среди других нот. имена файлов соответствуют названиям нот. Прохождение по мелодии и рендеринг IMG-элементов вполне меня устраивает.

Быстрее, Выше, Сильнее!

Весьма впечатляет, насколько простыми были идеи, использованные, при написании скрипта, способного вести себя, почти как настоящий композитор. Само собой, нет предела совершенству, ещё многое можно дописать и поправить. Можете считать это вашим первым опытом в освоении музыкального интеллекта. Дэвид Коуп, изучающий сочинение музыки компьютером с 1981 года сказал:

Простое раздробление музыкального произведения на более мелкие части и случайное объединение их в новом порядке почти наверняка произведёт бессмыслицу. Эффективное перестроение требует обширного музыкального анализа и очень осторожного перестроения чтобы оставаться эффективным даже на самом базовом уровне.

Помимо очевидных изменений, таких как изменения высоты матрицы для поддержания вероятностей, какие бы вы внесли улучшения? Может быть, заменить этот наивный подход на совершенно другой механизм для анализа музыки? Анализировать входные данные из файлов MIDI? Что необходимо для выявления гармонии? Как насчёт аккордов? Длительности нот? «Почерка» композиторов? Может ли ваш скрипт обучаться на основе анализа собственных хороших мелодий и использовать их для пополнения своей базы знаний? Каким образом вы могли бы перераспределить сэмплы для создания новых работ?

Мне было бы интересно читать ваши комментарии о результатах экспериментов в области искусственного интеллекта управляемого сочинения музыки.

Автор: pewpew

Источник

Поделиться

* - обязательные к заполнению поля