Программирование звуковых эффектов
Тон
Начнем с эффектов, основанных на генерации тона. Что они из себя представляют? Примерно то же, что и эффекты на бейсике: звук с плавно изменяющейся частотой. Для полного сходства можно даже использовать ту же подпрограмму из ПЗУ:
Assembler Z80:
10 LD B,30 ; B=количество нот
20 LD HL,100 ; HL=начальная частота
30 LOOP LD DE,2 ; DE=длительность
40 PUSH HL ; сохранение HL
50 PUSH BC ; сохранение BC
60 CALL 949 ; вызов подпрограммы ПЗУ
70 POP BC ; восстановление BC
80 POP HL ; восстановление HL
90 LD DE,25 ; DE=шаг изменения частоты
100 ADD HL,DE ; увеличение HL
110 DJNZ LOOP ; цикл
120 RET ; возврат в бейсик
Assembler Z80:
10 LD B,60 ; B=количество нот
20 LD C,50 ; C=шаг изменения частоты
30 LD HL,300 ; HL=начальная частота
40 LOOP LD DE,10 ; DE=длительность
50 PUSH HL ; сохранение HL
60 PUSH BC ; сохранение BC
70 CALL 949 ; вызов подпрограммы ПЗУ
80 POP BC ; восстановление BC
90 POP HL ; восстановление HL
100 LD A,L ; увели-
110 ADD C ; чение
120 LD L,A ; L
130 DJNZ LOOP ; цикл
140 RET ; возврат в бейсик
Assembler Z80:
10 DI ; запрет прерываний
20 XOR A ; A=цвет бордюра (0)
30 LD B,255 ; B=начальная частота
40 LD C,255 ; C=длительность
50 BEEP XOR 16 ; инвертирование бита D4
60 OUT (254),A ; вывод A в порт 254
70 PUSH BC ; сохранение BC
80 LOOP DJNZ LOOP ; задержка
90 POP BC ; восстановление BC
100 DEC B ; уменьшение B
110 DEC C ; C=C-1
120 JR NZ,BEEP ; если C<>0, то цикл
130 EI ; разрешение прерываний
140 RET ; возврат в бейсик
Assembler Z80:
10 DI ; запрет прерываний
20 XOR A ; A=цвет бордюра (0)
30 LD B,255 ; B=количество нот
40 LD C,1 ; C=начальная частота
50 LOOP1 PUSH BC ; сохранение BC
60 LD B,5 ; B=длительность
70 LOOP2 XOR 16 ; инвертирование бита D4
80 OUT (254),A ; вывод A в порт 254
90 PUSH BC ; сохранение BC
100 LD B,C ; B=C
110 LOOP3 DJNZ LOOP3 ; задержка
120 POP BC ; восстановление BC
130 DJNZ LOOP2 ; цикл
140 POP BC ; восстановление BC
150 INC C ; увеличение C
160 DJNZ LOOP1 ; второй цикл
170 EI ; разрешение прерываний
180 RET ; возврат в бейсик
Assembler Z80:
10 DI ; запрет прерываний
20 XOR A ; A=цвет бордюра (0)
30 LD B,255 ; B=начальная частота
40 LD C,255 ; C=длительность
50 BEEP XOR 16 ; инвертирование бита D4
60 OUT (254),A ; вывод A в порт 254
70 PUSH BC ; сохранение BC
80 LOOP DJNZ LOOP ; задержка
90 POP BC ; восстановление BC
100 EX AF,AF' ; смена регистров A и F на альтернативные
110 LD A,B ; A=B
120 SUB 3 ; A=A-3
130 LD B,A ; B=A
140 EX AF,AF' ; обратная смена регистров
150 DEC C ; C=C-1
160 JR NZ,BEEP ; если C<>0, то цикл
170 EI ; разрешение прерываний
180 RET ; возврат в бейсик
Assembler Z80:
10 DI ; запрет прерываний
20 LD D,10 ; D=задержка 1
30 LD E,100 ; E=задержка 2
40 LD C,255 ; C=длительность
50 XOR A ; A=цвет бордюра (0)
60 LOOP1 XOR 16 ; инвертирование бита D4
70 OUT (254),A ; вывод A в порт 254
80 LD B,D ; B=D
90 LOOP2 DJNZ LOOP2 ; задержка
100 XOR 16 ; инвертирование бита D4
110 OUT (254),A ; вывод A в порт 254
120 LD B,E ; B=E
130 LOOP3 DJNZ LOOP3 ; задержка
140 INC D ; увеличение D
150 INC E ; увеличение E
160 DEC C ; C=C-1
170 JR NZ,LOOP1 ; если C<>0, то цикл
180 EI ; разрешение прерываний
190 RET ; возврат в бейсик
Вот пример такого эффекта:
Assembler Z80:
10 DI ; запрет прерываний
20 LD C,53 ; C=задержка 1
30 LD B,207 ; B=задержка 2
40 LD E,203 ; E=длительность
50 LD D,0 ; D=цвет бордюра
60 LD A,128 ; A=темп (0/128)
70 LD R,A ; R=A
80 BEGIN LD A,R ; A=R
90 PAUS1 DEC A ; A=A-1
100 JR NZ,PAUS1 ; если A<>0, то цикл
110 LD A,D ; A=D
120 OR 16 ; установка бита D4
130 OUT (254),A ; вывод A в порт 254
140 LD A,C ; A=C
150 PAUS2 DEC A ; A=A-1
160 JR NZ,PAUS2 ; если A<>0, то цикл
170 LD A,D ; A=D
180 OUT (254),A ; вывод A в порт 254
190 LD A,B ; A=B
200 PAUS3 DEC A ; A=A-1
210 JR NZ,PAUS3 ; если A<>0, то цикл
220 INC C ; C=C+1
230 INC B ; B=B+1
240 DEC E ; E=E-1
250 JR NZ,BEGIN ; если E<>0, то цикл
260 EI ; разрешение прерываний
270 RET ; возврат
В строке 60 имеет смысл употреблять только значения 0 и 128, так как все остальные будут аналогичны этим.
В строках 220 и 230 Вы можете установить закон изменения обеих задержек (команды INC, DEC и NOP с регистрами B и C в любой комбинации).
Шум
Что из себя представляет шум? Это последовательность импульсов случайной длительности. Поэтому для его создания нам понадобятся случайные данные. Где их взять? Единственный подходящий источник — ПЗУ, в котором записан интерпретатор бейсика. ПЗУ расположено с адреса 0 до 16383 (#3FFF). Правда, значения эти будут не совсем случайные, точнее совсем не случайные, но нас они устроят.
Assembler Z80:
10 LD HL,0 ; HL=адрес ПЗУ
20 LD BC,1000 ; BC=длительность
30 BEGIN PUSH BC ; сохранение BC
40 LD A,(HL) ; A=содержимое ячейки ПЗУ
50 AND 240 ; сброс битов бордюра
60 OUT (254),A ; вывод A в порт 254
70 LD B,50 ; B=частота
80 LOOP DJNZ LOOP ; задержка
90 INC HL ; HL=HL+1
100 POP BC ; восстановление BC
110 DEC BC ; BC=BC-1
120 LD A,B ; BC=
130 OR C ; 0 ?
140 JR NZ,BEGIN ; цикл
150 RET ; возврат в бейсик
Assembler Z80:
10 LD HL,0 ; HL=адрес ПЗУ
20 LD BC,1000 ; BC=длительность
30 XOR A ; A=цвет бордюра (0)
40 BEGIN PUSH BC ; сохранение BC
50 XOR 16 ; инвертирование бита D4
60 OUT (254),A ; вывод A в порт 254
70 LD B,(HL) ; B=содержимое ячейки ПЗУ
80 LOOP1 DJNZ LOOP1 ; задержка
90 LD B,40 ; B=частота
100 LOOP2 DJNZ LOOP2 ; задержка
110 INC HL ; HL=HL+1
120 POP BC ; восстановление BC
130 DEC BC ; BC=BC-1
140 LD D,A ; сохранение A
150 LD A,B ; BC=
160 OR C ; 0 ?
170 LD A,D ; восстановление A
180 JR NZ,BEGIN ; цикл
190 RET ; возврат в бейсик
Assembler Z80:
10 LD HL,0 ; HL=адрес ПЗУ
20 LD B,100 ; B=длина эффекта
30 LD C,10 ; C=начальная частота
40 LOOP1 PUSH BC ; сохранение BC
50 LD B,20 ; B=длительность
60 LOOP2 LD A,(HL) ; A=содержимое ячейки ПЗУ
70 AND 240 ; сброс битов бордюра
80 OUT (254),A ; вывод A в порт 254
90 PUSH BC ; сохранение BC
100 LD B,C ; B=C
110 LOOP3 DJNZ LOOP3 ; задержка
120 INC HL ; HL=HL+1
130 POP BC ; восстановление BC
140 DJNZ LOOP2 ; цикл
150 POP BC ; восстановление BC
160 INC C ; увеличение задержки
170 DJNZ LOOP1 ; цикл
180 RET ; возврат в бейсик
Assembler Z80:
10 LD HL,0 ; HL=адрес ПЗУ
20 LD D,100 ; D=задержка 1
30 LD E,10 ; E=задержка 2
40 LD C,255 ; C=длительность
50 XOR A ; A=цвет бордюра (0)
60 BEGIN XOR 16 ; инвертирование бита D4
70 OUT (254),A ; вывод A в порт 254
80 LD B,(HL) ; B=содержимое ячейки ПЗУ
90 LOOP1 DJNZ LOOP1 ; задержка 1
100 LD B,D ; B=D
110 LOOP2 DJNZ LOOP2 ; задержка 2
120 XOR 16 ; инвертирование бита D4
130 OUT (254),A ; вывод A в порт 254
140 LD B,(HL) ; B=содержимое ячейки ПЗУ
150 LOOP3 DJNZ LOOP3 ; задержка 3
160 LD B,E ; B=E
170 LOOP4 DJNZ LOOP4 ; задержка 4
180 INC HL ; HL=HL+1
190 INC D ; увеличение D
200 INC E ; увеличение E
210 DEC C ; C=C-1
220 JR NZ,BEGIN ; если C<>0, то цикл
230 RET ; возврат в бейсик
Assembler Z80:
10 DI ; запрет прерываний
20 LD E,100 ; E=длительность цикла
30 LD C,0 ; C=цвет бордюра
40 LD B,4 ; B=число циклов
50 LD L,1 ; L=смещение частоты
60 LD H,30 ; H=начальная частота
70 LOOP1 LD D,E ; D=E
80 LOOP2 LD A,C ; A=C
90 XOR 16 ; инвертирование бита D4
100 OUT (254),A ; вывод A в порт 254
110 LD C,A ; C=A
120 LD A,H ; A=H
130 ADD A,L ; прибавить L к A
140 LD H,A ; H=A
150 LOOP3 DEC A ; A=A-1
160 JR NZ,LOOP3 ; если A<>0, то цикл
170 DEC D ; D=D-1
180 JR NZ,LOOP2 ; если D<>0, то цикл
190 LD A,L ; A=L
200 NEG ; изменение знака регистра A
210 LD L,A ; L=A
220 DJNZ LOOP1 ; цикл
230 EI ; разрешение прерываний
240 RET ; возврат
Комплексы эффектов
Обычно в играх (да и в любых других программах) используется не один и не два звуковых эффекта, а гораздо больше. Поэтому эффекты удобно объединять в группы (комплексы) и вызывать их на исполнение одной подпрограммой, передавая ей в качестве параметра номер эффекта.
Assembler Z80:
10 ADD A,A ; A=A*2
20 LD E,A ; DE
30 LD D,0 ; =A
40 LD HL,TABLE ; HL=адрес таблицы
50 ADD HL,DE ; HL=HL+DE
60 LD E,(HL) ; DE=
70 INC HL ; адрес
80 LD D,(HL) ; подпрограммы
90 EX DE,HL ; обменять значениями HL и DE
100 JP (HL) ; вызов подпрограммы эффекта
110 TABLE DEFW … ; таблица адресов
Assembler Z80:
10 LD E,A ; A
20 ADD A,A ; =
30 ADD A,E ; A*3
40 LD E,A ; DE
50 LD D,0 ; =A
60 LD HL,TABLE ; HL=адрес таблицы
70 ADD HL,DE ; HL=HL+DE
80 LD C,(HL) ; C=длительность
90 INC HL ; HL=HL+1
100 LD E,(HL) ; E=частота
110 INC HL ; HL=HL+1
120 LD A,(HL) ; A=изменение частоты
130 LD (CHNG),A ; установка изменения частоты
140 DI ; запрет прерываний
150 XOR A ; A=цвет бордюра (0)
160 BEGIN XOR 16 ; инвертирование бита D4
170 OUT (254),A ; вывод A в порт 254
180 LD B,E ; B=E
190 PAUSE DJNZ PAUSE ; задержка
200 CHNG NOP ; резерв для изменения частоты
210 DEC C ; C=C-1
220 JR NZ,BEGIN ; если C<>0, то цикл
230 EI ; разрешение прерываний
240 RET ; возврат
250 TABLE DEFB 0,0,28 ; таблица
260 DEFB 0,0,29 ;
270 DEFB 0,128,28 ; эффектов
280 DEFB 0,128,29 ;
Управление громкостью
PROG1:
Assembler Z80:
10 DI ; запрет прерываний
20 LD B,255 ; B=частота
30 LD C,255 ; C=длительность
40 XOR A ; A=цвет бордюра (0)
50 LOOP1 XOR 16 ; инвертирование бита D4
60 OUT (254),A ; вывод A в порт 254
70 PUSH BC ; сохранение BC
80 LOOP2 DJNZ LOOP2 ; задержка
90 POP BC ; восстановление BC
100 DEC C ; C=C-1
110 JR NZ,LOOP1 ; если C<>0, то цикл
120 EI ; разрешение прерываний
130 RET ; возврат в бейсик
PROG2:
Assembler Z80:
10 DI ;
20 LD B,255 ;
30 LD C,255 ;
40 XOR A ;
50 LOOP1 XOR 16 ; все
60 OUT (254),A ;
70 XOR 16 ; (кроме этих двух строк,
80 OUT (254),A ; повторяющих предыдущие)
90 PUSH BC ;
100 LOOP2 DJNZ LOOP2 ; аналогично
110 POP BC ; предыдущему
120 DEC C ; примеру !
130 JR NZ,LOOP1 ;
140 EI ;
150 RET ;
Assembler Z80:
10 DI ; запрет прерываний
20 LD B,50 ; B=громкость
30 LD C,50 ; C=частота
40 LD D,15 ; D=длительность
50 LD E,50 ; E=количество нот
60 XOR A ; A=цвет бордюра (0)
70 LOOP1 PUSH DE ; сохранение DE
80 LOOP2 PUSH BC ; сохранение BC
90 XOR 16 ; инвертирование бита D4
100 OUT (254),A ; вывод A в порт 254
110 LOOP3 DJNZ LOOP3 ; задержка (громкость)
120 XOR 16 ; инвертирование бита D4
130 OUT (254),A ; вывод A в порт 254
140 LD B,C ; B=C
150 LOOP4 DJNZ LOOP4 ; задержка (частота)
160 POP BC ; восстановление BC
170 DEC D ; D=D-1
180 JR NZ,LOOP2 ; если D<>0, то цикл
190 POP DE ; восстановление DE
200 NOP ; резерв
210 NOP ; резерв
220 NOP ; резерв
230 DEC E ; E=E-1
240 JR NZ,LOOP1 ; если E<>0, то цикл
250 EI ; разрешение прерываний
260 RET ; возврат в бейсик
Управление тембром
Для управления тембром ZX‑Spectrum так же не приспособлен, как и для управления громкостью. Но, как и громкостью, им можно управлять в небольших пределах.
Assembler Z80:
10 DI ; запрет прерываний
20 LOOP1 LD B,8 ; B=темп
30 LOOP2 XOR A ; A=цвет бордюра (0)
40 RLC E ; скроллинг тембра через флаг CY
50 JR NC,NOSIGN ; если флаг CY=0, то перейти на NOSIGN
60 OR 16 ; установка бита D4 регистра A
70 NOSIGN OUT (254),A ; вывод A в порт 254
80 LD D,H ; D=задержка
90 PAUSE DEC D ; D=D-1
100 JR NZ,PAUSE ; если D<>0, то цикл
110 DJNZ LOOP2 ; цикл темпа
120 LD A,H ; A=H
130 ADD A,L ; прибавить L к A
140 LD H,A ; H=A
150 DEC C ; C=C-1
160 JR NZ,LOOP1 ; если C<>0, то цикл
170 EI ; разрешение прерываний
180 RET ; возврат
1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 37, 43, 45, 51 и 85.
Все остальные значения на слух воспринимаются аналогично этим.