Не как у людей — история проекта 1tracker

в 9:00, , рубрики: AngelScript, ruvds_статьи, sdl2, Блог компании RUVDS.com, Демосцена, звук, интерфейсы, музыка, Программирование, ретрокомпьютеры, трекерная музыка, чиптюн

Не как у людей — история проекта 1tracker - 1


Весной 2012 года я опубликовал первую версию экспериментального мультисистемного кроссплатформенного музыкального редактора с интерфейсом типа «трекер» — 1tracker v0.1. Экспериментальность заключалась в проверке новых подходов и отказе от общепринятых стандартов для подобного рода программ. Прошло десять лет, редактор до сих пор экспериментальный, до сих пор мало кому известен, и до сих пор не добрался до версии 1.0. Тем не менее, он регулярно пригождается нескольким странным людям на планете, а новые концепции вполне успешно выдержали проверку временем, хотя вряд ли когда-нибудь станут новым стандартом. Пора выйти из сумрака и рассказать обо всём этом подробнее. И заодно про историю трекеров.

▍ Оглавление

▍ Ретроспектива

Без нашей любимой историко-познавательной справки никак не справиться.

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

Концепция трекеров оформилась в 1987 году в виде программы The Ultimate Soundtracker для компьютера Commodore Amiga. Пресса назвала новинку нелогичной, сложной и непредсказуемой, публика сказала — «да, это отвратительно, пожалуйста, продолжай», и за последующие десятилетия появилось ещё несколько сотен аналогичных программ, основная часть которых так или иначе скроена по лекалам исходного Soundtracker. Краткий обзор только самых популярных из них потянул бы на отдельную большую статью, так что я даже не буду и пытаться. Упомяну только несколько, названия которые наверняка на слуху у старожилов: Protracker, Scream Tracker, Fast Tracker II, Impulse Tracker — этого достаточно, чтобы понять, о чём идёт речь.

Не как у людей — история проекта 1tracker - 2

Самый первый Ultimate Soundtracker на Commodore Amiga

Особенностью трекеров является то, что они придуманы и создавались не профессионалами от мира музыки, как, например, предшествовавшие их появлению MIDI-секвенсоры, а компьютерными энтузиастами-одиночками. За всю историю было создано всего несколько трекеров, которые можно отнести к профессиональному музыкальному ПО (Renoise и Polyend). Такое происхождение накладывает очень заметный отпечаток на интерфейс и в целом на дизайн этих программ.

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

Не как у людей — история проекта 1tracker - 3

Protracker, Scream Tracker, Impulse Tracker. Fast Tracker II

Трекеры являются европейской традицией с немецко-финскими корнями, также успешно прижившейся на Западе. Из-за этого в западном мире одно время бытовало расхожее мнение, что трекеры были основным способом создания музыки для видеоигр, в частности, в 8- и 16-битную эпоху. Однако основным производителем игр и музыки к ним в то время была Япония, и именно в этой стране концепция трекера почему-то совершенно не прижилась — там композиторы как шли своим, куда более хардкорным путём (хекс-коды или язык MML, являющийся развитием синтаксиса оператора PLAY в Бейсике) до появления трекеров, так и продолжили после. Да и в западном мире в то время применение трекеров для видеоигр оказалось ограниченным. Основное же своё применение они нашли на демосцене и в кругах музыкантов-любителей.

Популярность трекеров росла на протяжении 1990-х, к концу десятилетия достигла пика, вероятно с появлением ModPlug Tracker, и постепенно начала спадать. Но ещё долгие годы создавались новые трекеры для старых платформ, сформировалась концепция трекеров следующего поколения. Разработка новых подобных программ продолжается до сих пор, хотя уже не в таких количествах.

Не как у людей — история проекта 1tracker - 4

ModPlug Tracker для Windows

▍ Их обычаи

Структура композиции практически в любом трекере одинакова. Она состоит паттернов (фрагментов), ордер-листа (списка позиций) и инструментов. Паттерны и ордер-лист представляют собой нотный текст композиции, а инструменты определяют её звучание. Изредка могут присутствовать дополнительные сущности. Всё это вместе, сохранённое в файл, часто называют модулем.

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

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

Строка паттерна делится на каналы, представляющие полифонию (многоголосие) композиции. Число каналов зависит от возможностей целевого звукового устройства или формата. Изначально это было четыре канала, для многих 8-битных звуковых чипов три, в целом же это может быть от одного до 32-х каналов, редко больше, так как навигация в таком широком паттерне крайне затрудняется.

Нота в позиции записывается в алфавитной нотации, знакомой многим по обозначениям гитарных аккордов — C, D, E, F, G, A, B (от До до Си), вместе с октавой. Например, C-4 или E#5. При этом ввод этих нот производится не одноимёнными клавишами, как мог бы подумать человек, ранее не имевший дела с компьютерными музыкальными программами. Ноту C обычно вводят клавишей Z, ноту D — клавишей X, ноту E — клавишей C, и так далее. Куда же далее, и где тут логика, спросите вы? Логика есть: это расположение клавиш компьютерной клавиатуры, геометрически соответствующее клавишам пианино. Октаву ноты также не впечатывают цифрой, она зависит от выбранной в момент ввода ноты октавы.

Не как у людей — история проекта 1tracker - 5

Соответствие клавиш пианино кнопкам компьютерной клавиатуры. Верхний ряд — на октаву выше, её начало частично дублируется концом нижнего ряда.

Такая система довольно удобна и давно стала стандартом для музыкального ПО, в том числе и профессионального. Некоторые проблемы возникают у пользователей клавиатур других раскладок, типа AZERTY и DVORAK, так как опрос клавиатуры производится не по геометрической позиции клавиши, а по скан-коду, и разработчики часто забывают учесть существование других клавиатурных раскладок.

Нота в паттерне может сопровождаться параметрами, такими как номер инструмента, громкость или глубина эффекта, например слайда, или вибрато. Всё это вводится шестнадцатеричными цифрами. Да, типы эффектов нужно помнить по номерам, благо, большинство трекеров придерживается заданного в оригинальном Soundtracker’е стандарта (1 — слайд вверх, 2 — слайд вниз, 3 — портаменто, 4 — вибрато, и так далее).

В трекерах принято повсеместно использовать шестнадцатеричные цифры — для номеров строк, для значений параметров, для чего угодно ещё. Это опять же следствие того, что создавались они программистами, можно сказать — хакерами. Логика в этом тоже есть, так как в музыке часто используются степени двойки, а двузначная шестнадцатеричная цифра кодирует диапазон значений 0..255 — байтового параметра, экономно представляя их на экране. Но порог вхождения при этом заметно повышается, а дополнительное веселье доставляет то, что многие трекеры смешивают десятичную и шестнадцатеричную системы в разных полях, и это никак не обозначается. Более того, некоторые трекеры пошли немного дальше, и для задания номера инструмента используют буквы вплоть до Z, то есть 36-тиричную систему.

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

Есть два основных вида ордер-листов. Классический, из Soundtracker, имеет одну цифру в позиции — номер многоканального паттерна. В некоторых последующих трекерах, в основном для 8-битных платформ, пошли дальше, и разделили паттерны на каналы. Это позволяет значительно эффективнее экономить память, а также добавляет гибкости — можно повторять один и тот же рисунок ударных, меняя паттерны каналов мелодии и баса. Но это также и затрудняет ориентирование в треке, так как позиции ордер-листа по-прежнему отображаются на экране в виде одного общего многоканального паттерна. Только на Amstrad CPC возникла традиция отображения одноканальных паттернов, но это тоже не добавляет наглядности.

В целом, у трекеров по сравнению с секвенсорами и цифровыми студиями как бы отсутствует самый верхний уровень представления проекта, глобальный вид, на котором можно оценить одним взглядом содержание модуля в целом. Основным визуальным представлением в трекере является паттерн, маленький редактируемый кусочек композиции, а его место в общей картине можно определить только по небольшому окошку с hex-значениями.

Что касается инструментов, которыми управляет нотный текст, есть три основных направления.

Исторически первым были трекеры, управляющие аппаратным сэмплером компьютера Commodore Amiga, то есть инструменты были представлены в виде оцифровок реальных инструментов. Так родилось направление, называемое просто «трекерной музыкой», представленное форматами MOD, XM, IT, S3M и рядом других. Если формат MOD был сильно привязан к конкретной аппаратуре Amiga (чипу Paula), последующие форматы двинулись в сторону развития возможностей MOD на основе более продвинутых программных синтезаторов.

Так как Amiga слегка опередила время, на рынке одновременно с ней было представлено множество 8-битных компьютеров, с более простыми звуковыми чипами, синтезирующими звуки из набора параметров. Практически сразу же появились трекеры и для них, в частности, для Commodore 64, ZX Spectrum, Amstrad CPC, семейства MSX, и множества других. Это направление именуется «чиптюном», и хотя термин возник на той же Amiga, обозначая трекерную музыку с очень маленькими сэмплами, звучание которой похоже на звуки 8-битных компьютеров, впоследствии он стал также применяться к музыке для реальных старых чипов, не обязательно трекерного происхождения.

На PC во времена MS DOS трекеры были представлены сначала Scream Tracker и форматами STM/S3M, воспроизводящими ранее достигнутое на Commodore Amiga, а далее как трекерами для более продвинутых форматов XM/IT, так и специализированными трекерами для чипов FM-синтезаторов OPL2/OPL3, использовавшихся в старых звуковых картах.

Не как у людей — история проекта 1tracker - 6

RAD Tracker для MS-DOS, для звукового чипа OPL2 (звуковой карты Adlib)

В 2000-х на PC также появились трекеры «новой школы», в которых вместо сэмплов и звуковых чипов используются продвинутые программные синтезаторы, встроенные или в виде плагинов формата VSTi, а также так называемые кросс-трекеры — эмулирующие звуковые чипы различных платформ прошлого, позволяющие комфортно писать музыку, которую можно впоследствии воспроизвести на таковых платформах, например, на том же Commodore 64, и использовать в программах для него. Сейчас это основной способ создания музыки при разработке программ и игр для ретроплатформ.

1tracker также относится к кросс-трекерам, и кажется, мы постепенно приближаемся к теме статьи. Но не слишком быстро.

▍ Трекер курильщика

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

Довольно значительной проблемой трекеров, и в особенности кросс-трекеров, является их разнообразие и привязанность к конкретным целевым платформам и форматам. Нужно написать музыку для ZX Spectrum для чипа AY — вот трекер Vortex Tracker II. Для того же ZX Spectrum, но для бипера — вот совершенно другой трекер Beepola, где отличается не только набор звуковых возможностей и ограничений, но и весь интерфейс. Для Commodore 64 — а вот ещё один, GoatTracker, с интерфейсом, больше похожим на hex-редактор, где даже опытный пользователь Vortex и Beepola не сможет разобраться без вдумчивого чтения описания, а оно вызывается по кнопке F12, тогда как F1 проигрывает трек. То есть для каждой целевой платформы помимо её собственных возможностей также нужно осваивать очередной интерфейс, со своими особенностями и управляющими клавишами.

Не как у людей — история проекта 1tracker - 7

Кроссплаформенный GoatTracker для звукового чипа SID (Commodore 64), король враждебных к пользователю интерфейсов. При этом очень мощный

Ситуация становится плачевной, если музыку нужно написать под чип или синтезатор, для которого трекера нет. Например, непопулярный чип прошлого, которому нужно дать вторую жизнь, или вновь созданный программный синтезатор для самодельной игровой консоли на микроконтроллере (Arduboy, ESPboy, десятки их). Выбор небогатый — или писать очередной трекер с нуля (1114 конкурирующих стандартов), или попытаться приспособить какой-нибудь существующий. До недавнего времени с последним было туго, переделывать было особо нечего, трекеры всегда были узкоспециализированным ПО, и их исходный код не предполагал возможности адаптации. Сейчас эту нишу худо-бедно закрывают код FamiTracker и его форков, обладающие хоть какой-то расширяемостью и абстракцией интерфейса от звукового устройства, а также многообещающий open source клон Deflemask’а — Furnace.

▍ Истоки проекта

Причиной, побудившей меня начать создать 1tracker, была пресловутая биперная, она же однобитная, музыка для ZX Spectrum.

Весной 2010 года на пике возрождения интереса к биперной музыке англичанин ccowley выпустил первую версию программы Beepola для Windows — первый кросс-трекер, нацеленный на биперную музыку. Изначально он поддерживал только классический движок Jason C. Brooke из игр серии Savage, позже автор добавил поддержку ещё нескольких классических и вновь созданных движков. Однако доступных движков становилось всё больше, и поддержав 10 штук (включая 4 моих), ccowley вынужден был признать невозможность дальнейшего развития проекта, так как с каждым движком росло количество костылей в коде, изначально не предполагающим расширяемости. К тому же, добавление движков требовало усилий со стороны оригинального автора, так как код на тот момент не был доступен публике, да и сам проект был написан на Delphi, что ограничивало круг людей, способных поучаствовать в его развитии. Несмотря на проблемы, это был прорыв, так как спустя многие годы возможность написания биперной музыки стала доступна широкому кругу желающих — до этого это было возможно только в рамках древних редакторов для ZX Spectrum или написания музыки непосредственно в ассемблерном исходном коде.

Не как у людей — история проекта 1tracker - 8

Beepola для Windows

Так как на тот момент я являлся большим энтузиастом этого направления компьютерной музыки и активным разработчиком новых движков, я искал способ передать новые разработки в руки музыкантов, не умеющих в ассемблер и хитроумные конвертеры из промежуточных форматов. До этого я уже сделал не менее пяти трекеров — это были как кросс-трекеры для Windows, так и программы для реального ZX Spectrum. Если мои предыдущие кросс-трекеры следовали общепринятым шаблонам, в трекерах для ZX Spectrum я вынужденно опробовал некоторые нестандартные подходы, которые были продиктованы и спецификой звуковых движков, и желанием максимально упростить разработку. Также задолго до Beepola я обдумывал концепцию универсального трекера, который в рамках одного интерфейса мог бы совместить несколько разных целевых платформ — нечто подобное позже было реализовано в FamiTracker и Deflemask.

▍ Архитектура 1tracker

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

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

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

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

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

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

Немаловажной технической особенностью трекера является повсеместное использование человеко-читаемого текстового формата для сохранения данных — композиций и инструментов — в файлы. Я когда-то подсмотрел этот подход в Vortex Tracker II, и с тех пор применяю его во всех своих проектах. Плюсы — простота парсинга для импорта-экспорта данных, интеграции с другими программами, возможность исправить что-то вручную в случае бага, либо преобразовать одно в другое стандартными утилитами обработки текста, и некоторая степень самоочевидности формата (нотный текст в файле выглядит как нотный текст). Обычно же трекеры, как и многие программы, сохраняют бинарные блобы, чаще непосредственные слепки памяти, реже сериализацию, изредка XML — разобраться со всем этим без документации невозможно, парсить крайне сложно. А документации, конечно же, как правило нет. Из минусов — пожалуй, размер файлов и скорость парсинга, но в 21 веке это уже не проблема.

Ещё одной небольшой особенностью проекта является лицензия — WTFPL, функционально равная CC0 или Public Domain, то есть полный отказ от прав, а заодно и от обязанностей. В ретроспективе пока непонятно, помогло ли это хоть как-то проекту, так как он сродни неуловимому Джо.

▍ Фронтенд

Фронтенд я решил делать самым минималистичным, насколько это возможно. В нём до сих пор даже нет поддержки мыши, что, как оказалось, не является большой проблемой. При этом изначально было задумано, что при необходимости пользователи могут написать другой, гораздо более комфортный и красивый фронтенд, а мой по сути будет proof of concept. Как известно, нет ничего более постоянного, чем временное, поэтому альтернативных фронтендов до сих пор не создано.

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

Не как у людей — история проекта 1tracker - 9

Разные облики 1tracker

Для озвучивания производимых бэкендом файлов я взял библиотеку Game_Music_Emu, которая на тот момент умела проигрывать форматы AY (ZX Spectrum, бипер и AY-3-8910), GBS (Gameboy), GYM и VGM (8 и 16-битная Sega), HES (PC Engine), KSS (MSX), NSF (NES и Famicom), SAP (8-битные компьютеры Atari с чипом Pokey) и SPC (Super Nintendo). Этой библиотекой определяются все потенциально доступные в трекере целевые платформы. Дополнительные платформы можно добавить доработкой или заменой библиотеки плеера во фронтенде.

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

▍ Бэкенд

Основная функциональная часть, она же самая ценная — бэкенд, реализуется на скриптовом языке. Поэтому главным вопросом на момент начала проекта был выбор скриптового движка. В наличных на тот момент претендентах были JavaScript, Lua, Python и TCL — я имел опыт работы со всеми. Проекту требовалась высокая производительность скриптов, им предстояло перелопачивать десятки килобайт данных за долю секунды, а мой рабочий компьютер не отличался производительностью. Также на тот момент я считал, что выбор языка с необычным синтаксисом сильно поднимет порог вхождения, и мало кто захочет писать скрипты, поэтому склонялся к JS. Однако, просмотрев рейтинги производительности скриптов и проведя тесты, я остановился на варианте, не представленном в списке выше: AngelScript.

AngelScript — скриптовый язык с C-подобным синтаксисом и очень простой интеграцией с проектами на C/C++. Для него есть динамический рекомпилятор, что существенно повышает производительность. В основном он использовался в играх для реализации игровых сценариев. В 2012 году мне ещё не было очевидно последующее доминирование Python (иначе я бы выбрал его), и это очень спорное решение было сделано.

В итоге вся инфраструктура бэкенда написана на AngelScript. Она включает в себя различные функции сборки, кросс-ассемблер Z80 (что позволяет использовать непосредственно исходный код движков, без промежуточных компиляций в бинарники), и загружаемые скрипты-движки. Выбор движка определяет целевой формат и возможности трекера в данный момент. Движков за годы существования проекта было добавлено более полусотни, в основном это биперные процедуры для ZX Spectrum, но также есть движки для чипов SN76489 и YM2413, и даже для платформы Commodore PET.

▍ Интерфейс

Помимо необычной архитектуры, в 1tracker реализован ряд нетрадиционных для трекеров концепций. Так как проект изначально задумывался как экспериментальный, а его части заменяемыми, эксперименты проводились смело.

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

Я решил сделать один паттерн огромной длины. Этот подход я ранее опробовал в паре трекеров для ZX Spectrum, но там но следовал из особенностей звуковых движков. Плюсом его является устранение влияния структуры на музыкальную форму: отсутствие коротких паттернов вынуждает писать более продолжительные и связные музыкальные партии, более разнообразные композиции. Повторяемость фрагментов легко получить простым копированием и вставкой блоков, но принципиальное отличие от ордер-листа в том, что изменение одного фрагмента не меняет все его вхождения в треке автоматически.

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

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

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

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

Не как у людей — история проекта 1tracker - 10

Элементы интерфейса

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

Так как трекер в основном предназначен для музыкальных движков с очень ограниченной полифонией, в 2-3 канала, для удобства создания сложных аранжировок был придуман скрэтч-пэд — теневой трек, который во всём подобен треку основному, только он теневой. Он синхронизирован по строкам и маркерам с основным треком, можно переключаться на него и работать с ним, а можно включить и видеть оба одновременно, и оба они будут воспроизводиться. Также есть функции быстрого перемещения данных из одного трека в другой. Всё это очень пригождается для создания сложных аранжировок в условиях ограниченной полифонии. Например, когда уже есть довольно плотная аранжировка аккомпанемента, и нужно как-то вписать туда ещё и мелодию — её можно спокойно наметить в теневом треке, после чего по нотам продумать взаимодействие с остальными партиями, и перенести в основной трек.

▍ Устройство движка

Для более наглядного объяснения внутреннего устройства трекера по возможности кратко разберу, как работает загружаемый движок. Да, это кратко.

Движки хранятся в файлах с расширением *.1te в папке /engines/. Это текстовые файлы с кодом на AngelScript.

При построении списка доступных движков фронтенд вызывает функцию Info каждого из них. В ней скрипт вызывает функции SetTitle, SetAbout, SetDocFile, чтобы передать фронтенду полезную информацию о себе — название для списка, краткое описание, и ссылку на текстовый файл с подробной документацией, который можно посмотреть во фронтенде:

void Info(void)
{
    SetTitle("ZX|1BIT|Phaser1");

    SetAbout("Original Z80 code and 1tracker version by Shiru, 2010-2015nnTwo channel ZX Spectrum engine. One channel has a phasing effect and supports instruments, the other is just square tone. Synthesized or sampled drums. The columns are speed, note 1, phase reset, instrument, note 2, drum.");
    
    SetDocFile("phaser1.txt");
}

В начале работы пользователь выбирает движок из списка, чтобы начать новый трек, или загружает существующий модуль. При этом вызывается функция Init скрипта. В ней скрипт конфигурирует фронтенд:

void Init(void)
{
    AddColumn(-1,2       	,99,0);
    AddColumn(-1,COLUMN_EMPTY,0 ,0);
    AddColumn( 0,COLUMN_NOTE ,0 ,1);
    AddColumn( 0,1       	,1 ,1);
    AddColumn( 0,2       	,99,1);
    AddColumn(-1,COLUMN_EMPTY,0 ,0);
    AddColumn( 1,COLUMN_NOTE ,0 ,1);
    AddColumn(-1,COLUMN_EMPTY,0 ,0);
    AddColumn( 2,1       	,8 ,1);

    SetNoteRange("C-1","B-5");
    SetPatternHeader(" Row  Sp T1 RIn T2  D");
    SetInstrumentsNumber(99);
    SetLoopSupport(1);
    SetSpeedColumn(0);
    
    for(int ins=0;ins<100;++ins)
    {
   	 SetInstrumentValue(ins,0,1);
    }
    
    SetInstrumentValue(255,0,0);

    ZXSetStandartExportOptions();
}

Функция AddColumn добавляет столбец в паттерн, обычно это столбец ноты, числового параметра, разделителя. Столбцам также присваивается номер канала. Он нужен для поканальных операций во фронтенде — выделения всех полей в канале, заглушения этого канала. Также в паттерне могут быть вспомогательные столбцы, не привязанные ни к одному каналу — это разделители, и, например, колонка скорости. Таким образом задаются все столбцы паттерна, и они могут иметь произвольную конфигурацию — в примере выше это сначала столбец темпа, потом в первом канале нота и два числовых параметра, во втором только нота, а в третьем только числовой параметр. Также задаётся и диапазон числовых параметров.

Функция SetNoteRange задаёт диапазон нот, которые можно вводить для данного движка.

SetPatternHeader задаёт строку-пояснение над паттерном, которая как-либо обозначает содержимое столбцов паттерна.

SetInstrumentsNumber задаёт количество инструментов, поддерживаемых движком — это может быть 0 или какое-то число, и столько инструментов можно будет переключать на вкладке редактора инструментов.

SetLoopSupport включает поддержку точек цикла. Если она не включена, в редакторе нельзя будет установить точки цикла.

SetSpeedColumn передаёт фронтенду номер колонки темпа (скорости проигрывания), если таковая есть. Эта информация нужна только для одной функции оптимизации нотного текста, которая убирает пустые строки, понижая темп предыдущих строк. Это пригождается для экономии памяти при экспорте на целевую платформу.

Функции SetInstrumentValue и SetInstrumentValue устанавливают начальное состояние ячеек памяти для хранения параметров инструментов, которые впоследствии использует редактор инструментов и движок.

Функция ZXSetStandartExportOptions не является частью API, она реализована во включаемой библиотеке zxspectrum.1tl (тоже текстовый файл с кодом на AngelScript). В ней реализованы различные функции, связанные с платформой ZX Spectrum, и данная функция конфигурирует диалог экспорта фронтенда соответствующим образом:


void ZXSetStandartExportOptions(void)
{
    AddExportFormat("AY file","ay");
    AddExportFormat("TAP file","tap");
    AddExportFormat("SCL file","scl");
    AddExportFormat("Assembly code","asm");
    
    Z80AssInit();
}

AddExportFormat добавляет пункт меню экспорта во фронтенде. Z80AssInit снова не является частью API, это инициализация кросс-ассемблера Z80, реализованного в файле z80ass.1tl.

Движок загружен, пользователь редактирует нотный текст согласно заданной схеме. Однако, конфигурация фронтенда ещё не завершена — есть ещё редактор инструментов. Он конфигурируется непосредственно в момент открытия его вкладки, так как в зависимости от текущего выбранного канала и номера инструмента редактор можно конфигурировать по разному. Это даёт возможность создания разнотипных инструментов. Конфигурация производится вызовом функции скрипта Instrument. Например:

void Instrument(void)
{
    if(GetCurrentChannel()<2)
    {
   	 ItemUnsignedByte("Multiple",0,16,0);
   	 ItemUnsignedWord("Detune",0,65535,1);
   	 ItemUnsignedByte("Phase",0,255,3);
    }
    else
    {
   	 if(GetCurrentInstrument()>0)
   	 {
   		 ItemLabel("Set instrument 1 to select drumset");
   	 }
   	 else
   	 {
   		 ItemOption("Drumset","Sampled|Synthesized",255);
   	 }
    }
}

В этом примере есть два варианта конфигурации редактора инструмента. Если курсор находится на первом или втором канале, создаётся конфигурация редактора тонального инструмента из трёх числовых параметров. Если курсор находится в третьем канале, вместо редактирования инструмента происходит выбор одного из двух наборов сэмплов перкуссии.

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

Например, строка ItemUnsignedByte(«Multiple»,0,16,0) добавляет пункт меню для ввода десятичного значения с именем Multiple, диапазон значений составляет от 0 до 16, а хранится это значение в ячейке 0. А строка ItemOption(«Drumset»,«Sampled|Synthesized»,255) добавляет пункт меню Drumset с выбором из двух значений, Sampled или Synthesized, и выбранное значение (соответственно 0 или 1) хранится в ячейке 255.

Наконец, пользователь делает что-то, что требует воспроизвести звук: нажимает клавишу ноты, запускает трек на проигрывание, экспортирует результат в файл. Все эти случаи обрабатывает функция движка Compile.

void Compile(uint format, uint startRow, uint oneShot, uint useMute)
{
   //???
   //PROFIT!
}

Разбирать реальный пример мы, конечно же, не будем — в нём как минимум три сотни строк. Опишу суть.

На входе функция получает параметр format. Он сообщает движку, что именно сейчас нужно скомпилировать. Формат 0 всегда применяется для воспроизведения звука во фронтенде, то есть скрипт движка должен собрать один из файлов-контейнеров, поддерживаемых Game_Music_Emu, причём он должен учесть значения всех прочих входных параметров: начальную строку для проигрывания, проигрывается ли только одна строка (при озвучивании ввода ноты), или весь дальнейший трек, нужно ли глушить каналы. Получив контейнер, фронтенд начинает его воспроизведение, звучит звук.

Если значение format не равно 0, скрипт должен собрать что-то для последующего экспорта в файл. Это номер пункта меню экспорта, которые добавлялись функцией AddExportFormat. Скрипт сам задал эти пункты и знает, что ожидает получить пользователь.

Чтобы скомпилировать собственно музыкальный трек, скрипт может получить всю необходимую информацию о модуле из фронтенда. Например, функции GetSongLength, GetSongLoopStart и GetSongLoopEnd возвращают длину актуальной части паттерна и точки зацикливания. GetChannelMute возвращает статус заданного канала, чтобы выяснить, нужно ли его озвучивать, или пропустить. Данные паттерна скрипт получает функцией GetSongData. В неё передаётся номер строки и столбца, она возвращает один байт — номер ноты или значение числового параметра. Аналогично получаются и данные инструментов, функция GetInstrumentValue возвращает значение заданной ячейки заданного инструмента.

Для удобства экспорта и создания движков реализовано несколько загружаемых библиотек со вспомогательными функциями. Так, в движках для ZX Spectrum очень удобно использовать их исходный код на Z80, а не заранее собранный бинарник (чтобы можно было легко изменять код при необходимости), и формировать данные трека в виде ассемблерного исходника, с метками, константами и прочими удобствами.

В частности, в библиотеке common.1tl реализовано формирование в памяти текстового файла — в него добавляется ассемблерный исходник самого движка, потом скрипт формирует данные и построчно добавляет их функцией TextFilePutString. Полученный текст можно просто экспортировать из фронтенда в текстовый файл, или передать упомянутому выше кросс-ассемблеру Z80Ass, который соберёт из исходника бинарный код, далее скрипт поместит его в один из контейнеров (AY для проигрывания, TAP и SCL для экспорта) и передаст во фронтенд.

В деталях функции API разобраны в файле docs/engines_howto.txt.

▍ Заключение

Хотя 1tracker вполне успешно пошатнул устои, и кто-то даже (не будем показывать пальцем, но это utz — также автор множества подобных вещей, конкурирующая фирма) называет его лучшим трекером в мире, известности и популярности он не снискал. Тем не менее, с его помощью удалось решить некоторые насущные вопросы, и проверить жизнеспособность множества идей — как в области дизайна трекеров, так и музыкальных движков для конкретных платформ. Возможно этот маленький шажок ещё сыграет роль в будущем трекеростроения, хотя бы в формате наглядной демонстрации ‘а так можно было’.

Тема трекерной музыки и трекеров очень обширна, а шатание устоев таит ещё множество неисследованных возможностей. Идеи витают в воздухе, эксперименты продолжаются — модульный трекер bintracker с эмулятором MAME в качестве бэкенда, трекер с piano roll интерфейсом FamiStudio, пока безымянные проекты трекеров с горизонтальным паттерном, трекеры без строк, трекеры с виртуализацией каналов. Но об этом, возможно, поговорим в следующий раз.

Telegram-канал с розыгрышами призов, новостями IT и постами о ретроиграх 🕹️

Автор: Александр Семенов

Источник

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


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js