Программирование и мемы
Программирование — процесс на грани науки и творчества. Без грамотно составленной программы компьютер — беспомощный агрегат, тратящий электроэнергию. Большинство современных специалистов работают с декларативными символьными языками — составляют программы из текстовых команд: простых — присваивание, умножение; и сложных — проверка условия, выполнения подпрограммы в цикле.
Но не все языки программирования предназначены для решения системных задач. Некоторые создаются в целях обучения, проведения презентаций и цифровых шоу. Когда производительность и функциональность отходят на второй план, на свет появляются такие оригинальные решения, как среда визуального программирования Pure Data.
В 2020-ом году танец из архивного видео похоронной церемонии агентства Dada awu из Ганы стал самым популярным мемом в России. Сегодня нам предстоит пошагово воссоздать легендарную музыкальную тему Coffin Dance, используя среду алгоритмического описания звука Pure Data.
О визуальном программировании
Под визуальным программированием принято понимать процесс описания программы для вычислительного устройства с использованием функциональных графических элементов. Так, использование конструктора форм WPF в среде разработки Visual Studio можно назвать визуальным программированием интерфейса. Графические конструкторы появились вместе с первыми персональными компьютерами. Сейчас на уроках информатики в некоторых школах изучают Визуальный Паскаль. а половину штатных доработок в 1C можно выполнить, не написав и строчки кода.
Но полноценные среды визуального программирования появились позже. Когда речь заходит о создании языка программирования, который состоит только из графических команд, возникает ряд трудностей, из-за которых функциональные возможности самого языка приходится урезать.
Так, множественные вызовы одной и той же подпрограммы в разных участках кода на языке C++ являются естественными для программиста. В графической же среде вызов подпрограммы может представляться, например, стрелкой от объекта в тексте основной программы к объекту-подпрограмме, и когда таких стрелок слишком много — графический код становится не читаемым. Разработчики учебных сред разработки часто сами накладывают ограничение (на количество вызовов, объектов в программе, используемых переменных) чтобы обезопасить пользователя и сделать процесс визуального программирования более наглядным.
Пример простой программы на языке Sanscript:
Но если отвлечься от программирования в широком смысле, можно найти важные прикладные задачи, которые в графическом виде решаются удобнее и эффективнее, чем в символьном. Одна из таких задач: описание виртуальных инструментов. Ещё в 80-х годах прошлого века большая часть музыкальных записей производилась «вживую». Цифровые синтезаторы уступали по качеству звучания акустическим инструментам, а по-настоящему «музыкальные» цифровые системы стоили дорого.
Всё изменилось с внедрением технологии VST, представленной в 1996-ом году в виде объёмной библиотеки по работе со звуком. Функционал VST позволял описать архитектуру реальных цифровых синтезаторов, процессоров эффектов и предельно точно эмулировать их работу на компьютере. Однако построить такое описание могла лишь команда квалифицированных программистов и звукорежиссеров. Вопрос о создании пользовательских виртуальных инструментов оставался открытым.
В те же годы в попытках популяризировать звуковой синтез среди любителей на свет появляется экспериментальный проект Pure Data — визуальная среда разработки, ориентированная на описание простых синтезаторов. Вокруг Pure Data сформировалась небольшая группа «фанатов», продвигавших визуальное программирование в FB, Youtube: они писали и переводили учебные материалы, публиковали примеры синтезаторов и целые музыкальные миксы на одном патче (так называется программа на Pure Data по аналогии с патчами в модульных аппаратных синтезаторах). Но широкого распространения в мире среда так и не получила.
Основы Pure Data
Среду разработки Pure Data можно скачать бесплатно с официального сайта. Программа распространяет с открытым исходным кодом и доступна для пользователей Windows, Linux и MacOS X. Установим и запустим Pure Data. В меню File создадим новый патч. Появится следующее окно редактирования:
В меню Put представлен список графических объектов, из которых нам предстоит составить наш первый синтезатор. После выбора соответствующего пункта, элемент добавляется на графическое поле (Canvas) текущего патча. Элементы можно редактировать (изменять подпись, которая влияет на их поведение, а также перемещать) и связывать линиями (зажав левую кнопку мыши на одном из выходе объекта-источника, передвинув курсор на вход объекта-приёмника и отпустив). Для примера создадим один элемент типа «Object» с названием key и один элемент типа «Number». Входы элемента выделены чёрным и расположены над рамкой самого элемента, а выходы — под ней. Соединим выход объекта key со входом элемента Number.
Получим:
Всё это время мы находились в режиме редактирования патча. Чтобы перейти в режим выполнения войдем в меню Edit и уберем галочку с пункта Edit Mode. Указатель мыши на канве должен измениться с руки на стрелку. Теперь нажмём любую клавишу на основной клавиатуре. Увидим как значение Number изменяется при нажатии:
Разберём результат работы патча. После назначения объекту имени key у него появился один выход, а входы отсутствуют. Объект с таким именем слушает клавиатуры и отправляет на выход код клавиши при каждом нажатии в режиме выполнения. Элемент Number имеет один вход и один вывод. Принимая на вход число, Number отображает его на экране и отправляет это же значение на выход. Заметим, что после отпускания клавиши, число продолжает отображаться. Это показывает, что входы сохраняют последнее принятое ими значение.
Вернемся в режим редактирования и добавим ещё один объект с именем sel 49. У него появится два входа и два выхода. Этот объект направляет сигнал на первый выход каждый раз когда на один из входов поступает значение и оно совпадает со значением, хранящемся на другом входе. Если на вход поступает значение, отличное от накопленного, что сигнал отправляется на второй выход. Первая часть имени sel задаёт тип объекта, вторая 49 — записывает значение по умолчанию для второго входа. 49 — это код клавиши «1».
Сигнал — особый тип данных, который нельзя просто вывести, например, отправив на вход Number. Но с помощью сигналов можно «запускать» работу некоторых элементов.
Вернемся в меню Put и добавим элемент Bang в наш патч. Соединим выход Number с первым входом sel 49 и первый выход sel 49 со входом Bang. Получим следующий патч:
Перейдем в режим выполнения. При нажатии различных клавиш будут появляться их коды. При нажатии «особой клавиши» с кодом 49 объект sel будет генерировать сингал, который попадая на кнопку bang будет автоматически нажимать ее:
Объект bang генерирует сигнал на своём единственном выходе при нажатии в режиме выполнения, попадании на вход сигнала или значения. Добавим в наш проект немного звука. Для корректной работы патча с аудиоданными, в меню Media должна стоять галочка напротив пункта DSP On. Также потребуется войти в меню Midi Settings и установить устройство вывода. Под Windows:
Нажмем OK и вернемся в режим редактирования. Для работы с Midi-сигналами чаще всего используются 2 элемента: объекты makenote и noteout. Добавим по одному такому объекту, а также создадим несколько сообщений Message со следующими значениями:
Сообщение — это элемент визуального программирования со сложным поведением. Если сообщение названо некоторым числом, то по клику на сообщении или при получении сигнала на его вход, на выход в обоих случаях будет отдаваться это число и сигнал одновременно.
Объект noteout при получении значения на первый вход генерирует на одном из миди-каналов звук. Номер канала может быть указан явно в названии объекта (в нашем случае 1 — фортепиано) или передан на третий вход noteout. Первый вход принимает высоту тона в терминах миди: (60 — это до первой октавы и далее +1 — повышение на полтона, -1 — понижение на полтона), а второй — описание ноты: громкость, длительность, характер звукоизвлечения.
Объект makenote получая на первый вход высоту тона и на второй громкость (0 — тишина, 127 — максимальная громкость) транслирует на первый выход высоту тона, а на втором генерирует описание ноты для второго входа noteout. Значения по умолчанию для первого и второго входа makenote указываются непосредственно после имени объекта. Соединим элементы как показано на рисунке и перейдём в режим выполнения:
При нажатии на сообщение должна прозвучать нота, соответствующая высоте тона в названии сообщения. 60 — нота до, 62 — ре, 67 — ля. Теперь в режиме редактирования соединим первый выход селектора (объекта sel) с сообщением 60. Добавим ещё 2 селектора с кодами для клавиш 2 и 3 на клавиатуре и получим простейший синтезатор:
При нажатии клавиш 1-3 в makenote будет отправляться сообщение с соответствующей высотой тона и звучать нота. В следующем разделе мы попробуем описать сложную мелодию, которая будет исполняться сама последовательно нота за нотой при отправке всего одного сигнала.
Воссоздаем Coffin Dance
Музыка
Для описания сложной мелодии нам потребуется изучить 2 важных объекта — metro и f, а также ряд вспомогательных объектов. Рассмотрим простой пример:
Объект metro имеет два входа. На первый поступает сигнал. Любой сигнал, отличный от stop включает метроном. Для отключения используется выходной сигнал сообщения с текстом stop. На второй вход metro подаётся интервал в миллисекундах. При включении метронома он немедленно подаст сигнал на свой единственный выход и будет повторно отправлять сигнал каждые n миллисекунд. Интервал n может быть задан значением по умолчанию в имени объекта или передан на второй вход. Перейдя в режим выполнения, мы можем запустить метроном кнопкой bang над ним и увидим как кнопка под ним нажимается каждые 0.4 секунды. Отправив сообщение с новым интервалом мы ускорим, либо замедлим частоту отправки сигналов. После отправки сообщения stop отправка сигналов прекратится.
Вспомним музыку из Coffin Dance:
Основную мелодию можно описать последовательностью из 64-х нот и пауз 8-ой длительности, т.е. в 8 тактов в 4/4. Значит, нам необходимо организовать циклическую отправку 64-ых сигналов на соответствующие сообщения-ноты. Для этого воспользуемся объектом f.
F — от float — представляет вещественную переменную. Объект f имеет два входа. На второй вход можно подать числовое значение (например, с выхода сообщения или выхода Number), которое запишется в качестве значения переменной. Значение по умолчанию 0. С первыми входом и выходом всё немного сложнее. Рассмотрим пример:
Итак, мы нашли магическую связь между мемами «Press F to pay respect» и «Coffin dance». Перейдем в режим выполнения. При нажатии на сообщение 3 значение будет записано в переменную-объект f, но в number мы этого не увидим. Если подать кнопкой bang сигнал на первый вход f, произойдёт то, что в терминах Pure Data называется итерация, а именно:
- На выход f будет отправлено текущее значение переменной (3)
- Объект + 5 получит на первый вход число 3 и вернёт сумму двух входов 8 (здесь значение на втором входе задано по умолчанию после имени + объекта сложения)
- Объект % 40 получит на первый вход число 8 и вернёт остаток от деления 8-и на 40 (значение второго входа, заданное по умолчанию)
- Переменная f получит на второй вход значение (3+5) % 40 = 8. Итерация завершена
Таким образом, после первой итерации мы видим значение 3 на входе number и имеет реальное значение 8 для переменной f. На следующей итерации number получит сохранённую ранее восьмёрку, а f увеличится ещё на 5 и примет значение 13. Дойдя до итерации с выводом 38 мы получим значение (38+5) % 40 = 3 для переменной f. Таким образом, значения f и значения вывода будут циклически повторяться. Использование сложения и деления по модулю позволяют получить цикл с любым периодом и любыми значениями для итераций.
Подключим к первому входу f выход метронома, а на выходе f установим несколько селекторов и кнопок. Запустим метроном и увидим, как кнопки загораются по очереди и после автоматического нажатия последней кнопки цикл повторяется. Патч:
Чтобы цикл каждый раз выполнялся со значения 3, я соединил выход сообщения stop со входом сообщением 3 отправки начального значения в f.
Опишем мелодию. Для этого создадим чистый патч и опишем метроном с интервалом 230 (именно 230 миллисекунд длится одна восьмая нота в мелодии) и цикл на 64 ноты. Для выборки значений используем переменную f и новый для нас элемент moses, о функционале которого будет рассказано ниже. Также добавим несколько объектов makenote, сообщений с нотами, noteout-ы с каналами 1 (фортепиано) и 10 (ударные), и соединим селекторы moses каждой итерации с соответствующими нотами. Получим патч:
Как видим, при визуальном программировании использование большого числа объектов снижает читабельность программы. На самом деле, патч представляет комбинацию простых элементов. Рассмотрим следующую «подпрограмму»:
Объект moses хранит на втором входе некоторое число, которое может быть задано значением по умолчанию. Если на первый вход поступает число меньшее, чем сохраненное значение второго входа, то с первого выхода moses отправляется сигнал, в противном случае сигнал отправляется со второго выхода. Таким образом, первый выход moses 1 ведёт себя аналогично первому выходу sel 0. Посмотрим на связку + 63 % 64. Она позволяет уменьшать значения в цикле на единицу. Если на вход была подана единица, то на выходе будет 0, для входа 0 на выходе будет 63 — получим цикл на 64 элемента в обратном порядке. Если вывод этой связки соединить со входом следующей конструкции + 63 % 64 и на выход каждой связки подключить moses 1, но для каждого из 64-х возможных значений f ровно один moses будет отправлять сигнал на первый выход. Так, число 7 нужно 7 раз уменьшить на 1, чтобы получить 0, значит 7-ой moses 1 на итерации 7 получит на первый вход 0 и отправит сигнал соответствующей ноте. Если для 64-х селекторов нужно вручную редактировать значения параметров по умолчанию, то подпрограммы + 63 % 64 moses 1 можно копировать и соединяя выходы предыдущих %64 со входами + 63 получить сложный 64-х позиционный циклический селектор.
В патче с мелодией используется дублирование объектов noteout. Такой приём позволяет повысить громкость в обход предельной отметки. Так, если на 2 noteout 1 подключить один и тот же makenote 67 127, но при отправке сигнала с первого входа makenote, нота ля (код 67, первая октава) будет воспроизводиться с громкостью 127*2 = 254. Миди-инструменты в Pure Data очень тихие, поэтому данный приём оказывается полезным при создании ансамблевых мелодий (в нашем случае играют 2 фортепиано на каналах 1 и 5 и ударная установка 10 с утроенной громкостью).
Для декорирования симуляции (процесса перемещения данных между объектами патч-кода в режиме выполнения) удобно использовать дублированные кнопки. В примере выше их 6-и объектов bang составлен силуэт кнопки Play, который автоматически подсвечивается при нажатии крайней кнопки bang справа. Обработка сигналов bang — ресурсоёмкий процесс, но в следующем разделе мы разместим целый массив таких кнопок и попробуем изобразить с их помощью танец из видео.
Анимация
Для анимации нам потребуются всё те же переменные f, объекты — арифметические операторы и метроном, а также селекторы. В этот раз нам нужно всего 8 кадров, поэтому воспользуемся объектами sel. Создадим поле 20х10 из кнопок bang (будем использовать операцию копирования). После помещения в центр патча с мелодией, получим следующий рисунок:
Можно просто подключить левые выходы каждого селектора к тем кнопкам, которые должны быть нажаты на данном кадре, и направить сигнал от метронома на вход новой переменной цикла f. Но у такого подхода есть 2 изъяна:
- 0.23 сек на кадр — слишком быстро для нашего танца. А использование второго метронома собьет нас с ритма: noteout и bang имеют разную задержку ответа на сигнал;
- кнопка остаётся нажатой в течении 0.05 секунд с момента получения сигнала, значит оставшиеся 0.18 сек «экран» анимации будет пустым и картинка в цикле будет мигать.
Для решения этих проблем мы используем деление. Следующая конструкция:
принимает на вход значение f основного цикла-мелодии. После деления на 2 мы получаем замедление смены кадров: для 0 и 1 будет показан кадр 1, для 2 и 3 кадр 2 и так далее. На каждый кадр по-прежнему отправляется по одному сигналу каждые 0.23 секунды. Теперь воспользуемся ещё одним циклом:
Для синхронизации с основной мелодией ему добавлена задержка: сигнал включения проходит от селектора сначала через кнопку и только потом к первому входу метронома. Теперь если создать bang и соединить его выход со всеми кнопками-пикселями некоторого кадра, то циклом выше можно подсвечивать кадр каждые 0.046 секунд, что быстрее частоты обновления кнопок -т.е. кадр будет гореть непрерывно до смены следующим кадром. Цикл в 5 итераций гарантирует, что кадры не будут наслаиваться, так как 0.046*5 = 0.23 сек — период селекторов кадров.
При создании анимации компьютер регулярно зависал, но удалось отрисовать 5 различных картинок и создать из них цикл на 8 кадров в порядке:
1 2 3 2 1 4 5 2
Итоговый патч с Coffin Dance (добавлено множество декоративных элементов — кнопок и комментариев) в режиме редактирования:
Едва ли в этой инфернальной картине можно разобрать исходный код симуляции, однако патч корректно работает:
В правом нижнем углу используется счётчик для автоматической остановки проигрывателя после 121-ой ноты. Изначально предполагалась анимация 48х24 пикселя, но компьютер от неё категорически отказался жалобным рёвом кулера и неожиданным перезапуском винды в безопасном режиме. Оригинальный пиксель арт 48х24 от художницы LikaLou прикрепил в конце видео. Итак, спустя 3 дня «рисования музыки» имеем Coffin Dance в Pure Data:
Заключение
Визуальное программирование мемов — занятие весёлое, хоть и отнимает много времени и иногда уничтожает ваше железо. Даже такая, на первый взгляд простая, задача о похоронном танце требует модульного подхода к созданию программы, знание особенностей не только языка реализации, но и среды разработки.
По примеру патча для Coffin Dance можно судить об уровне развития визуального программирования. Есть графические языки, хорошо справляющиеся с некоторыми прикладными задачами, если удобные среды разработки. Но индустрия визуального программирования в целом находится в зачаточном состоянии.
Ссылки
Официальный сайт Pure Data
Основы визуального программирования в Pure Data
О стандарте VST
Статья про Coffin Dance на Википедии
Coffin Dance в Pure Data: оригинальное видео