Весной 2012 года я опубликовал первую версию экспериментального мультисистемного кроссплатформенного музыкального редактора с интерфейсом типа «трекер» — 1tracker v0.1. Экспериментальность заключалась в проверке новых подходов и отказе от общепринятых стандартов для подобного рода программ. Прошло десять лет, редактор до сих пор экспериментальный, до сих пор мало кому известен, и до сих пор не добрался до версии 1.0. Тем не менее, он регулярно пригождается нескольким странным людям на планете, а новые концепции вполне успешно выдержали проверку временем, хотя вряд ли когда-нибудь станут новым стандартом. Пора выйти из сумрака и рассказать обо всём этом подробнее. И заодно про историю трекеров.
▍ Оглавление
- Ретроспектива
- Их обычаи
- Трекер курильщика
- Истоки проекта
- Архитектура 1tracker
- Фронтенд
- Бэкенд
- Интерфейс
- Устройство движка
- Заключение
▍ Ретроспектива
Без нашей любимой историко-познавательной справки никак не справиться.
Трекер — это не только про баги и торренты. Это также особый вид музыкальных редакторов. Их отличительной особенностью является интерфейс, враждебный к пользователю, и специфическая организация композиции.
Концепция трекеров оформилась в 1987 году в виде программы The Ultimate Soundtracker для компьютера Commodore Amiga. Пресса назвала новинку нелогичной, сложной и непредсказуемой, публика сказала — «да, это отвратительно, пожалуйста, продолжай», и за последующие десятилетия появилось ещё несколько сотен аналогичных программ, основная часть которых так или иначе скроена по лекалам исходного Soundtracker. Краткий обзор только самых популярных из них потянул бы на отдельную большую статью, так что я даже не буду и пытаться. Упомяну только несколько, названия которые наверняка на слуху у старожилов: Protracker, Scream Tracker, Fast Tracker II, Impulse Tracker — этого достаточно, чтобы понять, о чём идёт речь.
Самый первый Ultimate Soundtracker на Commodore Amiga
Особенностью трекеров является то, что они придуманы и создавались не профессионалами от мира музыки, как, например, предшествовавшие их появлению MIDI-секвенсоры, а компьютерными энтузиастами-одиночками. За всю историю было создано всего несколько трекеров, которые можно отнести к профессиональному музыкальному ПО (Renoise и Polyend). Такое происхождение накладывает очень заметный отпечаток на интерфейс и в целом на дизайн этих программ.
Исторически сформировался подход «один трекер — один формат», или трекер как инструмент для решения частной, очень специфической задачи, а не для написания музыки «вообще». Трекеры создавались каждый раз заново под новый вид звукового чипа, звуковой карты или программного синтезатора. Если основной интерфейс для ввода музыкальной композиции у разных трекеров более-менее схож, его частности и управление инструментами очень завязаны на конкретное звуковое устройство или формат. Для каждого популярного устройства и формата на каждом популярном компьютере создавалось множество трекеров.
Protracker, Scream Tracker, Impulse Tracker. Fast Tracker II
Трекеры являются европейской традицией с немецко-финскими корнями, также успешно прижившейся на Западе. Из-за этого в западном мире одно время бытовало расхожее мнение, что трекеры были основным способом создания музыки для видеоигр, в частности, в 8- и 16-битную эпоху. Однако основным производителем игр и музыки к ним в то время была Япония, и именно в этой стране концепция трекера почему-то совершенно не прижилась — там композиторы как шли своим, куда более хардкорным путём (хекс-коды или язык MML, являющийся развитием синтаксиса оператора PLAY в Бейсике) до появления трекеров, так и продолжили после. Да и в западном мире в то время применение трекеров для видеоигр оказалось ограниченным. Основное же своё применение они нашли на демосцене и в кругах музыкантов-любителей.
Популярность трекеров росла на протяжении 1990-х, к концу десятилетия достигла пика, вероятно с появлением ModPlug Tracker, и постепенно начала спадать. Но ещё долгие годы создавались новые трекеры для старых платформ, сформировалась концепция трекеров следующего поколения. Разработка новых подобных программ продолжается до сих пор, хотя уже не в таких количествах.
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, и так далее. Куда же далее, и где тут логика, спросите вы? Логика есть: это расположение клавиш компьютерной клавиатуры, геометрически соответствующее клавишам пианино. Октаву ноты также не впечатывают цифрой, она зависит от выбранной в момент ввода ноты октавы.
Соответствие клавиш пианино кнопкам компьютерной клавиатуры. Верхний ряд — на октаву выше, её начало частично дублируется концом нижнего ряда.
Такая система довольно удобна и давно стала стандартом для музыкального ПО, в том числе и профессионального. Некоторые проблемы возникают у пользователей клавиатур других раскладок, типа 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, использовавшихся в старых звуковых картах.
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 проигрывает трек. То есть для каждой целевой платформы помимо её собственных возможностей также нужно осваивать очередной интерфейс, со своими особенностями и управляющими клавишами.
Кроссплаформенный 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 или написания музыки непосредственно в ассемблерном исходном коде.
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
Для озвучивания производимых бэкендом файлов я взял библиотеку 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, но там но следовал из особенностей звуковых движков. Плюсом его является устранение влияния структуры на музыкальную форму: отсутствие коротких паттернов вынуждает писать более продолжительные и связные музыкальные партии, более разнообразные композиции. Повторяемость фрагментов легко получить простым копированием и вставкой блоков, но принципиальное отличие от ордер-листа в том, что изменение одного фрагмента не меняет все его вхождения в треке автоматически.
Разумеется, такой подход несёт с собой ряд проблем, в частности, затрудняется ориентирование и навигация по треку. Для компенсации этих неудобств постепенно придумывались и реализовывались костыли, ныне именуемые возможностями, в итоге обеспечившие неплохой уровень комфорта.
Во-первых, это система маркеров. Нажатие пробела делает визуальную отбивку последующей части паттерна в виде пустой строки. Этой части паттерна можно присвоить имя. Таким образом можно разметить весь трек, разбив его на именованные блоки произвольной длины — вступление, мелодия, разбивка, повтор мелодии.
В правой части экрана отображается получающаяся структура в виде названий блоков и номеров строк. Есть горячие клавиши для быстрого перемещения между блоками. Также можно быстро перейти на нужную строку по её номеру, как в текстовых редакторах. Есть режим визуального отображения блоков в виде отдельных паттернов, что при необходимости позволяет сфокусироваться на фрагменте композиции, исключив остальную часть трека.
Имена блоков позволяют быстрое копирование фрагментов, достаточно нажать горячую клавишу и ввести имя фрагмента, и он будет вставлен в заданную позицию. При этом он помечается особым образом, в виде ссылки на имя исходного фрагмента, и специальной комбинацией клавиш можно синхронизировать все ссылки в треке — в них заново будут скопированы соответствующие блоки (прекрасный способ выстрелить себе в ногу).
Элементы интерфейса
Вполне ожидаемо, что ради снижения порога вхождения я отказался от использования шестнадцатеричной системы счисления во всём пользовательском интерфейсе. Номера строк и все параметры задаются в десятичном виде, параметры инструментов могут быть также отрицательными. Впрочем, и буквенные параметры, и шестнадцатеричные значения предусмотрены, если таковые вдруг понадобятся в особых случаях.
Так как трекер в основном предназначен для музыкальных движков с очень ограниченной полифонией, в 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-2015\n\nTwo 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 и постами о ретроиграх ????️
Комментарии (14)
voldemar_d
01.08.2023 19:26+1Очень круто.
Мне вот что интересно: в Спектруме обычно чиптюн-музыка играется в прерываниях, которые происходят 50 раз в секунду. На других платформах, где используются другие звуковые чипы, частота прерываний для передачи данных в регистры звукового чипа такая же?
И схожий вопрос про биперный движок: с какой характерной частотой на Спектруме делается управление бипером, чтобы получить 2-голосие по всеми эффектами вроде фазера, ударных и т.д.?
shiru8bit Автор
01.08.2023 19:26+1Для звуковых чипов частота обновления обычно равна кадровой частоте, 50 герц для PAL, 60 для NTSC. Это самый простой источник синхронизации с реальным временем, который всегда есть у микрокомпьютеров (как минимум флаг вертикального гашения, почти всегда прерывание). И довольно часто он же единственный доступный внешний синхросигнал.
Биперные движки синхронизируются чисто по скорости выполнения команд процессора, точно рассчитываются по тактам, время выполнения веток кода выравнивается дополнительными задержками. Скорость внутреннего цикла в двухголосых движках порядка 120-500 тактов, то есть 3500000 (тактовая частота процессора) / 120..500 = 7000...29166 герц. Она зависит от способа смешивания каналов. При простом чередовании скорость цикла желательно иметь как можно выше, по крайней мере ближе к 20 кГц, иначе будет слышна несущая (свист). Способы генерирующие короткие импульсы разной длительности, не так требовательны к скорости цикла, но она должна быть вдвое выше самой высокой генерируемой ноты.
Ударные в большинстве биперных движков используют психоакустический трюк - они короткие и громкие, и на время звучания такого ударного инструмента можно совсем не играть каналы тона, а значит, они не влияют на скорость цикла синтезатора, и могут иметь цикл любой другой частоты. Есть всего несколько движков, в которых ударные звучат одновременно с тональными каналами, и это, конечно, заметно замедляет их цикл, вплоть до указанных выше ~7000 герц.
voldemar_d
01.08.2023 19:26NTSC, я так понимаю, в Японии использовался? Если взять чиптюн-трек с японского компьютера и воспроизвести на нашем, музыка будет медленнее играться?
shiru8bit Автор
01.08.2023 19:26Да. Это типичная история для приставки Денди, где японские NTSC-игры запускаются на переделанной под PAL приставке, и вся музыка (и дейстие) замедляется на 17 процентов.
cybermind8801
01.08.2023 19:26+7Возможно, это будет немного оффтопом, но раз в статье было упоминание про язык MML, напишу немного о нем (так как сам в одно время занимался аранжировками с использованием этого языка), а также в чем его особенности в сочинении музыки по сравнению с использованием трекеров.
Подход тут в корне отличается. Любой трекер, как описывается в статье, в большинстве случаев - это WYSIWYG-программа, с графическим интерфейсом, где паттерны и ордер-лист композиции достаточно наглядно ее представляет и редактирование партитуры также происходит наглядно. Тогда же как MML - это что-то наподобие программы (то есть обычный текст, редактируемый в любом текстовом редакторе), которая впоследствии компилируется в воспроизводимый звуковой модуль (универсального MML-диалекта нет, так как у каждого звукового чипа свои особенности). Разница примерно такая же, как если делать документ в Word, или с использованием TeX.
Текст-программа на языке MML чаще всего структурируется - вначале это разнообразная метаинформация (название трека, автор, ...), далее идут описания инструментов (которые можно редактировать напрямую, но обычно они редактируются в отдельном WYSIWYG-редакторе, а затем получившийся текст с параметрами вставляется в программу), затем настройки каналов (каждый из который обычно привязан к одному генератору звукового чипа компьютера), и после этого сама партитура - набор музыкальных данных (ноты, паузы, размеры, регулировки инструментов, эффекты и прочее), перед которым идет название канала, который должен это проигрывать. Паттерны, присущие трекерному формату, отсутствуют - каждый канал играет все свои данные от начала и до конца (при этом возможно указать точку циклического воспроизведения), и вся логическая разбивка трека ложится исключительно на плечи аранжировщика. Также в MML нотация, в отличие от трекерной музыки, музыкальная - ноты и паузы тут имеют классические описания длительности (обычно указывается знаменатель, например для 1/64 это просто 64)
Определенные сходства с трекерным форматом все же есть. Например, выше упоминавшаяся концепция каналов (чаще всего они имеют заглавные буквы латинского алфавита, A-Z), но, которые в отличие от трекерных аналогов, чаще всего полностью независимы друг от друга. Есть определенные преимущества, присущие текстовому формату - можно оформлять подпрограммы или макрокоманды, которые затем вызываются в основном коде партитуры, и за счет которых можно параметризовать повторяющиеся участки музыки. Также формат текста позволяет проводить манипуляции средствами самого текстового редактора (поиск, замена, регулярные выражения и так далее)
Недостатки также присутствуют - за счет того, что это обычный текст, "увидеть" целиком весь трек сложно. Плюс также за счет независимости каналов вся синхронизация между ними ложится на аранжировщика. Нередки ситуации, когда за счет разной длительности общего числа тактов в разных каналах один начинает играть раньше (или позже другого). В трекерном формате такого не происходит за счет жесткой "сетки", которая формирует паттерн. Для решения этой проблемы несколько музыкальных тактов группируется (получается своеобразный "паттерн") и каждый такой "паттерн" уже потом редактируется индивидуально от других.Причины популярности конкретно MML формата в японской компьютерной среде (в основном в саундтреках к играм и додзин-альбомах, демосцены в западном понимании там не было вообще) сугубо исторические. На ранних японских компьютерах в большинстве случаев имелся предустановленный диалект BASIC, в котором имелась упоминаемая в статье команда CMD PLAY. Эта команда проигрывала на звуковом чипе компьютера (который зависел от машины) ноты в простейшем диалекте MML-формата. На основе этого было разработано много MML-редакторов, которые часто оставляли BASIC-редактор как основу, а все музыкальные данные заносили в комментарии (так что MML-композиция оставалась также и валидной BASIC-программой). Обычно каждая компания или разработчик делали свой MML диалект под конретные нужды. Например, Юзо Косиро (известный как минимум по саундтреку Streets of Rage) написал свой MML-редактор MUCOM88 (распространыемый коммерчески под названием MUSIC LALF).
Тема MML-музыки довольно обширная, в спонтанный комментарий все нюансы не влезут, впору писать отдельную статью. От себя скажу, что как трекерный, так и MML формат имеют право на жизнь. Я, пытавшийся делать аранжировки как в том, так и в другом формате, так и не определился, какой из этих способов удобнее. Я думаю, было бы довольно интересно увидеть MML-диалект для биперной музыки.
shiru8bit Автор
01.08.2023 19:26+1Отдельная статья про MML нужна! Очень недоосвещённая тема как на русском, так и на английском, и при этом огромный пласт истории видеоигровой музыки.
На Спектруме был очень занимательный казус: когда только появился его местный Sound Tracker и первые мелодии из него в программах, одна из них попала к Klav'у, впоследствии заметному раннему композитору на платформе. Он не знал, в чём сделана эта музыка, трекеров ещё не видел. Тогда он разобрал код движка ST и написал вокруг него собственный редактор, в котором музыка вводится в текстовом редакторе, довольно похожим на MML образом. Вероятно это единственный MML-подобный редактор на платформе, да ещё с трекерным движком в основе.
Связь между MML и биперными движками относительно недавно была реализована в рамках bintracker, там есть плагин для ввода MML. Это также весьма занимательный проект универсального трекера, о котором я планирую написать через некоторое время в рамках обзора необычных трекеров.
voldemar_d
01.08.2023 19:26Движок от Fuxoft (музыка к Tetris 2 и т.д.) не является одним из вариантов музыкального языка вроде MML?
И да, мне тоже было бы интересно про MML почитать.
shiru8bit Автор
01.08.2023 19:26Припоминаю, что про это говорили. Но модулей в исходном текстовом виде я раньше не видел, а fxm для AY_Emul обычный бинарник. Сейчас поискал, четыре года назад выложено автором: https://github.com/fuxoft/fxmasm. Не очень похоже на MML, скорее на обычный набор данных вручную в ассемблере.
MentalSky
01.08.2023 19:26+1Очень круто!
Кстати, для меня лично самым необычным (когда-то) был - Buzz Tracker https://www.jeskola.net/buzz/
Собственно на нем и треки получались необычные, но это уже PC-шная история, а первые треки - да, были в Sound Tracker на ZX Spectrum, пардон за приступ ностальгии...shiru8bit Автор
01.08.2023 19:26+1Я долго сидел на Psycle, это архитектурный аналог Buzz. В этих трекерах очень крутой роутинг, можно без проблем создавать сколь угодно сложные и разнообразные параллельные цепочки эффектов. Чтобы повторить такое в каком-нибудь Reaper'е или FL, надо сильно напрячь извилины, а там всё делалось за секунды, максимально интуитивно, и при этом с теми же самыми VST.
voldemar_d
01.08.2023 19:26Насчет "выстрелить себе в ногу" - команды Undo во фронтэнд-редакторе нет?
shiru8bit Автор
01.08.2023 19:26Есть Undo на одно действие. Как показала практика, это разумный компромисс.
kh0
Колоссально!