Как работает музыка в NES

    Если тут есть музыканты, которые имеют свой ютуб-канал или паблик вконтакте, ответьте мне на один вопрос: знаком ли вам такой способ набрать популярность, как каверы на музыку из старинных видеоигр? Способ убойный не только из-за ностальгии. Smooth McGroove в одном из своих интервью упоминал, что был удивлён, насколько крутые мелодии делались для старых игр. Дело не только в том, что над старыми играми работали профи, но и в том, какие ограничения им приходилось преодолевать. То, что звучит круто, будучи собранным из ассемблера, спичек и желудей, расцветает ещё больше, если это сыграть на настоящих музыкальных инструментах. Ну, или спеть акапельно.



    В этой статье я расскажу о том, как в общих чертах работает звукогенерация в Ricoh 2A03/2A07, которые использовались в NES. Почему именно об этом? Ну, тут не столько я выбираю темы для своих статей, сколько мой pet-project выбирает их для меня.


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


    Что создавало звук на вашей старой «восьмибитке»?


    Это делал процессор Ricoh 2A03 в регионе NTSC и Ricoh 2A07 в регионе PAL. Отличия между ними сводятся к разнице между соответствующими телевизионными стандартами. Здесь я буду вести речь о NTSC.


    Итак, процессор NES работает с таковой частотой 1789773 герц (1662607 в версии PAL). Да, один миллион семьсот восемьдесят девять тысяч семьсот семьдесят три такта в секунду. Чем важно это волшебное число? Это будет видно дальше.


    Та часть процессора, которая отвечает за звук называется APU. Её тактовая частота в два раза меньше. Это тоже важный факт, который надо запомнить.


    Состоит APU из пяти звукогенераторов, или каналов, каждый из которых управляется четырьмя байтами. То есть, у вас есть четыре байта и вы с помощью местного ассемблера выставляете нужные биты, чтобы указать, как приставка должна генерировать белый шум, например. Ещё один байт включает/выключает каждый из каналов. И ещё один управляет Frame Counter (об этой штуке ниже).


    Два канала генерируют сигнал прямоугольной формы. Третий канал выдаёт «треугольный» звук (сигнал в форме эдакой пилы). Ещё один канал выдаёт белый шум. Последний канал позволяет развернуться и, в теории, проиграть любой звук, но с достаточно жёсткими ограничениями. Дополнительную жёсткость им добавляет то, что процессор из железки 1983 года выпуска кроме звука ещё должен целую видеоигру проигрывать.


    Общие компоненты


    А теперь поговорим о тех вещах, которые используются в нескольких каналах.


    Frame counter


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


    Есть счётчик, который с каждым циклом APU (два цикла CPU) увеличивается. На определённой цифре посылаются сигналы. Ниже табличка, где X.5 означает задержку на один такт CPU.


    Но сначала о том, какие сигналы даёт наш Frame Counter. Есть три типа сигналов. Quarter Frame, Half Frame и, внезапно, Frame. «Четверть» и «Половинка» используются другими частями APU. А сам Frame является процессорным прерыванием, работу которых в NES надо разбирать другой статьёй. Запуск процессорного прерывания можно отключить, если оно вам не нужно.


    N.B. Вкратце, процессорное прерывание — это событие, которое заставляет процессор выполнить код, его обрабатывающий. У кого в универе были лабы по ассемблеру могут помнить, как нажатие клавиши на клавиатуре выдёргивало вашу программу на определённый кусок кода вопреки всему.


    Циклов APU прошло Что происходит
    3728.5 Отправляется сигнал четверти фрейма.
    7456.5 Отправляются сигналы половины и четверти фрейма.
    11185.5 Отправляется сигнал четверти фрейма.
    14914 Выставляется флаг, запускающий процессорное прерывание
    14914.5 Выставляется флаг, запускающий процессорное прерываниеб отправляются сигналы половины и четверти фрейма.
    0 (14915) Выставляется флаг, запускающий процессорное прерывание, обнуляется счётчик циклов

    Чтобы числа из левой колонки нам о чём-то сказали, нужно умножить 14915 на два (получим 29830 циклов CPU), а потом наше «волшебное» число 1789773 герца разделить на полученный результат. Получается 59,99909 процессорных прерываний в секунду. Почти 60 фреймов в секунду. Ну, и соответственно, почти 240 «четвертинок» и 120 «половинок» в секунду.


    Стоит упомянуть ещё «замедленный» режим, за переключение в который есть отдельный флажок. Вот табличка:


    Циклов APU прошло Что происходит
    3728.5 Отправляется сигнал четверти фрейма.
    7456.5 Отправляются сигналы половины и четверти фрейма
    11185.5 Отправляется сигнал четверти фрейма.
    14914.5 Ничего
    18640.5 Отправляются сигналы половины и четверти фрейма
    0 (18641) Обнуляется счётчик

    Да, всё верно. В замедленном режиме нет процессорного прерывания. 48,00635 циклов в секунду дают нам приблизительно 96 половинок фрейма в секунду и около 192 четвертинок фрейма в секунду.


    Отмечу ещё, что Frame Counter он один на весь APU, в отличие от тех компонентов, которые я рассматриваю дальше.


    Length Counter (Счётчик длины ноты)


    Счётчики длины каждый для своего канала. Он есть у четырёх каналов из пяти: у генераторов прямоугольных, треугольных и шумовых волн.


    Состояние этого счётчика описывается флагом «вкл/выкл» (можно записывать туда значение) и, собственно, счётчиком. Когда у нас записано «выкл», этот счётчик принудительно обнуляется и значение, которое там было теряется.


    Непосредственно со счётчиком всё тоже не так просто. Мы не можем выставить его напрямую. Вместо этого у нас есть LUT на 32 позиции, и пять бит на выбор одного из предустановленных значений. Если я дам эти значения подряд, они вам покажутся бессмысленными, поэтому сразу копипаст значений этой таблицы с nesdev wiki, соответствующий тому, как это дело размещено на чипе физически (и хоть какой-то логике).


    Legend:
    <bit pattern> (<value of bit pattern&gt) => <note length&gt

    Linear length values:
    1 1111 (1F) => 30
    1 1101 (1D) => 28
    1 1011 (1B) => 26
    1 1001 (19) => 24
    1 0111 (17) => 22
    1 0101 (15) => 20
    1 0011 (13) => 18
    1 0001 (11) => 16
    0 1111 (0F) => 14
    0 1101 (0D) => 12
    0 1011 (0B) => 10
    0 1001 (09) => 8
    0 0111 (07) => 6
    0 0101 (05) => 4
    0 0011 (03) => 2
    0 0001 (01) => 254

    Notes with base length 12 (4/4 at 75 bpm):
    1 1110 (1E) => 32 (96 times 1/3, quarter note triplet)
    1 1100 (1C) => 16 (48 times 1/3, eighth note triplet)
    1 1010 (1A) => 72 (48 times 1 1/2, dotted quarter)
    1 1000 (18) => 192 (Whole note)
    1 0110 (16) => 96 (Half note)
    1 0100 (14) => 48 (Quarter note)
    1 0010 (12) => 24 (Eighth note)
    1 0000 (10) => 12 (Sixteenth)

    Notes with base length 10 (4/4 at 90 bpm, with relative durations being the same as above):
    0 1110 (0E) => 26 (Approx. 80 times 1/3, quarter note triplet)
    0 1100 (0C) => 14 (Approx. 40 times 1/3, eighth note triplet)
    0 1010 (0A) => 60 (40 times 1 1/2, dotted quarter)
    0 1000 (08) => 160 (Whole note)
    0 0110 (06) => 80 (Half note)
    0 0100 (04) => 40 (Quarter note)
    0 0010 (02) => 20 (Eighth note)
    0 0000 (00) => 10 (Sixteenth)

    Теперь о поведении этого счётчика. Я уже упоминал, что если он выключен, то сам счётчик занулён и игнорируется. Если он включён, то его уменьшает вышеупомянутый Frame Counter. Каждый «Half Frame» сигнал уменьшает счётчик на еденицу, если там есть что уменьшать. То есть, это -120/96 от значения счётчика в секунду. Целая нота у нас длится, выходит, $160/120=4/3$ секунды. Если счётчик равен нулю и включён, то звук на канале, естественно, заглушается.


    Envelope (огибающая, точнее пародия на неё)


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


    Громкость во всех трёх каналах задаётся четырьмя битами, что даёт нам выходные значения от 0 до 15. И у нас есть так называемый «Envelope Parameter» из четырёх бит, который позволяет задать громкость. Но не всё так просто. Сначала дадим общую схему механизма, который выдаёт нам эти значения. Общая схема опять утащена с nesdev wiki.


    Видите Quarter Frame Clock? Да, это наш старый знакомый, Frame Counter. И с частотой 240/192 герца (приблизительно) он нам меняет огибающую.


    Начнём со стартового флага. Он даёт нам отмашку на то, что можно начинать обратный отсчёт. Он устанавливается, если кто-то меняет длину (см. раздел про Length Counter выше) или частоту ноты. Так сделано потому, что длина и частота ноты расположены рядом и их можно только менять вместе, но об этом позже.


    Если стартовый флаг установлен, то мы его сбрасываем, в Decay Level загружаем 15, а в Divider загружаем текущий Envelope Parameter. Такова у нас стартовая расстановка. Если стартовый флаг уже сброшен, мы уменьшаем Divider на один.


    Если наш Divider был равен нулю, то вместо -1 мы вновь выставляем Envelope Parameter. В этот же момент мы уменьшаем Decay Level на еденицу. Decay Level и даёт нам постоянно убывающую громкость от 15 до 0. Получается что-то вроде двух вложенных циклов for, прокатывающихся $(V+1)*15$ раз, где V — это Envelope Parameter. V + 1, потому что один из циклов прокатывает все значения от 0 до V. Итоговая длительность ноты тогда будет $(V+1)*15/240$ секунды, если не включён замедленный режим Frame Counter.


    Когда Decay Level доходит до 0, в игру вступает Loop Flag. Если он установлен, мы снова заряжаем туда 15, и нота начинает играть заново с переменной громкостью. То есть, убывание громкости от максимума до нуля повторяется. При сброшенном Loop Flag наша убывающая громкость остаётся на нуле.


    Рассмотрим ещё один важный флаг, который влияет на конечный результат. Constant Flag. Он отвечает за переключение между «задаём громкость напрямую сами через Envelope Parameter» и «полагаемся на обратный отсчёт». Если этот флаг установлен, Decay Level игнорируется, но продолжает считаться по всё тем же правилам. Это значит, что если вы захотите в пределах одной ноты попереключаться между этими режимами, ваш ждёт много интересного и увлекательного. Видимо, схема на это не рассчитана, но что-то мне подсказывает, что могли найтись психи, которые выставляли полную громкость, а потом посреди ноты сбрасывали флаг Constant, мирясь с тем, что обратный отсчёт громкости по факту стартовал не с 15, а с, например, 10 до которых Decay Level успел опуститься.



    Halt/Loop Flag


    Да, Halt Flag из Length Counter и Loop Flag из Envelope это один и тот же бит. То есть, у вас есть выбор между конечной нотой и бесконечной нотой. Если вы выбрали конечную ноту, то она будет заглушена тем, кто первый успеет довести отсчёт до нуля. Придётся заняться подсчётами, чтобы предсказать на какой громкости оборвётся нота. А если ваша нота бесконечна, то или громкость задавайте сами, или имейте дело с громкостью, идущей по кругу от 15 до 1. Да, никто не говорил, что будет легко.


    Status


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


    Чтение работает симметрично в том плане, что мы получим там еденицу, если соответствующий Length Counter (или счётчик оставшихся байт сэмпла) больше нуля. Ещё два бита выставляются прерываниями из Frame Counter и канала дельта-модуляции (до этого ещё дойдём).


    Каналы


    Теперь, когда мы рассмотрели общие компоненты, давайте рассмотрим каждый звукогенератор по отдельности.


    Прямоугольный



    Сказать, что эта часть чипа генерирует звук будет не совсем верно. Она генерирует число от 0 до 15, из которого потом складывается звуковая волна определённой громкости. Громкость — это амплитуда, так уж работает звук. Амплитуда — это высота этих самых столбиков, которые вы видите на картинке выше. И это самое число от 0 до 15 определяется с помощью Envelope, который я описал выше.


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


    Значение Duty Соответствующий LUT Как выглядит выходной сигнал
    0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 (12.5%)
    1 0 0 0 0 0 0 1 1 0 1 1 0 0 0 0 0 (25%)
    2 0 0 0 0 1 1 1 1 0 1 1 1 1 0 0 0 (50%)
    3 1 1 1 1 1 1 0 0 1 0 0 1 1 1 1 1 (25% negated)

    С первыми двумя колонками, думаю, всё должно быть понятно. Третья же касается деталей реализации. Как вы могли уже догадаться, форма выходного сигнала задаётся циклом от 0 до 7. Точнее, от 0 до 1. На старте переменная, отвечающая за текущее значение из LUT инициализирована в 0, но она изменяется в сторону уменьшения. То есть:0, 7, 6, 5, 4, 3, 2, 1.


    Теперь рассмотрим вопрос частоты волны. Частота звука будет зависеть от того, как быстро прокручивается этот цикл. И задаётся эта скорость тем способом, который был бы удобен инженерам Nintendo, а не вам.


    Выше я уже упоминал, что частота и длина ноты задаются вместе. И задаются они двумя байтами, где 5 бит отведено на длину ноты, а 11 на частоту. У нас есть число от 0 до 2047. Пусть оно будет t. Каждый такт APU переменная, отвечающая за цикл секвенсора, уменьшается на еденицу. Если там ноль, становится t. При этом изменяется в сторону уменьшения текущая фаза звуковой волны (0, 7, 6, 5, 4, 3, 2, 1). Весь цикл колебания нашей квадратной волны занимает $2*8*(t+1)$ тактов CPU. Таким образом, если мы хотим частоту f, нам нужно посчитать t по следующей формуле:


    $t = \frac{f_{cpu}}{16*f}-1$


    Не забудьте округлить и помните, что если вы выставите t меньше 8, ваш прямоугольный звук превратится в тыкву. Не то, чтобы это изначально отличалось от тыквы. Чем меньше t, тем выше частота, потолок получается около 12,4 килогерц.



    Pulse Sweep, или единственное отличие между двумя квадратичными звукогенераторами NES.


    Для того, чтобы облегчить жизнь композиторам на NES целый байт был щедро выделен на управление механизмом sweep, позволяющим менять частоту на ходу. Он позволяет менять значение t. У нас есть:


    • Флажок вкл/выкл (1 бит)
    • Значение периода изменения частоты P (3 бита)
    • Флажок Negate, делает выбор между уменьшать/увеличивать t (1 бит)
    • S, которое определяет насколько надо менять (3 бита)

    Сначала про P. Там уже типичный для звукового чипа NES цикл P, P-1, P-2… 1, 0, P. Уменьшается он по Half-frame сигналу от Frame Counter (96/120 герц). Соответственно, когда мы переключаемся с 0 на P, раз в P+1 half-frame сигналов, происходит магия.


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


    1. Берётся копия текущего t, сдвигается на S бит вправо.
    2. Если флажок Negate установлен, значение из первого пункта делается отрицательным.
    3. t = t + c из пункта 2

    В чипе NES есть два прямоугольных канала. И вот в чём между ними разница: пункт 2, «сделать c отрицательным». В первом канале это делается с помощью обратного кода (получается -с-1), а во втором с помощью дополнительного (получается -c). Видимо, монетка с помощью которой разработчики чипа решали этот вопрос, встала на ребро.


    Кстати, помните я говорил, что t меньше 8 ставить нельзя? Вот для защиты от этого механизма оно и было сделано. Цикл завязанный P, чуть что, крутится продолжит, но само t меняться не будет. Именно поэтому, кстати, самую высокую из доступных октав некоторые разработчики предпочитали не использовать вовсе. От греха подальше.


    Треугольный



    Снова весёлые картинки. Обратите внимание, у нас здесь нет Envelope. Почему? Потому что громкость у треугольного канала одна. А она одна из-за того, что делать вариации было слишком сложно и того не стоило (на дворе начало 80-х, что вы хотели).


    На выход идёт текущее значение из следующей LUT:


    15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

    Мы имеем всё тоже 11-битное t, которое управляет частотой. На этот раз с каждым циклом мы посылаем на выход новое значение из LUT выше. Но есть нюанс — этот таймер тикает каждый цикл CPU. А частота считается уже по иной формуле:


    $t = \frac{f_{cpu}}{32*f}-1$


    Тут нет механизма Sweep, а потому и канал при t < 8 не заглушается. Тут у нас простор частот аж до 55 килогерц, то есть, за той планкой, где уже начинается ультразвук. Тем ироничнее, что треугольный звук используют как «басуху». Очень уж сочно треугольные волны в низких частотах звучат.


    Linear Counter — это такая обрезанная версия Length Counter, которая управляется одним байтом. Бит выделяется на вкл/выкл, а остальные семь — это значение счётчика. Счётчик уменьшается на еденицу с каждым quarter-frame сигналом. Семь бит — 127 quarter-frame сигналов. Это чуть больше полусекунды, поэтому этой возможностью пользовались достаточно редко.


    Шумовой



    А теперь откапываем свои университетские конспекты по криптографии, и вспоминаем, что такое LFSR, он же регистр сдвига с линейной обратной связью. Зачем оно нам надо? Скажем так, устройство этого канала похоже на квадратичный с той разницей, что 0/1 в местном секвенсоре определяется не LUT с ровными столбиками, а генератором случайных чисел. А какой там ещё может быть генератор случайных чисел, реализованный аппаратно в простеньком чипе?


    Напомню/расскажу, что оно из себя представляет. Есть регистр, в котором определённое число бит. В нашем случае — 15, и они пронумерованы так: 14 — 13 — 12 — 11 — 10 — 9 — 8 — 7 — 6 — 5 — 4 — 3 — 2 — 1 — 0. Каждый шаг происходит следующее:


    • Мы считаем по формуле, которую легко реализовать аппаратно, новый бит. Это один бит, 0/1.
    • Мы сдвигаем наш регистр на один бит вправо.
    • На освободившееся место ставится свежепосчитанный 14-ый бит.
    • Это уже не про LFSR, но обращу ваше внимание, что в секвенсор идёт бит 0.

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


    • Если флаг Mode сброшен, то результат — XOR 0 и 1 битов. Получается последовательность длиной 32767 чисел, которая звучит как «пш-ш-ш-ш-ш-ш» (белый шум на телевизоре).
    • Если флаг Mode установлен, то результат — XOR 0 и 6 битов. Получается последовательность 93 или 31 число (зависит от того, когда вы флаг выставили), которая звучит как «пи-и-и-и-и-и-и-и» (знаете этот звук на телевизоре, когда вещание уже закончилось и осталась только настроечная таблица).

    Здесь работает тот же Length Counter, который определяет длину ноты. А вот с переключением на следующий бит, то есть с управлением частотой, тут всё иначе. Местное T берётся из LUT на четыре бита, которая выглядит (в версии для NTSC) так:


    0 1 2 3 4 5 6 7 8 9 A B C D E F
    4 8 16 32 64 96 128 160 202 254 380 508 762 1016 2034 4068

    Эти четыре бита лежат в соседнем байте с конструкцией |LLLLLTTT|TTTTTTTT|, и устанавливаются отдельно. 11 бит, которые регулируют частоту в других каналах, здесь никакой силы не имеют.


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



    Дельта-модуляция



    Ох. Тут, что называется, «своя атмосфера». Например, двухбайтной конструкции из длины ноты и таймера частоты здесь нет вообще. Два вида писка и белый шум — этого мало, чтобы делать музыку, как ни крути. Поэтому можно подтаскивать из памяти коротенькие звуки, и играть их через этот канал. Само собой, без горки технических ограничений тут не обходится.


    Что такое дельта-модуляция? Это когда ты пишешь не текущую фазу звуковой волны напрямую, как это делается в PCM (.wav без всякого сжатия, например), а разницу между ними. В случае, например, с белым шумом толку ноль. Но если мы последовательность чисел «0, 1, 2, 3, 4, 5, 6, 8, 10, 11, 10...» заменим на «1, 1, 1, 1, 1, 1, 2, 2, 1, -1», то это будет проще и сжимать, и хранить (на маленькие числа нужно меньше бит).


    Здесь эта идея выкручена на максимум. Этот звукогенератор даёт вывод в пределах 0-127, вместо 0-15, как в остальных. Мелодия здесь однобитная, где 0 в потоке бит значит $x_n=x_{n-1}-2$, а 1 — $x_n=x_{n-1}+2$. Если получается меньше 0 или больше 127, то очередной бит игнорируется. Конечно, это сильно ограничивает вас, но другого пути разнообразить музыку или выдать особый звук по случаю прыжка, например, у нас нет.


    Теперь рассмотрим, чем это счастье управляется (всё write-only):


    • Адрес. Целый байт, указывающий номер 16-битного блока, с которого начинается сэмпл.
    • Длина. Целый байт, указывающий число таких блоков. Длина сэмпла $L*16+1$.
    • Rate. Частота определяется четырьмя битами, которые смотрят в LUT ниже. Время до переключения на следующий бит указано в циклах CPU.
    • Loop. Однобитный флаг назначение которого, надеюсь, очевидно.
    • IRQ. Этот флаг разрешает делать процессорное прерывание, когда текущий сэмпл закончил играть.
    • Direct load.Самое весёлое. Семь бит отданы на то, чтобы напрямую задать вывод в обход механизма загрузки дельта-сэмплов. Постоянно меняя это 0-127 в теории можно играть любую музыку. На практике вам надо ещё и игру показывать, поэтому применяется это счастье только в заставках.

    Тут LUT частот. Уточняю — это циклы CPU, то есть частота делится на это число напрямую.


    0 1 2 3 4 5 6 7 8 9 A B C D E F
    428 380 340 320 286 254 226 214 190 160 142 128 106 84 72 54

    И как из всё этого создаётся музыка?


    Мы имеем пять звукогенераторов. Четыре из них выдают числа от 0 до 15. Один из них от 0 до 127. Остаётся самое главное: составить из этого звуки.


    Вывод: Mixer


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


    Каждый из звукогенераторов имеет свой ЦАП. Прямо сейчас будет рассказ о той части, которую я недолюбливал в школе, поэтому ссылка на источник. Каждый ЦАП ведёт себя, как резистор по принципу, «чем больше вывод, тем меньше сопротивление»: $r_{ЦАП}=r_{базовое}/output$. Ниже rбазовое всех каналов.


    Канал Сопротивление, Ом
    Квадратичный 8128
    Треугольный 8227
    Шумовой 12241
    Дельта-модуляция. 22638

    Квадратичные ЦАП соединены параллельно. Остальные три соединены параллельно в другую группу. Выходное напряжение обеих групп формируется делителем напряжения с резистором в 100 Ом. На вход в этот делитель изнутри чипа подаётся ток в 1,17 вольт.


    Наконец, всё это смешивается с помощью двух резисторов в 12 и 20 кОм. Получается, пропорция 3/5, где 3 части отдаётся квадратичны звуками, а 5 всем остальным. Этот вольтаж и подаётся на динамики. Из всего этого была составлена следающая формула для использования в эмуляторах:


    $output=\frac{95.88}{\frac{8128}{pulse_1+pulse_2}+100}+\frac{159.79}{\frac{1}{\frac{triangle}{8227}+\frac{noise}{12241}+\frac{dmc}{22638}}+100}$


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


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


    Ввод: 22 волшебных байта


    Мы теперь знаем, как именно пять звукогенераторов создают звук. Но чем они управляются? Как заставить их играть музыку? Вот тут уже приходить включать магию программирования на местном ассемблере. Каждый из пяти звукогенераторов имеет четыре write-only регистра по одному байту. Все эти биты, флаги, байты, о которых я говорил выше, рассованы по четырём байтам для каждого звукогенератора. Ещё два байтовых регистра живут отдельно. Один управляет Frame Counter'ом, а второй используется, чтобы определять/изменять состояние вкл/выкл у всех каналов.


    Регистры Часть APU Примечания
    $4000-$4003 Первый квадратичный канал
    $4004-$4007 Второй квадратичный канал
    $4008-$400B Треугольный канал
    $400C-$400F Шумовой канал
    $4010-$4013 Канал дельта-модуляции
    $4015 Управление другими каналами, см. раздел Status выше Единственный регистр, который можно читать.
    $4017 Frame Counter

    Я не специалист по ассемблеру NES, но сумел выкопать тамошний аналог «Hello, world», который зацикливает писк, близкий к ноте «ля» малой октавы (212,79 герца).


    reset:
    lda #$01 ; square 1
    sta $4015
    lda #$08 ; period low
    sta $4002
    lda #$02 ; period high
    sta $4003
    lda #$bf ; volume
    sta $4000
    forever:
    jmp forever

    А теперь представьте, что вам нужно было, чтобы сыграть, к примеру, простенькую гамму.


    • Разместить где-то в памяти частоты и длины нот (а перед этим посчитать их).
    • Записать в нужные регистры нашего квадратичного канала все необходимые данные.
    • Организовать перехват процессорного прерывания от Frame Counter'а, где проверять, заглохла ли музыка через регистр $4015 (он же Status).
    • Перейти к следующей ноте, и завернуть это в цикл.
    • Не забываем, что это всё надо проделать отнюдь не на Python.

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


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


    Комментарии 27

      +7
      Осталось добавить, что аппаратные огибающие, sweep, frame counter и его прерывание в 90% игр не используются. Это всё наследие старых времён, когда ещё не было ясно, что именно требуется для звука в играх, и придумано оно было по большому счёту для звуковых эффектов — в ~1981, когда это задумывалось, не ожидали, что в играх будет фоновая музыка. В реальных же играх 1985+ все эти фичи оказались невостребованными, плееры музыки сами считают свои собственные программные огибающие и просто выставляют моментальные значения питча и громкости.
        0
        Добавлю в коллекцию:
        youtu.be/keqE8eYjRKw?t=10
          0
          Мне до сих пор удивительно, как на этом всем в игре Blades of steel умудрились голос записать в заставке
            0

            Канал дельта-модуляции позволяет ручками выставить любое значение. Цена — вся вычислительная мощь приставки.

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

            Полностью согласен.
            Для меня было какой-то запредельной магией — на восьмибитнике запрограммировать такую музыку как например эту
            .
            И как она же обалденно звучит на пианино

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

              Ещё "stage 3" классно звучит из этой же игры. Название может быть не совсем точным.

                0
                Sansoft в принципе славится своей музой. А когда в их ряды влился композитор (не помню, правда его имя), который привнёс в мелодию сэмплерный бас вместо ударных, тот вообще стал визитной карточкой. Куча игр, не только RAF World (Jorney to Silius).
                  0
                  Вставилось не то видео. Вот которое я хотел:
                    0
                    Вот это глюк. Когда я послал предыдущее сообщение, в моем исходном был видос из сообщения ED-209, хз почему. А теперь норм и получилось дублирование.
                    0
                    Наверно это был Наоки Кодака.
                      0
                      Наверно, просто то интервью я давно смотрел. Там так и говорилось, мол это его стиль и пока он там работал такой музычка у них стала на все проекты, где он участвовал.
                  0
                    0

                    Какая отличная статья! А в комментариях ещё перепись крутых композиций. Мои любимые: Battle toads and double dragon, Jurassic Park, Castlevania (bloody tears). С телефона, поэтому видео не прикладываю...

                      0
                      Еще Over Horizon. Там потрясная музыка.
                      0

                      Для меня было очень большим удивлением найти после 8-bit Guy ролики с крутой музыкой с NES на в виде визуализации. До этого я никогда не задумывался как это сделано в плане переходов, нарастаний и ограничений. Например "Легенда о Зельде":


                        +1
                        Только не APU а pAPU — Pseudo Audio Processing Unit потому, что это не отдельная часть со своими мозгами а просто звукогенератор, пристёгнутый к ядру 6502. Привет с проекта BreakNES.
                          +3
                          изнутри чипа подаётся ток в 1,17 вольт.

                          Тогда уже не ток, а напряжение. И, кстати, подключены они не к каким-то мифическим 1.17 вольт, а напрямую к VCC или 5 вольтам. а результирующее напряжение уже создастся на делителе между ЦАП и внешней нагрузкой, которая в данном случае сопротивление в 100 ом. Таким образом, напряжение будет полностью зависеть от активных в текущий момент токовых сопротивлений в ЦАПе и внешней нагрузки.

                          ЦАПы у него обычные, токовые, поэтому без внешней нагрузки не работают (те самые 100 ом на массу). Каналы соединены параллельно это верно:
                          image
                          image
                            0
                            С такой кучей железа генерировать звук неспортивно. Вот на ZX Spectrum был всего один бит для выхода — там да, программистам респект за звуковые эффекты.
                            8bitweapon.bandcamp.com/track/bach-prelude-in-c-minor
                              +1
                              Не только в битности дело. При наличии мощного процессора и возможности выводить этот 1 бит с частотой, скажем, 2MHz, можно получить качество звука, аналогичное 16 bit 44KHz
                                0
                                Это уже штатный аудиофильский DSD получается.
                                  0
                                  Который по сути обычный PWM.
                                    0
                                    Не совсем PWM, потому как PDM. А это уже дельта-сигма.
                                  0
                                  Однако в Спекки процессор был не сильно быстрее, 3.5МГц, но зато все операции минимум 4-тактные. И это не мешало тому же Тиму Фоллину делать свои шедевры.
                                  youtu.be/u-D24A_N4d4
                                  0
                                  Это не спектрум.
                                  Вот мультиканальная 1-битная музыка на спектруме
                                  zxart.ee/eng/music/top-100/beeper
                                  К тому на спектруме был только бипер но и AY, с совсем другим качеством.
                                • НЛО прилетело и опубликовало эту надпись здесь
                                    –3
                                    Подумав, одобрил этот комментарий, но вы ходите по очень тонкому льду.
                                    • НЛО прилетело и опубликовало эту надпись здесь

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

                                  Самое читаемое