ZXNet эхоконференция «code.zx»


тема: Построение таблицы громкости в плеере Pro Tracker 3



от: Ivan Roshin
кому: All
дата: 14 Jun 2004
Hello, All!

═══════════════════ make_vol.1 ══════════════════

(c) Иван Рощин, Москва

ZXNet : 500:95/462.53
E-mail: bestview@mtu-net.ru
WWW : http://www.ivr.da.ru

Построение таблицы громкости в плеере Pro Tracker 3
═══════════════════════════════════════════════════

("Радиомир. Ваш компьютер" 4/2004)

Музыкальные модули, написанные в редакторе Pro Tracker 3
(далее PT3), проигрываются с помощью специального набора
процедур, называемого плеером. В плеере (по крайней мере, для
версий 3.3-3.6) имеется так называемая таблица громкости,
расположенная по смещению #110 от начала плеера и занимающая 240
(#F0) байтов. Эта таблица представляет собой 15 строк по 16
значений в строке, каждое значение занимает один байт и является
числом от 0 до 15.
В PT3 каждый отсчёт сэмпла имеет громкость от 0 до 15, а сам
сэмпл может быть проигран с громкостью от 1 до 15 (нулевая
громкость не используется). Вот для определения громкости
проигрывания очередного отсчёта по значениям громкости сэмпла и
громкости этого отсчёта в сэмпле и нужна таблица. Hа пересечении
строки с номером, равным громкости сэмпла, и столбца с номером,
равным громкости отсчёта в сэмпле, и находится искомое значение
громкости.
Если обнулить в плеере место, занимаемое таблицей громкости,
при этом добавив в программу фрагмент для построения этой
таблицы, то при упаковке программы получится выигрыш.
Действительно, последовательность нулей на месте таблицы
сожмётся гораздо лучше самой таблицы, а длина добавленного

фрагмента программы, строящего таблицу, очень невелика (как мы
увидим далее), к тому же он тоже может быть сжат.
Pro Tracker 3 - наиболее широко используемый в настоящее
время музыкальный редактор для ZX Spectrum, написанные в нём
модули используются во множестве программ (игры, электронные
газеты и журналы, intro, demo). Соответственно, область
применения такого способа уменьшения длины программы обещает
быть весьма широкой.
Hиже приведён участок дампа стандартного плеера Pro Tracker
3.3-3.4, содержащий таблицу громкости.

#0110: 0000 0000 0000 0000 0101 0101 0101 0101
#0120: 0000 0000 0000 0101 0101 0102 0202 0202
#0130: 0000 0000 0101 0101 0202 0202 0303 0303
#0140: 0000 0000 0101 0102 0202 0303 0304 0404
#0150: 0000 0001 0101 0202 0303 0304 0404 0505
#0160: 0000 0001 0102 0203 0303 0404 0505 0606
#0170: 0000 0101 0202 0303 0404 0505 0606 0707
#0180: 0000 0101 0202 0303 0405 0506 0607 0708
#0190: 0000 0101 0203 0304 0505 0606 0708 0809
#01A0: 0000 0102 0203 0404 0506 0607 0808 090A
#01B0: 0000 0102 0303 0405 0606 0708 0909 0A0B
#01C0: 0000 0102 0304 0405 0607 0808 090A 0B0C
#01D0: 0000 0102 0304 0506 0707 0809 0A0B 0C0D
#01E0: 0000 0102 0304 0506 0708 090A 0B0C 0D0E
#01F0: 0001 0203 0405 0607 0809 0A0B 0C0D 0E0F

Я не знаю, какую формулу использовал для расчёта этой
таблицы автор Pro Tracker'а, но в точности подошла такая простая
формула:

┌ ┐
│ (i+1)*j │
A(i,j) = │ ─────── │.
│ 16 │
└ ┘

В этой формуле квадратные скобки обозначают выделение целой
части, i - номер строки таблицы (нумеруются с 1), j - номер
столбца (нумеруются с 0).
Так как плеер PT3 всегда начинается с адреса #XX00, а
таблица расположена по смещению #110 от начала плеера и, таким
образом, всегда начинается с адреса #XX10, получается, что
координаты i и j элемента таблицы можно получить из младшего
байта адреса этого элемента: значение старшего полубайта - это
i, а младшего - j.
Учитывая всё вышесказанное, удалось написать достаточно
короткий фрагмент программы, строящий таблицу:

LD HL,VOL_TAB ;Адрес таблицы - число вида #XX10.
LD DE,#000F ;D - начальное значение i. Проще хранить
;и увеличивать i отдельно, а не
;получать из L. Сейчас i=0, но перед
;вычислением первого элемента таблицы
;i увеличится до 1. E - константа #0F.
;Главный цикл.

M1 LD A,L ;Младший байт адреса текущего элемента.
AND E ;=AND #0F - получили j.
LD C,A ;Запомнили j.
JR NZ,M2 ;Каждый раз, когда j=0 (т.е. когда
INC D ;начинаем обрабатывать новую строку),
;увеличиваем i на 1.
M2 LD B,D ;Счётчик=i.
;Сейчас A=j.
M3 ADD A,C ;A=A+j.
DJNZ M3

;Вычислили A=(i+1)*j. Теперь делим на 16.

RRCA
RRCA
RRCA
RRCA
AND E ;=AND #0F.
LD (HL),A ;Запись значения элемента.
INC L ;Переход к адресу следующего элемента.
;Если вся таблица заполнена, то L=0.
JR NZ,M1 ;L<>0 - переходим к началу главного
;цикла (продолжение заполнения таблицы).

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 14 Jun 2004
Hello, All!

═══════════════════ make_vol.1 ══════════════════

(c) Иван Рощин, Москва

ZXNet : 500:95/462.53
E-mail: bestview@mtu-net.ru
WWW : http://www.ivr.da.ru

Построение таблицы громкости в плеере Pro Tracker 3
═══════════════════════════════════════════════════

("Радиомир. Ваш компьютер" 4/2004)

Музыкальные модули, написанные в редакторе Pro Tracker 3
(далее PT3), проигрываются с помощью специального набора
процедур, называемого плеером. В плеере (по крайней мере, для
версий 3.3-3.6) имеется так называемая таблица громкости,
расположенная по смещению #110 от начала плеера и занимающая 240
(#F0) байтов. Эта таблица представляет собой 15 строк по 16
значений в строке, каждое значение занимает один байт и является
числом от 0 до 15.
В PT3 каждый отсчёт сэмпла имеет громкость от 0 до 15, а сам
сэмпл может быть проигран с громкостью от 1 до 15 (нулевая
громкость не используется). Вот для определения громкости
проигрывания очередного отсчёта по значениям громкости сэмпла и
громкости этого отсчёта в сэмпле и нужна таблица. Hа пересечении
строки с номером, равным громкости сэмпла, и столбца с номером,
равным громкости отсчёта в сэмпле, и находится искомое значение
громкости.
Если обнулить в плеере место, занимаемое таблицей громкости,
при этом добавив в программу фрагмент для построения этой
таблицы, то при упаковке программы получится выигрыш.
Действительно, последовательность нулей на месте таблицы
сожмётся гораздо лучше самой таблицы, а длина добавленного
фрагмента программы, строящего таблицу, очень невелика (как мы
увидим далее), к тому же он тоже может быть сжат.
Pro Tracker 3 - наиболее широко используемый в настоящее
время музыкальный редактор для ZX Spectrum, написанные в нём
модули используются во множестве программ (игры, электронные
газеты и журналы, intro, demo). Соответственно, область
применения такого способа уменьшения длины программы обещает
быть весьма широкой.
Hиже приведён участок дампа стандартного плеера Pro Tracker
3.3-3.4, содержащий таблицу громкости.

#0110: 0000 0000 0000 0000 0101 0101 0101 0101
#0120: 0000 0000 0000 0101 0101 0102 0202 0202
#0130: 0000 0000 0101 0101 0202 0202 0303 0303
#0140: 0000 0000 0101 0102 0202 0303 0304 0404
#0150: 0000 0001 0101 0202 0303 0304 0404 0505
#0160: 0000 0001 0102 0203 0303 0404 0505 0606
#0170: 0000 0101 0202 0303 0404 0505 0606 0707
#0180: 0000 0101 0202 0303 0405 0506 0607 0708
#0190: 0000 0101 0203 0304 0505 0606 0708 0809
#01A0: 0000 0102 0203 0404 0506 0607 0808 090A
#01B0: 0000 0102 0303 0405 0606 0708 0909 0A0B
#01C0: 0000 0102 0304 0405 0607 0808 090A 0B0C
#01D0: 0000 0102 0304 0506 0707 0809 0A0B 0C0D
#01E0: 0000 0102 0304 0506 0708 090A 0B0C 0D0E
#01F0: 0001 0203 0405 0607 0809 0A0B 0C0D 0E0F

Я не знаю, какую формулу использовал для расчёта этой
таблицы автор Pro Tracker'а, но в точности подошла такая простая
формула:

┌ ┐
│ (i+1)*j │
A(i,j) = │ ─────── │.
│ 16 │
└ ┘

В этой формуле квадратные скобки обозначают выделение целой
части, i - номер строки таблицы (нумеруются с 1), j - номер
столбца (нумеруются с 0).
Так как плеер PT3 всегда начинается с адреса #XX00, а
таблица расположена по смещению #110 от начала плеера и, таким
образом, всегда начинается с адреса #XX10, получается, что
координаты i и j элемента таблицы можно получить из младшего
байта адреса этого элемента: значение старшего полубайта - это
i, а младшего - j.
Учитывая всё вышесказанное, удалось написать достаточно
короткий фрагмент программы, строящий таблицу:

LD HL,VOL_TAB ;Адрес таблицы - число вида #XX10.
LD DE,#000F ;D - начальное значение i. Проще хранить
;и увеличивать i отдельно, а не
;получать из L. Сейчас i=0, но перед
;вычислением первого элемента таблицы
;i увеличится до 1. E - константа #0F.
;Главный цикл.

M1 LD A,L ;Младший байт адреса текущего элемента.
AND E ;=AND #0F - получили j.
LD C,A ;Запомнили j.
JR NZ,M2 ;Каждый раз, когда j=0 (т.е. когда
INC D ;начинаем обрабатывать новую строку),
;увеличиваем i на 1.
M2 LD B,D ;Счётчик=i.
;Сейчас A=j.
M3 ADD A,C ;A=A+j.
DJNZ M3

;Вычислили A=(i+1)*j. Теперь делим на 16.

RRCA
RRCA
RRCA
RRCA
AND E ;=AND #0F.
LD (HL),A ;Запись значения элемента.
INC L ;Переход к адресу следующего элемента.
;Если вся таблица заполнена, то L=0.
JR NZ,M1 ;L<>0 - переходим к началу главного
;цикла (продолжение заполнения таблицы).

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 14 Jun 2004
Hello, All!

═══════════════════ make_vol.2 ══════════════════

Длина этого фрагмента - всего 25 байтов, а длина исходной
таблицы, сжатой с помощью HRUST 1.3, - 94 байта (без учёта
служебных 6 байтов получаемого файла, в которых хранится
идентификатор "HR", длина исходного блока и длина упакованного
блока). Тогда выигрыш от программного построения таблицы
составит примерно 94-25=69 байтов (примерно - по нескольким
причинам: во-первых, при сжатии всей программы, а не только
таблицы, таблица может сжаться не в точности до 94 байтов;
во-вторых, сжатая последовательность нулей на месте таблицы тоже
займёт сколько-то места, пусть и очень мало; в-третьих,
рассматриваемый фрагмент программы также может быть сжат).
Кстати, если известны значения каких-либо регистров к
моменту выполнения этого фрагмента, то в некоторых случаях
возможна дополнительная оптимизация. Hапример, если D=0, то
можно заменить LD DE,#000F на LD E,#0F, а если E=0, то можно
переписать фрагмент так, чтобы поменять местами функции
регистров D и E, тогда вместо LD DE,#000F будет LD DE,#0F00, а
эту команду уже можно заменить на LD D,#0F. Также при
необходимости можно поменять местами функции регистровых пар
HL и DE (например, если к моменту выполнения фрагмента в DE уже
содержится адрес VOL_TAB).
В плеере Pro Tracker 3.5-3.6 используется несколько другая
таблица громкости:

#0110: 0000 0000 0000 0000 0101 0101 0101 0101
#0120: 0000 0000 0101 0101 0101 0101 0202 0202
#0130: 0000 0001 0101 0101 0202 0202 0203 0303
#0140: 0000 0101 0101 0202 0202 0303 0303 0404
#0150: 0000 0101 0102 0202 0303 0304 0404 0505
#0160: 0000 0101 0202 0203 0304 0404 0505 0606
#0170: 0000 0101 0202 0303 0404 0505 0606 0707
#0180: 0001 0102 0203 0304 0405 0506 0607 0708
#0190: 0001 0102 0203 0404 0505 0607 0708 0809
#01A0: 0001 0102 0303 0405 0506 0707 0809 090A
#01B0: 0001 0102 0304 0405 0607 0708 090A 0A0B
#01C0: 0001 0202 0304 0506 0607 0809 0A0A 0B0C
#01D0: 0001 0203 0304 0506 0708 090A 0A0B 0C0D
#01E0: 0001 0203 0405 0607 0708 090A 0B0C 0D0E
#01F0: 0001 0203 0405 0607 0809 0A0B 0C0D 0E0F

В этом случае не так просто подобрать формулу для вычисления
элементов таблицы. Hо, к счастью, мне удалось найти подпрограмму
построения такой таблицы в [1]. Там был приведён исходный текст
плеера Pro Tracker 2.101, в котором таблица громкости строится
специальной подпрограммой, а при сравнении обнаружилось, что эта
таблица - такая же, как в плеере Pro Tracker 3.5-3.6. Кстати,
странно: исходный текст плеера в [1] был прокомментирован как
"стандартный проигрыватель", а между тем в оригинальном плеере
PT2 таблица содержится непосредственно, а не строится
подпрограммой.
Приведённую в [1] подпрограмму длиной 43 байта мне удалось
существенно оптимизировать, несколько изменив логику её работы
и учитывая, что адрес расположения таблицы - число вида #XX10.
В результате получилась вот такая подпрограмма длиной 32 байта:

LD BC,VOL_TAB ;Адрес таблицы - число вида #XX10.
XOR A
LD D,A

INITV2 ADD A,#11 ;Если в результате этого сложения
;получится #100 (т.е. A=0 и флаг C=1),
SBC A,D ;то 0 превратится в #FF после SBC A,D.
LD E,A
LD H,D
LD L,D ;HL=0 (D всегда равно 0).

INITV1 LD A,L
RLA
LD A,H
ADC A,D ;=ADC A,0.
LD (BC),A
ADD HL,DE
INC C
RET Z
LD A,C
AND 15
JR NZ,INITV1

LD A,E
CP #77
JR NZ,INITV2
INC A
JR INITV2

Чтобы использовать её в виде непосредственно встраиваемого
в программу фрагмента (чтобы не тратиться на команду CALL),
достаточно заменить команду RET Z на JR Z,M1 (где M1 - адрес
следующей за фрагментом команды). Получится фрагмент программы
на байт длиннее - 33 байта.
Исходная таблица сжимается с помощью HRUST 1.3 до 97 байтов
(также без учёта служебных 6 байтов). Таким образом, примерный
выигрыш от программного построения таблицы PT 3.5-3.6 составит
97-33=64 байта.
При рассмотрении этой подпрограммы я понял, что можно
написать похожую подпрограмму для построения уже рассмотренной
ранее таблицы PT 3.3-3.4:

LD BC,VOL_TAB ;Адрес таблицы - число вида #XX10.
LD DE,#10

INITV2 LD HL,#10
ADD HL,DE ;После сложения флаг C сброшен.
EX DE,HL ;DE=DE+#10.
SBC HL,HL ;HL=0.

INITV1 LD A,H
LD (BC),A
ADD HL,DE
INC C
RET Z
LD A,C
AND 15
JR NZ,INITV1

JR INITV2

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 14 Jun 2004
Hello, All!

═══════════════════ make_vol.3 ══════════════════

Длина этой подпрограммы - 25 байтов, а при оформлении её в
виде фрагмента программы - 26 байтов. В сравнении с 25-байтовым
фрагментом, приведённым в начале статьи, выигрыша в размере не
получилось. Hо всё-таки работа была проделана не зря: возникла
идея объединить две похожие подпрограммы - эту и предыдущую - в
одну универсальную подпрограмму построения таблицы громкости.
Если приходится проигрывать одним и тем же плеером модули,
написанные в различных версиях PT3, то желательно перед
проигрыванием каждого модуля проверять, в какой версии PT3 он
написан, и в соответствии с этим строить нужную таблицу
громкости. Тогда каждый модуль будет звучать так, как задумывал
его автор. Именно так сделано в моей программе BestView.
Первые байты проигрываемого PT3-модуля представляют собой
строку "ProTracker 3.x", где x - номер подверсии. Таким образом,
можно, проверив байт, в котором хранится символ x (он находится
по смещению 13 от начала модуля), узнать, какую из двух таблиц
необходимо сформировать.
Hиже приведён текст универсальной подпрограммы, которая
именно это и делает: определяет, какую из двух таблиц надо
построить, и строит нужную таблицу. Метка MODULE - это адрес
проигрываемого модуля.

LD A,(MODULE+13) ;Проверка версии.
CP "5"
LD HL,#11 ;Подготовка данных для построения
LD D,H ;таблицы PT 3.5-3.6.
LD E,H ;DE=0.
LD A,#17 ;Код команды RLA.
JR NC,M1 ;Переход, если версия - 3.5 или 3.6.
DEC L ;HL=#10.
LD E,L ;DE=#10.
XOR A ;A=0 (код команды NOP).
M1 LD (M2),A ;По адресу M2 будет или NOP, или RLA.

LD BC,VOL_TAB ;Адрес таблицы - число вида #XX10.

INITV2 PUSH HL ;Значение слагаемого (#10 или #11)
;храним в стеке.
ADD HL,DE ;После сложения флаг C сброшен.
EX DE,HL ;DE=DE+#10 или DE=DE+#11, смотря какую
;таблицу строим.
SBC HL,HL ;HL=0.

INITV1 LD A,L
M2 DB #7D ;Здесь будет RLA или NOP.
LD A,H

;Если по адресу M2 находится команда NOP, то флаг C сейчас точно
;сброшен (либо после команды SBC HL,HL, либо после команды
;AND 15 в конце цикла), и следующая команда не изменит A.

ADC A,0
LD (BC),A
ADD HL,DE
INC C
LD A,C
AND 15
JR NZ,INITV1

POP HL ;Восстановили значение слагаемого.
LD A,E ;Если строим таблицу PT 3.3-3.4,
CP #77 ;то E никогда не будет равняться #77.
JR NZ,M3
INC E
M3 LD A,C ;Если вся таблица заполнена, то C=0.
AND A
JR NZ,INITV2

RET

Длина этой подпрограммы - 53 байта, а при оформлении её в
виде фрагмента программы - 52 байта. Как видим, это меньше, чем
суммарная длина двух отдельных подпрограмм и команд для
определения, какую из них надо вызывать.
Hебольшой комментарий к этой подпрограмме. Перед построением
таблицы происходит самомодификация кода: по адресу M2 заносится
либо код команды NOP, либо код команды RLA, в зависимости от
того, какую таблицу надо строить. Как можно видеть, изначально
по адресу M2 находится байт #7D. Почему именно он? Потому что он
равен значению предыдущего байта фрагмента - коду команды
LD A,L. Таким образом, в программе будут два следующих друг за
другом одинаковых байта, что положительно скажется на степени её
сжатия. Подробнее о таком способе оптимизации рассказывается в
[2].
Если таблицу надо строить только один раз, либо если
подпрограмма построения таблицы хранится в упакованном виде
(возможно, вместе с плеером) и распаковывается заново перед
каждым проигрыванием нового модуля, то можно дополнительно её
оптимизировать. По адресу M2 тогда можно сразу поместить команду
RLA, а из начала подпрограммы убрать команду LD A,#17 и
переставить метку M1 на следующую команду (LD BC,VOL_TAB).
Осталось напомнить, что вопрос уменьшения размера программ,
в которых используются музыкальные модули (не обязательно
написанные в Pro Tracker 3), я уже затрагивал ранее. Так, в [3]
рассмотрено улучшения сжатия музыкальных модулей за счёт
автоматического определения тех ячеек модуля и плеера,
первоначальные значения которых не используются при
проигрывании, и заполнения этих ячеек значением из предыдущей
используемой ячейки. А в [4], среди прочего, приводится
процедура построения частотной таблицы - таким образом, можно не
хранить эту таблицу в плеере и за счёт этого улучшить сжатие
программы.


Источники
─────────

1. VfNG/NEW. Описание формата модулей Pro Tracker 2.101.
Электронная газета "Echo" #2.

2. И.Рощин. "Улучшение сжатия программ на ассемблере Z80".
"Радиомир. Ваш компьютер" 4/2003.

3. И.Рощин. "Повышение степени сжатия музыкальных модулей".
"Радиомир. Ваш компьютер" 7/2002.

4. И.Рощин. "Частотная таблица с нулевой погрешностью".
"Радиолюбитель. Ваш компьютер" 6/2001, "Радиомир. Ваш
компьютер" 7,8/2001.

════════════════════════════════════════════════════════════════

Другие мои статьи об AY-музыке:

1. "Правильное изменение частоты огибающей в Pro Tracker 3".
"Радиолюбитель. Ваш компьютер" 10,11/2000.

2. "Hекоторые особенности музыкального сопроцессора".
"Радиолюбитель. Ваш компьютер" 11/2000, под псевдонимом
BV_Creator.

3. "Оптимизация на примере intro 'Start'". "Радиомир. Ваш
компьютер" 7-10/2001.
(В этой статье можно найти сведения о программировании
проигрывания музыки.)

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Vladimir Karpenko
кому: Ivan Roshin
дата: 13 Aug 2004
Hello Ivan.

14 Jun 04 17:38, you wrote to All:

IR> ("Радиомир. Ваш компьютер" 4/2004)

А хде его в 95 купить моно?
Vladimir

[I.ZX] [Sprinter Developer] [MГАПИ-УП-1'2003] [Sprinter2000Pro] [Daewoo CPC]
[Sprinter UNofficial site: www.shaos.net/nuke/]
[Liberation of Liberia]

от: Eugene Palenock
кому: Vladimir Karpenko
дата: 13 Aug 2004

Привет, Vladimir!

13 Авг 04 03:16, Vladimir Karpenko -> Ivan Roshin:

IR>> ("Радиомир. Ваш компьютер" 4/2004)
VK> А хде его в 95 купить моно?

Вероятно, в Чип-Дип на Беговой - там много подобных журналов и за несколько
месяцев.

С уважением, Евгений.

от: Vladimir Karpenko
кому: Eugene Palenock
дата: 13 Aug 2004
Hello Eugene.

13 Aug 04 04:39, you wrote to me:

IR>>> ("Радиомир. Ваш компьютер" 4/2004)
VK>> А хде его в 95 купить моно?

EP> Вероятно, в Чип-Дип на Беговой - там много подобных журналов и за
EP> несколько месяцев.
Вашего компьютера там нету:(
Vladimir

[I.ZX] [Sprinter Developer] [MГАПИ-УП-1'2003] [Sprinter2000Pro] [Daewoo CPC]
[Sprinter UNofficial site: www.shaos.net/nuke/]
[Liberation of Liberia]




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

Похожие статьи:
Обзор - обзор игрушек: НЛО2, Ментура, Dina Blaster, Super Cars, Darkman, Final Fight, Street Fighter 2, Pang 128, Time Cop, The Turn, Ice Climber, Приключение Винни Пуха, Prince of Persia.
Интервью - Интервью CDS'а газете.
Улыбнись - Распорядок рабочего дня.

В этот день...   14 октября