Эмулятор в эмуляторе для проигрывания чиптюн-мелодий на YM2149F
Кто помнит Tetris 2 на Спектруме? Там были куча уровней, возможность играть вдвоём и классная музыка.
Недавно мы сделали 8-битный компьютер для несложных игр, но никаких звуковых возможностей в нём не предусмотрели. И вот захотелось добавить туда какую-нибудь 8-битную музыку. Мне вспомнилась именно мелодия из Tetris 2 (много за ним часов проведено), поэтому я стал с ней ковыряться.
Процессор ATmega328P в нашем компьютере большую часть времени занят отрисовкой изображения, поэтому времени на синтезирование нормальной музыки совсем нет. Значит нам понадобится звуковой процессор YM2149F (он же AY-3-8910), такой же как в ZX Spectrum и других компьютерах.
Подключаем YM2149F к Arduino
Начал я с подключения звукового синтезатора к плате Arduino и с вывода на него простых нот. YM управляется с помощью записи значений в один из 15 (на самом деле мы используем 13) регистров. В регистры записываются частоты звука в каждом из трёх каналов, частота шума, уровень громкости, частота и форма огибающей. Для адресации и передачи данных используется 8 сигналов. Ещё парочка нужны для управления режимом шины — выбор регистра или загрузка значения.
Для тактирования YM используется встроенный таймер процессора с делителем частоты. В итоге на вход звукового чипа приходит 2МГц, что неправильно с точки зрения соответствия оригиналу, но для наших тестов пока сойдёт. Мы же программисты, а не железячники.
Тестовые ноты проигрались успешно и надо было двигаться дальше.
Я раньше не очень интересовался спектрумовской музыкой отдельно от самого Спектрума, но всё же слышал про формат AY. Ещё в одной из статей я видел упоминание формата PSG. Он похож на всякие WAV в MP3 в том смысле, что там содержится линейная последовательность действий с регистрами музыкального сопроцессора. Поэтому файлы получаются большие и в память ATmega не влезают.
Файлы в формате AY намного меньше. В чём же там секрет? А в том, что это куски кода из игр или демок для проигрывания мелодии, а также некоторые массивы данных для этого кода. Обычно проигрыватели просто эмулируют центральный процессор Спектрума, чтобы выполнить эту программу и так проиграть мелодию.
Почему бы не сэмулировать Z80 на AVR?
… подумал я и поискал какую-нибудь библиотеку-симулятор процессора Z80 на языке C. Такой симулятор нашёлся. Ему надо было подсунуть только функции чтения/записи памяти и портов ввода-вывода.
Небольшая сложность возникла с памятью — ведь у ZX Spectrum 48 килобайт ОЗУ, а у ATmega328P всего 2 — напрямую создать массив памяти для функций чтения/записи не получится. Пришлось сделать массив адресов и ячеек, а в нем искать значения при обращениях от процессора.
Оказалось (кто бы мог подумать!), что эмулировать один 8-битный компьютер на другом 8-битном — не очень хорошая идея. Какой-то звук выводится, но всё происходит настолько медленно, что мелодией это назвать сложно. Тогда я решил разобраться с кодом проигрывателя и переписать его на C.
Декомпиляция или закат Солнца вручную
Код оказался немного запутанным. Это был интерпретатор какого-то байт-кода, управляющего музыкальным сопроцессором. В байт-коде даже есть поддержка циклов и подпрограмм. Каждый канал YM управляется отдельной программой с отдельным стеком. Получилось, что изначально я эмулировал компьютер, который эмулирует другой трехпроцессорный компьютер. И хоть программа получалась небольшая (и запускалась лишь 50 раз в секунду), всё равно это было очень медленно.
После того, как я почти раскусил формат этого байт-кода, я случайно наткнулся на его описание. Это был Fuxoft AY Language. Его разработал Frantisek Fuka (Fuxoft), который написал и сам тетрис, и музыку к нему. Этот язык используется в нескольких десятках композиций. И их код даже извлечён из игр в виде файлов FXM. Тот код, что я уже проанализировать, пришлось выкинуть, чтобы начать всё заново (но его по-прежнему можно увидеть в истории изменений репозитория).
Проигрыватель FXM
Краткое описание формата и его декодер мне попались в исходниках бульбовского проигрывателя. Сравнивая его с дизассемблированным кодом, я нашёл всего пару небольших отличий, но семантика структур данных стала намного понятнее.
Теперь проигрыватель работает достаточно быстро. Музыка играется, можно объединять компьютер и прогрыватель. Каждая композиция занимает всего пару килобайт, поэтому если взять Arduino Mega (на процессоре ATmega2560), то можно уместить в его память все существующие мелодии в формате FXM.
Что дальше?
Осталось добавить правильный кварцевый генератор, чтобы частота работы микросхемы была как на Спектруме. Ещё можно написать декодеры других трекерных форматов, добавить SD-карту и получится аппаратный плеер спектрумовской музыки. А когда подсоединим это к нашей игровой коробке — получится настоящий игровой автомат.
Правда пока почему-то не получается, чтобы громкость динамика была нормальной, сейчас всё очень тихо. Может кто-нибудь знает как заставить такой динамик из игрушечного телефона звучать? Китайский телефон с этим справляется, а выходы YM2149F почему-то не очень. Поэтому видео работы плеера я пока не выкладываю.