habrahabr

Устройство «музыкалки» AY-3-8910 и эмулятор на Arduino

  • четверг, 27 февраля 2025 г. в 00:00:17
https://habr.com/ru/companies/ruvds/articles/884436/

Астрологи объявили месяц статей про ZX Spectrum, звук и Arduino. Количество публикаций увеличилось вдвое!

В прошлый раз я показывал, как можно перенести однобитные, они же «биперные», музыкальные «движки» с ZX Spectrum на Arduino. Но всё-таки это была максимально нишевая тема внутри нишевой темы, весьма узкоспециализированный проект.

Сегодня сделаю шаг ближе к народу. Расскажу, как работает легендарный звуковой чип AY-3-8910, как написать его простой эмулятор полностью с нуля, и как проиграть им музыкальный файл формата PT3 на обычной Arduino Nano.

Что такое AY-3-8910


AY-3-8910 и его прочие совместимые собратья — одна из первых специализированных микросхем, она же «чип», для синтеза звука и музыки в видеоиграх и электронных игрушках. Разработана она аж в 1978 году компанией General Instruments, и с тех пор применялась в десятках домашних компьютеров, игровых консолях, простеньких музыкальных синтезаторах, да и где только не применялась.

AY-3-8910A производства Microchip, 1989 год

Эта микросхема дорога сердцу отечественного компьютерного энтузиаста, заставшего начало 1990-х годов, из-за того, что применялась в ZX Spectrum 128K, клоны которого начали появляться в наших краях в то время. Если изначальные «Синклеры» могли только потрескивать бипером и иногда скрипеть кое-какие мелодии, наличие этой микросхемы в 128K модели добавляло компьютеру воспроизводить довольно приятную на слух музыку, не хуже народно любимой Денди.

Называли эту заморскую диковину довольно громким именем «музыкальный сопроцессор», потому что она позволяла воспроизводить звук, не загружая центральный процессор так, как это делал бипер. Позже в народе стало применяться расхожее наименование «музыкалка», а в нашу эпоху её обычно именуют «аигреком», по первым буквам наименования — хотя в серии этих микросхем от GI был много чего ещё, например, AY-3-8500, реализация игры «Понг» на одном чипе.

Yamaha 2149F, 1990 год. Фото из Интернета

Настоящий американский AY, хотя бы и тайваньского производства, достать было непросто. К счастью, существовал японский аналог, YM2149F, применявшийся в компьютерах линейки MSX, которые дружественная Япония поставляла в конце 1980-х годов под названием Yamaha КУВТ в советские школы в рамках реализации программы информатики. Вероятно оттуда и просочились в народ эти чипы, шедшие в качестве ЗИПа, и чаще всего в отечественных клонах Спектрума можно было встретить именно их.

Помимо Спектрума, AY пробрался и во многие отечественные компьютеры в качестве устанавливаемого энтузиастами дополнения — это было лучшее из доступного в наших краях, никаких альтернатив не было и близко. Его подключили и к Вектору, и к БК и, похоже, вообще ко всему. В том числе перенесли и код проигрывателей, а местами сделали и свои музыкальные редакторы.

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

По популярности и значимости для истории «чиптюна» и видеоигровой музыки это накопленное наследие, пожалуй, входит в первую тройку, разделяя место в ней с легендарным MOS 6581 SID в компьютере Commodore 64 и Ricoh 2A03, звуковым синтезатором в NES/Famicom (Денди). И я уверен, что отечественные энтузиасты ещё поспорили бы, кто из них поважнее будет.

Мифы древности


Вокруг AY издревле наросло немало мифов про его внутреннее устройство.

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

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

Блок-схема из оригинальной документации

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

Для начала, нужно отметить, что AY не совсем музыкальный и не вполне процессор.

На роль процессора AY претендует довольно условно: да, он обрабатывает команды и выполняет простейшие действия, но это не машина Тьюринга. Получаемые команды просто меняют характер синтезируемого звука, никаких вычислений не производится, и результаты не возвращаются в систему. Посчитать 2+2 на этом «процессоре» не получится.

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

Фото кристалла AY-3-8910 (низкое разрешение)

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

Более сложные мифы связаны с тонкими деталями внутреннего устройства. Дело в том, что изначально чип был для окружающего мира чёрным ящиком, и точного его устройства программисты не знали. Многие годы они пытались угадать его, наблюдая за поведением чипа снаружи, проводя различные тесты. Иногда догадки были верными, иногда нет. Они фиксировались в истории, транслировались из года в год. И сейчас в сети можно найти разные версии про некоторые тонкие моменты, часть из которых оказалась неверна.

Транзисторная схема AY-3-8910 в уменьшенном донельзя виде

Окончательно расставила все точки над Й декапсуляция чипа и восстановление его полной транзисторной схемы, проведённое в 2019 году. Теперь мы действительно достоверно знаем внутреннее устройство AY, и можем найти в первоисточнике ответы практически все на заковыристые вопросы.

Схема реплики AY-3-8910 на микросхемах 74xx серии в уменьшенном донельзя виде

В начале 1990-х годов ходили слухи про самодельную реализацию на мелкой логике. Подтверждений её существования до сих пор не найдено. Интересно, что в наши дни реализация «на рассыпухе» таки воплощается в реальность, правда, уже не из практических соображений, а из технического любопытства. На данный момент этот проект доведён до рабочего состояния в симуляторе. Получается аж сто с лишним микросхем мелкой и не очень логики.

Микроконтроллер R100-XP, фото «Русь-Телеком»

К сожалению, отечественный аналог AY-3-8910 так и не был создан, хотя попытки предпринимались. По слухам, в конце 1990-х существовала его версия в составе БМК для компьютера Magic-07, но в массовое производство она не пошла. А в 2003 году на мощностях TSMC начал производиться 80C51-совместимый микроконтроллер R100-XP компании «Русь-Телеком», предназначенный для телефонных аппаратов с определителем номера. На кристалле был реализован аналог AY для озвучивания мелодий вызова и будильников — так AY-музыка пробралась в дома ничего не подозревающих мирных горожан.

Устройство


Устройство чипа я опишу в два захода. Сначала это будет поверхностное описание происходящих в нём процессов, а потом перейдём ко всем частностям.

Синтезирует звук AY очень простым способом: программируемые счётчики-делители делят входящую в чип извне тактовую частоту чипа на программируемое целое число.

Работа делителя частоты в изложении капитана Очевидность

Принцип работы счётчика-делителя отражается в его названии: он считает периоды входящей частоты, и когда насчитает заданное количество, меняет состояние своего выхода с 0 на 1, или наоборот. Таким образом получается более низкая частота с сохранением квадратной формы сигнала. Чем больше значение в регистре, тем ниже высота тона, так как коэффициент деления внешней тактовой частоты больше.

Выход счётчика-делителя подключён к ЦАП. Если на выходе делителя 0, то и на выходе ЦАП нулевой уровень, а если 1 — то уровень напряжения, заданный регистром громкости канала. Так регулируется громкость звука.

Управление громкостью с помощью ЦАП

Таких наборов из счётчика-делителя и ЦАП в чипе три штуки — это три звуковых канала, каждый из которых может генерировать тон со своей высотой и громкостью, реализуя таким образом трёхголосую полифонию. Характер звучания этих каналов всегда «квадратный», то есть самый классический меандр с заполнением 0.5.

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

Изменение «частоты» генератора шума

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

Работа генератора амплитудной огибающей

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

У генераторов шума и огибающей нет своих собственных ЦАП. Они подключаются к выбранным ЦАП трёх каналов тона. Шум может звучать вместо тона или одновременно с ним, чем управляет «микшер», соединяющий выходы разных генераторов со входами ЦАП.

Снаружи чипа есть три аналоговых выхода от трёх ЦАП. Они могут быть соединены вместе, как сделано в большинстве западных компьютеров и игровых консолей, что даёт монофоническое звучание. В отечественных клонах ZX Spectrum 128K было популярно псевдо-стерео, для которого три канала чипа смешиваются в два. Как правило, пара каналов идут налево и направо, а третий-лишний звучит посередине.

Частности


Если не учитывать все тонкости устройства AY-3-8910, или делать что-то неправильно, часть музыки всё равно будет звучать вполне нормально. Но другая часть композиций рассчитывает на определённое поведение чипа в пограничных случаях, например, при использовании очень маленьких делителей частоты, и несоблюдение точного устройства чипа при эмуляции приведёт к заметным отличиям в звучании. Поэтому покопаемся во всевозможных частностях.

Авторское видение блок-схемы AY-3-8910

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

Все управляющие регистры AY-3-8910

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

▍ Предварительный делитель


На входе чипа присутствует тактовая частота, представляющая собой сигнал квадратной формы, последовательность 0 и 1, следующих с равномерными интервалами. Для ZX Spectrum это половина от тактовой частоты процессора, то есть примерно 1.75 МГц. Внутри чипа она сразу же делится на 8, что даёт примерно 219 КГц. Все прочие операции внутри чипа происходят уже с этой частотой. Важный момент: официальная документация утверждает, что предварительный делитель имеет коэффициент 16, а не 8, но фактически из-за особенностей устройства чипа это не так.

▍ Каналы тона


Три тональных канала устроены одинаково, и действительно представлены в чипе тремя экземплярами одинаковой транзисторной схемы. То есть никаких внутренних трюков с мультиплексированием не применяется, всё по честному. Нумеруются они, как правило, буквами: A, B, C.

Счётчики-делители у каналов тона 12-битные. То есть можно поделить входящие 219 КГц на число от 1 до 4095 и получить 4095 возможных значений выходной частоты. Загружается делитель через пары регистров: R0 и R1, R2 и R3, R4 и R5. В чётных регистрах хранятся младшие 8 бит делителя, в нечётных — оставшиеся четыре бита. Лишние старшие биты в нечётных регистрах отбрасываются и никак не используются.

Регистры частотных делителей каналов тона

С тональными каналами связано два тонких момента.

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

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

С предыдущим моментом связан и ответ на вопрос, что будет, если записать в регистры делителя 0. Он будет считаться равным делителю 1, то есть делители 0 и 1 дают одинаковый эффект: отсутствие деления, прямая передача исходной (после предварительного делителя) тактовой частоты на выход.

▍ Генератор шума


Генератор шума в AY всего один. Он не имеет своего личного ЦАП, его выход можно направить в один из каналов тона, замещая тон или смешивая шум с тоном по OR.

Регистр генератора шума

Управляется генератор одним 5-битным регистром R6. Это период шума, программируемый делитель тактовой частоты. Чем выше его значение, тем более «низкочастотным» становится спектр шума.

С генератором шума связан только один тонкий момент: что именно он, собственно говоря, генерирует, и как он это делает.

Выяснение устройства генератора шума в своё время потребовало немало экспериментов, но энтузиастам таки удалось воспроизвести правильный алгоритм задолго до декапсуляции с помощью анализа данных. Оказалось, что генерация шума в AY производится 17-битным LFSR, а длина его псевдослучайной последовательности равна 131072 бит.

Некогда совершенно секретная угаданная энтузиастами схема генератора шума

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

▍ Микшер


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

Регистр микшера

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

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

Устройство одного канала микшера

С микшером также связан один неочевидный момент: даже если оба источника сигнала в канале выключены, его ЦАП всё равно продолжает функционировать. Можно напрямую менять его выходное напряжение, меняя значение в регистре громкости, что позволяет воспроизводить «оцифровки». Также можно включить в регистре ЦАП огибающую на этом канале, и тогда будет слышен только генерируемый ей тон, так называемая «чистая огибающая», что повсеместно используется в музыке.

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

▍ Управление амплитудой


Регистры амплитуды R8, R9, R10 позволяют задать громкость звучания каждого из трёх каналов записью 4-битного значения в младшие четыре бита. Всего уровней 16: 0 — полная тишина, 15 — максимальная громкость.

Обратите внимание, что в оригинальной документации на AY-3-8910 нумерация регистров восьмеричная. Поэтому в ней говорится, что регистры громкости имеют номера 10, 11, 12, и нумерация всех последующих регистров также смещена.

Регистры управления амплитудой

Хотя уровней мало, их шкала нелинейная (логарифмическая), то есть такая, которая на слух ощущается как равномерное возрастание громкости.

Фактическая характеристика ЦАП очень долго была предметом споров, её измеряли и так, и эдак, высчитывали теоретически — получилось вполне точно. Окончательно вопрос был закрыт после декапсуляции кристалла: восстановили фактическую схему ЦАП, высчитали реальные уровни. В MAME табличка громкости даже считается по параметрам схемы. Но для простоты эмуляции, впрочем, достаточно простой таблицы заранее рассчитанных уровней.

Если в эмуляторе сделать неправильную, не соответствующую реальной, характеристику ЦАП, это будет ощущаться как «компрессия» звука, изменится соотношение тихих и громких составляющих в музыке.

График характеристики ЦАП из оригинальной документации

Хотя график в документации подразумевает размах выходного сигнала от 0 до 1 вольта, на самом деле он нормализован. В реальном чипе минимальный выходной уровень канала не нулевой. Он составляет 0.2 вольта для AY-3-8910, и аж 2.5 вольта для YM2149F. В эмуляторах обычно производят нормализацию к диапазону 0…65535 или подобному, вычитая постоянную составляющую.

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

▍ Генератор огибающей


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

Устройство генератора огибающей

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

Программируемый счётчик-делитель огибающей имеет разрешение 16 бит и управляется регистрами R11 и R12. Каждое переполнение счётчика-делителя вызывает продвижение счётчика амплитуды согласно выбранному режиму.

Регистры частотного делителя огибающей

Документация утверждает, что тактовая частота для счётчика огибающей делится на 256 от внешней тактовой частоты. На самом деле это дополнительное деление на 16 в расчёт делителя вносит счётчик амплитуды, полной период работы которого занимает 16 шагов. Однако, есть нюанс: при эмуляции нужно делить входную частоту счётчика-делителя огибающей на два, то есть на 16 от внешней тактовой частоты.

Также есть отличие для пограничного случая, когда делитель равен 0: он не считается равным 1, как у каналов тона, а даёт вдвое меньшее деление — видимо, это как раз связано с наличием дополнительного делителя на два.

Регистр режимов работы огибающей

Регистр R13 задаёт режим работы счётчика амплитуды. В регистре 4 бита, управляющие отдельными настройками счётчика, и комбинация настроек приводит к генерации той или иной формы изменения амплитуды. Но некоторые комбинации дают одинаковый результат, поэтому, хотя у четырёх бит 16 возможных комбинаций, форм амплитуды всего восемь. Доступные настройки: удержание, альтерация направления, атака, повторение.

  • Бит 0: удержание. Если бит сброшен, счётчик работает согласно настройке альтерации управления. Если установлен — счёт остановится на конечном значении, которое в зависимости от направления будет 0 или 15.
  • Бит 1: альтерация направления. Если он сброшен, в конце счёта происходит сброс на начальное значение, то есть получается последовательность 0..15, 0..15. Если бит установлен, направление счёта меняется каждый период: 0..15, 15..0.
  • Бит 2: атака. Фактически это бит начального направления счёта. Если он сброшен, это «спад», работа огибающей начинается с 15 и счёт идёт вниз, от 15 до 0. Если установлен — это «атака», то есть нарастание, счёт начинается с 0, идёт от 0 до 15.
  • Бит 3: повторение. Этот бит дополняет логику удержания. Если он установлен, логика будет определяться битом удержания. В противном случае после окончания первого периода счёта значение счётчика будет зафиксировано на 0 (даже если счёт шёл вверх).

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

Формы огибающей из оригинальной документации

Важный момент, который необходимо учитывать для правильного звучания музыки, использующей генератор огибающей для генерации тона: запись в регистр R13 любого значения (даже если там уже было такое же) сбрасывает весь генератор огибающей, то есть счётчик-делитель начинает отсчёт заново, а счётчик амплитуды устанавливается в соответствующее режиму значение, 0 или 15.

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

Но в начале 1990-х годов музыканты открыли интересный трюк: при очень коротком периоде нарастания или спада можно получить на выходе генератора огибающей частоты в слышимом диапазоне, хотя и не очень хорошо попадающие в ноты в верхнем регистре.

Если генератор огибающей работает в таком режиме, и на ЦАП направлен только его выход, это позволяет получать треугольный или пилообразный звуковой сигнал, хотя и без управления громкостью. А используя такую «быструю» огибающую совместно с каналами тона можно создавать разнообразные эффекты модуляции, что стало самой узнаваемой особенностью звучания AY.

Модулированный сигнал и его составляющие: квадрат и пила

Дать определение типу модуляции, получаемой таким образом, затруднительно — это не AM, не FM и даже не PM: не амплитудная, не частотная, и даже не фазовая. По сути выход генератора тона маскирует выход огибающей по функции AND, выдавая на ЦАП ноль либо уровень огибающей.

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

▍ Порты ввода-вывода


Помимо синтеза звука, у AY-3-8910 есть ещё одна не так уж часто используемая на практике функция: он содержит два двунаправленных 8-битных порта ввода-вывода, то есть, говоря современным языком, GPIO.

Неожиданный способ использования регистров ввода-вывода из оригинальной документации

Два «лишних» старших бита в регистре микшера R6 выбирают направление работы каждого из этих двух портов — все восемь бит на вход или на выход.

Фактически порты ввода-вывода AY были задействованы, например, в игровой консоли Vectrex. Там к ним подключены кнопки управления и многофункциональный ЦАП, служащий как для управления интеграторами отклонения луча, так и для опроса потенциометров аналоговой рукоятки двух джойстиков.

Аналоги


У AY-3-8910 есть с десяток аналогов. Их можно разделить на два семейства: оригинальные версии чипа от GI и совместимая реализация от Yamaha.

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

AY-3-8910A и AY-3-8912 производства GI, 1985 год. Фото из Интернета

Сам AY-3-8910 не является первой версией чипа, как можно было бы подумать. Его кристалл датирован 1979 годом, а кристалл более распространённой версии AY-3-8910A и вовсе 1983 годом. При этом самая ранняя известная официальная «предварительная» документация датирована 1978 годом, и описание в ней не вполне точно совпадает с фактическим устройством AY-3-8910.

Версии AY-3-8912 и AY-3-8913 используют корпуса меньшего размера: на 28 выводов и на 24 выводов соответственно, вместо полноценных 40. На данный момент их декапсуляция не произведена, но предположительно, в их кристаллах нет отличий от AY-3-8910, просто наружу не выводится один или оба порта ввода-вывода.

AY-3-8914 довольно редкий и интересный зверь, использовавшийся только в игровой консоли Mattel Intellivision — под которую, предположительно, изначально и создавался звуковой чип: дело в том, что её заказной видеоконтроллер носит наименование AY-3-8900. Эта версия в точности соответствует документации 1978 года. Им же датирован и кристалл, который довольно сильно отличается, и вероятно, это и есть оригинальный AY.

AY-3-8914 производства GI, 1984 год. Фото из Интернета

У AY-3-8914 есть три отличия от прочих версий:

  • Другая нумерация управляющих регистров.
  • Дополнительный бит в регистрах громкости, который позволяет сдвигать выходное значение огибающей на 0, 1 или 2 бита. Увы, в AY-3-8910 эту особенность убрали, а она могла бы очень сильно пригодиться музыкантам.
  • Счётчики тона здесь действительно считают вниз, а не вверх. Поэтому нулевое значение делителя даёт период максимальной длительности.

AY-3-8916 и AY-3-8917 применялись в игровой консоли Mattel Intellivision II и в аксессуаре Intellivision ECS. Как минимум AY-3-8917 отличается нумерацией управляющих регистров: не $00..0F, а $F0..FF. В остальном про их отличия мало что известно, но скорее всего это уже вариация привычного 8910, а не 8914.

Японский аналог, Yamaha YM2149F, пока не был декапсулирован и изучен полностью, но предположительно, согласно внешним наблюдениям, довольно сильно отличается по внутреннему устройству. Его совместимость очень высока — можно просто установить его вместо оригинала, и всё будет работать просто замечательно. Тем не менее, он содержит несколько интересных отличий.

  • Опциональный дополнительный предварительный делитель. Он позволяет как устанавливать чип вместо AY-3-8910 без изменений схемы, так и проектировать новые схемы, где на чип подаётся сразу тактовая частота процессора, те же 3.5 МГц. Внутри она будет дополнительно поделена на два, и далее всё будет работать полностью одинаково.
  • Счётчик амплитуды генератора огибающей имеет не 16, а 32 шага, но счётчик-делитель огибающей тактируется вдвое быстрее, поэтому отличий в скорости нарастания-спада или частоте генерируемых звуков нет. Это добавляет промежуточные ступеньки амплитуды, чуть сглаживающие алиасинг генерируемых огибающей звуковых частот. Говорят, на слух это ощущается как более «мягкий» звук.
  • Другие выходные ЦАП. Они имеют 32 уровня вместо 16. Дополнительный уровень используется только генератором огибающей. Записями в регистры R8-R10 можно установить только традиционные 16 уровней, без младшего дополнительного бита. Также у ЦАП присутствует постоянная составляющая в 2.5 вольта.

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

Например, ядро YM2149F применялось во многих других чипах Yamaha. В том числе оно встроено в некоторые чипы FM-синтезаторов. Также существует вариант в формате 16-выводной микросхемы YMZ284 с упрощённым подключением, одним портом ввода-вывода и общим заранее смешанным монофоническим выходом звука.

AY8930/P производства Microchip, непонятно какой год. Фото из Интернета

Наконец, существуют не вполне совместимые аналоги, развивающие оригинальную архитектуру AY-3-8910. Из них стоит отметить микросхему AY8930, реализующую 16-битные делители и управление скважностью каналов тона, три отдельных генератора огибающих, а также режим программной совместимости с оригинальным чипом.

Пишем эмулятор


Обладая пониманием устройства чипа, написание его эмулятора является довольно тривиальным делом.

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

Для подобной эмуляции код логики работы чипа будет выглядеть крайне просто. Например, тональный канал в псевдокоде:

tone_counter++
if tone_counter >= tone_period:
 tone_counter = 0
 tone_output ^= 1

Однако, спортивным интересом данной статьи является запуск эмулятора именно на Arduino. По проектам прошлых лет известно, что для качественной эмуляции AY едва хватает мощностей даже разогнанной до 24 Мгц ATmega с написанием кода на полностью ассемблере. У нас же будет стандартная классическая Arduino на 16 МГц и довольно жирный код на C. Поэтому будут грязные хаки и их жертвы.

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

Качество эмуляции при таком подходе, конечно, значительно пострадает: не будет никакой реальной передискретизации и интерполяции, «лишние» сэмплы просто не генерируются. Соответственно, будет сильно заметен алиасинг. Зато работает быстро! И если умудриться запустить поток сэмплов на частоте 219 КГц (простой Ардуины никак не хватит, а вот STM32 может и потянуть), получится даже вполне точно. Чем ниже частота дискретизации, тем хуже будет становиться звук, всё более превращаясь в тыкву.

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

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

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

Счётчики с дробным шагом реализованы на 32-битных переменных, что для Arduino, конечно, перебор, но 16 битами тут никак не обойтись, а работать с 24-битными переменными на C не очень-то сподручно. Работают счётчики следующим образом, на примере канала тона:

uint32_t tone_a_cnt = 0;
uint32_t tone_a_div = period << 8;
uint8_t tone_a_out = 0;

tone_a_cnt += frac_clock;

while (tone_a_cnt >= tone_a_div)
{
	++tone_a_out;

	tone_a_cnt -= tone_a_div;
}

При шаге больше единицы счётчик может переполниться больше, чем на единицу, но не обязательно на значение шага. А на высоких частотах (малых делителях) переполнение может произойти даже несколько раз за одно обновление. Поэтому нужно сохранить «перескок» и дробную часть счётчика, просто на 0 его сбрасывать нельзя. Каждый инкремент tone_a_out меняет состояние его младшего бита, что позволяет получать в нём корректное состояние выхода генератора.

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

if (tone_a_cnt >=tone_a_div)
{
	tone_a_out +=tone_a_cnt / tone_a_div;
	tone_a_cnt %=tone_a_div;
}

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

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

Генератор огибающей воспроизводит всю оригинальную логику с разбором управляющих бит, хотя оптимальнее было бы сделать switch-case с частными случаями для каждой их комбинации.

Ограниченная вычислительная мощность ограничивает и возможную частоту дискретизации. Её нужно подобрать по максимуму, при котором хватит скорости для стабильной работы. Для примерного определения этого максимума я сделал функцию профилирования, которая измеряет время выполнения 1000000 вызовов эмулятора AY, включая также и вызовы проигрывателя музыки. Оказалось, что код со скрипом, но успевает делать примерно 32000 итераций в секунду, что позволило выбрать частоту дискретизации вполовину от частоты PWM, 31250 Гц. Довольно удобно оказалось и то, что 31250 = 3500000 / 2 / 8 / 7, если вы понимаете, о чём я.

Проигрыватель PT3


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

В истории ZX Spectrum было много музыкальных редакторов и соответствующим их форматов хранения мелодий. С середины 1990-х годов и по настоящее время самым популярным из них является PT3, формат редактора Pro Tracker 3, с которым совместимы и некоторые другие редакторы, в частности, главный редактор современности, Vortex Tracker II.

Современная наружность Vortex Tracker II

Так как ресурсы ZX Spectrum довольно ограничены, формат PT3 достаточно непрост. Он ориентирован на очень компактное хранение данных и их быстрый парсинг. Разбираться с ним в рамках статьи и писать свой собственный проигрыватель мы не будем — это очень надолго и не очень познавательно. Лучше возьмём готовое решение. Благо, уже давно есть реализации на C, подходящие для наших целей.

Одна из реализаций находится в составе библиотечки libayfly. Эта библиотека предназначена для проигрывания самых разных около-спектрумовских музыкальных форматов, а их парсеры разделены по отдельным файлам исходного кода, что позволяет достаточно просто адаптировать их для других нужд. Давеча я уже делал это в проекте своего аппаратного плеера чиптюнов hway, о котором есть статья на Хабре.

Однако, просто взять исходник и внедрить его в проект не получится. Ресурсы Arduino сильно ограничены, а проигрыватель в своём исходном виде рассчитывает на использование ОЗУ, в которое многие модули просто не влезут: они в среднем имеют размер под 4 килобайта, а некоторые и заметно больше. Придётся размещать музыку в PROGMEM, туда же отправить некоторые необходимые проигрывателю таблички, и доработать код, чтобы он мог использовать все эти данные.

Я не стану описывать все необходимые переделки в весьма своеобразно написанном коде оригинального проигрывателя из libayfly. Просто подготовил рабочий вариант, из которого выкинул всё лишнее, что не потребуется в рамках нашего проекта. Помимо прочего, я разделил функции обновления состояния проигрывателя и записи регистров AY после обновления. Ознакомиться с кодом и попытаться разобрать его логику можно в файле PT3Play.h.

Вывод звука


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

Но AY чип более интересный, с полифонией, уровнями громкости, разными тембрами, и даже каким-никаким стерео. По-хорошему для озвучивания результата работы его эмулятора нужен нормальный человеческий ЦАП. Но у Arduino на борту его нет. Зато есть широтно-импульсная модуляция (PWM) и три таймера, чем мы и обойдёмся сегодня.

Картинка о сути PWM: разное заполнение — разное среднее напряжение

Использование PWM для вывода звука имеет немалое количество тонкостей, описание которых тянет на ещё одну статью такого же размера. Поэтому ограничимся просто готовым кодом, подходящим для нашего случая, без лишних пояснений. В двух словах суть происходящего: с помощью таймера на выходном пине генерируются импульсы высокой частоты и изменяющейся ширины. Чем шире импульсы, тем больше «коэффициент заполнения» — больше энергии передаётся на выход, громче звук. Таким образом с помощью однобитного выхода имитируется ЦАП.

Калькулятор RC-фильтра

Применение PWM требует применения фильтра низких частот, срезающего высокочастотную составляющую выходного сигнала. Для эффективной фильтрации частота среза должна быть в десять раз меньше, чем частота несущей, но для воспроизведения цифрового звука она должна быть выше частоты дискретизации. В качестве компромисса для этих противоречивых требований я выбрал частоту в 16 КГц. Фильтр у меня представляет собой простейшую RC-цепочку: резистор на 1 кОм и конденсатор на 10 нФ.

Подключение стереофонических наушников через RC-фильтры

Для того, чтобы что-то расслышать, применил платку усилителя на PAM8403 и пару динамиков, а также добавил линейный выход, чтобы записать сигнал не через микрофон. Вход усилителя через RC цепочки подключается к пинам 3 и 9 на плате Arduino.

Результат


Осталось соединить все компоненты вместе. Для простоты понимания происходящего реализация будет максимально примитивной.

Таймеры 1 и 2 задействованы для генерации «быстрого» 8-битного PWM с частотой 62500 Гц.

Таймер 0 генерирует прерывания с частотой 31250 Гц. В обработчике прерывания вызывается эмулятор AY, продвигая счётчики на соответствующее значение. Бинарные уровни (0..15) с ЦАП эмулируемого чипа преобразуются по табличке в значения амплитуды, складываются в простенькое стерео, и выводятся через два канала PWM.

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

Проигрыватель PT3 файла работает в основном потоке. Его состояние обновляется раз в 1/50 секунды, и он производит записи в регистры эмулируемого AY. Так как таймер 0 задействован для генерации частоты дискретизации, функция delay недоступна, поэтому для синхронизации проигрывателя с реальным временем применяется счётчик внутри обработчика прерывания таймера 0.

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

Исходный код проекта (внимание: внутри есть BAT-файл и скрипт bin2h на Python!)

Включаем, собираем, и слушаем демо-трек.

Да, качество не сильно впечатляет. Улучшить его можно многими способами: не использовать запрет прерываний, повысить частоту дискретизации, добавить нормальный ЦАП, сделать передискретизацию с фильтром. Где-то на этом пути нужно также выбросить Arduino и взять контроллер помощнее.

Стоит упомянуть о загадочном моменте, возникшем во время отладки кода. На современной Arduino Uno с USB Type C код играл несколько секунд и падал. Долго пытался найти причину, потом запустил на современной Nano Type С, и там он работает нормально, как и на древней версии с Micro USB. Понижение частоты дискретизации на это никак не влияет. Загадка дыры.

Заключение


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

© 2025 ООО «МТ ФИНАНС»

Telegram-канал со скидками, розыгрышами призов и новостями IT 💻