- PVSM.RU - https://www.pvsm.ru -
В этой части базовая информация о работе со звуком. Оно весьма запутано и использует специфическую терминологию, так что внятно описать его не особо получается.
Источник [1]
Обзор инструментов, которые представляет нам платформа NES. Впрочем, дальше мы уйдем на более высокий уровень и будем использовать библиотеку Famitracker.
Проще всего пощупать звуковые возможности консоли можно с помощью демки [2] Sound Test, разработанной SnoBrow. Она совместима не со всеми эмуляторами, но FCEUX поддерживается.
Кнопка Селект переключает звуковые каналы, Старт включает их. Доступны 4 канала:
1 — меандр 1
2 — меандр 2
3 — треугольный сигнал
4 — шум
Звуковой сопроцессор (APU) управляется через регистры $4000-$4017.
$4000-$4003 = Меандра 1
$4004-$4007 = Меандра 2
$4008-$400B = Треугольного сигнал
$400C-$400F = Канала шума
$4010-$4013 = DMC, канал с дельта-модуляцией
$4015 = Управление каналами
$4017 = Счетчик кадров
Меандр 1
Канал управляется битами в регистре $4000 по схеме DDLC VVVV.
D — скважность. 10 — плавный звук, 01 или 11 — терпимый, 00 — противный
L и C — режимы работы канала
V — громкость
L=0; C=0:
Получаем генератор огибающей. Громкость будет затухать, а V соответствует длительности звучания.
L=1; C=0:
Теперь V регулирует интервал между повторениями ноты на полной громкости.
L=0; C=1:
V управляет громкостью канала. Длительность ноты регулируется битами L в регистре $4003.
L=1; C=1:
V также управляет громкостью, нота играет непрерывно до новой записи в регистр $4000. Обычно длительность звучания завязана на счетчик кадров, переходы делаются таким же покадровым затуханием громкости.
Канал выключается обнулением громкости — пишем 0x30 в регистр $4000.
$4001 – Регистр качания частоты, биты: EPPP NSSS
E — включает эффект
P — период качания
N — направление. 0 — вниз, 1 — вверх
S — еще одно управление периодом, но другое
Если этот эффект включен, то нота играется только до его окончания. В комбинации с L=1 в регистре $4000 могут получаться интересные эффекты. Бит N влияет на низкие частоты, даже когда эффект выключен. Некоторые игры используют этот хак.
$4002 — 8 младших бит таймера, задающего частоту ноты
$4003 — LLLL LTTT — 5 бит таймера длительности ноты (работает только если хотя бы один из параметров L и C обнулен в регистре $4000) и 3 старших бита таймера частоты. Чем меньше значение таймера длительности, тем дольше звучит нота. По непонятной причине, частота ноты ограничена значением 000-00001000. Более высокий звук — то есть с более коротким периодом колебания — сделать не получится. Впрочем, и так это будет противный писк.
По адресам $4004-$4007 расположены абсолютно аналогичные регистры управления вторым каналом.
$4008-$400B — треугольный канал
$4008 – CRRR RRRR. C — флаг постоянно включенной ноты. R — непонятный регистр. Теоретически, он должен влиять на длительность ноты — 0xFF для постоянно включенной и 0x80 для выключенной. Но при установке здесь 0x7F длительность будет регулироваться через биты L в регистре $400B.
$4009 – не используется.
$400A — младшие биты таймера частоты ноты
$400B — LLLL LTTT. 5 битов длительности и 3 старших бита таймера частоты. Логика такая же, как и канала меандра.
Громкость канала не регулируется. Кроме того, он играет звук на 1 октаву ниже, чем меандр с той же настройкой. Ограничения по частоте тут нет, так что можно получить очень высокий писк.
$400C-$400F — канал шума
$400C — xxLC VVVV — так же как и меандр, но нет скважности.
$400D — не используется
$400E — ZxxxTTTT. Z — тип шума. 0 дает белый шум, а единица — металлический лязг. T — таймер частоты шума, чем меньше значение, тем выше тон.
$400F — LLLL Lxxx. Длительность ноты. Биты L и C из регистра $400C работают как для меандра.
Для выключения канала надо обнулить громкость ($400C = 0x30)
Канал DMC позволяет проиграть несжатый звук из памяти. Частота сэмплирования регулируется, и тут надо пойти на компромисс. Большая частота дает приличное качество, но требует неприлично много памяти для хранения звука. Низкая частота сильно портит качество и добавляет неприятный свист. Этот инструмент хорош для коротких звуков типа озвучки отдельных слов ("Fight!") или линии барабанов.
$4010 – ILxx RRRR. I включает прерывания канала, L — зацикливание сэмпла, R — частота.
$4011 – xDDD DDDD — громкость. Если дергать этот регистр в нужное время, то в принципе можно получить приличный по качеству звук.
$4012 — адрес начала сэмпла. 8 бит дополняются до 16 вот таким образом: 11AA AAAA AA00 0000. Так что получаем диапазон доступных адресов от $C000 до $FFC0.
$4013 – длительность сэмпла в байтах. Тоже дополняется, но до 12 бит: LLLL LLLL 0001. Получаем разрешенный размер от $11 до $FF1 байт. Если этого не хватит, то обычно можно сцепить несколько сэмплов и проиграть их подряд.
$4015 — xxxDNT21. Включает каналы.
D — DMC
N — шум
T — треугольный канал
2 — второй меандр
1 — первый меандр
$4017 — теоретически это счетчик кадров, но он редко используется. Большинство игр пишут сюда 0x40 при старте и этим ограничиваются.
DMC включается при записи соответствующего бита в регистр $4015 и выключается после окончания сэмпла. Чтобы проиграть его повторно, надо еще раз дернуть за бит.
Остальные каналы включаются записью старшего по адресу из их регистров ($4003 для первого меандра, и т.д.). Если писать туда каждый кадр, то будут неприятные щелчки.
Некоторые мапперы добавляют свои каналы звука. VRC7 вообще имеет FM-синтезатор, но он используется только в одной игре — Lagrange Point.
PCM-звук использовать в принципе можно, но мне не приходилось. Это критически затратно и по памяти, и по процессорному времени — ресурсов хватит разве что на статичную заставку.
Сделаем какую-нибудь простейшую пищалку. Ее можно добавить к какой-нибудь из демок в предыдущих уроках. Только проверьте, что звуковые каналы включены.
*((unsigned char*)0x4015) = 0x0f;
// Бип
if (((joypad1old & START) == 0)&&((joypad1 & START) != 0)){
*((unsigned char*)0x4000) = 0x0f;
*((unsigned char*)0x4003) = 0x01;
}
// Звук для прыжка
if (((joypad1old & START) == 0)&&((joypad1 & START) != 0)){
*((unsigned char*)0x4000) = 0x0f;
*((unsigned char*)0x4001) = 0xab;
*((unsigned char*)0x4003) = 0x01;
}
// А теперь пошумим
if (((joypad1old & START) == 0)&&((joypad1 & START) != 0)){
*((unsigned char*)0x400c) = 0x0f;
*((unsigned char*)0x400e) = 0x0c;
*((unsigned char*)0x400e) = 0x00;
}
Когда наиграетесь с этим кодом и SoundTest, немедленно все забудьте и открывайте Famitracker.
Известный туториал Nerdy Nights [3] намекал, что надо писать свой музыкальный движок. Оказывается, все уже написано — Famitracker и Famitone2.
http://famitracker.com/downloads.php [4]
Подойдет самая свежая бинарная версия. Нюанс: иногда надо импортировать MIDI-файл, тогда надо ограничиться версией 0.4.2. Импорт всегда работал плохо, и после этой версии его сломали окончательно.
Famitracker умеет всё, но медленно и громоздко. Так что лучше использовать Famitone2.
https://shiru.untergrund.net/code.shtml [5]
Он имеет некоторые ограничения, но их можно обойти. Нет регулировки громкости, но это можно имитировать, создав дополнительные инструменты. Местами громкость огибающей меньше максимума — это тоже обходится через другие инструменты. Нет эффектов, кроме изменения темпа, закольцовывания и затухания.
… здесь идет описание реализации всяких музыкальных тонкостей, которые мне вообще неведомы. Оригинал: https://nesdoug.com/2015/12/02/15-adding-music/ [6]
Главное ограничение — меньший диапазон нот, от С1 до D6. Лимиты можно подкрутить в ассемблерном исходнике. Вот таблица пересчета частоты звука в отсчеты таймера:
http://wiki.nesdev.com/w/index.php/APU_period_table [7]
Хороший видеомануал по трекеру:
… описание кнопок в трекере опускаю.
Главное — упаковать все треки в один файл. Далее он конвертируется в ассемблерный:
text2data TestMusic.txt -ca65
И вставляется в код инициализации:
.include "MUSIC/famitone2.s"
music_data:
.include "MUSIC/TestMusic.s"
В коде инициализации есть метка detectNTSC:, которая позволяет проверить версию консоли — NTSC (США/Япония), или европейский стандарт PAL. Это критично, потому что влияет на тайминги.
Надо кое-что добавить в reset.s для работы фамитоновской конструкции IF:
FT_BASE_ADR =$0100 ;вообще это область стека, но он до этого места не дорастает
.define FT_THREAD 1
.define FT_PAL_SUPPORT 1
.define FT_NTSC_SUPPORT 1
FT_DPCM_OFF = $C000
FT_SFX_STREAMS = 1
.define FT_DPCM_ENABLE 0
.define FT_SFX_ENABLE 0 ;Гасим каналы DPCM и SFx
Метки для функций в Famitone2.s :
.export _Reset_Music, _Play_Music, _Music_Update
...
_Reset_Music:
lda NTSC_MODE
ldx #<music_data ; младший бит
ldy #>music_data ; старший бит
FamiToneInit:
...
_Play_Music:
FamiToneMusicPlay:
...
_Music_Update:
FamiToneUpdate:
Теперь можно объявить прототипы функций и дергать их из сишного кода:
void Reset_Music(void);
void __fastcall__ Play_Music(unsigned char song); // вызов через fastcall кладет аргумент в регистр, а не в стек
void Music_Update(void);
Так что теперь можно включить трек вызовом Reset_Music() и переключиться на другой через Play_Music(1). Раз в кадр надо вызывать Music_Update(). Можно еще импортировать функции паузы или остановки, но это проще делать вручную через управление громкостью:
*((unsigned char*)0x4015) = 0;
Выключить музыку можно пропустив вызов Music_Update. Для запуска снова надо записать 0x0F в $4015 и снова вызывать Music_Update. Канал шума надо будет перезапустить вручную, потому что Famitone включает его только при общей инициализации.
*((unsigned char*)0x4015) = 0x0f; // включение каналов
*((unsigned char*)0x400c) = 0x30; // звук в ноль
*((unsigned char*)0x400f) = 0x00; // включение канала шума
… или забить на это и все-таки использовать библиотечные функции Stop и Play.
Вот предыдущая демка с музыкой:
Дропбокс [8]
Гитхаб [9]
Каждый эффект был отдельным треком в Фамитрекере, с импортом в один файл.
Ограничения есть разве что на длительность эффектов. Каждый канал надо завершить эффектом C00, и сохранить все в 1 NSF-файл. Его надо положить в Famitone2/tools и конвертировать в ассемблер:
nsf2data SoundFx.nsf -ca65
и включить получившийся SoundFx.s в reset.s:
sounds_data:
.include “MUSIC/SoundFx.s”
а потом включить эффекты в движке:
.define FT_SFX_ENABLE 1
Надо также добавить соответствующие метки для импорта:
.export _Play_Fx
_Play_Fx:
ldx #0
FamiToneSfxPlay:
Используется только один канал SoundFx, поэтому в X пишется его номер. Если же каналов больше, то надо удалить эту строку и включать нужный канал перед вызовом FamiToneSfxPlay.
Эффект включается вызовом функции Play_Fx(), аргумент — номер канала, он же номер трека в NSF-файле, нумерация с нуля.
void __fastcall__ Play_Fx(unsigned char effect);
В нашей демке три эффекта, первый на прыжок, второй и третий на кнопки Вверх и Вниз соответственно
if (((joypad1old & UP) == 0) && ((joypad1 & UP) != 0))
Play_Fx(1);
Кнопка Старт переключает фоновые треки.
Дропбокс [10]
Гитхаб [9]
В туториале никак не освещена тема DPCM-сэмплов. Они круты для эффектов, хоть и требуют много памяти в картридже. Если так уж хочется, то можно импортировать wav-файл в Famitracker и конвертировать его в DMC, а потом обращаться к нему из Famitone2. Думаю, когда-нибудь напишу раздел на эту тему
Автор: Вадим Марков
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/razrabotka/274331
Ссылки в тексте:
[1] Источник: http://www.synthtopia.com/content/2015/08/31/famirom-puts-the-sound-of-a-nes-sound-chip-in-your-daw/
[2] демки: http://nesdev.com/sndtest.zip
[3] Nerdy Nights: http://nintendoage.com/pub/faq/NA/index.html?load=nerdy_nights_out.html
[4] http://famitracker.com/downloads.php: http://famitracker.com/downloads.php
[5] https://shiru.untergrund.net/code.shtml: https://shiru.untergrund.net/code.shtml
[6] https://nesdoug.com/2015/12/02/15-adding-music/: https://nesdoug.com/2015/12/02/15-adding-music/
[7] http://wiki.nesdev.com/w/index.php/APU_period_table: http://wiki.nesdev.com/w/index.php/APU_period_table
[8] Дропбокс: http://dl.dropboxusercontent.com/s/4cl6dqvrzuyq2eq/lesson12.zip
[9] Гитхаб: https://github.com/BubaVV/nesdoug
[10] Дропбокс: http://dl.dropboxusercontent.com/s/q5fvtis646lmh18/lesson13.zip
[11] Источник: https://habrahabr.ru/post/349742/?utm_source=habrahabr&utm_medium=rss&utm_campaign=349742
Нажмите здесь для печати.