Музыка на Commodore PET — Faulty Robots

После выпуска 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-х. Это рабочий, но очень трудоёмкий и утомительный метод, требующий большого количества времени и оставляющий

© Habrahabr.ru