© Иван Рощин, Москва
| ZXNet | : 500:95/462.53 |
| : bestview@mtu-net.ru | |
| WWW | : http://www.ivr.da.ru |
(«Радиомир. Ваш компьютер» 4/2004)
Музыкальные модули, написанные в редакторе Pro Tracker 3 (далее PT3), проигрываются с помощью специального набора процедур, называемого плеером. В плеере (по крайней мере, для версий 3.33.6) имеется так называемая таблица громкости, расположенная по смещению #110 от начала плеера и занимающая 240 (#F0) байтов. Эта таблица представляет собой 15 строк по 16 значений в строке, каждое значение занимает один байт и является числом от 0 до 15.
В PT3 каждый отсчёт сэмпла имеет громкость от 0 до 15, а сам сэмпл может быть проигран с громкостью от 1 до 15 (нулевая громкость не используется). Вот для определения громкости проигрывания очередного отсчёта по значениям громкости сэмпла и громкости этого отсчёта в сэмпле и нужна таблица. На пересечении строки с номером, равным громкости сэмпла, и столбца с номером, равным громкости отсчёта в сэмпле, и находится искомое значение громкости.
Если обнулить в плеере место, занимаемое таблицей громкости, при этом добавив в программу фрагмент для построения этой таблицы, то при упаковке программы получится выигрыш. Действительно, последовательность нулей на месте таблицы сожмётся гораздо лучше самой таблицы, а длина добавленного фрагмента программы, строящего таблицу, очень невелика (как мы увидим далее), к тому же он тоже может быть сжат.
Pro Tracker 3 наиболее широко используемый в настоящее время музыкальный редактор для ZX Spectrum, написанные в нём модули используются во множестве программ (игры, электронные газеты и журналы, intro, demo). Соответственно, область применения такого способа уменьшения длины программы обещает быть весьма широкой.
Ниже приведён участок дампа стандартного плеера Pro Tracker 3.33.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 номер столбца (нумеруются с 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 - переходим к началу главного
;цикла (продолжение заполнения таблицы).
Длина этого фрагмента всего 25 байтов, а длина исходной
таблицы, сжатой с помощью HRUST 1.3, 94 байта (без учёта
служебных 6 байтов получаемого файла, в которых хранится идентификатор
«HR», длина исходного блока и длина упакованного блока). Тогда
выигрыш от программного построения таблицы составит примерно 9425=69
байтов (примерно по нескольким причинам:
Кстати, если известны значения
В плеере Pro Tracker 3.53.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 |
В этом случае не так просто подобрать формулу для вычисления элементов таблицы. Но, к счастью, мне удалось найти подпрограмму построения такой таблицы в [1]. Там был приведён исходный текст плеера Pro Tracker 2.101, в котором таблица громкости строится специальной подпрограммой, а при сравнении обнаружилось, что эта таблица такая же, как в плеере Pro Tracker 3.53.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.53.6 составит 9733=64 байта.
При рассмотрении этой подпрограммы я понял, что можно написать похожую подпрограмму для построения уже рассмотренной ранее таблицы PT 3.33.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
Длина этой подпрограммы 25 байтов, а при оформлении её в виде
фрагмента программы 26 байтов. В сравнении с
Если приходится проигрывать одним и тем же плеером модули, написанные в различных версиях PT3, то желательно перед проигрыванием каждого модуля проверять, в какой версии PT3 он написан, и в соответствии с этим строить нужную таблицу громкости. Тогда каждый модуль будет звучать так, как задумывал его автор. Именно так сделано в моей программе BestView.
Первые байты проигрываемого
Ниже приведён текст универсальной подпрограммы, которая именно это и делает: определяет, какую из двух таблиц надо построить, и строит нужную таблицу. Метка 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 байта. Как видим, это меньше, чем суммарная длина двух отдельных подпрограмм и команд для определения, какую из них надо вызывать.
Небольшой комментарий к этой подпрограмме. Перед построением таблицы происходит самомодификация кода: по адресу M2 заносится либо код команды NOP, либо код команды RLA, в зависимости от того, какую таблицу надо строить. Как можно видеть, изначально по адресу M2 находится байт #7D. Почему именно он? Потому что он равен значению предыдущего байта фрагмента коду команды LD A,L. Таким образом, в программе будут два следующих друг за другом одинаковых байта, что положительно скажется на степени её сжатия. Подробнее о таком способе оптимизации рассказывается в [2].
Если таблицу надо строить только один раз, либо если подпрограмма построения таблицы хранится в упакованном виде (возможно, вместе с плеером) и распаковывается заново перед каждым проигрыванием нового модуля, то можно дополнительно её оптимизировать. По адресу M2 тогда можно сразу поместить команду RLA, а из начала подпрограммы убрать команду LD A,#17 и переставить метку M1 на следующую команду (LD BC,VOL_TAB).
Осталось напомнить, что вопрос уменьшения размера программ, в которых используются музыкальные модули (не обязательно написанные в Pro Tracker 3), я уже затрагивал ранее. Так, в [3] рассмотрено улучшения сжатия музыкальных модулей за счёт автоматического определения тех ячеек модуля и плеера, первоначальные значения которых не используются при проигрывании, и заполнения этих ячеек значением из предыдущей используемой ячейки. А в [4], среди прочего, приводится процедура построения частотной таблицы таким образом, можно не хранить эту таблицу в плеере и за счёт этого улучшить сжатие программы.
|
Другие мои статьи об
|