Как написать игру на ассемблере для ZX Spectrum/Глава 10

Материал из Emuverse
Перейти к: навигация, поиск
Red copyright.png Данный материал защищён авторскими правами!

Использование материала заявлено как добросовестное, исключительно для образовательных некоммерческих целей.

Автор: А. Евдокимов, А. Капульцевич, И. Капульцевич, ИД «Питер»

ГЛАВА ДЕСЯТАЯ,
в которой показано, как заставить компьютер звучать по вашим нотам

Как вы понимаете, никакая игра не сможет называться полноценной, если она будет протекать при гробовой тишине. Посему получение различных акустических эффектов и написание хотя бы простейшей компьютерной музыки является достаточно важным этапом создания игровой программы. Из данной главы вы узнаете, какими средствами достигаются эти цели, получите представление о способах вывода звука как в стандартный канал ZX Spectrum, так и о работе с трехканальным музыкальным сопроцессором AY-3-8912, которым снабжен компьютер Spectrum 128.

ПОЛУЧЕНИЕ ЧИСТОГО ТОНА

Наиболее простой способ получения звука определенной длительности и высоты — обратиться к подпрограмме ПЗУ, ответственной за выполнение оператора Бейсика BEEP. Мы уже упоминали о ней в шестой главе, но тем не менее напомним, что располагается она по адресу 949 и требует определения регистровых пар HL и DE. Например:

       LD    DE,440
       LD    HL,964
       CALL  949
       RET

Таким способом можно получить звук практически любой высоты и продолжительности — ограничения Бейсика здесь отсутствуют. Однако при этом отсутствуют и удобства, предоставляемые интерпретатором. Чтобы написать даже очень коротенькую музыкальную фразу, придется немало попотеть, рассчитывая значения задаваемых параметров. А рассчитываются они так. В регистровую пару DE заносится число, определяемое как f×t, где f — частота, измеряемая в герцах, а t — время в секундах (при извлечении звука ЛЯ первой октавы, который имеет частоту 440 Гц, длительностью в 1 секунду получится 440×1=440). Пара HL на входе должна содержать число, равное 437500/f-30.125 (если выполнить указанные вычисления, то получим величину 964). Таким образом, приведенная выше программа делает то же самое, что и оператор Бейсика

BEEP 1,9

Чтобы упростить задачу, можно предложить небольшую программку на Бейсике, которая, конечно, не может претендовать на роль музыкального редактора, но, по крайней мере, автоматизирует расчеты требуемых значений. После ее запуска на экране появится некоторое подобие меню. Нажав цифровую клавишу 1, вы сможете прослушать свое произведение. При нажатии клавиши 2 на экран выводится два столбика чисел: значения из левого столбика предназначены для загрузки пары DE, а для HL числа берутся из правого столбика. Клавиша 0 позволяет выйти в редактор Бейсика. После нажатия клавиш 1 или 2 нужно ввести желаемый темп исполнения по метроному (количество четвертных нот, исполняемых в минуту).

Ноты записываются в операторе DATA, начиная со строки 1000, парами чисел, первое из которых определяет высоту и задается так же, как и в операторе BEEP (0 — нота ДО первой октавы), а второе представляет собой относительную длительность звуков, то есть четверти, например, записываются дробью 1/4, восьмушки — 1/8 и так далее. Для обозначения конца блока данных в самом его конце нужно написать два нуля (строка 8990).

Ниже приводится текст программы, в котором для примера уже включены несколько строк данных короткой музыкальной фразы (строки 1010…1030). Наберите и сохраните программу без этих строк, а затем, дописав их, можете проверить ее работу.

  10 DIM f(12): RESTORE 9000
  20 FOR n=1 TO 12: READ f(n): NEXT n
  50 CLS : PRINT "1. Listen"'"2. Code"'"0. Stop"
  60 PAUSE 0: IF INKEY$="1" THEN CLS : GO TO 100
  70 IF INKEY$="2" THEN CLS : GO TO 200
  80 IF INKEY$="0" THEN STOP
  90 GO TO 60
 100 REM Прослушивание
 110 RESTORE 1000: INPUT "TEMPO: (M.M.) = ";temp
 120 READ n,d: IF d THEN BEEP d/temp*240,n: GO TO 120
 130 GO TO 50
 200 REM Расчет значений для DE и HL.
 210 RESTORE 1000: INPUT "TEMPO: (M.M.) = ";temp
 220 READ n,d: IF NOT d THEN GO TO 300
 230 LET d=d/temp*240
 240 LET f1=INT (n/12): LET f2=n-f1*12
 250 LET f=f(f2+1)/2<font face="symbol">­</font>(4-f1)
 260 PRINT "LD DE,";INT (f*d+.5),"LD HL,";INT ((437500/f-30.125)+.5)
 270 GO TO 220
 300 PRINT #0;"Press any key": PAUSE 0: GO TO 50
1000 REM Данные мелодии.
1010 DATA 7,1/16,5,1/16,4,1/16,2,1/16
1020 DATA 4,1/8,7,1/8
1030 DATA -5,1/8,-1,1/8,0,1/4
8990 DATA 0,0
9000 REM Частота в герцах для звуков одной октавы
9010 DATA 4186.01,4434.92,4698.64,4978.03
9020 DATA 5274.04,5587.65,5919.91,6271.93
9030 DATA 6644.87,7040,7458.62,7902.13

Получив тем или иным способом ряд чисел для загрузки регистров, нужно каким-то образом обратить их в мелодию. Для этих целей напишем процедуру, последовательно считывающую из блока данных, адресованного парой HL, двухбайтовые значения регистровых пар и извлекающую соответствующие звуки. Подпрограмма завершит свою работу либо при достижении конца блока данных (два байта 0) либо при нажатии на любую клавишу:

MELODY XOR   A           ;опрос клавиатуры
       IN    A,(254)
       CPL
       AND   #1F
       JR    NZ,MELODY   ;пока все клавиши не отпущены
MELOD1 XOR   A           ;опрос клавиатуры
       IN    A,(254)
       CPL
       AND   #1F
       RET   NZ          ;выход, если нажата любая клавиша
       LD    E,(HL)      ;считывание в пару DE
       INC   HL
       LD    D,(HL)
       LD    A,D
       OR    E
       RET   Z           ;выход, если конец блока данных (DE=0)
       INC   HL
       LD    A,(HL)      ;считывание данных для пары HL
       INC   HL
       PUSH  HL
       LD    H,(HL)      ;загрузка пары HL
       LD    L,A
       CALL  949         ;вывод звука
       POP   HL
       INC   HL
       JR    MELODY      ;следующая нота

Прежде чем обратиться к данной процедуре выпишем числа, полученные с нашим импровизированным «редактором» при заданном темпе, равном 150, в виде блока двухбайтовых данных, который завершим числом 0 (тоже двухбайтовым):

DATMEL DEFW  39,1086,35,1223,33,1297
       DEFW  29,1460,66,1297,78,1086
       DEFW  39,2202,49,1742,105,1642
       DEFW  0                         ;маркер конца блока данных

Теперь можно вызвать процедуру MELODY, например, таким образом:

       ORG   60000
       ENT   $
       LD    HL,DATMEL
       CALL  MELODY
       RET
DATMEL .........
MELODY .........

Существует и другой способ извлечения звуков, при котором все необходимые расчеты выполняются в самой программе. Хотя он и несколько сложнее только что описанного, но может показаться вам более привлекательным. Во всяком случае он более привычен, так как исходными данными при этом являются те же числа, что и параметры оператора BEEP. Подпрограмма, расположенная по адресу 1016, сама выполняет приведенные выше вычисления и обращается к процедуре вывода звука 949, а значения длительности и высоты звука передаются ей через стек калькулятора:

       LD    A,1         ;загружаем в аккумулятор значение
                         ; длительности звучания (1 секунда).
       CALL  11560       ;содержимое аккумулятора заносим
                         ; в стек калькулятора.
       LD    A,12        ;нота ДО второй октавы
       CALL  11560       ;посылаем в стек калькулятора
       CALL  1016        ;вызываем процедуру извлечения звука
       RET

Этот метод хорош всем, за исключением одной «мелочи» — числа, записываемые в аккумулятор могут быть только целыми и не отрицательными. Чтобы исправить этот недостаток, можно условиться, что длительности будут задаваться не в секундах, а в сотых долях секунды, а значение высоты звука будем интерпретировать как число со знаком. Такой подход позволит получать продолжительность звучания нот примерно до двух с половиной секунд, что в большинстве случаев вполне достаточно; диапазон звуков останется таким же, как и в Бейсике: от −60 до +68. Параметры для этой подпрограммы будем задавать в регистрах B (длительность в сотых долях секунды) и C (высота в полутонах):

BEPER  PUSH  BC
       LD    A,B         ;берем в аккумулятор первый параметр
       CALL  11560       ;заносим его в стек калькулятора
       LD    A,100
       CALL  11560       ;помещаем в стек число 100
       RST   40
       DEFB  5,56        ;выполняем деление
       POP   BC
       LD    A,C         ;берем второй параметр
       AND   A
       JP    M,BEPER1    ;если отрицательный, переходим на BEPER1
       CALL  11560       ; иначе помещаем в стек без изменений
       JP    1016        ; и извлекаем звук
BEPER1 NEG               ;получаем абсолютное значение
       CALL  11560       ;отправляем в стек
       RST   40
       DEFB  27,56       ;меняем знак
       JP    1016        ; и извлекаем звук

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

       ORG   60000
       ENT   $
       LD    HL,D_MEL1
       CALL  MELBEP
       RET
MELBEP LD    B,(HL)      ;в B - длительность
       INC   B           ;один из способов проверки
       DEC   B           ; содержимого регистра на 0
       RET   Z
       INC   HL
       LD    C,(HL)      ;в C - высота звука
       INC   HL
       PUSH  HL
       CALL  BEPER       ;извлечение звука
       POP   HL
       JR    MELBEP
BEPER  .........
D_MEL1 DEFB  10,-5,10,0,10,1,10,2
       DEFB  20,5,10,2,20,5,10,7
       DEFB  0

Во всех приведенных выше фрагментах в конечном итоге использовалась подпрограмма ПЗУ 949. В этом случае, пока звучит очередная нота, компьютер оказывается полностью выключен из работы. Поэтому иногда бывает очень важно уметь получать чистый тон на низком уровне, непосредственно программируя порт динамика. Принцип получения звука таким способом предельно прост: достаточно с определенной частотой попеременно то включать, то выключать динамик. С таким способом в некоторой степени вы также уже знакомы, поэтому отметим лишь некоторые важные моменты, без знания которых невозможно получить качественный звук. Во-первых, напомним, что динамик управляется четвертым битом 254-го порта. При установке или сбросе этого бита слышны короткие щелчки, которые при быстром чередовании сливаются в сплошной звук. Но кроме динамика этот же порт отвечает и за цвет бордюра, поэтому кроме четвертого бита нужно правильно устанавливать и три младших разряда выводимого байта. Не менее важно при извлечении звука помнить о необходимости запрета прерываний. Если этого не сделать, то невозможно будет получить чистый тон. Это связано с тем, что 50 раз в секунду микропроцессор будет отвлекаться на выполнение подпрограммы обработки прерываний, что неминуемо скажется на частоте создаваемого звука.

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

       ORG   60000
       ENT   $
       DI
       LD    A,(23624)   ;получаем в аккумуляторе цвет бордюра
       AND   #38         ;выделяем биты 3, 4 и 5
       RRA               ;сдвигаем на место битов 0, 1 и 2
       RRA
       RRA
       LD    E,A         ;запоминаем в регистре E
ORGAN1 CALL  8020        ;проверка нажатия клавиши BREAK
       JR    NC,EXIT     ;если нажата, выход из программы
       PUSH  DE
       LD    DE,#100     ;счетчики для определения
                         ; нажатых клавиш
       LD    A,#F7       ;опрос полуряда 1...5
       CALL  KEYS
       LD    A,#EF       ;опрос полуряда 6...0
       CALL  KEYS
       LD    D,0         ;выбор высоты звука из таблицы
       LD    HL,DATNOT
       ADD   HL,DE
       POP   DE
       LD    B,(HL)
       INC   B
       DEC   B
       JR    Z,ORGAN1    ;если в B ноль, клавиши не нажаты, звука нет
       LD    A,E
       OUT   (254),A     ;извлечение звука
       XOR   16
       LD    E,A
ORGAN2 LD    C,20        ;цикл задержки для получения звука
                         ; определенной высоты
ORGAN3 DEC   C
       JR    NZ,ORGAN3
       DJNZ  ORGAN2
       JR    ORGAN1
EXIT   EI                ;выход из программы
       RET
KEYS   IN    A,(254)     ;опрос выбранного полуряда
       LD    B,5         ;5 клавиш в полуряду
KEYS1  RRCA              ;сдвигаем биты вправо
       JR    C,KEYS2     ;если младший бит установлен,
                         ; клавиша отпущена
       LD    E,D         ;иначе запоминаем номер нажатой клавиши
KEYS2  INC   D           ;увеличиваем номер определяемой клавиши
       DJNZ  KEYS1       ;переходим к следующей
       RET
; Данные для получения необходимой задержки для каждого звука
DATNOT DEFB  0,55,49,44,41,36
       DEFB  21,24,27,29,32

СОЗДАНИЕ ЗВУКОВЫХ ЭФФЕКТОВ

Различные звуковые и шумовые эффекты, которыми изобилуют компьютерные игры, достигаются через изменение по тому или иному закону частоты выводимого звука. Нетрудно догадаться, что высота звука напрямую зависит от продолжительности цикла задержки между командами вывода в порт динамика: чем больше задержка, тем более низким получится звук.

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

TWEET  LD    A,(23624)   ;определение цвета бордюра
       AND   #38
       RRA
       RRA
       RRA
       DI
TWEET1 XOR   16          ;переключение 4-го бита
       OUT   (254),A
       PUSH  BC
       DJNZ  $           ;цикл задержки
       POP   BC
       DJNZ  TWEET1
       EI
       RET

Длительность эффекта перед обращением к процедуре TWEET задается в регистре B, например:

       LD    B,200
       CALL  TWEET
       RET

Прежде чем привести следующий пример, скажем несколько слов, относящихся не только к этой подпрограмме, но и ко всем остальным. Поскольку в реальных программах цвет бордюра обычно не изменяется и определен заранее, то он, как правило, не вычисляется в программе, а задается в явном виде загрузкой в аккумулятор кода нужного цвета. Вы также можете вместо первых строк от метки TWEET до команды DI просто написать XOR A для получения черного бордюра или, например, LD A,4 — для зеленого.

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

       DJNZ  $

Как известно, символ доллара при трансляции принимает значение текущего адреса размещения машинного кода, а точнее, адрес начала строки ассемблерного текста. Поэтому такая запись полностью равноценна записи

LOOP   DJNZ  LOOP

но позволяет обойтись без дополнительных меток.

После такого небольшого лирического отступления давайте продолжим «изобретение» звуковых эффектов.

Особо часто в игровых программах можно услышать множество разновидностей вибрирующих звуков. Получить такой эффект можно, периодически увеличивая и уменьшая частоту (то есть количество циклов задержки). Вибрация характеризуется двумя параметрами: собственной частотой и глубиной (амплитудой), поэтому для такой процедуры потребуется, кроме длительности звучания, задавать и некоторые другие входные данные. Сначала приведем текст подпрограммы для получения вибрирующего звука, а затем объясним, какие значения в каких регистрах следует разместить перед обращением к ней.

VIBR   LD    A,(23624)
       AND   #38
       RRA
       RRA
       RRA
       LD    C,A
       DI
VIBR1  LD    D,E         ;продолжительность цикла спада (подъема)
VIBR2  LD    A,C
       XOR   16
       LD    C,A
       OUT   (254),A
       LD    A,H         ;изменение частоты звука
       ADD   A,L
       LD    H,A
VIBR3  DEC   A           ;цикл задержки
       JR    NZ,VIBR3
       DEC   D
       JR    NZ,VIBR2
       LD    A,L         ;смена направления изменения частоты
       NEG
       LD    L,A
       DJNZ  VIBR1
       EI
       RET

В регистр H нужно занести начальную частоту звука (имеется в виду, конечно, частота не в герцах, а в относительных единицах). Содержимое регистра E влияет на частоту вибрации: чем меньше его значение, тем быстрее спад будет сменяться подъемом и наоборот. В регистре B задается количество циклов вибрации, то есть в конечном счете — длительность звука, а в L заносится величина, определяющая глубину вибрации, или иначе, скорость изменения высоты звука. Мы предлагаем такие значения регистров:

       LD    H,100
       LD    E,120
       LD    B,4
       LD    L,1
       CALL  VIBR
       RET

однако это только один из многих возможных вариантов. Попробуйте поэкспериментировать и подобрать наиболее интересные варианты звучания, которые впоследствии сможете использовать в своих собственных разработках.

Приведем еще одну подпрограмму, создающую другой тип вибрации, при котором частота звука, достигнув наивысшей (или же низшей) точки, возвращается к начальной своей величине. Тем самым частотная характеристика имеет внешний вид, схожий с зубьями пилы. Подпрограмма, создающая похожий звук, имеется в известном пакете Suprcode и значится там под именем «Laser». Вот как примерно она может выглядеть:

LASER  LD    A,(23624)
       AND   #38
       RRA
       RRA
       RRA
       DI
LASER1 PUSH  BC
       LD    L,H
LASER2 XOR   16
       OUT   (254),A
       LD    B,H
       DJNZ  $
       INC   H           ;другой вариант - DEC H
       DEC   C
       JR    NZ,LASER2
       LD    H,L
       POP   BC
       DJNZ  LASER1
       EI
       RET

Прежде чем обратиться к данной процедуре, необходимо в регистр B загрузить количество «зубчиков пилы», в C — продолжительность каждого «зубца», а в регистре H задать исходную высоту звука. Например:

       LD    B,5
       LD    C,200
       LD    H,50
       CALL  LASER
       RET

Если вы работали с музыкальным редактором Wham, то, вероятно, задавались вопросом, как в одном звуковом канале удается получить сразу два тона различной высоты. Во многих играх, особенно последних лет, музыкальное сопровождение выполнено в аранжировке той или иной степени сложности. Иногда можно слышать не два, а три и более голосов (например, в DEFLEKTOR или MIG-29). Конечно, написание музыки на два голоса — дело вовсе непростое, но принцип получения подобных звуков знать все же стоит. Тем более, что таким способом можно создать ряд весьма недурных эффектов. Двуголосие достигается наложением двух различных частот, поэтому программа, генерирующая одновременно два тона, может выглядеть примерно так:

TWOTON LD    A,(23624)
       AND   #38
       RRA
       RRA
       RRA
       LD    H,D
       LD    L,E
       DI
TWOTN1 DEC   H           ;задержка для получения первого тона
       JR    NZ,TWOTN2
       XOR   16
       OUT   (254),A     ;извлечение первого звука
       LD    H,D         ;восстановление значения задержки
                         ; для первого тона
TWOTN2 DEC   L           ;задержка для получения второго тона
       JR    NZ,TWOTN1
       XOR   16
       OUT   (254),A     ;извлечение второго звука
       LD    L,E         ;восстановление значения задержки
                         ; для второго голоса
       PUSH  AF
       LD    A,B         ;проверка окончания звучания
       OR    C
       JR    Z,TWOTN3
       POP   AF
       DEC   BC
       JR    TWOTN1
TWOTN3 POP   AF
       EI
       RET

Перед обращением к процедуре в регистровой паре BC нужно указать длительность звучания, а в регистрах D и E — высоту звука соответственно в первом и втором голосах. Если в регистрах D и E задать близкие значения, то вместо двух различных тонов получится звук приятного тембра, слегка вибрирующий и как бы объемный. Послушайте, например, такое звучание:

       LD    BC,500      ;длительность звучания
       LD    D,251       ;высота первого тона
       LD    E,250       ;высота второго тона
       CALL  TWOTON
       RET

Не меньшим спросом в игровых программах пользуются и различные шумовые эффекты, имитирующие выстрелы, разрывы снарядов, стук копыт и т. п. Такие звуки характеризуются отсутствием какой-то определенной частоты — в них присутствуют частоты всего спектра. Это так называемый «белый» шум. Но обычно в шуме все же преобладают тона определенной высоты, что позволяет отличить шипение змеи от грохота обвала. И это необходимо учитывать при создании нужного эффекта.

Первый звук этого типа, который мы хотим предложить, при подборе соответствующей длительности позволяет имитировать звуки от хлопков в ладоши до шипения паровоза, выпускающего пар. Продолжительность звучания задается в регистровой паре DE, однако ее значение не должно превышать 16384, так как в качестве генератора «случайных» чисел, определяющих частоту тона используются коды ПЗУ:

HISS   LD    A,(23624)
       AND   #38
       RRA
       RRA
       RRA
       LD    B,A
       LD    HL,0        ;начальный адрес ПЗУ
       DI
HISS1  LD    A,(HL)      ;берем байт в аккумулятор
       AND   16          ;выделяем 4-й бит
       OR    B           ;объединяем с цветом бордюра
       OUT   (254),A     ;получаем звук
       INC   HL          ;переходим к следующему байту
       DEC   DE          ;уменьшаем значение длительности
       LD    A,D
       OR    E
       JR    NZ,HISS1    ;переходим на начало,
                         ; если звук не закончился
       EI
       RET

Следующий пример немного похож на предыдущий, но позволяет выделять более низкие частоты. При наличии воображения его вполне можно сравнить с отдаленными раскатами грома. Продолжительность звучания здесь определяется в регистре B:

CRASH  LD    A,(23624)
       AND   #38
       RRA
       RRA
       RRA
       DI
       LD    HL,100      ;начальный адрес в ПЗУ
CRASH1 XOR   16
       OUT   (254),A     ;извлекаем звук
       LD    C,A         ;сохраняем значение аккумулятора
       LD    E,(HL)      ;получаем в паре DE
       INC   HL          ; продолжительность цикла задержки
       LD    A,(HL)
       AND   3           ;ограничиваем величину старшего байта
       LD    D,A
CRASH2 LD    A,D         ;цикл задержки
       OR    E
       JR    Z,CRASH3
       DEC   DE
       JR    CRASH2
CRASH3 LD    A,C         ;восстанавливаем значение аккумулятора
       DJNZ  CRASH1
       EI
       RET

Неплохие результаты можно получить, если изменять во времени среднюю частоту выводимого шума. Ниже приведен текст подпрограммы, построенной именно по такому принципу:

EXPLOS LD    A,(23624)
       AND   #38
       RRA
       RRA
       RRA
       LD    L,A
       DI
EXPL1  PUSH  BC
       PUSH  DE
EXPL2  PUSH  DE
EXPL3  LD    B,E
       DJNZ  $           ;задержка
       LD    A,(BC)      ;в паре BC один из первых 256 адресов ПЗУ
       AND   16
       OR    L
       OUT   (254),A
       INC   C
       DEC   D
       JR    NZ,EXPL3
       POP   DE
; Изменение высоты шума (понижение среднего тона;
;  если заменить на DEC E, тон будет наоборот повышаться)
       INC   E
       DEC   D
       JR    NZ,EXPL2
       POP   DE
       POP   BC
       DJNZ  EXPL1       ;повторение всего эффекта
       EI
       RET

Перед обращением к ней в регистр B заносится количество повторений эффекта (что позволяет получить звук, напоминающий описанный выше эффект «Laser»), в D задается длительность звучания и в E — величина, определяющая начальную среднюю частоту. Например, для создания звука, напоминающего взрыв бомбы, можно предложить такие значения регистров:

       LD    B,1
       LD    D,100
       LD    E,-1
       CALL  EXPLOS
       RET

а пулеметную очередь можно получить с другими исходными данными:

       LD    B,5
       LD    D,35
       LD    E,0
       CALL  EXPLOS
       RET

У всех перечисленных процедур есть один общий недостаток: на время звучания выполнение основной программы затормаживается. Существует несколько способов обхода этой неприятности. Наиболее удачным из них представляется использование прерываний. Конечно, в этом случае получить чистый тон окажется совершенно невозможно, но для создания звуковых имитаций это и не особенно важно.

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

Основная сложность в написании такой процедуры состоит, пожалуй, только в способе передачи параметров подпрограмме, генерирующей тон. Поскольку прерывания выполняются автономно и не зависят от работы основной программы, входные значения не могут передаваться через регистры, а только через память. То есть необходимо составить блок данных, описывающих высоту и длительность каждого отдельного звука — по два байта на каждый. Адреса начальной и текущей пары значений в блоке данных также должны передаваться через переменные, чтобы каждое очередное прерывание «знало», какие параметры требуется считывать. Кроме этого нужна еще одна переменная, которая будет выполнять роль флага разрешения извлечения звука. Эта же переменная может служить счетчиком циклов, если вы хотите иметь возможность многократно исполнять запрограммированный в блоке данных фрагмент.

Учитывая все сказанное, можно написать такую процедуру обработки прерываний:

INTERR PUSH  AF          ;сохраняем используемые
       PUSH  BC          ; в прерывании регистры
       PUSH  HL
INTER1 LD    A,(REPEAT)
       AND   A
; Если эффект прозвучал нужное количество раз,
;  завершаем обработку прерывания
       JR    Z,EXITI
       LD    HL,(CURADR) ;определяем текущий адрес
                         ; в блоке данных
       LD    B,(HL)      ;высота звука
       INC   B
       DEC   B
; Если встретился маркер конца блока данных,
;  переходим к следующему повторению
       JR    Z,EXITI0
       INC   HL
       LD    C,(HL)      ;длительность звука
       INC   HL
       LD    (CURADR),HL ;запоминаем текущий адрес
       CALL  BEEP        ;извлекаем звук
EXITI  POP   HL          ;восстанавливаем регистры
       POP   BC
       POP   AF
       JP    56          ;переходим к стандартному
                         ; обработчику прерываний
; Переход к началу эффекта - повторение
EXITI0 LD    HL,(ADREFF) ;восстанавливаем начальный
       LD    (CURADR),HL ; адрес блока данных
       LD    HL,REPEAT
       DEC   (HL)        ;уменьшаем счетчик повторений
       JR    INTER1
REPEAT DEFB  0           ;количество повторений эффекта
ADREFF DEFW  0           ;начальный адрес блока данных эффекта
CURADR DEFW  0           ;текущий адрес в блоке данных
; Извлечение звука
BEEP   XOR   A
BEEP1  XOR   16
       OUT   (254),A
       PUSH  BC
       DJNZ  $
       POP   BC
       DEC   C
       JR    NZ,BEEP1
       RET

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

IMON   XOR   A           ;в начале на всякий случай
       LD    (REPEAT),A  ; запрещаем вывод звука
       LD    A,24        ;код команды JR
       LD    (65535),A
       LD    A,195       ;код команды JP
       LD    (65524),A
       LD    HL,INTERR   ;переход на процедуру
       LD    (65525),HL  ; обработки прерываний
       LD    HL,#FE00    ;формируем таблицу векторов прерываний
       LD    DE,#FE01
       LD    BC,256
       LD    (HL),#FF    ;на адрес 65535 (#FFFF)
       LD    A,H         ;запоминаем старший байт адреса таблицы
       LDIR
       DI
       LD    I,A         ;загружаем регистр вектора прерываний
       IM    2           ;включаем 2-й режим
       EI
       RET

Сразу же напишем и процедуру восстановления первого режима прерываний, которая будет вызываться при окончании работы программы. Она вам уже известна, но тем не менее повторим:

IMOFF  DI
       LD    A,63
       LD    I,A
       IM    1
       EI
       RET

Теперь можно написать блоки данных, характеризующие различные эффекты. При их составлении нужно учитывать две вещи: во-первых, как мы уже говорили, каждый звук должен быть достаточно коротким, чтобы он не задерживал выполнение основной программы (не более нескольких сотых долей секунды), а во-вторых, при увеличении первого параметра (высота тона) второй (длительность звучания) нужно уменьшать, иначе более низкие звуки окажутся и более продолжительными. Завершаться каждый блок данных обязан нулевым байтом, обозначающим конец звучания эффекта и переход на его начало. Приведем два приблизительных варианта:

EFF1   DEFB  200,5,220,4,200,5
       DEFB  100,8,80,9,50,20
       DEFB  0
EFF2   DEFB  50,20,100,6,200,3,100,6
       DEFB  0

Наконец, напишем управляющую часть, которая позволит легко обратиться к любой подпрограмме: включения или выключения второго режима прерываний, а также активизации того или иного эффекта. Напомним, что для «запуска» любого из заданных в блоках данных эффектов необходимо сначала занести в переменные ADREFF и CURADR начальный адрес соответствующего блока и в переменной REPEAT указать количество повторений звука.

Чтобы любую процедуру было удобно вызывать даже из Бейсика, не имеющего ни малейшего представления о метках ассемблерного текста, применим распространенный прием, часто используемый в таких случаях и которым мы однажды уже воспользовались (см. программу ГЕНЕРАТОР СПРАЙТОВ) — вставим в самом начале программы в машинных кодах ряд инструкций «длинного» перехода (JP) с указанием адресов каждой из «внешних», то есть вызываемых из другой программы, процедур. Тогда адреса обращения к любой из них будут увеличиваться с шагов в 3 байта (размер команды JP). Таким образом, наш пакет процедур будет выглядеть так:

       ORG   60000
; 60000 - включение второго режима прерываний
       JP    IMON
; 60003 - переход к подпрограмме выключения 2-го режима прерываний
       JP    IMOFF
; 60006 - включение эффекта1
       JP    ONEFF1
; 60009 - включение эффекта2
ONEFF2 LD    HL,EFF2
       LD    A,5
       JR    ONEFF
ONEFF1 LD    HL,EFF1
       LD    A,3
ONEFF  LD    (ADREFF),HL
       LD    (CURADR),HL
       LD    (REPEAT),A
       RET
; Блоки данных эффектов
EFF1   .........
EFF2   .........
; Инициализация второго режима прерываний
IMON   .........
; Выключение второго режима прерываний
IMOFF  .........
; Процедура обработки прерываний
INTERR .........

А вот фрагмент программы на Бейсике, демонстрирующий использование приведенных звуковых эффектов:

 10 INK 5: PAPER 0: BORDER 0: CLEAR 59999
 20 RANDOMIZE : LET x=INT (RND*30)+1: LET y=INT (RND*20)+1
 30 LET dx=1: IF RND>=.5 THEN LET dx=-1
 40 LET dy=1: IF RND>=.5 THEN LET dy=-1
 50 INK 2: PLOT 0,0: DRAW 255,0: DRAW 0,175: 
    DRAW -255,0: DRAW 0,-175: INK 5
 60 RANDOMIZE USR 60000
100 PRINT AT y,x; INK 8; OVER 1;"O": LET x1=x: LET y1=y: PAUSE 3
110 IF x=0 OR x=31 THEN RANDOMIZE USR 60006: LET dx=-dx
120 IF y=0 OR y=21 THEN RANDOMIZE USR 60009: LET dy=-dy
130 LET x=x+dx: LET y=y+dy
140 IF INKEY<pre>
       LD    BC,65533    ;в паре BC - адрес порта
                         ; для выбора регистра
       LD    A,8         ;в аккумуляторе - номер регистра
       OUT   (C),A       ;выбор
lt;>"" THEN GO TO 200

150 PRINT AT y1,x1; INK 8; OVER 1;"O": GO TO 100 200 RANDOMIZE USR 60003 </pre> После запуска этой программки экран окрасится в черный цвет, по краю его будет нарисована рамка и в случайном месте возникнет шарик, который начнет метаться из стороны в сторону, отскакивая от «стенок». При соприкосновении с преградами будет раздаваться протяжный вибрирующий звук, причем разный в зависимости от того, в горизонтальную или в вертикальную «стенку» ударился мячик. Для остановки программы достаточно нажать любую клавишу.

МУЗЫКАЛЬНЫЙ СОПРОЦЕССОР

Несоизмеримо большими возможностями для создания звукового оформления игровых программ обладают компьютеры ZX Spectrum 128 и Scorpion ZS 256, благодаря встроенному в них трехканальному музыкальному сопроцессору.

Мы уже показали, как можно в одном стандартном звуковом канале совместить два голоса, однако из-за наложения частот звук при этом получается «грязным», чересчур насыщенным гармониками, напоминая по тембру широкораспространенный в свое время гитарный эффект FUZZ. Музыкальный же сопроцессор позволяет получить одновременно до трех чистых тонов. Но его достоинства не ограничиваются только этим.

Как известно, музыкальный звук характеризуется тремя основными параметрами: частотой, которая определяет высоту тона, тембром (окраской), зависящим от количества и состава гармоник и амплитудой, определяющей громкость звучания. Используя только стандартный звуковой канал, легко можно управлять высотой звука, несколько сложнее получить различную окраску тона и практически невозможно влиять на громкость, а вот музыкальный сопроцессор позволяет манипулировать и этим третьим параметром, варьируя громкость звука от Forte до полного его исчезновения. Правда, что касается тембра, то и здесь, к сожалению, нет простых решений и приходится прибегать примерно к тем же методам, что и при управлении стандартным выводом. Зато вы можете без труда получить эффект «белого» шума, причем его среднюю высоту также можно регулировать.

Нельзя не упомянуть и еще об одной особенности музыкального сопроцессора. Он работает совершенно независимо, без опеки центрального процессора, поэтому последний может быть занят каким-нибудь полезным делом (например, опрашивать клавиатуру либо выводить на экран текст или графику), в то время как музыкальный сопроцессор самостоятельно извлекает звук. CPU лишь изредка нужно отвлекаться от своей работы, чтобы дать своему «коллеге» указание перейти к следующей ноте, а затем он вновь может вернуться к решению более насущных проблем.

Вы знаете, что для управления работой музыкального сопроцессора из Бейсика-128 имеется дополнительный (по отношению к стандартному Spectrum-Бейсику) оператор PLAY, но он на самом деле не реализует и десятой доли всех немыслимых возможностей, которые могут быть осуществлены только из ассемблера. Об этом можно судить хотя бы по тем играм, которые написаны специально для Spectrum 128.

Звук извлекается программированием собственных регистров сопроцессора, которые так же, как и регистры CPU имеют по 8 разрядов. Всего их насчитывается 16 (обозначаются от R0 до R15), но нас будут интересовать только 14 из них, так как остальные два служат для несколько иных целей, о чем сказано, например, в [2]. Сначала мы рассмотрим функции этих регистров, а затем расскажем о том, как с ними обращаться.

Первые шесть регистров (R0…R5) образуют три пары и задают высоту звука для каждого из трех каналов в отдельности (сами каналы обозначаются буквами A, B и C). То есть регистровая пара R0/R1 определяет частоту тона в канале A, пара R2/R3 делает то же самое для канала B и R4/R5 — для C. Хотя каждая пара состоит из 16 бит, используются только 12 младших разрядов: все 8 бит младшего регистра (R0, R2 и R4) и 4 младших бита старшего регистра (R1, R3 и R5). Таким образом, числа, определяющие высоту звука, находятся в пределах от 0 до 4095 включительно. В табл. 10.1 приводится соответствие звуков из диапазона неполных девяти октав и чисел, определяющих эти ноты.

Таблица 10.1. Значения для регистров R0…R5
 СККБМ12345
До 338916958474242121065326
До диез 319916008004002001005025
Ре 30201510755377189944724
Ре диез 28501425712356178894522
Ми 26901345673336168844221
Фа 25391270635317159794020
Фа диез 23971198599300150753719
Соль 22621131566283141713518
Соль диез 21351068534267133673317
Ля403120151008504252126633116
Си бемоль38041902951476238119593015
Си35911795898449224112562814

Следующий регистр — R6 — определяет среднюю частоту выводимого шума. Поскольку получение шумовых эффектов одновременно в разных голосах не имеет практического применения (их все равно невозможно будет различить на слух), то этот регистр является общим для всех трех каналов. Для него можно задавать значения от 0 до 31, то есть значащими являются только пять младших битов.

Регистр R7 служит для управления звуковыми каналами. Он подобен флаговому регистру центрального процессора и значение имеет каждый отдельный бит. Младшие три бита используются для управления выводом чистого тона в каждый из трех каналов. Если бит установлен, вывод запрещен, а при сброшенном бите вывод звука разрешается. Бит 0 связан с каналом A, 1-й бит относится к каналу B и 2-й — к C. Биты 3, 4 и 5 заведуют выводом в каналы A, B и C соответственно частоты «белого» шума. При установке бита вывод также запрещается, а при сбросе его в 0 — разрешается. 6-й и 7-й биты для извлечения звука значения не имеют.

Регистры R8, R9 и R10 определяют громкость звука, выводимого соответственно в каналы A, B и C. С их помощью можно получить 16 уровней громкости, посылая в них значения от 0 до 15. То есть значение для получаемой амплитуды в этих регистрах имеют 4 младших бита. Однако следует знать еще об одной интересной особенности этих трех регистров. Если в каком-нибудь из них установить 4-й бит (например, послав в него число 16), то получится звук не с постоянной, а с изменяющейся во времени громкостью. В этом случае необходимо указать дополнительную информацию в регистрах R11, R12 и R13.

Спаренные регистры R11 и R12 задают скорость изменения громкости звука: чем больше число, тем более плавной будет огибающая. В них можно записывать значения от 0 до 65535. Надо сказать, что изменение младшего регистра почти не ощущается, поэтому чаще достаточно определять лишь регистр R12.

Регистр R13 формирует огибающую выходного сигнала. Установкой одного или нескольких битов из младшей четверки можно получить несколько разнообразных эффектов. При установке нулевого бита звук получается затухающим, если установить 2-й бит, громкость будет наоборот увеличиваться, а установив одновременно 1-й и 3-й биты, вы получите звук, постоянно изменяющийся по громкости.

Последними тремя регистрами действительно иногда бывает удобно пользоваться, хотя гораздо чаще огибающая формируется программным путем, что позволяет получить значительно большее разнообразие оттенков звучания.

Перейдем теперь к вопросу, как программируются регистры музыкального сопроцессора. Связь с ними осуществляется через порты с адресами 49149 (#BFFD) и 65533 (#FFFD). Чтобы записать какое-либо значение в любой из регистров, его необходимо прежде всего выбрать (или назначить), выполнив команду OUT в порт 65533. Например, регистр R8 выбирается следующими командами:

       LD    BC,65533    ;адрес порта для чтения
       IN    A,(C)       ;читаем значение текущего регистра
       JR    Z,ZERO      ;если 0, обходим
       DEC   A           ;уменьшаем на 1
; Выбираем порт 49149 для записи
;  (значение регистра C остается прежним - #FD)
       LD    B,#BF
       OUT   (C),A       ;записываем значение в выбранный регистр
ZERO   .........         ;продолжение программы

После этого в выбранный регистр можно записывать данные либо читать его содержимое. Для записи используется порт 49149, а для чтения — опять же 65533. Приведем фрагмент программы, в котором читается значение установленного ранее регистра и если оно не равно 0, содержимое регистра уменьшается на единицу:

OUTREG DI                ;запрещаем прерывания
; Данные будем считывать из массива DATREG
;  в обратном порядке, начиная с последнего элемента
       LD    HL,DATREG+13
       LD    D,13        ;начальный номер загружаемого регистра
       LD    C,#FD       ;младший байт адреса порта сопроцессора
OUTR1  LD    B,#FF       ;адрес для выбора регистра
       OUT   (C),D       ;выбираем регистр
       LD    B,#BF       ;адрес для записи в регистр
; Записываем в порт байт из ячейки, адресуемой парой HL,
;  и уменьшаем HL на 1
       OUTD
       DEC   D           ;переходим к следующему регистру
                         ; (с меньшим номером)
       JP    P,OUTR1     ;повторяем, если записаны еще не все
                         ; регистры (D >= 0)
       EI                ;разрешаем прерывания
       RET
DATREG DEFS  14          ;массив данных для регистров сопроцессора

Добавим к сказанному, что выбор регистров музыкального сопроцессора и все манипуляции с ними лучше производить при запрещенных прерываниях, хотя в приведенных примерах это и не отражено.

Можно написать универсальную подпрограмму, которая считывает из блока данных значения всех регистров и тем самым задает параметры звуков одновременно для всех трех каналов:

0/1   - базовый адрес блока данных эффекта
2/3   - текущий адрес в блоке данных
4/5   - частота тона
6     - частота шума
7     - флаг разрешения вывода в канал
8     - длительность звучания эффекта
9     - количество повторений эффекта
10    - вывод тона (1), шума (8) или их комбинации (9)
11/12 - базовый адрес данных для формирования частоты
13/14 - текущий адрес данных для формирования частоты
15/16 - базовый адрес данных для формирования огибающей
17/18 - текущий адрес данных для формирования огибающей

Эта процедура вполне может быть использована как для получения отдельных звуковых эффектов, так и для создания музыкальных произведений. Основная сложность заключается в написании программы, которая бы изменяла соответствующим образом элементы массива DATREG и тем самым управляла работой сопроцессора. Чтобы получить действительно первоклассное звучание, потребуется достаточно серьезная программа, которую объем книги, к сожалению, не позволяет здесь привести (надеемся, ее все же удастся включить в одну из последующих книг серии «Как написать игру»). Поэтому мы предлагаем более простую процедуру, извлекающую отдельные звуки, характер которых, тем не менее, вы сможете изменять практически в неограниченных пределах. (Упомянутая музыкальная программа строится, в общем, по такому же принципу, что и приводимая здесь процедура. Поэтому после ее досконального изучения вы можете попытаться самостоятельно написать программу, пригодную для исполнения музыкальных произведений.)

Эту программу также лучше составить в виде прерывания, чтобы можно было получать звуки любой продолжительности и не отвлекаться на их формирование в основной программе. Начнем с выяснения, какие переменные нам здесь потребуются. Помимо уже известных по предыдущему примеру значений базового и текущего адресов в блоке данных, описывающем характер звучания и флага разрешения вывода звука понадобятся переменные, задающие частоту тона и шума, длительность звучания, количество повторений эффекта, флаг разрешения вывода в канал тона или шума. Кроме этого, необходимо каким-то образом задавать огибающую звука и характер изменения высоты тона или шума. Эти две последние характеристики должны изменяться независимо друг от друга, поэтому порядок их изменения лучше всего определять в двух дополнительных блоках данных. То есть к перечисленным переменным добавятся еще четыре: базовые и текущие адреса для каждого из этих двух блоков.

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

0 - конец данных (возврат на повторение эффекта)
1 - адрес данных для изменения тона (+2 байта адреса)
2 - адрес данных для формирования огибающей (+2 байта адреса)
3 - управление выводом тона/шума (+2 байта: 1-й определяет вывод тона,
    шума или комбинированный вывод, 2-й - длительность звучания)

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

128         - задание частоты тона (+2 байта частоты тона)
129         - задание частоты шума (+1 байт частоты шума)
130         - метка повторения эффекта
131         - возврат на начало или метку
-124...+127 - приращение частоты

Когда программа встретит код 0, вывод звука либо прекратится, либо весь эффект повторится еще раз — в зависимости от заданного изначально количества повторений. Следующие два байта после кодов 1 или 2 интерпретируются как адреса дополнительных блоков данных, определяющих характер изменения частоты звука и огибающей соответственно. С этих двух кодов должен начинаться любой блок данных, иначе программа не будет «знать», каким образом изменять звук. Последний код (3) служит собственно для извлечения звука. После него необходимо указать еще два однобайтовых параметра: первый задает вывод тона (1), шума (8) либо одновременно и тона и шума (9), второй определяет продолжительность заданного звука в 50-х долях секунды.

В дополнительных блоках данных также используем некоторые управляющие коды. После байта со значением 128 будет задаваться двухбайтовая величина частоты тона (см. табл. 10.1), а за кодом 129 последует байт средней частоты шума, который может иметь значения от 0 (самый высокий звук) до 31 (самый низкий). Определив частоты, можно поставить метку начала их изменения, поставив код 130. Далее должны следовать значения приращения частоты, которые лежат в диапазоне от −124 до +127. За один такт прерываний (1/50 секунды) будет выполнен один из этих кодов. Завершаться этот блок данных должен кодом 131, после которого все составляющие его числа будут проинтерпретированы с начала или от кода 130, если таковой был использован. Соберем все коды данных для изменения частоты воедино:

128    - возврат на начало данных огибающей
0...15 - значение громкости

В блоке данных для формирования огибающей будет использован только один управляющий код 128, отмечающий конец интерпретации записанных значений и переход на повторение эффекта. Остальные коды, задающие громкость звука, могут передаваться числами от 0 до 15. Каждое из этих чисел также обрабатывается за одно прерывание.

GETSND LD    (N_CHAN),A  ;запоминаем номер канала
       LD    A,(IX+7)
       AND   A
       RET   Z           ;выход, если вывод в канал не разрешен
       DEC   (IX+8)      ;уменьшаем счетчик длительности звука
       JR    NZ,GETS5
       LD    L,(IX+2)    ;текущий адрес основного блока данных
       LD    H,(IX+3)
GETS1  LD    A,(HL)
       INC   HL
       AND   3
       JR    NZ,GETS2
; Код 0 - возврат на повторение эффекта
       LD    A,(IX+11)   ;установка текущего адреса
       LD    (IX+13),A   ; данных изменения частоты
       LD    A,(IX+12)   ; на начало
       LD    (IX+14),A   ; блока
       LD    L,(IX)      ;начальный адрес основного
       LD    H,(IX+1)    ; блока данных
       DEC   (IX+9)      ;уменьшение количества повторений
       JR    NZ,GETS1
       XOR   A           ;завершение звучания в канале
       LD    (IX+7),A    ;запрет вывода звука в канал
       LD    (IX+10),A
       LD    A,(N_CHAN)
       ADD   A,8
       LD    E,A
       XOR   A
       JP    SETREG      ;выключение громкости
GETS2  DEC   A
       JR    NZ,GETS3
; Код 1 - адрес данных для изменения тона
       LD    A,(HL)      ;младший байт адреса
       LD    (IX+11),A
       INC   HL
       LD    A,(HL)      ;старший байт адреса
       LD    (IX+12),A
       INC   HL
       JR    GETS1
GETS3  DEC   A
       JR    NZ,GETS4
; Код 2 - адрес данных для формирования огибающей
       LD    A,(HL)      ;младший байт адреса
       LD    (IX+15),A
       LD    (IX+17),A
       INC   HL
       LD    A,(HL)      ;старший байт адреса
       LD    (IX+16),A
       LD    (IX+18),A
       INC   HL
       JR    GETS1
; Код 3 - управление выводом тона/шума
GETS4  LD    A,(HL)      ;1 - тон, 8 - шум, 0 - пауза,
                         ; 9 - тон и шум одновременно
       INC   HL
       AND   9
       LD    (IX+10),A
       LD    A,(HL)      ;продолжительность вывода
       INC   HL
       LD    (IX+8),A
       LD    (IX+2),L
       LD    (IX+3),H
; Восстановление текущего адреса данных для изменения тона
       LD    A,(IX+11)
       LD    (IX+13),A
       LD    A,(IX+12)
       LD    (IX+14),A
GETS5  LD    L,(IX+13)
       LD    H,(IX+14)
GETS6  LD    A,(HL)
       INC   HL
       CP    128         ;задание частоты тона
       JR    NZ,GETS7
       LD    A,(HL)
       LD    (IX+4),A
       INC   HL
       LD    A,(HL)
       AND   15
       LD    (IX+5),A
       INC   HL
       JR    GETS6
GETS7  CP    129         ;задание частоты шума
       JR    NZ,GETS8
       LD    A,(HL)
       AND   31
       LD    (IX+6),A
       JR    GETS6
GETS8  CP    130         ;метка нового начала
       JR    NZ,GETS9
       LD    (IX+11),L
       LD    (IX+12),H
       JR    GETS6
GETS9  CP    131         ;возврат к началу
       JR    NZ,GETS10
       LD    L,(IX+11)
       LD    H,(IX+12)
       JR    GETS6
; Изменение частоты звука или шума
GETS10 LD    (IX+13),L
       LD    (IX+14),H
       LD    D,0
       BIT   7,A
       JR    Z,GETS11
       LD    D,255
GETS11 LD    E,A
       LD    L,(IX+4)
       LD    H,(IX+5)
       ADD   HL,DE
       LD    (IX+4),L
       LD    (IX+5),H
       ADD   A,(IX+6)
       LD    (IX+6),A
; Определение элементов массива DATREG, задающих частоту
       LD    (DATREG+6),A ;частота шума
       LD    A,(N_CHAN)
       ADD   A,A
       LD    E,A
       LD    A,L
       PUSH  HL
       CALL  SETREG      ;младший байт частоты тона
       POP   HL
       INC   E
       LD    A,H
       CALL  SETREG      ;старший байт частоты тона
; Формирование огибающей
       LD    L,(IX+17)
       LD    H,(IX+18)
GETS12 LD    A,(HL)
       INC   HL
       CP    128         ;повторение с начала
       JR    NZ,GETS13
       LD    L,(IX+15)
       LD    H,(IX+14)
       JR    GETS12
GETS13 LD    (IX+17),L
       LD    (IX+18),H
       AND   15
       PUSH  AF
       LD    A,(N_CHAN)
       ADD   A,8
       LD    E,A
       POP   AF
       JP    SETREG      ;задание громкости звука

Прежде чем продолжить, хочется обратить ваше внимание на то, что при построении собственных блоков данных необходимо следить за порядком следования управляющих кодов, иначе результат будет очень далек от ожидаемого. Кроме того, ни один из блоков не может состоять только из управляющих кодов, поскольку в этом случае компьютер просто «зависнет».

Выяснив, что мы хотим в итоге получить, напишем процедуру для интерпретации описанных блоков данных. Она имеет довольно внушительные размеры, но все же попытайтесь с ней разобраться. На входе перед обращением к ней в аккумуляторе задается номер канала (0 для A, 1 для B и 2 для C), а индексный регистр IX, как мы уже говорили, адресует таблицу переменных соответствующего канала:

SND128 PUSH  AF
       PUSH  BC
       PUSH  DE
       PUSH  HL
       PUSH  IX
       CALL  NXTSND
       POP   IX
       POP   HL
       POP   DE
       POP   BC
       POP   AF
       JP    56
NXTSND LD    IX,CHAN_A
       XOR   A
       CALL  GETSND      ;задание переменных для канала A
       LD    IX,CHAN_B
       LD    A,1
       CALL  GETSND      ;задание переменных для канала B
       LD    IX,CHAN_C
       LD    A,2
       CALL  GETSND      ;задание переменных для канала C
; Вычисление значения регистра R7,
;  управляющего выводом в каналы тона и шума
       LD    A,(CHAN_C+10)
       AND   9           ;выделяем биты 0 и 3
       RLCA              ;сдвигаем влево
       LD    B,A         ;результат сохраняем в регистре B
       LD    A,(CHAN_B+10)
       AND   9           ;то же самое для других двух каналов
       OR    B
       RLCA
       LD    B,A
       LD    A,(CHAN_A+10)
       AND   9
       OR    B
       CPL               ;инвертируем биты
       LD    E,7         ;устанавливаем данные регистра R7 в DATREG
       CALL  SETREG
; Извлечение звука
OUTREG LD    HL,DATREG+13
       LD    D,13
       LD    C,#FD
OUTR1  LD    B,#FF
       OUT   (C),D
       LD    B,#BF
       OUTD
       DEC   D
       RET   M           ;выход, если D < 0
       JR    OUTR1
DATREG DEFS  14
; Задание элемента E массива DATREG значением из аккумулятора
SETREG LD    HL,DATREG
       LD    D,0
       ADD   HL,DE
       LD    (HL),A
       RET
N_CHAN DEFB  0           ;номер текущего канала
; Таблицы переменных для каждого канала
CHAN_A DEFS  19
CHAN_B DEFS  19
CHAN_C DEFS  19

В процедуре обработки прерываний приведенная подпрограмма будет вызываться трижды для определения характера звучания в каждом из трех каналов независимо друг от друга. Это дает возможность использовать в программе одновременно три самостоятельных источника звука, закрепив за каждым игровым объектом свой звуковой канал. Правда, здесь есть одно небольшое ограничение: поскольку средняя частота «белого» шума общая для всех трех каналов, то его лучше выводить только в какой-то один, а другие два использовать для вывода изменяющегося тона.

Вот описываемая процедура обработки прерываний:

INITI  LD    HL,CHAN_A   ;инициализация таблиц переменных
       LD    DE,CHAN_A+1
       LD    BC,19*3-1
       LD    (HL),0
       LDIR
       LD    HL,SND128   ;установка прерывания
IMON   .........
STOPI  CALL  IMOFF       ;возврат к 1-му режиму
       LD    A,#FF       ;выключение звука
       LD    E,7
       CALL  SETREG      ;запись в регистр сопроцессора R7
                         ; значения #FF
       JP    OUTREG
IMOFF  .........

Наше прерывание остается дополнить процедурами включения и выключения 2-го режима. Попутно желательно выполнить и некоторые другие действия, а именно, при установке 2-го режима нужно очистить все три таблицы переменных, инициализировав их нулевым байтом, а при возврате первого режима прерываний необходимо еще убедиться, что звук выключен:

D_SND1 DEFB  1
       DEFW  FREQ1
       DEFB  2
       DEFW  ENV1
       DEFB  3,8,2,3,8,1,0
FREQ1  DEFB  129,0,130,5,131 ;изменение частоты
ENV1   DEFB  15,14,12,128    ;изменение громкости

Порядок действий при использовании описанной программы должен быть следующим. В начале работы нужно включить 2-й режим прерываний, вызвав процедуру INITI. Извлечение очередного звука необходимо начинать с определения некоторых переменных в таблицах CHAN_A, CHAN_B или CHAN_C. Для этого нужно записать в первые два байта таблицы, соответствующей выбранному каналу, адрес начала основного блока данных и то же значение продублировать в следующих двух байтах таблицы. Затем указать количество повторений звука по смещению +9, а элементы таблицы +8 и +7 инициализировать байтом 1 (переменную по смещению +7 нужно задавать обязательно в последнюю очередь, так как именно она «запускает» звук). По окончании работы (или если в программе предусмотрены обращения к дисководу) следует восстановить стандартный режим обработки прерываний и выключить звук, обратившись к подпрограмме STOPI.

Продемонстрируем применение описанной процедуры извлечения звуков на примере небольшой игры, которую назовем БИТВА С НЛО. По земле катается грузовик с зенитной лазерной установкой, который методично расстреливает маячащую в небе «летающую тарелку» (рис. 10.1). НЛО также не остается внакладе и отвечает хоть и малоприцельным, но зато плотным веерным огнем.

Рис. 10.1. Программа БИТВА С НЛО

Вначале создадим акустическое сопровождение программы. Нам понадобятся такие звуки: выстрелы с обеих сторон, попадания в НЛО и в лазерную установку, а также соударение НЛО со «стенкой», ограничивающей игровое поле. Учитывая характер звуков, а также и то, что шум можно выводить только в один из каналов, составим блоки данных по правилам, описанным выше.

Начнем с выстрелов управляемой игроком лазерной установки. Для получения этого звука используем «белый» шум. Этот и следующий звуки закрепим за каналом музыкального сопроцессора A.

D_SND2 DEFB  1
       DEFW  FREQ2
       DEFB  2
       DEFW  ENV2
       DEFB  3,1,2,1
       DEFW  FREQ3
       DEFB  3,8,20,0
FREQ2  DEFB  128
       DEFW  1000
       DEFB  130,10,131
FREQ3  DEFB  129,3,130,1,131
ENV2   DEFB  15,14,15,12,15,14,12,10,14,12
       DEFB  9,8,7,6,5,4,3,2,11,0,128

Для создания эффекта попадания в автомобиль с лазерной установкой также будем выводить шум, но для большей убедительности в самом начале звука дадим короткий сигнал низкого тона:

D_SND3 DEFB  1
       DEFW  FREQ4
       DEFB  2
       DEFW  ENV3
       DEFB  3,1,15,0
FREQ4  DEFB  128
       DEFW  200
       DEFB  130,20,131
ENV3   DEFB  15,14,14,13,12,12,11,128

Звуки, сопровождающие выстрелы «летающей тарелки» и попадания в нее будем выводить в канал B. Попробуем сымитировать эти звучания изменением чистого тона. Блок данных для формирования частоты и огибающей «выстрела» может выглядеть примерно так:

D_SND4 DEFB  1
       DEFW  FREQ5
       DEFB  2
       DEFW  ENV4
       DEFB  3,1,24,0
FREQ5  DEFB  128
       DEFW  700
       DEFB  130,100,131
ENV4   DEFB  15,14,14,15,15,12,10,11,8,7
       DEFB  7,5,6,7,10,12,14,15,10,8,128

Звук, подражающий попаданию в НЛО, должен быть более протяжным, поэтому и блок данных, описывающий огибающую окажется несколько длиннее:

D_SND5 DEFB  1
       DEFW  FREQ6
       DEFB  2
       DEFW  ENV5
       DEFB  3,1,9,0
FREQ6  DEFB  128
       DEFW  20
       DEFB  130,10,-15,40,-30,-5,131
ENV5   DEFB  15,9,12,10,15,13,128

Все предыдущие звуки были достаточно глухими, поэтому удар НЛО о «стенку» сделаем напоминающим хрустальный звон. Если вам это покажется уж слишком нелогичным, попытайтесь создать более подходящее звучание самостоятельно. Этот эффект будет выводиться в канал C:

SETUDG LD    HL,UDG
       LD    (23675),HL
       RET
; НЛО (A, B, C и D)
UDG    DEFB  1,65,32,23,13,27,59,55
       DEFB  128,130,4,232,240,248,252,252
       DEFB  127,125,0,173,173,0,7,1
       DEFB  254,190,0,181,181,0,224,128
; Лазерная установка (E, F, G, H, I и J)
       DEFB  173,97,191,127,191,127,192,158
       DEFB  199,207,201,201,207,207,207,79
       DEFB  252,254,35,33,225,225,255,255
       DEFB  63,97,204,146,173,37,18,12
       DEFB  1,126,255,126,126,0,0,0
       DEFB  254,134,51,73,181,148,72,48

В игре нам понадобится создать изображения грузовика и НЛО. Общую часть программы мы собираемся написать на Бейсике, поэтому проще всего для вывода графических изображений вновь обратиться к символам UDG. Можно было бы закодировать их и в бейсик-программе, но, во-первых, как вы знаете, циклы в интерпретаторе — самое больное место и выполняются они в десятки, если не в сотни раз медленнее, чем в машинных кодах. Но самое главное даже не в этом, а в том, что область памяти, в которой располагаются коды определяемых символов, занята командами перехода на процедуру обработки прерываний (адреса 65524…65526 и 65535, соответствующие положению последних двух символов — T и U). Поэтому, чтобы застраховаться от неожиданностей, коды символов лучше расположить в ассемблерной части программы, что мы и делаем:

       ORG   60000
; 60000 - включение 2-го режима прерываний и инициализация массива DATREG
       JP    INITI
; 60003 - выключение 2-го режима прерываний и звука
       JP    STOPI
; 60006 - выстрел лазерной установки
       JP    SND1
; 60009 - попадание в лазерную установку
       JP    SND2
; 60012 - выстрел НЛО
       JP    SND3
; 60015 - попадание в НЛО
       JP    SND4
; 60018 - соударение НЛО со «стенкой»
       JP    SND5
; 60021 - символы UDG
SETUDG .........
SND5   LD    HL,D_SND5
       LD    (CHAN_C),HL
       LD    (CHAN_C+2),HL
       LD    A,1
       LD    (CHAN_C+9),A
       LD    (CHAN_C+8),A
       LD    (CHAN_C+7),A
       RET
SND4   LD    HL,D_SND4
       JR    SND3_1
SND3   LD    HL,D_SND3
SND3_1 LD    (CHAN_B),HL
       LD    (CHAN_B+2),HL
       LD    A,1
       LD    (CHAN_B+9),A
       LD    (CHAN_B+8),A
       LD    (CHAN_B+7),A
       RET
SND2   LD    HL,D_SND2
       LD    A,1
       JR    SND1_1
SND1   LD    HL,D_SND1
       LD    A,3
SND1_1 LD    (CHAN_A),HL
       LD    (CHAN_A+2),HL
       LD    (CHAN_A+9),A
       LD    A,1
       LD    (CHAN_A+8),A
       LD    (CHAN_A+7),A
       RET
; Блоки данных, описывающие каждый из пяти используемых в программе звуков
D_SND1 .........
D_SND2 .........
D_SND3 .........
D_SND4 .........
D_SND5 .........
; Далее следуют уже описанные процедуры прерывания
INITI  .........
STOPI  .........
SND128 .........
NXTSND .........
OUTREG .........
SETREG .........
GETSND .........

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

  10 REM *** НЛО ***
  20 BORDER 0: PAPER 0: INK 7: CLEAR 59999
  30 RANDOMIZE USR 15619 : REM : LOAD "snd_ufo"CODE
  40 LET cd=USR 60021: REM *** UDG ***
  50 LET cd=USR 60000: REM *** Прерывания ***
  60 REM --------------
  70 CLS
  80 FOR n=19 TO 20: PRINT AT n,0; PAPER 4; TAB 31;" ": NEXT n
  90 FOR n=0 TO 18: PRINT AT n,0; PAPER 4;" ";AT n,31;" ": NEXT n
 100 LET kl=0: LET ik1=7: LET ik2=ik1: LET pow=0: LET pow1=pow:
     LET x1=4: LET y1=1: LET w=0: LET xp=0: LET yp=0: LET s1=10:
     LET s=10: LET x=4: LET y=1
 110 LET a$=INKEY$
 120 IF a$="p" AND s<28 THEN LET s=s+1: GO TO 180
 130 IF a$="o" AND s>1 THEN LET s=s-1: GO TO 190
 140 IF a$=" " THEN LET cd=USR 60006: GO TO 230
 150 IF a$="e" THEN LET cd=USR 60003: STOP
 160 REM --------------
 170 GO TO 200
 180 PRINT AT 17,s1;" ";AT 18,s1;" ": GO TO 200
 190 PRINT AT 17,s1+2;" ";AT 18,s1+2;" "
 200 INK 6: PRINT AT 17,s;"EFG";AT 18,s;"HIJ"
 210 LET s1=s
 220 LET ik2=7: GO TO 280
 230 IF INT x=s OR INT x+1=s THEN LET yy=INT y+1: LET pow1=pow1+1:
     LET cd=USR 60015: LET ik2=2: GO TO 250
 240 LET yy=0
 250 GO SUB 270: OVER 1: GO SUB 270: OVER 0
 260 GO TO 280
 270 INK 7: PLOT s*8+5,41: DRAW 0,126-yy*8: RETURN
 280 REM ----- нло ----
 290 IF w=0 THEN LET xp= (RND*1.5+.5): LET yp= (RND*1.5):
     LET w=INT (RND*25+5): LET xp=xp*SGN (RND*4-2):
     LET yp=yp*SGN (RND*4-2)
 300 IF x+xp>=1 AND x+xp<30 AND y+yp>=0 AND y+yp<=12
     AND w>0 THEN LET x=x+xp: LET y=y+yp: LET w=w-1: LET kl=0:
     GO TO 330
 310 IF ((INT (x+xp)<1 AND INT x=1) OR (INT x=29 AND INT (x+xp)>29))
     AND kl=0 THEN LET kl=1: LET cd=USR 60018
 320 LET w=0: GO TO 290
 330 PRINT AT INT y1,INT x1;"••";AT INT y1+1,INT x1;"••"
 340 INK 5: PRINT AT INT y,INT x;"AB";AT INT y+1,INT x;"CD"
 350 LET x1=x: LET y1=y
 360 IF RND*10<3 THEN LET cd=USR 60012: LET xf=RND*255:
     GO SUB 410: OVER 1: GO SUB 410: OVER 0: LET xf1=INT (xf/8):
     IF xf1=s OR xf1=s+1 OR xf1=s+2 THEN LET pow=pow+1:
     LET cd=USR 60009: LET ik1=2: GO TO 380
 370 LET ik1=7
 380 PRINT AT 21,1; INK ik2;"Score car:";INT pow1;" "
 390 PRINT AT 21,19; INK ik1;"Score ufo:";INT pow;" "
 400 GO TO 110
 410 INK 2: PLOT INT (x)*8+7,159-INT (y)*8:
     DRAW xf-(INT (x)*8+7),25-(159-INT (y)*8): RETURN

А вот бейсик-программа самой игры:

RANDOMIZE USR 64000

Прокомментируем немного строки этой программы и поясним значение некоторых переменных.

  • 40 — «включение» символов UDG;
  • 50 — включение 2-го режима прерываний;
  • 80…90 — рисование грунта;
  • 100 — задание начальных значений переменных:
    • kl — управление звуком при подлете НЛО к стенке (kl=0 — звук разрешен, kl=1 — запрещен);
    • ik1, ik2 — цвет надписей;
    • pow — количество попаданий НЛО;
    • pow1 — количество попаданий автомобиля;
    • x1, y1 — координаты для удаления предыдущего изображения НЛО;
    • w — количество перемещений НЛО до изменения направления движения;
    • xp, yp — приращения координат НЛО;
    • s1 — координата для удаления предыдущего изображения автомобиля;
    • s — текущая координата автомобиля;
    • x, y — текущие координаты НЛО.
  • 110…150 — опрос клавиатуры;
  • 180…200 — восстановление фона позади движущегося автомобиля и перемещение его в новое положение;
  • 230 — проверка попадания луча лазера в НЛО;
  • 250 — рисование луча лазера;
  • 290…400 — управление полетом НЛО;
  • 410 — рисование следа от выстрела НЛО.

МУЗЫКАЛЬНЫЙ РЕДАКТОР WHAM FX

Кодирование вручную звуковых эффектов, а тем более — музыки, дело весьма утомительное. Хотя такой способ при наличии определенного опыта в конечном итоге и дает наилучшие результаты, но для начинающих он мало пригоден. А что говорить о простых пользователях, общающихся с компьютером только на уровне игровых или, в лучшем случае, прикладных программ. Поэтому стоит сказать хотя бы несколько слов о программе, специально предназначенной для создания компьютерной музыки — музыкальном редакторе.

В первой книге серии «Как написать игру для ZX Spectrum» мы рассказали о работе с редактором Wham. Этот же редактор был достаточно подробно описан и в книге [2]. Поэтому здесь мы не станем еще раз повторяться, а объясним, как работать с другой версией этой программы, специально рассчитанной на возможности музыкального сопроцессора.

Редактор Wham FX был создан тем же автором и внешне не слишком отличается от своего предшественника. Не особенно сильно изменен и принцип управления, а также способ ввода мелодии. Но, конечно, имеются и некоторые существенные отличия, на которых в основном мы и хотим заострить ваше внимание.

После загрузки программы вы услышите мелодию, демонстрирующую возможности редактора. Нажав любую клавишу, можно прервать прослушивание и попасть в главное меню, многие пункты которого повторяют функции первой версии Wham. Среди них уже знакомые LOAD TUNE и SAVE TUNE (загрузка и сохранение пьесы), SET TEMPO (изменение темпа), HELP PAGE (подсказка) и EDIT MODE (режим ввода и редактирования). Остальные две опции — SYST MENU (системное меню) и ENVELOPES (формирование огибающих звука) — мы рассмотрим ниже, а сейчас сразу перейдем к редактированию мелодии, нажав клавишу 6.

Большая часть экрана в режиме редактирования (рис. 10.2) занята нотными линейками, на которых отображается вводимая вами мелодия. Нижнюю часть экрана занимает изображение клавиатуры фортепиано, которое помогает установить соответствие между клавишами компьютера и вводимыми звуками. В средней части экрана расположено несколько окон: три небольших окна слева символизируют характер звучания в каждом из трех каналов; в четвертом окне указываются номера редактируемого канала (CHN) и текущей октавы (OKT); в следующем, самом большом окне выводится информация об используемых в произведении эффектах; последнее окно, изображающее прибор со стрелкой, носит скорее декоративный характер — при проигрывании мелодии стрелка постоянно прыгает в такт звукам, отражая уровень громкости.

Рис. 10.2. Режим редактирования

Прежде чем начинать вводить новую мелодию, нужно убрать из памяти старую, оставшуюся после загрузки программы или от предыдущих упражнений. Нажмите клавишу 7 и на запрос ERASE CURRENT TUNE (Y/N)? — удалить текущую мелодию (да/нет)? — ответьте утвердительно нажатием клавиши Y. Теперь можно записывать ноты.

Ввод мелодии в Wham FX в принципе ничем не отличается от записи музыки в первой версии программы. Для получения звуков здесь также используются клавиши двух нижних рядов и Enter — для ввода пауз, октавы переключаются клавишами 1…4, а переход к редактированию другого голоса происходит при нажатии клавиши T. Правда, данная версия рассчитана на компьютер Spectrum 128, а следовательно, на расширенную клавиатуру, поэтому нота ДО извлекается нажатием клавиши Caps Lock (на обычной клавиатуре — Caps Shift/2), а также задействованы кнопки с символами запятой и точки. Клавиши Caps Shift и Symbol Shift служат здесь для других целей, поэтому при вводе звуков не используются.

При записи музыки вы можете по мере надобности сдабривать ее различными шумовыми эффектами, имитирующими ударные инструменты. Таких инструментов в одной пьесе можно иметь до девяти, а вставляются подобные звуки при одновременном нажатии клавиши Symbol Shift и одной из клавиш второго ряда сверху от Q до O.

Если вы ошиблись при вводе очередного звука, вернуться на одну позицию назад можно с помощью клавиши Delete (Caps Shift/0), а для быстрой прокрутки назад на несколько тактов воспользуйтесь клавишей True Video (Caps Shift/3). Для продвижения вперед нажимайте клавиши O (быстро) или P (медленно). Чтобы прослушать полученную музыку, нужно вернуться в самое начало пьесы, нажав R, а затем включить проигрывание клавишей Q.

Наиболее интересной особенностью редактора Wham FX является возможность изменения характера звучания каждого голоса в отдельности. Для этого нужно подвести курсор к тому месту в пьесе, начиная с которого вы хотите получить иной звук (первая нота фрагмента должна появиться у правого края экрана) и нажать клавишу Extend Mode (Caps Shift/Symbol Shift). Внизу экрана появится дополнительное меню, состоящее из пяти пунктов:

ENVELOPE VOLUME BLANK SLIDE LOOP

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

После выбора ENVELOPE компьютер даст два дополнительных запроса: какой формы должна быть огибающая (нужно ввести число от 1 до 7) и на какой голос данный эффект будет распространяться (нажмите клавишу от 1 до 3 или 0, если хотите иметь одинаковое звучание во всех трех голосах).

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

Опция SLIDE имитирует такой распространенный в эстрадной музыке прием исполнения, как BEND. При этом звук плавно изменяется по высоте в пределах нескольких полутонов, повышаясь или же наоборот, понижаясь. Компьютер попросит сначала ввести интервал (от 0 до 7 полутонов), на который звук «поедет», затем направление (UP или DOWN — вверх или вниз) и, как и для предыдущих пунктов, номер канала.

BLANK используется в том случае, если вы по ошибке поставили эффект не в том месте, где хотели. Никаких дополнительных запросов в этой опции не предусмотрено.

Последний пункт LOOP служит для установки метки цикла в канале эффектов FX и обязательно должен использоваться перед началом компиляции пьесы. Однако до этих пор прибегать к нему не следует, так как если вы решите продолжить ввод мелодии, снять поставленную метку не удастся.

Немного потренировавшись во вводе нот и изменении их звучания, можно приступать к программированию настоящей музыки. Чтобы помочь вам в этом нелегком предприятии, предлагаем сначала ввести небольшой фрагмент, приведенный на рис. 10.3 и в табл. 10.2. Просим музыкальных критиков не придираться к правописанию нот, так как рисунок отражает то, что вы увидите на экране монитора, а не то, как должны быть записаны ноты для исполнения пьесы грамотными музыкантами. В таблице выписана последовательность нажатия клавиш при вводе звуков каждого голоса. Сокращение Okt. обозначает октаву, Ent — клавишу Enter, SS — Symbol Shift (запись SS/E, например, обозначает одновременное нажатие Symbol Shift и E), а буквы и цифры справа от обозначения клавиш указывают на установку того или иного эффекта (V — VOLUME, E — ENVELOPE).

Рис. 10.3. Ноты пьесы
Таблица 10.2. Ввод пьесы
ГОЛОС 1ГОЛОС 2ГОЛОС 3
Okt. 3Ent EntOkt. 1VE5
 Ent Ent Ent 
 VE7Ent Ent 
 M SS / O Ent 
Okt. 4A Ent Ent 
 Z Ent Ent 
 C Ent H 
 Ent Ent Ent 
 VVAEnt Ent 
 VV5SS / E Ent 
 Ent Ent Ent 
 Ent Ent Ent 
 HE5Ent B 
 Ent Ent Ent 
 V Ent Ent 
 Ent SS / O Ent 
 Ent Ent Ent 
 MVAEntOkt. 2Z 
 MV5Ent Ent 
 Ent Ent Ent 
 Ent SS / QOkt. 1M 
 Ent SS / O H 
 Ent Ent Ent 
 Ent SS / Q B 
Okt. 3Ent Ent V 
 Ent Ent Ent 
 VE7Ent Ent 
 M SS / O Ent 
Okt. 4A Ent Ent 
 Z Ent Ent 
 C Ent H 
 Ent Ent Ent 
 VVAEnt Ent 
 VV5SS / E Ent 
 Ent Ent Ent 
 Ent Ent Ent 
 Ent Ent B 
 Ent Ent Ent 
 ME5Ent Ent 
 H SS / O Ent 
 Ent Ent Ent 
 V EntOkt. 2Z 
 C SS / O Ent 
 F Ent Ent 
 V SS / W Z 
 ZVFSS / ROkt. 1F 
 ZV7Ent Ent 
 CE7SS / Q Ent 

После того как мелодия введена в память, желательно сохранить ее на ленте или диске. Для этого выйдите в главное меню, нажав клавишу 6, и прежде чем воспользоваться пунктом 2 (SAVE TUNE) выберите устройство, на котором пьеса будет сохранена. Это делается так. Войдите в системное меню, нажав клавишу 3. Перед вами предстанет новый список, в котором нас сейчас интересует третий пункт. В нем белым цветом будет выделена надпись BETA DISK или CASS TAPE. Если надпись не соответствует желаемой, нажмите 3, а затем выберите нужное устройство, иначе нажмите клавишу 0 для возврата в главное меню. Теперь остается сохранить пьесу, предварительно указав имя файла (при работе с диском возможен также вывод на экран каталога).

Когда работа над пьесой завершена, ее нужно скомпилировать, чтобы получить файл, исполняемый независимо от редактора. Вернитесь в режим редактирования, прокрутите мелодию до конца и расставьте метки цикла во всех голосах: нажмите клавишу W и на запрос SET LOOP HERE? (Y/N) ответьте клавишей Y, затем повторите то же самое для других двух голосов. Не забудьте поставить метку также и в канале эффектов FX, нажав Extend Mode и выбрав в дополнительном меню пункт LOOP.

Расставив метки, переходите в главное меню, а затем в системное, откуда вызывается опция COMPILE & SAVE (компиляция и сохранение). Это пятый пункт системного меню. Если все метки расставлены и компиляция прошла успешно, на экран выводится ряд сообщений, из которых можно понять, что для проигрывания мелодии нужно ввести из Бейсика команду

       CALL  64000

или аналогичную ей, а из ассемблера полученная подпрограмма вызывается соответственно командой undefined Тут же сообщается, что для регулирования темпа исполнения нужно записать некоторое число по адресу 64062 (при исполнении нашей пьесы это число будет равно примерно 230). Изучив выданную информацию, наберите имя файла для сохранения скомпилированной мелодии и нажмите Enter.

На этом можно было бы поставить точку в данном кратком описании, но все же хочется сказать пару слов и о других возможностях, предоставляемых редактором Wham FX. Если вас не слишком устраивают предлагаемые программой формы огибающих звуков, то вы можете с помощью пункта 4 главного меню исправить положение и создать свои собственные уникальные звуки.

Нажав клавишу 4, вы попадаете во встроенный редактор для формирования огибающих. В верху экрана выстроятся 8 диаграмм, показывающих существующие формы звуков: первые 7 можно заказывать в дополнительном меню режима редактирования, а восьмой, помеченный двумя звездочками, относится к шумовым эффектам. Нажмите цифровую клавишу, соответствующую звуку, который вы хотите изменить. На экране появится выбранная диаграмма в увеличенном масштабе (рис. 10.4), а под ней — курсор в виде стрелки. Управляя клавишами Left (Caps Shift/5) и Right (Caps Shift/8) переместите стрелку в нужную точку огибающей и отрегулируйте громкость с помощью клавиш Down (Caps Shift/6) и Up (Caps Shift/7). Получив таким образом нужную форму огибающей закончите редактирование, нажав Enter.

Рис. 10.4. Формирование огибающей

Другая возможность касается «ударных инструментов». Их звучание также можно варьировать в некоторых пределах. Выберите в системном меню опцию SET PRESET NOISE VALUES, нажав клавишу 1. На экране вы увидите таблицу, показанную на рис. 10.5. В первой графе указан порядковый номер эффекта, а во второй — клавиша, за которой этот эффект закреплен в режиме редактирования мелодии. Нажав соответствующую цифровую клавишу, вы сможете изменить другие параметры, обозначенные в таблице. Сначала появится запрос об установке нового значения для графы FREQUENCY (частота), на который нужно ввести число от 0 до 31. Затем вводится номер огибающей (клавиши 1…8), помещаемый в графу ENVELOPE. Если вы хотите задать звук постоянной громкости, введите на этот запрос 0. В этом случае компьютер попросит указать уровень звука (графа VOLUME) вводом шестнадцатеричного числа от 0 до F.

Рис. 10.5. Редактирование шумовых эффектов

Аналогично можно отредактировать любой из девяти эффектов, а для возврата в главное меню нужно нажать клавишу 0.

В системном меню есть еще одна интересная функция — SET CHANNEL LOOP PARAMETERS, служащая для установки начальных меток циклов в каждом канале. К услугам этого пункта полезно прибегать в тех пьесах, где мелодия начинается не с сильной доли (то есть не на счет «раз»), а с затакта. Нажмите клавишу 2 и укажите, в каком канале и с какого смещения мелодия в выбранном голосе будет начинаться при повторении. Начальные метки задаются для каждого канала в отдельности (в том числе и для канала эффектов FX), а смещения могут иметь как положительные, так и отрицательные значения. Добавим, что ошибиться в расстановке этих меток не страшно, ибо в любой момент их можно переместить как вперед, так и назад (если ввести отрицательное число).