Как написать игру на ассемблере для ZX Spectrum/Глава 10
- ГЛАВА ДЕСЯТАЯ,
- в которой показано, как заставить компьютер звучать по вашим нотам
Как вы понимаете, никакая игра не сможет называться полноценной, если она будет протекать при гробовой тишине. Посему получение различных акустических эффектов и написание хотя бы простейшей компьютерной музыки является достаточно важным этапом создания игровой программы. Из данной главы вы узнаете, какими средствами достигаются эти цели, получите представление о способах вывода звука как в стандартный канал 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
После запуска этой программки экран окрасится в черный цвет, по краю его будет нарисована рамка и в случайном месте возникнет шарик, который начнет метаться из стороны в сторону, отскакивая от «стенок». При соприкосновении с преградами будет раздаваться протяжный вибрирующий звук, причем разный в зависимости от того, в горизонтальную или в вертикальную «стенку» ударился мячик. Для остановки программы достаточно нажать любую клавишу.
МУЗЫКАЛЬНЫЙ СОПРОЦЕССОР
Несоизмеримо большими возможностями для создания звукового оформления игровых программ обладают компьютеры 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 приводится соответствие звуков из диапазона неполных девяти октав и чисел, определяющих эти ноты.
СК | К | Б | М | 1 | 2 | 3 | 4 | 5 | |
До | 3389 | 1695 | 847 | 424 | 212 | 106 | 53 | 26 | |
До диез | 3199 | 1600 | 800 | 400 | 200 | 100 | 50 | 25 | |
Ре | 3020 | 1510 | 755 | 377 | 189 | 94 | 47 | 24 | |
Ре диез | 2850 | 1425 | 712 | 356 | 178 | 89 | 45 | 22 | |
Ми | 2690 | 1345 | 673 | 336 | 168 | 84 | 42 | 21 | |
Фа | 2539 | 1270 | 635 | 317 | 159 | 79 | 40 | 20 | |
Фа диез | 2397 | 1198 | 599 | 300 | 150 | 75 | 37 | 19 | |
Соль | 2262 | 1131 | 566 | 283 | 141 | 71 | 35 | 18 | |
Соль диез | 2135 | 1068 | 534 | 267 | 133 | 67 | 33 | 17 | |
Ля | 4031 | 2015 | 1008 | 504 | 252 | 126 | 63 | 31 | 16 |
Си бемоль | 3804 | 1902 | 951 | 476 | 238 | 119 | 59 | 30 | 15 |
Си | 3591 | 1795 | 898 | 449 | 224 | 112 | 56 | 28 | 14 |
Следующий регистр — 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). НЛО также не остается внакладе и отвечает хоть и малоприцельным, но зато плотным веерным огнем.
Вначале создадим акустическое сопровождение программы. Нам понадобятся такие звуки: выстрелы с обеих сторон, попадания в НЛО и в лазерную установку, а также соударение НЛО со «стенкой», ограничивающей игровое поле. Учитывая характер звуков, а также и то, что шум можно выводить только в один из каналов, составим блоки данных по правилам, описанным выше.
Начнем с выстрелов управляемой игроком лазерной установки. Для получения этого звука используем «белый» шум. Этот и следующий звуки закрепим за каналом музыкального сопроцессора 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); в следующем, самом большом окне выводится информация об используемых в произведении эффектах; последнее окно, изображающее прибор со стрелкой, носит скорее декоративный характер — при проигрывании мелодии стрелка постоянно прыгает в такт звукам, отражая уровень громкости.
Прежде чем начинать вводить новую мелодию, нужно убрать из памяти старую, оставшуюся после загрузки программы или от предыдущих упражнений. Нажмите клавишу 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 компьютер даст два дополнительных запроса: какой формы должна быть огибающая (нужно ввести число от 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).
ГОЛОС 1 | ГОЛОС 2 | ГОЛОС 3 | |||||
Okt. 3 | Ent | Ent | Okt. 1 | V | E5 | ||
Ent | Ent | Ent | |||||
V | E7 | Ent | Ent | ||||
M | SS / O | Ent | |||||
Okt. 4 | A | Ent | Ent | ||||
Z | Ent | Ent | |||||
C | Ent | H | |||||
Ent | Ent | Ent | |||||
V | VA | Ent | Ent | ||||
V | V5 | SS / E | Ent | ||||
Ent | Ent | Ent | |||||
Ent | Ent | Ent | |||||
H | E5 | Ent | B | ||||
Ent | Ent | Ent | |||||
V | Ent | Ent | |||||
Ent | SS / O | Ent | |||||
Ent | Ent | Ent | |||||
M | VA | Ent | Okt. 2 | Z | |||
M | V5 | Ent | Ent | ||||
Ent | Ent | Ent | |||||
Ent | SS / Q | Okt. 1 | M | ||||
Ent | SS / O | H | |||||
Ent | Ent | Ent | |||||
Ent | SS / Q | B | |||||
Okt. 3 | Ent | Ent | V | ||||
Ent | Ent | Ent | |||||
V | E7 | Ent | Ent | ||||
M | SS / O | Ent | |||||
Okt. 4 | A | Ent | Ent | ||||
Z | Ent | Ent | |||||
C | Ent | H | |||||
Ent | Ent | Ent | |||||
V | VA | Ent | Ent | ||||
V | V5 | SS / E | Ent | ||||
Ent | Ent | Ent | |||||
Ent | Ent | Ent | |||||
Ent | Ent | B | |||||
Ent | Ent | Ent | |||||
M | E5 | Ent | Ent | ||||
H | SS / O | Ent | |||||
Ent | Ent | Ent | |||||
V | Ent | Okt. 2 | Z | ||||
C | SS / O | Ent | |||||
F | Ent | Ent | |||||
V | SS / W | Z | |||||
Z | VF | SS / R | Okt. 1 | F | |||
Z | V7 | Ent | Ent | ||||
C | E7 | SS / 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.
Другая возможность касается «ударных инструментов». Их звучание также можно варьировать в некоторых пределах. Выберите в системном меню опцию SET PRESET NOISE VALUES, нажав клавишу 1. На экране вы увидите таблицу, показанную на рис. 10.5. В первой графе указан порядковый номер эффекта, а во второй — клавиша, за которой этот эффект закреплен в режиме редактирования мелодии. Нажав соответствующую цифровую клавишу, вы сможете изменить другие параметры, обозначенные в таблице. Сначала появится запрос об установке нового значения для графы FREQUENCY (частота), на который нужно ввести число от 0 до 31. Затем вводится номер огибающей (клавиши 1…8), помещаемый в графу ENVELOPE. Если вы хотите задать звук постоянной громкости, введите на этот запрос 0. В этом случае компьютер попросит указать уровень звука (графа VOLUME) вводом шестнадцатеричного числа от 0 до F.
Аналогично можно отредактировать любой из девяти эффектов, а для возврата в главное меню нужно нажать клавишу 0.
В системном меню есть еще одна интересная функция — SET CHANNEL LOOP PARAMETERS, служащая для установки начальных меток циклов в каждом канале. К услугам этого пункта полезно прибегать в тех пьесах, где мелодия начинается не с сильной доли (то есть не на счет «раз»), а с затакта. Нажмите клавишу 2 и укажите, в каком канале и с какого смещения мелодия в выбранном голосе будет начинаться при повторении. Начальные метки задаются для каждого канала в отдельности (в том числе и для канала эффектов FX), а смещения могут иметь как положительные, так и отрицательные значения. Добавим, что ошибиться в расстановке этих меток не страшно, ибо в любой момент их можно переместить как вперед, так и назад (если ввести отрицательное число).