Info Guide #13
01 апреля 2021

Music - софтовый движок OPL синтеза для AY (часть 2)

<b>Music</b> - софтовый движок OPL синтеза для AY (часть 2)
 OPL синтез на AY (часть 2) 
djnzx48 

            8-битные сэмплы

   Теперь я объясню способ вывода, который
я использовал в этом проигрывателе.Если вы
знакомы  с  микросхемой AY, используемой в
Spectrum 128, вы знаете, что каждый канал, 
которых  всего  три, может  воспроизводить
только 16 отдельных уровней громкости ( 31
для  YM, но это более сложно и требует ис─
пользования огибающих). Итак, как это поз─
воляет  нам  выводить  8-битные сэмплы? Мы
можем  сделать это, объединив уровни гром─
кости из трёх доступных каналов. Поскольку
расстояние  между соседними уровнями гром─
кости  варьируется, мы можем комбинировать
их  для  создания  промежуточных уровней и
получения 8-битного значения.
   У этого метода есть и недостатки. Одним
из таких недостатков является то, что каж─
дая выборка требует изменения трех регист─
ров, а не одного, а несколько регистров не
могут быть изменены мгновенно. Пока регис─
тры  находятся  в промежуточном состоянии,
возникают нежелательные промежуточные уро─
вни  громкости, вызывающие  искажения. Ещё
одним недостатком является то,что этот ме─
тод  работает  только  для монофонического
аудиовыхода с AY. Для получения правильных
результатов  на стерео требуется отдельная
4-битная процедура вывода.
   Для эффективности мы можем использовать
таблицу, которая дает соответствующую ком─
бинацию уровней для каждого 8-битного зна─
чения выборки. Я пробовал несколько разных
подходов к созданию такой таблицы. Базовая
таблица принимает 0 + 0 + 0 как самый низ─
кий уровень и f + f + f как самый  высокий
- я  пробовал  подбирать значения вручную,
чтобы  выбрать  более подходящий диапазон,
который  вносит  меньше искажений, но я не
думаю, что результаты были настолько хоро─
ши. Также попытался использовать существу─
ющий отсчёт звука в качестве входного дан─
ного для взвешивания отсчётов,чтобы наибо─
лее  часто  используемые значения отсчётов
были ближе всего к их идеальным значениям.
Независимо  от метода, который я использо─
вал, некоторые искажения были неизбежны, в
основном те,которые вызваны промежуточными
уровнями  громкости при переключении с од─
ного уровня на другой.
   Если  каждая выборка рассчитывается как 
a + b + c, то  общая  разница  между двумя 
последовательными  выборками  может   быть
смоделирована как

   |a1 - a2| + |b1 - b2| + |c1 - c2|

если каналы обновляются последовательно. Я
обнаружил,что если один канал (скажем, A )
применён для наибольшего из трех значений,
другой  канал  (B)  с средним значением, а
оставшийся канал (C) с наименьшим значени─
ем, тогда общее расстояние будет минималь─
ным для всех возможных комбинаций значений
каналов.

            Программа вывода

   Пример кода,использующего описанный ал─
горитм  для  достижения  8-битного  звука,
близкий к используемому в плеере:

setup: 
   ; выбираем AY регистры
   ld bc,#fffd   ;порт регистров AY
   ld a,7        ;регистр AY
   out (c),a

   ; выключаем генерирование тона и шума
   ld b,#bf      ;порт данных AY
   ld a,#3f      ;выключаем тон и шум
   out (c), a

   ; ...

play_sample: 
;предполагаем, что отсчёт сгенерирован и 
;хранится в регистре А 

;таблица уровней громкости,по одному байту 
;на три канала AY для каждого 8-битного 
;значения отсчёта: 
   ld h,HIGH output_table

   ld bc,#fffd   ;порт регистра AY
   ld a,8        ;выбираем канал A
   out (c),a

   ld b,#bf      ;порт данных AY
   ld a,(hl) ;берём значение для канала A
   out (c),a ;устанавливаем туда значение
   inc l

   ld b,#ff      ;регистр порта AY
   ld a,9        ;выбираем канал B
   out (c),a

   ld b,#bf      ;порт данных AY
   ld a,(hl) ;берём значение для канала B
   out (c),a ;устанавливаем туда значение
   inc l

   ld b,#ff      ;регистр порта AY
   ld a,10       ;выбираем канал C
   out (c), a

   ld b,#bf      ;порт данных AY
   ld a,(hl) ;берём значение для канала C
   out (c),a ;устанавливаем туда значение
   inc l

   Используя этот метод, мы можем получить
простой  8-битный  вывод. Но  этот  код не
идеален, если мы собираемся выводить тыся─
чи  выборок  в секунду. Обратите внимание,
как  мы  должны переключаться между портом
выбора  регистра  и  портом  данных каждый
раз, когда мы записываем в канал. Эти пор─
ты:

   11-- ---- ---- --0-     выбор регистра
   10-- ---- ---- --0-     данные

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

play_sample: 
   ; наш отсчёт в регистре А

   ld h,HIGH output_table
   ld bc,#fffd   ;порт регистра AY

   ld a,8        ;выбираем канал A
   out (c),a

   ld a,(hl) ;берём значение для канала А
   out (#fd),a ;устанавливаем туда знач-е
   inc l

   ld a,9        ;выбираем канал B
   out (c),a

   ld a,(hl) ;берём значение для канала B
   out (#fd),a ;устанавливаем туда знач-е
   inc l

   ld a,10       ;выбираем канал C
   out (c),a

   ld a,(hl) ;берём значение для канала C
   out (#fd),a ;устанавливаем туда знач-е
   inc l

   Теперь  мы  устранили необходимость за─
гружать в регистр B требуемый адрес порта,
сэкономив 38 тактов на отсчёт. Но есть ещё
одно улучшение, которое мы можем сделать.
   Когда мы пишем в порт #fffd, чтобы выб─
рать  желаемый регистр AY для записи, выб─
ранный регистр запоминается. Если мы запи─
сываем только в один регистр AY, это изба─
вляет от необходимости выбирать один и тот
же регистр несколько раз. В настоящее вре─
мя мы записываем данные в каналы в порядке 
A, B, C, A, B, C,  что  требует выбора ре─ 
гистра  для каждого нового канала. Но что,
если  мы будем чередовать порядок каналов?
Примерно так:

   отсчёт 0:       вывод A, B, C
   отсчёт 1:       вывод C, B, A
   отсчёт 2:       вывод A, B, C
   отсчёт 3:       вывод C, B, A
   отсчёт 4:       вывод A, B, C

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

play_sample0: 
   ; наш отсчёт в регистре А

   ld a,(hl) ;берём значение для канала А
   out (#fd),a ;устанавливаем туда знач-е
   inc l

   ld a,9        ;выбираем канал B
   out (c),a

   ld a,(hl) ;берём значение для канала B
   out (#fd),a ;устанавливаем туда знач-е
   inc l

   ld a,10       ;выбираем канал C
   out (c),a

   ld a,(hl) ;берём значение для канала C
   out (#fd),a ;устанавливаем туда знач-е
   inc l

   ; ...

play_sample1: 
   ; наш отсчёт в регистре А

   ld a,(hl) ;берём значение для канала C
   out (#fd),a ;устанавливаем туда знач-е
   dec l

   ld a,9        ;выбираем канал B
   out (c),a

   ld a,(hl) ;берём значение для канала B
   out (#fd),a ;устанавливаем туда знач-е
   dec l

   ld a,8        ;select channel A
   out (c),a

   ld a,(hl) ;берём значение для канала А
   out (#fd),a ;устанавливаем туда знач-е
   dec l

   Теперь мы выиграли дополнительно 19 та─
ктов, при только 5 OUT на выборку, а не 6.
Есть  еще одно преимущество: нам больше не
нужно перезагружать адрес таблицы значений
громкости,а просто увеличивать и уменьшать
указатель.(Это важно!Если мы изменим поря─
док каналов,но прочитаем таблицу громкости
в  одном и том же порядке для каждого отс─
чёта,мы вызовем резкое жужжание, поскольку
каналы A и C быстро меняют свои уровни.)
   Осталось только одно последнее дополне─
ние к нашей программе вывода.Мы должны по─
лучить  данные формы волны из буфера в па─
мяти, и пока мы это делаем, мы также можем
микшировать сэмплы ударных из смещения IY.
В  более  ранней версии я копировал сэмплы
ударных в буфер с развернутыми LDI, но это
оказалось  слишком  медленным. Вот оконча─
тельная  пара  процедур, каждая из которых
занимает  в общей сложности 134 такта и 25
байт:

                  ;такты     ;байты 
sample_out_routine_ay_mono_0: 
   ; получаем данные отсчёта
   ld a,(hl)      ;7 / 7     ;1 /  1
   inc l          ;4 / 11    ;1 /  2

   add a,(iy+0)   ;19 / 30   ;3 /  5
   ld e,a         ;4 / 34    ;1 /  6

   ; вывод канала A
   ld a,(de)      ;7 / 41    ;1 /  7
   out (#fd),a    ;11 / 52   ;2 /  9
   inc d          ;4 / 56    ;1 / 10

   ; вывод канала B
   ld a,#09       ;7 / 63    ;2 / 12
   out (c),a      ;12 / 75   ;2 / 14
   ld a,(de)      ;7 / 82    ;1 / 15
   out (#fd),a    ;11 / 93   ;2 / 17
   inc d          ;4 / 97    ;1 / 18

   ; вывод канала C
   ld a,#0a       ;7 / 104   ;2 / 20
   out (c),a      ;12 / 116  ;2 / 22
   ld a,(de)      ;7 / 123   ;1 / 23
   out (#fd),a    ;11 / 134  ;2 / 25

sample_out_routine_ay_mono_1: 
   ; получаем данные отсчёта
   ld a,(hl)      ;7 / 7     ;1 /  1
   inc l          ;4 / 11    ;1 /  2

   add a,(iy+0)   ;19 / 30   ;3 /  5
   ld e,a         ;4 / 34    ;1 /  6

   ; вывод канала C
   ld a,(de)      ;7 / 41    ;1 /  7
   out (#fd),a    ;11 / 52   ;2 /  9
   dec d          ;4 / 56    ;1 / 10

   ; вывод канала B
   ld a,#09       ;7 / 63    ;2 / 12
   out (c),a      ;12 / 75   ;2 / 14
   ld a,(de)      ;7 / 82    ;1 / 15
   out (#fd),a    ;11 / 93   ;2 / 17
   dec d          ;4 / 97    ;1 / 18

   ; вывод канала A
   ld a,#08       ;7 / 104   ;2 / 20
   out (c),a      ;12 / 116  ;2 / 22
   ld a,(de)      ;7 / 123   ;1 / 23
   out (#fd),a    ;11 / 134  ;2 / 25

          Другие методы вывода

   Наряду  с методом вывода для моно чипов
AY, я разработал программу,позволяющую ис─ 
пользовать  и  другие методы вывода. К ним
относятся подпрограмма вывода для SpecDrum
(по  сути, 8-битного  ЦАП, обеспечивающего
более  высокое  качество вывода), одна для
одного канала микросхемы AY (предназначена
для достижения монофонического воспроизве─
дения  на стереочипе, где объединение нес─
кольких  каналов  больше  не  работает), а
также по одному для левого и правого кана─
лов стерео микросхемы AY (тональные каналы 
A, B  и C слева и канал выборки D справа). 
Каждый дополнительный метод вывода развёр─
нут, чтобы сделать его точно такой же дли─
ны  и времени выполнения, что и первый ме─
тод вывода, 25 байт и 134 такта. Это упро─
щает  внесение изменений в каждой програм─
ме, в  которой  она используется, во время
выполнения (нет необходимости в перекомпи─
ляции).

                Тайминги

   Чтобы  достичь  того, что проигрыватель
занимает  строго постоянное время, порядок
выполнения должен быть тщательно спланиро─
ван.Некоторые ветки требуют для выполнения
больше  тактов, чем  другие, поэтому инст─
рукции,не имеющие никакой другой цели,кро─
ме как тратить время,оказались очень кста─
ти. Из  всех инструкций, доступных на Z80, 
EX (SP),HL  выполняется  за  самое  долгое 
время по отношению к размеру в байтах ( 19
тактов к  1 байту), следующая - EX (SP),IX
( 23 такта к  2 байтам).
   Самой универсальной,на мой взгляд,инст─
рукцией была  ADD HL,HL. Среди её полезных
свойств: использует только один байт памя─
ти,выполняется за 11 тактов, не изменяются
регистры, кроме HL, и нет обращения к про─
извольному адресу памяти.
   Другими инструкции, которые я нашёл по─
лезными,были RLD и RRD (18 тактов,по 2 ба─
йта каждая),расположенные попарно,потенци─
ально  нежелательные  эффекты сдвига влево
отменяются   последующим  сдвигом  вправо.
Обычно  мне  нужны задержки в 5 тактов, но
их  можно  было  получить только с помощью
условного RET с ложным условием. Для таких
случаев  была проведена тщательная провер─
ка, чтобы  убедиться, что условие не может
быть истинным!
   Эта таблица демонстрирует список полез─
ных инструкций для синхронизации в порядке
эффективности (измеряемой соотношением та─
ктов к байтам).

Команда  такты  байты эффективность портит 
=======  =====  ===== ============= ====== 
EX (SP),HL 19     1       19       (SP),HL 
ADD HL,rr  11     1       11         HL,F 
RRD/RLD    18     2        9       (HL),AF 
CPI        16     2        8         HL,BC 
CP (HL)     7     1        7           F 
LD A,(rr)   7     1        7           A 
INC rr      6     1        6           rr 
JR $+2     12     2        6 
RET cc      5     1        5 
LD A,R      9     2       4.5          AF 
NOP         4     1        4 

          Использование памяти

   Наряду  с тактами память также является
дефицитным ресурсом,и её необходимо эффек─
тивно  использовать, чтобы  хранить  более
нескольких  8-битных  аудиосэмплов.  Когда
скорость имеет приоритет, некоторая память
неизбежно  будет использована развёрнутыми
циклами, но я всё же нашел способы сэконо─
мить несколько сот байт в разных местах.
   Из  всех  вещей, которые  могут тратить
впустую память,таблицы с выровненными дан─
ными,вероятно,являются одними из самых ос─
новных.Таблица с выравниванием на 256 байт
тратит  до  255  лишних байт памяти, или в
среднем 127,5 байт.
   Чтобы  сгладить  проблему, я переместил
все  таблицы, размер которых был равен 256
байт (или кратный  ему) в начало банка па─
мяти. Это  позволяет держать их вместе, не
тратя лишнего места.
   А  как насчет выровненных таблиц длиной
менее 256 байт? Размещение их рядом друг с
другом  создает бесполезное неиспользуемое
пространство. В  моём случае я хотел иметь
возможность выполнять эффективную индекса─
цию  с  младшим байтом адреса (чтобы избе─
жать дорогостоящей арифметики),поэтому эти
таблицы  должны были уместиться в пределах 
256 байт.Однако я понял,что ни одна из них 
на самом деле  не нуждается в выравнивании
по началу 256-байтного сегмента.
   Таким образом,я смог разместить эти та─
блицы  более или менее в любом месте прог─
раммы. Макрос ассемблера выдаёт предупреж─
дение, если  таблица  случайно  пересекает
256-байтовую границу, это даёт возможность
узнать, когда надо искать другое место для
размещения  таблицы. Единственная  особен─
ность, связанная с таким подходом,заключа─
ется в том, что индексы в этих таблицах не
совсем предсказуемы и не основаны на базе,
равной 0,но поскольку ассемблер генерирует
эти индексы на этапе компиляции,то в прин─
ципе  это не является большой проблемой. В
целом,оставление невыровненных таблиц спо─
собствовало значительной экономии памяти.

                 Выводы

   Этот  проект получился не совсем таким,
каким  я  наивно  его представлял в начале
разработки два года  назад, но в некоторых
отношениях  он  оказался всё же лучше, и я
многому научился в процессе разработки.
   Его  истинный  потенциал  ещё предстоит
должным  образом  использовать, в основном
из-за  отсутствия трекера (музыку приходи─
лось вручную  записывать в виде инструкций 
DB ). Но рано или поздно это может измени─ 
ться.
   Будут  ли  дальнейшие  эксперименты  со
звуком  для  Speccy? Оставайтесь с нами, и
узнаете! (А может, и нет...)



Другие статьи номера:

Help - Об оболочке журнала

Editor - От редактора

Scene - ZX Spectrum в 1997 году: создание Спектрумовского клуба (в Питере)

Scene - ZX Spectrum в 1998 году: хакерская деятельность в отношении Черного Ворона

Scene - ZX Spectrum в 1999 году: трейдеры перестали покупать софт

Scene - ZX Spectrum в 1999 году: никаких трейдеров в Хакасии не осталось, да и не было практически

Scene - ZX Spectrum в 2000 году: можно со спокойным сердцем завязывать со Спектрумом и уходить на РС

Scene - ZX Spectrum в 2001 году: А много ли вообще пользователей Спpинтеpа?

Code - этюды по программированию на ZX Spectrum

Code - эффекты ротаторов и ротозумеров

Code - О туннелях и небоскреба

Code - 3D движок для Elite

GFX - Фотореализм: способы передать ненасыщенные цвета, присущие реальным фотографиям

GFX - Подготовка графических ресурсов при создании игр на ZX Spectrum

Music - софтовый движок OPL синтеза для AY (часть 1)

Music - софтовый движок OPL синтеза для AY (часть 2)

Music - Bintracker: в поисках идеального трекера для создания биперной музыки

Системки - NedoOS истоки: История NedoOS уходит корнями в далёкие 90-е годы.

Системки - NedoOS истоки: в 2007 Fido фактически умерло и я перешёл в Интернет

Системки - NedoOS истоки: 2 октября 2018 года наконец вышел первый релиз графического редактора gfxed

Системки - Разработка с помощью NedoOS

Hard - 8 битный порт Kempston джойстика с 3 дополнительными кнопками

Wild - тетрис в 256 байт и змейка размером 55 байт

Games - Войти в геймдев: переизобретение текстовых игр

Games - устройство игры Super Mario Bros от Gogin

Games - Marsmare устройство игры и самописный инструментарий: ассемблер, редактор спрайтов, редактор карт, IDE

Games - Смесь игр Twins и Tetris

Games - Глюки с ULAplus: несколько игр, сделанных в AGD устанавливают неправильную палитру ULAplus

Mail - errata: игры из СССР

Mail - Отзывы на Info Guide #12

Mail - Авторы журнала


Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Авторы - список авторов журнала и адрес редакции.
Разборка - Описание игры THE GOONIES.
Обзор - обзор свежих релизов: Lord of Chaos, F-19 Stealth Fighter, Carrier Command, The Simpsons, Gauntlet 3, The Addams Family, Wild West Seymour, Sly Spy, Paris To Dakar, Duck Out, Johangir Khan World Championchip Squash, The Amazing Adventures of Robin Hood.
От редакции - Первые отклики о газете.
Credits - авторство предыдущих выпусков газеты.

В этот день...   21 августа