Музыкальный D: синтезатор в 100 строк

Моя статья 2018 года, изначально написанная для блога LightHouse Software. Приведенный код актуален и сегодня, статья служит неплохим вводным руководством к dlib.audio.

Библиотека dlib предоставляет базовые инструменты для работы с аудиоданными, которые позволяют написать синтезатор с сохранением полученных звуков в WAV. В этой статье я покажу, как с их помощью сгенерировать знаменитую мелодию «Popcorn» Гершона Кингсли, используя для этого всего три функции, умещающиеся в 100 строк кода.

(далее…)

Обновления

dlib 1.3.0

Вышла новая версия dlib. В библиотеке появился новый пакет dlib.math.random с реализацией генератора псевдослучайных чисел на основе C-функции rand. Проделан ряд улучшений в математическом пакете: добавлена поддержка компилятора GDC в модуль dlib.math.sse, появилась новая функция интерполяции bezierQuadratic.

Количество скачиваний dlib в реестре DUB достигло 1400 в месяц — рекордный показатель за все время существования проекта!

Подготовка к релизу Dagon 0.16

Новая версия Dagon планируется к выпуску совсем скоро — на днях внес ряд багфиксов и улучшений в физику Newton (в частности, исправлен прыжок контроллера персонажа на плоских поверхностях), а также добавил встроенную функцию создания скриншотов — Application.takeScreenshot.

Статус по проектам

Начало года — неплохое время для того, чтобы поразмыслить над тем, что я буду делать в ближайшем будущем, и в какую сторону будут двигаться мои OpenSource-проекты. Также у меня есть ряд других направлений деятельности, о которых я тут еще не упоминал. Если интересно, читайте дальше.

К сожалению, далеко не все, что я делаю, имеет какие-либо перспективы серьезного развития. Некоторые из проектов остались на уровне любительских, другие я просто физически не успеваю развивать надлежащими темпами. Третьи — это своего рода эзотерика в сфере IT, понятная и интересная лишь единицам)

Dagon. Развивается, но медленно. В данный момент я работаю над версией 0.16, в которую войдут нововведения прошлого года: подповерхностное рассеивание и зонды освещения среды. Я планирую работать над движком и дальше, планы по нему довольно масштабные: так, я хочу сделать встроенный редактор на основе ImGUI — он будет генерировать исходники на D и вызывать DUB для сборки проектов, таким образом движок станет намного дружелюбнее для начинающих. Также в планах переписать стек постпроцессинга.

dlib. Понемногу развивается ветка 1.x, а вот 2.x пока заморожена. Исследую возможность поддержки прогрессивных JPEG.

Electronvolt (проект Atrium). Временно заморожен, но не исключено, что я к нему еще вернусь.

bindbc-wgpu, dusk и прочие наработки по WebGPU на D. Периодически обновляю, но для серьезного перехода на WebGPU пока нет мотивации и ресурса. Никаких особых планов на этот счет тоже нет.

dray. Новый проект — движок рейкастинга на основе dlib. Находится в разработке. Я в него переношу код из моего старого физ. движка dmech, а то пропадают даром хорошие алгоритмы) На сегодняшний день dray уже поддерживает пересечение луча с произвольной выпуклой геометрией, движок включает классы для таких стандартных тел, как сфера, параллелепипед, цилиндр, конус, эллипсоид. Тела могут быть произвольным образом трансформированы при помощи матриц 4×4.

Журнал FPS. Он жив-здоров, просто уже не имеет PDF-версии и существует в виде новостной странички. У меня есть идея окончательно объединить его с порталом Xtreme3D — думаю, это будет сайт или даже целая соцсеть по любительской разработке игр.

Этос Метамодерна. Telegram-канал для пространных философских рассуждений, которые по формату не подходят для других моих площадок.

Живопись и 3D-моделирование для стоков я окончательно забросил — коммерческий выхлоп там теперь уже нулевой, а времени работать для души совершенно перестало хватать.

Не упоминаю тут свои бизнес-процессы — это большая тема для отдельной статьи, и даже не одной.

Итоги 2023 года

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

  • Можно считать, что я достиг своей цели — стать профессиональным разработчиком игр. На сегодняшний день солидную долю моих доходов составляют фрилансерские проекты по разработке рекламных мини-игр и интерактивных баннеров. И, хотя все они пишутся на JavaScript, я стал чаще по работе использовать D для создания различных утилит, что не может не радовать.
  • Выпустил Dagon 0.15, в котором появилась поддержка Hald CLUT, 1D и 3D-текстуры, новые параметры постобработки для ручного управления эффектом Depth of Field, новые методы для текстур, обновлен биндинг Newton.
  • В репозиторий с примерами Dagon добавлены примеры создания ландшафта и работы с пользовательскими шейдерами.
  • Выпустил dlib 1.2.0 и 1.2.1. Добавлены функции гомотетии (масштабирования относительно точки), функции конвертации радианов в обороты и обратно, медианный фильтр, а также функция отрисовки прямоугольника. Спасибо Олегу Бахареву aka aquaratixc, Aaron Nédélec aka ReactiveAlkali, Razvan Nitu aka RazvanN7, Nick Treleaven aka ntrel за багфиксы и новые фичи.
  • Я наконец-то дописал вводную статью о PBR для начинающих, она доступна на сайте журнала «FPS». Кстати в уходящем году журналу исполнилось уже 15 лет.
  • Начал изучать микроконтроллеры — Arduino и ESP32. Полным ходом иду к своей детской мечте — сделать своими руками радиоуправляемую машинку (как-нибудь напишу пост об этом проекте).

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

  • Выход Blender 4.0
  • Выход Godot 4
  • Смена ценовой политики Unity
  • Появление ChatGPT (хотя я не думаю, что нейросети в обозримом будущем кого-то заменят).

Обновления

Лето подошло к концу — это печально 🙁 Но при этом спешу порадовать читателей хорошими новостями: одновременно вышли новые версии dlib и Dagon.

dlib 1.2.1 — в основном, исправляющий релиз. Исправлены ошибки в dlib.filesystem.posix.common, предупреждения о депрекациях в dlib.core.thread и dlib.math.utils. В dlib.image добавлен медианный фильтр (модуль dlib.image.filters.median).

Dagon 0.15.0 — первый релиз за последний год. Добавлена поддержка 1D и 3D-текстур и новые методы для класса Texture (setFaceImage, setFaceBit, createFromImage3D). Шейдер цветовых таблиц (LUT) теперь поддерживает таблицы в формате Hald CLUT — нужно создать 3D-текстуру из изображения Hald CLUT и просто передать в game.postProcessingRenderer.colorLookupTable. Появились новые параметры шейдера глубины резкости (DoF) для ручного управления эффектом: dofManual, dofNearStart, dofNearDistance, dofFarStart, dofFarDistance. Загрузчик glTF теперь декодирует встроенные в JSON-файл текстуры. Менеджер событий обзавелся поддержкой относительного режима мыши (EventManager.setRelativeMouseMode) для более надежной реализации управления мышью в играх. Соответственно, компонент управления FirstPersonViewComponent теперь использует по умолчанию относительный режим.
Обновился биндинг Newton Dynamics до свежей версии библиотеки: функция NewtonUserJointSetRowSpringDamperAcceleration удалена в пользу NewtonUserJointSetRowMassDependentSpringDamperAcceleration и NewtonUserJointSetRowMassIndependentSpringDamperAcceleration. Добавлен новый метод для ньютоновских твердых тел: NewtonRigidBody.localPointVelocity.

dlib 1.2 и другие обновления

Несколько обновлений за последние месяцы:

dlib 1.2.0 — добавлены функции гомотетии (homothetyMatrix, homothetyMatrix2D) в модуль dlib.math.transformation, функции конвертации радианов в обороты и обратно (radtorev, revtorad) в модуль dlib.math.utils. В dlib.image.render.shapes появилась функция отрисовки прямоугольника (drawRect) — автор реализации ReactiveAlkali. Добавлен файл CODE_OF_CONDUCT.md — краткий кодекс поведения для сообщества dlib:

Code of Conduct

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

1. Мы не приемлем нецензурные выражения, грубые и/или пренебрежительные сообщения, использование сексуальной, жестокой и иной оскорбительной лексики и изображений.

2. Любая критика должна быть конструктивной и разумной. Простого личного недовольства без каких-либо объективных причин недостаточно, чтобы делать критические заявления и влиять на развитие dlib.

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

Также добавлена поддержка Doxygen для генерирования документации.

bindbc-wgpu 0.16.0 — синхронизация с wgpu-native 0.16.0.1. Кстати, также обновил демку wgpu-dlang, добавил поддержку мипмаппинга и перевел на dlib 1.2.0.

Dagon 0.15 пока, к сожалению, откладывается на неопределенный срок — не доходят руки сделать поддержку 1D-текстур, которой не хватает для того, чтобы реализовать загрузчик KTX. В марте добавил новые параметры постобработки для ручного управления эффектом Depth of Field (dofManual, dofNearStart, dofNearDistance, dofFarStart, dofFarDistance) — это нужно, если необходимо реализовать кастомную расфокусированность, труднодостижимую стандартными параметрами focalDepth, focalLength и fStop.

Обращение к пользователям dlib, Dagon и других моих публичных проектов в связи с происходящими событиями

Я, Тимур Гафаров, создатель и мэйнтейнер dlib, Dagon, bindbc-wgpu, bindbc-newton, bindbc-soloud и др., в настоящее время живу в России без возможности в обозримом будущем покинуть эту страну.

В целях личной безопасности, не буду сейчас комментировать саму ситуацию и делать заявления, которые могут быть расценены как политические. Скажу только, что при работе над свободным ПО между людьми не должно быть границ. dlib объединяет труд разработчиков из Украины, России и многих других стран. Политика не должна мешать людям во всем мире делиться знаниями, создавать нечто новое, решать интеллектуальные задачи.

Тем не менее, реальность такова, что разработчики из России в любой момент могут оказаться отрезанными от мирового сообщества СПО. Это серьезно повлияет на проекты, в которых они принимают участие. Я искренне надеюсь, что этого не произойдет. В противном случае я, к моему огромному сожалению, не смогу управлять разработкой dlib, Dagon и других моих пакетов в реестре Dub. В отсутствие сопровождения эти пакеты будут постепенно терять актуальность. Также у меня пока нет уверенности, что я смогу получать финансирование через Patreon и PayPal.

До ухудшения ситуации, пока у меня есть доступ к GitHub и реестру пакетов, я продолжаю сопровождение dlib и Dagon, в настоящее время уделяя этому минимум своего свободного времени. Я на неопределенный срок приостанавливаю разработку dlib 2.0 и новых версий Dagon из-за негативного психологического фона и неясной картины будущего. В случае катастрофического развития событий я не обещаю, что смогу посвящать проектам даже минимальные время и силы.

В связи с этим, я с сегодняшнего дня не рекомендую использовать dlib, Dagon и другие мои пакеты в качестве критических зависимостей в важных программных продуктах. Кто хочет взять активную разработку на себя, создать форк — буду только рад. Пожалуйста, не делайте pull request’ы с большим количеством коммитов, существенными изменениями и новыми фичами, которые мне придется читать и проверять — у меня для этого может физически не оказаться возможности. Если у меня сохранится доступ к GitHub, я, скорее всего, увижу ваш форк и сам сделаю слияние, как только смогу.

P.S. Я в срочном порядке выпустил Dagon 0.13.0.

P.P.S. Облачная папка с примерами Dagon (Sponsor Folder), которая была ранее доступна только для подписчиков на Patreon, теперь открыта для всех.

dlib 1.0

Спустя 10 лет разработки моя библиотека общего назначения для геймдева наконец-то стабилизировалась — итог этой работы отражает первый мажорный релиз проекта, dlib 1.0.0.

Из наиболее важных нововведений могу отметить ускорение загрузки изображений (от 2 до 10 раз в зависимости от формата и веса файла) — оптимизация заключается в том, чтобы декодировать из буфера в памяти, а не напрямую из файлового потока. Добавлена валидация при создании POSIX-потоков, улучшен модуль dlib.math.interpolation.hermite — добавлена функция вычисления производной для сплайна Эрмита. Исправлено несколько важных багов в математическом и геометрическом пакетах.

В связи с этим знаковым релизом, а также тем, что в этом году dlib как публичному OpenSource-проекту исполняется ровно 10 лет, хочу немного рассказать об истории этой разработки.

dlib фактически начался как порт разнородных исходников с C++ на D. В 2010-11 годах, когда я познакомился с D, у меня был свой небольшой игровой движок на C++ (Phantom3D), а также библиотека общего назначения Sparx, и я решил портировать их на D2 — язык в те годы как раз стабилизировался. Начал, естественно, с векторной алгебры — так появился dlib.math, старейший и наиболее законченный из пакетов dlib. Сам 3D-движок, конечно, претерпел немало трансформаций и, в итоге, от старого кода почти ничего не осталось — мой следующий движок DGL был написан почти с нуля, хотя и начинался как полный порт Phantom3D и сначала носил то же название.

Sparx я поначалу использовал в D в виде динамически слинкованной библиотеки, через Derelict. Но это было не очень удобно, поэтому следующим этапом стало переписывание Sparx на D, в результате чего родился dlib.image — Sparx использовался в основном как загрузчик ресурсов (изображений PNG, JPEG, JPEG2000, TGA, BMP, DDS, моделей OBJ и MD5). Сейчас, правда, старого кода из Sparx в библиотеке очень мало — но, например, отдельные участки кода dlib.image, некоторые математические и геометрические функции напрямую унаследованы оттуда.

Сама идея создать такую библиотеку появилась в результате осмысления чужих открытых проектов, в особенности Давида Анри (чудо, что его сайт до сих пор существует!). Изучая чей-нибудь код, я часто замечал, что многие функции и классы можно сделать обобщенными и независимыми, что позволяет переиспользовать их в самых разных проектах — наверное, около половины кода любой игры можно вынести в библиотеку общего назначения. Это, в первую очередь, код, который не зависит от графического API и логики движка — линейная алгебра, вычислительная геометрия, работа с файловой системой, многопоточностью, взаимодействие с ОС, сетью и т.д. Главный принцип — скрыть платформозависимый код под абстрактным интерфейсом, так, чтобы приложению не приходилось взаимодействовать с операционной системой напрямую. Это делает код приложения на удивление простым.

Но вернемся к проектам Давида Анри. Меня очень впечатлили его загрузчики PNG, TGA и BMP — они очень сильно повлияли на мои собственные реализации декодеров этих форматов. Декодер JPEG — отдельная история, я написал его полностью самостоятельно, не заглядывая в готовые реализации — только читая спеки и техническую литературу (фактически, из заимствований в нем только ДКТ-преобразователь — хардкорная fixed-point математика, написанная темными магами). Модули для работы с векторами и матрицами — тоже отчасти влияние Анри, а именно его библиотеки Mathlib. Мои реализации, конечно, за 10 лет стали намного лучше — в dlib все алгебраические объекты обобщенные, одно и то же описание используется для векторов и квадратных матриц всех стандартных размерностей (2, 3 и 4). Выше 4 я поддерживать не стал, так как в компьютерной графике они практически не используются. Также у меня есть оптимизированные функции инвертирования и декомпозиции матриц, свизлинг векторов, огромное множество функций-фабрик и различных операторов — практически все, что может понадобиться для любых вычислений, связанных с трехмерными моделями.

Точная дата юбилея dlib — 28 сентября, так как именно в этот день я создал репозиторий на Google Code (я тогда еще использовал SVN, а не Git). Самый интересный этап в жизни проекта начался в 2013, когда разработка была перенесена на GitHub. Появился модуль dlib.core, к проекту примкнули новые разработчики: Martin Сejp провел огромную работу по реализации потоков ввода-вывода и абстрактной файловой системы (dlib.core.stream, dlib.filesystem), Eugene Wissner написал сетевой пакет (dlib.network) и аллокаторы памяти (dlib.memory), Nick Papanastasiou написал модуль комбинаторики, Вадим Лопатин (к слову, автор знаменитой читалки Cool Reader) помог улучшить декодер PNG, Роман Чистоходов и Андрей Пенечко внесли множество исправляющих патчей. Отдельное спасибо Роману за улучшенную поддержку BMP и TGA, а также ценные советы.

В 2015 году я начал рефакторинг, связанный с поддержкой ручного управления памятью. Эта грандиозная работа была полностью завершена в 2019 году. Динамическая память — это отдельная большая история, далеко выходящая за рамки D, я мог бы написать целую книгу на эту тему. Если теория вычислений разработана математиками и инженерами очень глубоко и досконально, то «теории памяти», в общем-то, до сих пор нет. Все, что мы имеем научного в этой сфере — это теория типов и ее высшее достижение, система типов Хиндли-Милнера. К сожалению, нет какого-то общего, формального подхода к управлению динамической памятью — сколько языков, столько и практик. Полагаю, что необходимо выделять парадигмы памяти, подобно тому, как существуют парадигмы программирования в целом.

В dlib я реализовал парадигму единственного владельца (single ownership), которая очень хорошо ложится на базовое ООП. Каждый объект может «владеть» другими объектами. У любого объекта бывает только один владелец, либо нет владельца вовсе (у корневых объектов). Когда удаляется владелец, удаляются и все объекты, которыми он владеет. Такой подход позволяет полностью избавиться от сборщика мусора и сделать высвобождение памяти детерминированным. Объектами в этом контексте может быть все, что угодно — парадигма не зависит от логики приложения — но, естественно, речь идет о данных, которые создаются один раз и надолго (в предельном случае — о неизменяемых наборах данных, которые создаются при старте приложения и удаляются при завершении работы). Эта модель не очень хорошо работает с алгоритмами, где нужно создавать и уничтожать объекты многократно, в цикле — но на то есть аллокация в стеке. Именно подход к работе с динамической памятью я считаю одним из главных достижений dlib — эту фичу, я думаю, можно и нужно нести в мир, который, кажется, сдался без боя сборщикам мусора.

Впрочем, рефакторинг, связанный со сменой парадигмы памяти, не привел к тому, что dlib стала @nogc-библиотекой (то есть, формально, по контракту гарантирующей отсутствие вызовов сборщика). Этой целью пришлось пожертвовать для сохранения обратной совместимости API. Но у меня есть план вернуться к этой проблеме в следующей версии, dlib 2.0 — расскажу о своих идеях в одном из будущих постов.

В 2016 году появился пакет dlib.audio, который может служить бэкендом для звуковых движков, плееров, DAW и т.д. Пока реализованы только базовые функции, но пакет будет развиваться. Также появился пакет dlib.network, который содержит независимую от Phobos поддержку сокетов и функции, связанные с вебом. В 2021 году работа над основной функциональностью библиотеки была завершена.

dlib — это, наверное, самый грандиозный мой проект за всю жизнь. Начавшись как маленький набор модулей «на все случаи жизни» для экспериментов с OpenGL, библиотека превратилась в один из самых популярных пакетов в реестре Dub. Сейчас количество загрузок dlib составляет 800-1000 ежемесячно. На основе dlib пишутся игровые движки, инструменты анализа изображений, GUI-тулкиты, даже синтезаторы и библиотеки генетических алгоритмов. О такой широкой области применения я, честно говоря, изначально даже не думал! К сожалению, D все еще остается довольно нишевым языком, и востребованность библиотек на нем ограничена востребованностью самого языка — но я счастлив уже тем, что сделал для сообщества что-то полезное. Накопление алгоритмов, достижений математической и инженерной мысли в одном месте, в удобочитаемом виде — я считаю, дело нужное. И, в конечном счете, это был интересный путь.

Как ускорить загрузку изображений

Совет пользователям dlib. Не декодируйте изображение напрямую из файлового потока, это слишком медленно. Вместо этого рекомендую загрузить файл в память целиком, создать поток массива (ArrayStream) и уже его передавать в функцию-декодер:

InputStream input = openForInput("image.jpg");
ubyte[] data = New!(ubyte[])(input.size);
input.fillArray(data);
ArrayStream arrStrm = New!ArrayStream(data);
SuperImage img = loadJPEG(arrStrm);
Delete(arrStrm);
Delete(data);
input.close();

Для JPEG, например, это дает ускорение в 5-10 раз, в зависимости от размера картинки.

Несколько новостей

  • Готовлю к выпуску dlib 1.0. Бета-релиз, скорее всего, состоится уже в январе — осталось довести покрытие до 50% (поставил такую цель несколько лет назад) и провести аудит некоторых модулей
  • Выложил все свои старые игры на этот сервер, ссылки в разделе Игры обновлены.