После выпуска System Beeps, музыкального альбома для PC Speaker, я не планировал возвращаться к псевдомногоголосой одноканальной музыке в формате подобных крупных самостоятельных релизов, считая тему достаточно раскрытой. Это, конечно, не означало отказа от более утилитарного применения подобных наработок при подходящем случае, например, в ретро-игровых или демосценовых проектах для старых компьютеров. Осенью прошлого года на горизонте появился очередной проект подобного плана от автора популярного Youtube-канала The 8-bit Guy, Дэвида Мюррея — игра Attack of the PETSCII Robots для линейки 8-битных компьютеров Commodore, включая PET, VIC-20 и C64. Я уже сотрудничал с Дэвидом на его предыдущем проекте, игре Planet X3 для MS-DOS. Новая затея как нельзя лучше соответствовала моему интересу к персоналкам до-графической эпохи и большому опыту как в области минималистичного компьютерного звука, так и программировании на ассемблере для процессора 6502, поэтому я срочно вписался в работу над проектом, надеясь на этот раз помимо написания звукового кода поучаствовать и в сочинении музыки.
В рабочем процессе возникали разнообразные проблемы, плавно перетёкшие в небольшой производственный ад (скандалы, интриги и расследования можно найти в серии постов в моём Patreon), в результате чего мой код и звуки были использованы только в версии для VIC-20, а музыку к остальным версиям игры написали другие композиторы. Но у меня оставались наработки в виде рабочего кода для PET и набросков композиций. Было жалко отправлять их в стол, ведь релизы для этой платформы — явление крайне редкое, и нового шанса задействовать то, что уже было сделано, пришлось бы ждать долго. Поэтому, с одобрения Дэвида, я принял решение дописать наброски до полноценных треков и выпустить свой альтернативный саундтрек в виде небольшого альбома под названием Faulty Robots, как в виде аудио, так и в формате самостоятельной программы для PET.
Традиционно, сначала демонстрация результата, потом описание вполне эпичных преодолений на тернистом пути к этому релизу.
Художественная часть
На этот раз я решил не мучить слушателя продолжительным кровопусканием из ушей, ограничившись всего восемью треками, которые также получились довольно короткими из-за технических ограничений, в результате чего длительность звучания альбома составила всего 13 минут. Концептуально альбом и его оформление являются деконструкцией на минималках — другой взгляд на содержание игры, нелёгкую долю роботов из сюжета, и на неудачи в процессе разработки. В целом содержание можно описать одним словом: минимализм. В остальном — что-то как-то пищит, кому-то это нравится, кто-то не переносит и страдает.
Несколько слов об истории создания каждого из треков:
Faulty Robots — изначально планировался для титульного экрана, мелодии построена вокруг распевки названия. Делался одним из первых, сразу был дописан до второго проведения мелодии, а для альбома дополнительно обзавёлся бриджем и повтором.
Rusty Gears — первый полноценный трек, изначально написан для проверки и демонстрации возможностей звукового движка. Не планировался к использованию в игре, так как я и Дэвид посчитали аранжировку слишком перегруженной, сложной для восприятия, и не особо подходящей игре стилистически. Был незначительно изменён в альбомной версии.
Conveyor Belt — предполагалось использовать в виде короткого цикла на экране статистики после пройденного или проигранного уровня. Для альбомной версии был добавлен второй повтор с арпеджио, вступающего в бридже и далее играющего минималистичным контрапунктом к основной мелодии.
Old Model — набросок был подготовлен ближе к моменту, когда стало ясно, что музыку к игре будет писать другой композитор. Поэтому здесь произошёл переход от изначального плана минималистичного геймплейного трека в сторону увеличения мелодического наполнения.
Crosswired — первый кандидат на трек для игрового процесса. Для него стояла задача сделать прозрачную аранжировку с большим количеством пауз и достаточно продолжительное развитие в рамках очень ограниченной памяти. Я опасался, что настолько малого мелодического содержания окажется недостаточно для трека в формате альбома, подразумевающего самостоятельное прослушивание, однако после релиза некоторые слушатели называли своим фаворитом именно этот трек.
Scraplord — изначально был придуман начальный рифф, как база для одного из будущих геймплейных треков. Найти путь его развития в полноценную композицию оказалось очень непросто. В итоге это было сделано с помощью записи многих импровизаций и нарезании удачных кусочков в Reaper. Большая же часть треков писалась прямо из головы в трекер.
Bosstown Dynamics — писался последним, специально для альбома, чтобы получилось восемь треков. Работа над мелодической частью также шла с затруднениями, применялся метод сборки из удачных элементов импровизации. Название является отсылкой к пародийным видео студии Corridor.
Not Obsolete — начинался как самый первый тест движка, изначально представлявший собой один первый паттерн с полуслучаными нотами. Когда шесть треков было готово, захотелось добавить ещё пару для круглого счёта. Паттерн был украшен изменениями тембра, а далее, как это иногда случается, трек сам подсказал своё развитие, написался сам собой и удачно вписался в качестве немного печального, но оптимистичного завершающего аккорда. Название цитирует фразу киборга-убийцы из неудачного кинофильма.
Изначально все треки были бесшовно зациклены, как того обычно требует музыка для видеоигр. По готовности материала и программной оболочки я понял, что хотелось бы большей завершённости, и добавил к каждому из треков простейшую концовку, состоящую практически из одной-двух нот.
Подводя итог, могу сказать, что лично мне конечный результат понравился больше предыдущего альбома, System Beeps. В обоих случаях я пытался уйти от звучания типичного, хотя и очень ограниченного чиптюна, и приблизиться к эстетике настоящей музыки из компьютера — к ощущению, что компьютер просто подаёт системные сигналы, которые каким-то образом складываются в мелодии. В этот раз, как мне кажется, это удалось лучше.
Исторически-техническая справка
Если художественная составляющая в основном апеллирует к относительно немногочисленной аудитории поклонников чиптюна, техническая сторона дела будет интересна более широкому кругу энтузиастов устаревшей компьютерной техники. В наших краях мало кто осведомлён о самом существовании и роли Commodore PET в истории, не говоря о его действительных возможностях и особенностях разработки программ для него, и здесь есть о чём рассказать.
PET — один из самых ранних (1977) полноценных персональных компьютеров, то есть с собственным процессором, дисплеем, клавиатурой, и один из первых успешных массовых компьютеров такого формата. Он едва ли домашний и определённо не игровой. Графические возможности у него отсутствуют полностью, есть только 40 или 80-символьный текстовый монохромный режим. Первые модели вообще не предусматривали какой-либо звук, в более поздних добавился встроенный динамик для подачи простейших системных сигналов. В результате трудно придумать менее подходящую для создания музыки платформу, но тем интереснее вызов для программиста и музыканта.
Персональные компьютеры настолько ископаемого периода, когда сам формат этого устройства ещё не устоялся, зачастую обладают довольно яркой индивидуальностью, и PET один из лидеров в этом вопросе. Здесь и броский футуристический дизайн, и знаменитый PETSCII — самобытный набор символов текстового режима с элементами псевдографики, и оригинальная клавиатура без смещения рядов, в основном поле которой отсутствуют цифры, а курсор управляется всего двумя клавишами. Есть и незаметные снаружи, но занимательные детали внутреннего устройства. Например, оператор PEEK встроенного Бейсика не позволяет читать память по адресам выше 49152, где находится ПЗУ самого Бейсика, предположительно в целях защиты от несанкционированного изучения его кода пользователями. Одной из таких деталей, своего рода изюминкой платформы, стала и оригинальная реализация звуковой системы.
В компьютерах прошлого можно выделить три подхода.
В наиболее бюджетных машинах звук представлял собой один бит порта ввода-вывода, к которому непосредственно подключался динамик, и процессор должен был переключать этот бит со строго определённой скоростью, формируя таким образом колебания нужной звуковой частоты, что занимает всё время процессора и не даёт ему выполнять другие действия одновременно с воспроизведением звука.
Компьютеры домашне-игровой направленности склонялись к другой крайности, используя довольно сложные специализированные звуковые чипы, способные самостоятельно генерировать многоканальный звук, требуя лишь относительно редкой передачи им параметров синтеза и освобождая процессор для других дел.
В офисно-деловых компьютерах, к которым можно отнести PET, часто встречался средний вариант — нечто, подобное PC Speaker в IBM PC/XT, встроенный динамик для подачи простейших звуковых сигналов, но не требующий постоянного контроля со стороны процессора. Обычно это делалось на основе микросхем таймера, типа i8253 в IBM PC.
Особенности местного звукоизвлечения
В оригинальных моделях серии 30xx PET звуковые возможности отсутствовали полностью — в них не было ни динамика, ни соответствующего аппаратного обеспечения. Некоторые пользователи компьютера оказались недовольны таким положением вещей и придумали свой способ получения звука, сначала в виде доработки, а позже он был утверждён и штатно реализован в более распространённых моделях PET серий 40xx и 80xx.
Для общения с внешними устройствами в PET используется микросхема MOS 6522 (VIA, Versatile Interface Adapter), предоставляющая два параллельных порта и весьма рудиментарный последовательный порт ввода-вывода. Функционал последовательного порта представлен 8-битным сдвиговым регистром и двумя программируемыми счётчиками-таймерами. Конкретный же протокол обмена реализовывался программно.
Когда энтузиасты решили добавить в PET первых моделей звук, они пошли оригинальным путём: подключили динамик не к одной из линий параллельного порта, что было бы простым и вполне ожидаемым решением, а к так называемому CB2, выходу сдвигового регистра 6522. Регистр может циклически сдвигать своё содержимое с заданной одним из таймеров частотой. Это позволило генерировать квадратную волну аппаратно, не нагружая процессор. Хотя подобное решение выглядит довольно похожим на PC Speaker, оно одновременно имеет и более ограниченные возможности, и некоторое преимущество.
Главным ограничением является диапазон возможных частот. В режиме управления сдвиговым регистром таймер имеет разрешение всего 8 бит. Входная частота 500 КГц делится на количество бит в сдвиговом регистре и далее на заданное значение 1..255. Таким образом при загрузке в сдвиговый регистр бинарного значения 11110000 самая низкая частота составляет 500000/8/255 — примерно 245 герц. Можно загрузить в сдвиговый регистр значения 11001100 или 10101010, но это только повысит частоту получаемого звука в два или четыре раза. Таким образом, на PET нельзя аппаратно генерировать ноты ниже ноты До первой октавы, то есть привычный для всей современной музыки бас, а частоты нот в доступном диапазоне имеют заметные отклонения от нормального строя, и комбинация этих факторов никак не помогает в деле воспроизведения приемлемо звучащей музыки.
Преимущество же MOS 6522 в деле звукоизвлечения заключается в том, что, во-первых, в сдвиговый регистр можно загружать любые битовые паттерны, а не только квадратную волну, что даёт неплохую тембральную вариативность, а во-вторых, микросхема может генерировать прерывания для процессора как по второму, уже 16-разрядному, таймеру, так и по окончанию полного цикла сдвига, и по некоторым другим условиям, и это позволяет несколько компенсировать аппаратные ограничения программным способом.
В годы популярности PET книги по программированию по большей части не вдавались в подробности, публикуя лишь табличку значений для двух регистров (сдвига и таймера) для трёх доступных октав, и даже сама официальная документация на микросхему 6522 в одной из своих редакций признавала недостаточную подробность описания устройства этих регистров. Были и редкие исключения, предлагавшие идеи по улучшению звуковых возможностей — от перезагрузки сдвигового регистра по прерыванию до подключения внешнего ЦАП к параллельному порту (аналогично Covox). Но из-за стремительного прогресса в области персональных компьютеров PET успел оказаться на свалке истории раньше, чем эти идеи нашли отклик среди энтузиастов не очень многочисленной пользовательской базы платформы. В результате звук в программах для PET обычно представлял собой примерно следующее:
В новое же время энтузиасты с помощью обозначенных выше техник достигли очень впечатляющих результатов, однозначно превосходящих проделанную мной работу, но достигаемых за счёт использования практически всего времени процессора и значительных объёмов памяти, а значит, не применимых в реальных играх:
Краткая история неуспеха
Приступая к работе над проектом, я был мало знаком с особенностями Commodore PET. На мои представления о его возможностях наложило знакомство с компьютерами, подобными Robotron 1715 — я ожидал найти там обычный однобитный порт для непосредственного управления динамиком. Поэтому моё изначальное видение того, каким должен быть звук в игре, предполагало подход, аналогичный ZX Spectrum 48K или Apple II: программный синтез для музыкального трека в начале игры и для джинглов, занимающий всё время процессора, и короткие, также программно генерируемые, звуковые эффекты, приостанавливающие игровой процесс во время своего звучания.
Я уже имел огромную базу наработок для такого подхода, которая вот уже десятилетие используется во множестве игр. Её можно было бы легко адаптировать для PET, и тогда работа над проектом потребовала бы совсем немного времени и сил. Однако, в предварительном обсуждении выяснилось, что мои идеи не соответствуют видению Дэвида. Он хотел бы иметь в своей игре простой одноканальный звук, аналогичный PC Speaker, только воспроизводимый с использованием таймера 6522 через CB2, который мог бы работать одновременно с игровым процессом, оставляя максимум ресурсов для последнего.
Изначальная постановка задачи включала также версии звуковой системы для Commodore VIC-20 и Commodore 64, и в перспективе для Commodore Plus/4, обладающих более продвинутым звуком. Все эти платформы обладают очень разными возможностями, с уникальными, практически нигде более не встречающимися, ограничениями. Звуковая система должна была быть спроектирована с учётом всех особенностей каждой из платформ. При этом изначально предполагалось, что звуковые данные будут общими для всех версий игры. Основной платформой считался PET, а версии для остальных платформ должны были воспроизводить примерно такой же звук, но уже с полифонией и прочими небольшими улучшениями.
Другим ключевым требованием, существенно повлиявшим на дизайн системы, была максимальная экономия памяти, так как её крайне не хватало в версии для VIC-20. При этом компактным должен был быть как код проигрывателя, так и данные музыки и звуковых эффектов. И то и другое сразу же ограничивало сложность кода и формата, а значит и возможности проигрывателя, и сложность музыкальных аранжировок. Всего под код с парой десятков постоянно находящихся в памяти звуковых эффектов, и под один загружаемый музыкальный трек отводилось 2.5 килобайта памяти, что 2-3 раза меньше типичных объёмов этих составляющих. В связи с этим раскрытие полного потенциала более мощных платформ, такого, как разные формы волны и аналоговый фильтр на Commodore 64, не предполагалось изначально — это потребовало бы сильно других средств создания музыки, более сложного по устройству проигрывателя и значительно большего объёма памяти для кода и музыкальных данных.
Дополнительной особенностью стало то, что интеграция кода изначально предполагалась не на уровне исходного кода, а в виде бинарника, загружаемого по определённым адресам и использующего определённую область ОЗУ для своих нужд, а общение игры со звуковой подсистемой должно было быть реализовано даже не в виде традиционной таблицы вызовов подпрограмм, а посредством передачи команд через специально выделенную ячейку ОЗУ. Довольно странная идея (для однопроцессорной платформы), которая в конечном итоге не сработала.
В середине процесса разработки произошла значительная смена приоритетов: от музыки в версии для PET было решено отказаться вовсе, по причине слишком тихого штатного динамика и возникавших при разработке затруднений, затягивающих процесс. Версия для VIC-20 стала основной для моей звуковой системы, что предполагало совсем другой подход к звуку, а версия для C64 с улучшенной графикой получила совершенно независимое звуковое оформление, созданное Noelle с использованием музыкального редактора goattracker и его штатного проигрывателя.
В итоге этот грандиозный план с большим количеством экзотических целей, а также меняющимися приоритетами и требованиями, вылился в провал моей части проекта. Код, отвечающий всем поставленным требованиям, был в итоге разработан и отлажен, также был придуман способ подготовки данных для музыки и звуковых эффектов, созданы все звуковые эффекты и сделаны наброски музыки. Всё это работало в виде отдельных тестов в эмуляторах VICE и MAME. Однако, эти же тесты по какой-то причине не заработали ни на реальных компьютерах Дэвида, ни точно в тех же самых версиях эмуляторов, которые я использовал для отладки. Также код не заработал при интеграции с игрой ни в одной версии, кроме VIC-20. Все сроки были на исходе, времени разбираться просто не было, и, находясь в довольно неудобном положении перед своей аудиторией, Дэвид принял решение выпустить первые копии игры без звука вообще, а далее написать свой код для PET-версии. В итоговом релизе мой код, тестовый музыкальный трек и звуковые эффекты попали только в версию для VIC-20.
Преодоление ограничений
Первой проблемой, которую предстояло решить, была невозможность воспроизведения простыми средствами звуков с частотой ниже 245 Гц на PET. Дэвид не считал это проблемой, но для меня это было принципиальным моментом. Предполагалось, что музыка будет звучать во время игрового процесса, а я не представлял, как можно сочинить музыку, которую не захочется выключить через несколько секунд, без басовой партии.
Так как 245 Гц — довольно низкая частота, я предложил следующее решение. На частотах выше 245 Герц звуки воспроизводятся средствами 6522. На звуках же более низких частот включается программная имитация 6522, работающая на маскируемых прерываниях (IRQ) от таймера и выдающая звук на ту же самую линию порта. В худшем случае это около пяти прерываний за кадр, что теоретически не должно вносить заметной нагрузки на процессор и замедлять игровую логику.
Дэвиду идея не понравилась, он переживал за влияние на производительность и считал её слишком сложной и потенциально проблематичной — что, в общем-то, в итоге оказалось близко к истине. Тем не менее, я реализовал задуманное, сначала в виде простого теста, который показал работоспособность общей идеи, но также выявил одну значительную проблему.
Вектор IRQ на PET, как и на всех платформах на процессоре 6502, находится в ПЗУ. Для обеспечения возможности подключения пользовательского обработчика IRQ в коде предусматривается так называемый трамплин — переход по заданному в ОЗУ адресу. Однако, авторы встроенного ПО PET, вероятно, не рассчитывали всерьёз, что для этого компьютера будут писаться низкоуровневые программы. Поэтому трамплин на PET реализован очень неэффективно — он вызывается не сразу же по приходу IRQ, а после сохранения всех регистров процессора на стеке. Код штатного обработчика выглядит следующим образом:
; Main IRQ Entry Point
E442 PHA
E443 TXA
E444 PHA
E445 TYA
E446 PHA
E447 TSX
E448 LDA $0104,X
E44B AND #$10
E44D BEQ $E452
E44F JMP ($0092) ; Vector: BRK Instr. Interrupt
E452 JMP ($0090) ; Vector: Hardware Interrupt
$0090 — это адрес пользовательского вектора, он хранится в Zero Page. Адрес отличается для разных версий ПЗУ Бейсика, поэтому при запуске программы нужно определить версию и подставлять соответствующий адрес. По умолчанию адрес указывает на штатный обработчик Бейсика, опрашивающий клавиатуру и обновляющий системные часы. Его также было необходимо вызывать в моём новом обработчике, сохраняя при этом правильную частоту, чтобы продолжал работать опрос клавиш в игре и системные часы продолжали идти согласно реальному времени. Для этого в начале моего обработчика требовалось определить, какое устройство вызвало прерывание — кадровый синхроимпульс или таймер 6522.
Помимо повышенной нагрузки на процессор (порядка 15% при 245 Гц) такой неэффективный обработчик прерывания создавал паразитную несущую с частотой в 50 Гц в звуке, что очень сильно искажало звук и давало совершенно неправильные ноты в басу, делая всю затею бессмысленной. Это происходило из-за того, что штатный обработчик прерываний выполняется довольно долго и мог занимать больше времени, чем время между приходами двух прерываний. Проблему могло бы решить повторное разрешение маскируемых прерываний сразу перед вызовом штатного обработчика, но по неизвестной мне причине это не работало, программа просто зависала.
Попытки решения этой проблемы продолжались долгое время, параллельно с ними велась разработка по всем другим направлениям. Дело затягивалось, решения не было видно, но ситуацию спас небезызвестный utz, который нашёл и упомянул в нужное время в нужном месте некоторые особенности реализации прерываний в PET, а также помог настроить эмулятор MAME для запуска тестов. Оказалось, что кадровое прерывание вызывается одной из линий чипа параллельного интерфейса PIA, и её необходимо вручную сбрасывать (путём простого чтения регистра статуса чипа) до повторного разрешения прерываний, иначе она сразу же вызывает повторное прерывание, вводя программу в бесконечный цикл.
После того, как был написан код проигрывателя музыки и подготовлен тестовый трек, и тест был запущен на реальном PET, обнаружилась другая серьёзная проблема. Некоторые ноты на пограничном диапазоне, в момент между переключениями с 6522 на программный синтез, просто не звучали, хотя в эмуляторе VICE всё работало нормально. Благодаря помощи utz и mr287cc, позволившей мне использовать для отладки MAME, который оказался более точным и воспроизводил эту проблему, мне удалось найти и устранить причину — неправильную перезагрузку регистра сдвига, который из-за этого начинал работать с задержкой.
Помимо PET, имело место быть и преодоление ограничений для VIC-20. Звуковой чип этого компьютера штатно способен генерировать исключительно квадратную волну или белый шум. Его внутреннее устройство настолько нетипично, что могло бы стать темой для ещё одной статьи, но в двух словах, с помощью записей определённых значений в регистры чипа в точно выверенные моменты времени становится возможным получать ещё 15 битовых паттернов вместо квадратной волны, имеющих другую тембральную окраску. Этот трюк был обнаружен и задокументирован демосценером viznut в начале 2000-х годов, но до сих пор почти не применялся в новых разработках, хотя и был поддержан в эмуляторах.
Когда я узнал о существовании подобной скрытой возможности, я также попытался реализовать её поддержку в своём коде. Однако, на практике оказалось, что дополнительные формы сигнала только добавляют в него верхние гармоники, а так как звуковой чип VIC-20 способен воспроизводить довольно ограниченный диапазон частот, это делает трюк не очень полезным для музыкальных целей, по крайней мере при использовании общей аранжировки музыки для всех целевых платформ. К тому же, отладка в не самых совершенных эмуляторах не позволила получить достаточно стабильно работающий выбор формы сигнала, в итоге звук на VIC-20 получился довольно странным, и это так и не было исправлено.
Компактность
Следуя оригинальному плану, в дизайне архитектуры звуковой системы я ориентировался на PET в качестве основной платформы, предполагая реализовать для неё музыку и звук с использованием виртуальной полифонии, аналогично сделанному ранее для PC Speaker в Planet X3. Идея заключается в том, что композиция содержит несколько виртуальных каналов, но в каждый квант времени (1/50 секунды) звучит только наиболее важный из них. В порядке убывания важности это ударные, мелодия и басовая партия. Этим создаётся иллюзия многоголосой аранжировки, хотя фактически звук всегда остаётся одноголосым. Для более мощных платформ основная логика и код проигрывателя остаются теми же самыми, но в зависимости от платформы используются разные менеджеры каналов, назначающие виртуальные каналы на имеющиеся реальные, то есть при наличии многоканального звукового чипа обеспечивается реальная полифония на основе тех же самых входных музыкальных данных.
Требование наибольшей компактности музыкальных данных, порядка килобайта на один трек, сразу закрывало наиболее простой путь с проигрыванием заранее подготовленных регистровых дампов, как в случае с System Beeps. Требовался особый сверхкомпактный формат, а значит и особый подход к сочинению музыки, и особая подготовка музыкальных данных в этом формате.
Первыми ключевыми моментами для достижения поставленной цели были ограничение продолжительности звучания треков — чем меньше нот, тем меньше данных, минимальное количество дополнительных параметров для каждой ноты — опять же, чем меньше параметров, тем меньше данных. Также была сделана ставка на высокую повторяемость некоторых элементов (рисунки ударных и баса) внутри каждой из композиций.
Мой предыдущий опыт с компактизацией данных, в частности, в музыкальном движке Huby (100-байтовый движок для ZX Spectrum 48K) показывал, что использование поканального списка позиций с 8-16 нотными строками внутри позиции даёт наилучший эффект. Поканальный список позиций увеличивает объём данных самого списка, но позволяет комбинировать одни и те же элементы композиции, например, одну и ту же басовую партию с разными рисунками ударных. Это даёт более компактные данные самих позиций, по сравнению с подходом, когда в одной позиции кодируются все каналы одновременно (как в форматах MOD, XM, IT).
Для уменьшения количества бит, требуемых для кодирования одного нотного поля позиции была использована идея с неравнозначной конфигурацией однотипных каналов, которую подсказало само устройство звукового чипа VIC-20. У него частотный диапазон каждого следующего канала отстоит от предыдущего на октаву, чтобы решить проблему низкой разрядности частотных делителей (всего 7 бит) и расширить общий диапазон возможных частот.
Я сделал нечто подобное: один канал используется в основном для баса, другой для мелодии, третий для ударных, при этом нотный диапазон для мелодического канала сдвинут на октаву выше относительно двух других. Начало нотного диапазона также выбрано исходя из возможностей VIC-20 — самый низкий его канал может играть частоты примерно от 50 герц, поэтому самой низкой нотой стала Ля контроктавы, а диапазон нот каждого канала составил 2.5 октавы. Это позволило обойтись без программной генерации низкочастотных звуков для VIC-20 и C64.
Также каждый канал имеет свой уникальный набор из семи инструментов. Номер инструмента кодируется в каждом поле ноты тремя битами, а высота ноты пятью битами. Нулевой инструмент кодирует глушение, пустую позицию, а также повторяющиеся значения с помощью RLE. С учётом достаточно коротких позиций как раз хватает одного байта на кодирование любого количества повторов нотного поля в пределах позиции. Таким образом каждое нотное поле или серия одинаковых полей кодируется всего одним байтом, и минимально возможный размер позиции (все строки пусты) составляет три байта.
Так как одни и те же музыкальные данные должны были использоваться во всех версиях игры и потому должны были загружаться в разные адреса ОЗУ, пришлось использовать относительные смещения во всех структурах музыкальных данных. Это несколько усложнило и увеличило размер кода проигрывателя, но добавило универсальности.
Данные инструментов в виду их примитивности были закодированы очень просто: две огибающие, в каждой один байт на один квант времени, без RLE сжатия. Одна огибающая задаёт форму волны, скважность или громкость (в зависимости от платформы), другая — смещение в полутонах или в единицах питча относительно текущей ноты, с переключением между режимами по специальному тегу внутри огибающей.
Изначально для сокращения объёма кода проигрывателя все его переменные размещались в Zero Page. Это даёт очень заметный выигрыш, так как доступ к переменным происходит очень часто, а такое расположение позволяет адресовать переменные одним байтом вместо двух. Однако, из-за того, что все компьютеры Commodore до C64 также располагают системные переменные в нулевой странице, занимая её почти целиком, и отсутствия достоверной информации, какие её локации можно использовать без нежелательных последствий, от этой оптимизации пришлось отказаться.
Трудности отладки
Наибольшую проблему при разработке звуковой системы представлял почтенный возраст целевых платформ. Если Commodore 64 до сих пор сохраняет огромную популярность среди любителей старых компьютеров, его более возрастные предшественники — PET, VIC-20, Plus/4 и прочие — изначально не имели большой популярности вследствие малых тиражей, и с тех пор изрядно её утратили.
Результатом этого стало исчезающе малое количество вменяемой документации по этим платформам в электронном виде и в современных форматах. В большинстве случаев это сканы древних манускриптов в форматах PDF и DJVU, в основном посвящённые программированию на Бейсике. В частности, так и не удалось найти упоминаний, какие локации Zero Page можно безопасно использовать в собственной программе в машинном коде, не мешая работе интерпретатора Бейсика (по совместительству BIOS), что стало большой проблемой на этапе интеграции моей звуковой системы с реальной игрой.
Другой стороной той же проблемы является малое количество ПО для данных платформ, и это выливается в довольно низкое качество их эмуляции и малое количество эмуляторов в целом. В частности, в эмуляторе VICE реализованы не все режимы работы 6522, а в MAME они реализованы с некоторыми существенными неточностями — просто потому что старые игры не пользовались этими возможностями, и проверить правильность их эмуляции затруднительно.
В наших краях всё усугубляется недоступностью 8-битных компьютеров Commodore в 80-х и 90-х, и как следствие, в сейчас менее популярные линейки можно найти разве что в музеях. Собственно, так и вышло — я впервые увидел реальный PET 8032 в Музее Истории Электросвязи МТУСИ уже после выхода и игры, и моего альбома. И даже если реальный компьютер был бы доступен, остаётся ещё непростая задача переноса на него файлов с PC, так как компьютеры Commodore используют свои особые, ни с чем не совместимые дисководы и накопители на магнитной ленте, как и свои особые порты ввода-вывода.
Таким образом, я был вынужден проверять свой код в откровенно некорректно работающих эмуляторах с рудиментарными средствами отладки. Редкие запуски кода на реальных компьютерах, которые были в наличии у Дэвида, быстро его утомили, и это стало ещё одной из причин непопадания конечного результата в реальную игру — проблемы, которые у нас возникли, было невозможно отладить в имеющихся эмуляторах, и сложно тестировать даже на реальных компьютерах, тем более не имея к ним прямого и оперативного доступа.
Чтобы сделать отладку кода хоть сколько-то возможной, я реализовал в нём поддержку ещё одной платформы с процессором 6502 — игровой приставки NES. За счёт куда большей популярности, для неё существуют десятки качественных эмуляторов, и часть из них обладает адекватными отладочными возможностями. Это существенно помогло делу и позволило достаточно комфортно тестировать код в эмуляторе FCEUX. Дизайн кода таков, что основная логика проигрывателя музыки остаётся неизменной, меняется только преобразование её выходных данных в формат, подходящий для конкретных звуковых устройств. Когда код заработал на NES, он был перенесён на остальные платформы и дополнен их аппаратно-зависимой частью.
Создание музыки и звуковых эффектов
После того, как был придуман формат и написан проигрыватель, нужно было решить проблему создания контента, то есть собственно музыки: как и чем можно набрать её ноты и преобразовать в бинарные данные заданного формата. Чем менее ограничен по возможностям формат, тем проще это решить — можно приспособить уже существующий редактор, и сделать конвертор из его формата (например, MIDI или XM), а музыку писать, соблюдая необходимые ограничения вручную. Чем более же ограничены возможности и специфичны требования выходного формата, тем сложнее придумать конвертер из более продвинутых форматов, вплоть до невозможности ввиду слишком большого неудобства. Это был как раз данный случай.
В таких случаях обычно прибегают к методу ручного введения данных в нужном формате — то есть, сочиняют музыку любым удобным способом, держа в уме все необходимые ограничения, записывают её в виде нот, и далее вручную переводят в бинарный вид. Так делалась музыка для видеоигр до начала 90-х. Это рабочий, но очень трудоёмкий и утомительный метод, требующий большого количества времени и оставляющий слишком много возможностей для ошибки. Время поджимало, поэтому требовалось создать какой-то специальный редактор.
Сначала я обдумывал возможность создания редактора, работающего на самом PET — его можно было бы написать достаточно быстро на C. Но он был бы сильно неудобен в использовании, особенно в части опроса клавиатуры (у PET значительно меньше клавиш, чем у PC) и в части переноса файлов, которые нужно было бы как-то извлекать из образа диска. Также требовалось предусмотреть поддержку других целевых платформ, а делать отдельный редактор для каждой из них было бы нецелесообразно, и в результате я отказался от этой идеи. Однако, в итоге нечто подобное, но в более упрощённом виде, реализовал сам Дэвид для PET-версии игры, после отказа от использования в ней моего кода.
Следующим вариантом решения проблемы было создание кросс-редактора для PC, подобного FamiTracker и его аналогами. С одной стороны, это решало многие проблемы, но всё равно заняло бы слишком много времени — звуковые чипы моих целевых платформ толком не эмулировались, и я не мог просто взять и использовать уже готовый исходный код из существующих эмуляторов. Написание эмулятора малоизвестного и плохо задокументированного устройства, не имея его самого под рукой — задача, решение которой потребовало бы неопределённого количества времени. От этой идеи я тоже отказался.
Оставался ещё один вариант. Лет десять назад я разработал собственный экспериментальный кросс-редактор 1tracker, как раз с целью упрощения создания музыки для экзотических платформ. Он полностью конфигурируется с помощью внешних скриптов на AngelScript, не требуя перекомпиляции для поддержки новых форматов. Это был бы самый подходящий случай применить его по прямому назначению. Однако, звуковая библиотека Game_Music_Emu, используемая в нём, не поддерживала даже SID, не говоря про звуковые чипы VIC-20 и PET.
Для решения проблемы отсутствия поддержки звуковых чипов, как и возможности внедрения таковой по причине отсутствия отлаженных решений, возникла следующая идея: как и в случае с отладкой кода, использовать для озвучивания музыки в процессе редактирования один из музыкальных форматов для других платформ на основе процессора 6502 — NSF, SAP или HES, и эмулировать приблизительно правильный звук прямо в коде 6502. Однако, это был первый случай, когда потребовалось сделать что-то для 6502 в 1tracker, и необходимая инфраструктура, главным образом встроенный кросс-ассемблер, в нём просто отсутствовала. Эту проблему можно было бы решить, но опять потребовалось бы значительное количество времени, сравнимое с разработкой нового трекера.
Окончательным решением стала довольно сомнительная идея, которая, впрочем, сработала: использовать 1tracker и его инфраструктуру для Z80, написав версию кода музыкальной системы на ассемблере Z80, и на нём же написать программный синтезатор звука, аналогичный по возможностям и ограничениям чипу 6522. Таким образом при редактировании музыки можно было бы слышать более-менее похожий на правду звук, а после экспорта музыкальных данных скомпилировать с ними программу для запуска в эмуляторах PET, VIC-20 и C64, где уже можно было бы услышать полностью аутентичный звук.
В этой идее было одно слабое место: ZX Spectrum с процессором Z80, эмулирующийся в контейнере AY не отличается быстротой (3.5 МГц), и не имеет никаких прерываний, помимо кадрового. И если этой скорости ещё как-то хватает для эмуляции звука 6522, перерывы в этом процессе каждые 1/50 секунды, чтобы обработать логику музыкального проигрывателя, создают сильное гудение. Я решил эту проблему в лоб, введя виртуальный разгон до бесконечной частоты (0 тактов на команду), который включается и выключается прямо из кода Z80 неиспользуемыми опкодами типа LD H,H. Этот разгон включается в момент обработки логики и выключается для продолжения эмуляции звукового потока, что убрало весь посторонний шум.
Таким образом я написал полный эквивалент кода проигрывателя на ассемблере Z80, вместе с эмуляцией звука PET, и обошёлся только скриптовыми средствами 1tracker. Также были сделаны небольшие доработки фронт-енда, сделавшие редактирование музыки с такими специфическими ограничениями более комфортным. Был добавлен экспорт бинарных файлов с музыкальными данными для включения их в код для 6502. Этого решения оказалось вполне достаточно. На время первых тестов я не стал делать эмуляцию звуковой части VIC-20 и C64, тестируя эти платформы только через экспорт и соответствующие эмуляторы, а при последующей смене приоритетов их поддержка и вовсе перестала быть востребованной.
Звукозапись
Казалось бы, после того, как все технические проблемы были решены, музыка сочинена, а оболочка написана, ничто не мешало выпуску альбома. За неимением доступа к реальному компьютеру оставалось только записать звук из эмулятора Commodore PET в аудиофайлы. Однако, даже такая простая задача таила неожиданные сложности.
Эмуляция звука в актуальной на момент релиза альбома версии WinVICE использовала очень странный фильтр, очень сильно изменяющий тембр звука и, что гораздо хуже, громкость звучания, в зависимости от частоты воспроизводимого звука — вплоть до почти полного пропадания звука на определённых частотах. Вдобавок, сборка самой свежей на тот момент версии xpet для Windows почему-то просто не запускалась, работоспособной была только предыдущая.
Качество звучания в актуальной на тот момент версии MAME было более адекватным, но, во-первых, частота сдвига регистра была вдвое выше нормальной, причём таймер T1 работал с нормальной частотой. Также звук полностью не поддерживался для моделей 40xx, на которые и был рассчитан альбом, но оболочка эмулятора поддерживала только эту линейку, а при выборе других моделей он просто не запускался.
После некоторых раздумий, как же разорвать сложившийся замкнутый круг, было принято решение модифицировать один из эмуляторов. Оказалось проще починить эмуляцию звука в MAME, чем убрать фильтр из VICE, что и было проделано с помощью mr287cc, который помог настроить компиляцию кода. Была изменена частота сдвига регистра и разрешена эмуляция звука для нужной модели. Звук и изображение были захвачены стандартными средствами записи эмулятора, после чего на изображение был наложен фильтр в VirtualDub. Звук я пропустил через самодельный импульс небольшого динамика, чтобы придать ему аутентичное звучание, более интересное по сравнению с идеализированным и поэтому нереалистичным звучанием эмулятора.
Уже после выпуска альбома были отправлены баг-репорты авторам эмуляторов, с программой и записью её работы на различных реальных моделях PET в качестве доказательства необходимости внесения исправлений. Я не проверял и не знаю, были ли решены эти проблемы в актуальных на данный момент версиях.
Наследие
Написание статьи затянулось почти на год. И игра, и альбом были опубликованы прошлой зимой, и за прошедшее время с ними случилось несколько неожиданных вещей, о которых стоит рассказать.
Игра Attack of the PETSCII Robots вышла в версиях для трёх изначально запланированных платформ. Хотя она довольно проста и имеет довольно высокий порог вхождения (без изучения местных правил игровой процесс кажется неинтересным), эта простота и популярность автора сыграли ей в плюс: подписчики Дэвида начали портировать её на всевозможные платформы с процессором 6502, а потом и на некоторые другие. На текущий момент она уже может претендовать на лавры одной из самых мультиплатформенных любительских ретроигр, при этом исходный код игры не является открытым и доступен только по запросу у автора оригинала, а сама игра остаётся коммерческим продуктом.
На сайте Дэвида к приобретению доступно уже девять версий — для PET, VIC-20, C64, Plus/4, C128, Apple II, Atari 8-bit, Amiga, и даже для ZX Spectrum (об этом ниже), причём версия для каждой платформы обладает своими занимательными особенностями. Ещё десяток портов находятся в активной разработке, среди них версии для приставок Sega Genesis, NES, SNES, версия для MS-DOS, Commander X16 и других платформ. Больше подробностей о версиях и процессе их создания можно узнать из серии авторских видео.
Осенью игра обзавелась саундтреком, выпущенным на реальной компакт-кассете. В состав саундтрека вошёл оригинальный саундтрек из версии для Commodore 64, написанный Noelle Aman, а также два альтернативных: полностью новая музыка от Андерса Энгера Дженсена, частого автора музыкального оформления для Youtube-канала Дэвида, записанная на современных синтезаторах, и весь мой альбом Faulty Robots. Это первый случай издания моей музыки на физических носителях, и большая честь для меня оказаться в такой компании.
Примерно тогда же я сделал порт альбома на ESPboy по просьбе создателя этой платформы. Никаких особых целей при этом не преследовалось, просто ещё один занимательный способ поделиться релизом с окружающими.
Этим моё участие в судьбе оригинальной игры не ограничилось. Ближе к концу года мы с моим соратником в делах ретро-разработки, mr287cc, обсуждали идею статической трансляции исходного кода на ассемблере между старыми процессорами разных архитектур, что было бы полезным для наших проектов. Возникла идея создать и отладить на простом проекте транслятор кода 6502 в Z80, и я предложил потренироваться на коде Attack of the PETSCII Robots, для которого на тот момент не существовало Z80-версии. В процессе мы отказались от идеи трансляции, но увлеклись идеей портирования самой игры, и в итоге переписали её код под Z80 вручную, максимально аутентично оригинальному исходному коду. Это довольно негативно отразилось на его оптимальности как по размеру, так и по скорости, но таким образом была сохранена вся оригинальная игровая механика и даже ошибки.
Наш код для Z80 мы использовали для создания версии для ZX Spectrum 48K. mr287cc выполнил основную часть работы по портированию, я же переписал часть кода, отвечающую за логику игровых объектов, помог с отладкой, нарисовал дополнительную графику, и, конечно же, написал всё, что касается звука. В результате получилось аж четыре версии одной и той же игры, отличающихся визуальной частью, но все они используют одно и то же звуковое оформление, которое было создано полностью согласно моему первоначальному видению. В него вошли прерывающие игру довольно сложные звуковые эффекты в большом количестве, созданные с помощью моего редактора BeepFX, а также три трека из Faulty Robots, играющие на титульном экране и после победы или поражения. В довесок к игре был портирован полный альбом со всеми треками в виде отдельно запускаемой программы-оболочки.
Версию игры для ZX Spectrum 48K также можно приобрести на сайте Дэвида, пока что только в цифровой версии. Возможно, через некоторое время удастся выпустить его и на физическом носителе для особых ценителей.
Специально к выходу статьи, после длительного маринования я также наконец опубликовал полный исходный код моей звуковой системы, получившей название PeskyTone, с поддержкой PET, VIC-20, C64 и NES. Не думаю, что он имеет большую практическую ценность вне платформы PET (где хоть такой, но сгодится), но он может послужить базой для старта в программировании этих платформ, ведь для них наблюдается дефицит даже простейших примеров типа "Hello world".