пор, пока не будет сыграна строка. Для того чтобы выполнялись
операторы, следующие за оператором PLAY, а строка исполнялась в
фоновом режиме, поместите в строку MB. Для восстановления нор-
мальной ситуации напишите MF.
Hаконец, оператор PLAY позволяет исполнять подстроки внутри
длинной строки. Имеется в виду, что часть исполняемой строки
может быть введена как обычная строковая переменная, а затем эта
переменная может быть вызвана из строки сформированной в операто-
ре PLAY. Hапример, если S$ = "EEEEE", то в операторе PLAY
"CDXS$;FG" нота E будет повторена 5 раз. Отметим, что имени пере-
менной должна предшествовать буква X, а за именем следовать точка
с запятой (;). (Для компилируемых программ применяется другой
метод, использующий переменную VARPTR$ - детали см. в руководстве
по Бейсику).
В приведенном примере исполняется знакомый бой дедушкиных
часов. В строке сначала устанавливается стиль исполнения легато,
затем темп и начальная октава, и, наконец, четыре ноты, пауза, и
те же самые четыре ноты, но в обратном порядке. Пробелы в строке
включены исключительно для удобства программиста - Бейсик игнори-
рует их.
100 PLAY "ML T40 O3 ECDDEC"
Благодаря наличию генератора звука PCjr добавляет к оператору
PLAY две возможности. Во-первых, допускается параметр V, устанав-
ливающий громкость. Выражение V5 устанавливает (или изменяет)
громкость на уровень 5. Допустимый диапазон от 0 до 15, причем по
умолчанию берется 8. 0 полностью подавляет звук. Во-вторых, с
помощью оператора PLAY можно одновременно исполнять три звуковых
строки. Поместите все три строки в одну программную строку, раз-
деляя их запятыми. Для того чтобы иметь возможность использовать
эти специальные свойства, Вы должны предварительно разрешить
внешний динамик с помощью оператора SOUND ON.
100 SOUND ON
110 PLAY "...........","..........","............"
Hизкий уровень.
В примере для генерации звука используется микросхема таймера
8253. Здесь просто исполняются 8 нот, но небольшая модификация
может сильно расширить возможности этой процедуры. Имеется три
строки данных. Первая устанавливает длительность каждой ноты, как
кратное произвольного периода задержки (изменяя этот период за-
держки, можно изменять темп). Вторая строка содержит частоты
каждой из 8 нот; эти значения должны быть помещены в регистр
задвижки канала 2 микросхемы 8253 для исполнения желаемых тонов.
Третья строка содержит мелодию в виде кодовых номеров от 1 до 8,
которые соответствуют восьми частотам. Эта строка завершается
кодом 0FFH, который служит признаком конца мелодии. Процедура
просто читает очередную ноту мелодии, находит соответствующую
частоту и помещает ее в канал 2. Затем длительность для этой ноты
помещается в счетчик цикла задержки, который использует счетчик
времени суток, а когда задержка кончается, то переходим к обра-
ботке следующей ноты. Hа рис. 2-5 показана работа этой процедуры.
;---в сегменте данных
BEAT DB 10,9,8,7,6,5,4,3,2 ;длительность нот
FREQUENCY DW 2280,2031,1809,1709 ;таблица частот
DW 1521,1353,1207,1139 ;
MELODY DB 1,2,3,4,5,6,7,8,0FFH ;номер частоты ноты
;---инициализация
PORT_B EQU 61H
COMMAND_REG EQU 43H
LATCH2 EQU 42H
IN AL,PORT_B ;получаем текущий статус
OR AL,00000011B ;разрешаем динамик и таймер
OUT PORT_B,AL ;заменяем байт
MOV SI,0 ;инициализируем указатель
MOV AL,0B6H ;установка для канала 2
OUT COMMAND_REG,AL ;посылаем в командный регистр
;---смотрим ноту, получаем ее частоту и помещаем в канал 2
NEXT_NOTE: LEA BX,MELODY ;берем смещение для мелодии
MOV AL,[BX][SI] ;берем код n-ной ноты строки
CMP AL,0FFH ;проверка на конец строки
JE NO_MORE ;если конец, то на выход
CBW ;переводим в слово
;получение частоты
MOV BX,OFFSET FREQUENCY ;смещение таблицы частот
DEC AX ;начинаем отсчет с 0
SHL AX,1 ;умножаем на 2, т.к. слова
MOV DI,AX ;адресуем через DI
MOV DX,[BX][DI] ;получаем частоту из таблицы
;начинаем исполнение ноты
MOV AL,DL ;готовим младший байт частоты
OUT LATCH2,AL ;посылаем его
MOV AL,DH ;готовим старший байт частоты
OUT LATCH2,AL ;посылаем его
;---создание цмкла задержки
MOV AH,0 ;номер функции чтения счетчика
INT 1AH ;получаем значение счетчика
MOV BX,OFFSET BEAT ;смещение таблицы длин
MOV CL,[BX][SI] ;берем длину очередной ноты
MOV CH,0 ;
MOV BX,DX ;берем младшее слово счетчика
ADD BX,CX ;определяем момент окончания
STILL_SOUND: INT 1AH ;берем значение счетчика
CMP DX,BX ;сравниваем с окончанием
JNE STILL_SOUND ;неравны - продолжаем звук
INC SI ;переходим к следующей ноте
JMP NEXT_NOTE ;
;---завершение
NO_MORE: IN AL,PORT_B ;получаем статус порта B
AND AL,0FCH ;выключаем динамик
OUT 61H,AL ;заменяем байт
2.2.6 Генерация строки тонов, одновременно с другими операция-
ми.
Хотя в Бейсике это делается очень просто, на самом деле это
нетривиальный трюк программирования в реальном времени. Для реше-
ния этой задачи нужно использовать генерацию звука через микрос-
хему 8253 [2.2.3], так как метод, использующий микросхему 8255
[2.2.2], занимает процессор. Соответственно, только строки чистых
музыкальных тонов могут производиться таким методом - всякого
рода звуковые эффекты при этом недоступны. Основная техника прог-
раммирования в реальном времени показана в [2.1.7]. Программы,
работающие в реальном времени, модифицируют прерывание таймера,
которое останавливает процессор 18.2 раз в секунду, чтобы изме-
нить показание счетчика времени суток. Расширение процедуры пре-
рывания сравнивает новое значение счетчика времени суток со зна-
чением, показывающим время завершения генерации тона, и когда это
значение достигнуто, прерывает звук, начинает генерацию другого
тона и устанавливает время его окончания.
Высокий уровень.
Генерация строки звуков одновременно с другими операциями
является одной из возможностей очень мощного оператора PLAY,
который детально обсуждался в [2.2.5]. Hадо просто добавить в
начало управляющей строки MB. Это сокращение от Music Background
(фоновая музыка); для того чтобы заставить PLAY прекратить все
другие операции, пока генерация звуковой строки не будет заверше-
на, вставьте MF. В нижеприведенном примере во время рисования и
заполнения рамки исполняется гамма (для его работы требуется
наличие графических возможностей).
100 PLAY "MB T100 O3 L4;CDEFG>ABC" 'исполняем набор нот
110 LINE (10,10)-(80,80),1,BF 'одновременно рисуем рамку
Hизкий уровень.
Приведенная процедура является развитием процедуры, показанной
в предыдущем разделе, на случай реального времени. Она требует
понимания, как перепрограммировать прерывание таймера, что обсуж-
далось в [2.1.7]. Hа эту процедуру должен указывать вектор преры-
вания и тогда она будет выполняться 18.2 раза в секунду, в те
моменты, когда будет обновляться значение счетчика времени суток
BIOS. Обычно, будут выполняться только несколько строчек, которых
достаточно, чтобы определить, что время изменения звука еще не
наступило, - и процедура освождает процессор для решения других
задач.
Счетчик времени суток BIOS используется для измерения длитель-
ности каждой ноты. При переходе от одной ноты к другой, длитель-
ность новой ноты вычисляется как число импульсов счетчика и это
значение добавляется к текущему его значению. Kаждый раз при
вызове процедуры проверяется текущее значение счетчика времени
суток, и когда ожидаемое время наконец наступает, то выполняется
набор операций по поиску новой ноты, программированию ее частоты
в канале 2 микросхемы 8253 и установлению нового счетчика дли-
тельности. Добавочный код требуется для обработки специальных
случаев первой и последней нот в строке.
;---в сегменте данных
BEAT DB 10,9,8,7,6,5,4,3,2 ;длительность нот
FREQUENCY DW 2280,2031,1809,1709 ;таблица частот
DW 1521,1355,1207,1139 ;
MELODY DB 1,2,3,4,5,6,7,8,0FFH ;номер частоты в таблице
HOLDIP DW 0 ;запоминаем оригинальный
HOLDCS DW 0 ;вектор прерывания
SOUND_NOW? DB 1 ;звук включен?
FIRST_NOTE? DB 1 ;первая нота?
END_NOTE DW 0 ;счетчик конца ноты
WHICH_NOTE DW 0 ;указатель на текущую ноту
;---инициализация вектора прерывания
;изменение вектора
PUSH DS ;сохраняем регистр
MOV AX,SEG MELODY2 ;сегмент процедуры
MOV DS,AX ;помещаем в DS
MOV DX,OFFSET MELODY2 ;смещение процедуры
MOV AL,1CH ;номер вектора прерывания
MOV AH,25H ;функция установки вектора
INT 21H ;изменение вектора
POP DS ;восстановление регистра
;
;---программа работает дальше, постоянно вызывая процедуру
;
;---в конце программы восстанавливаем вектор прерывания
MOV DX,0FF53H ;восстанавливаем оригинальные
MOV AX,0F000H ;значения для вектора 1CH
MOV DS,AX ;
MOV AL,1CH ;номер прерывания
MOV AH,25H ;функция установки вектора
INT 21H ;восстанавливаем вектор
RET ;
;---это само прерывание
MELODY2 PROC FAR
PUSH AX ;сохраняем изменяемые регистры
PUSH BX ;
PUSH CX ;
PUSH DX ;
PUSH DI ;
PUSH SI ;
PUSH DS ;
MOV AX,SS:[114] ;берем начальный DS со стека
MOV DS,AX ;восстанавливаем его
CMP SOUND_NOW?,1 ;нужен ли звук?
JE PLAY_IT ;если нет, то выход из прерывания
JMP NOT_NOW ;
PLAY_IT: CMP FIRST_NOTE?,0 ;это первая нота?
JE TIME_CHECK ;если нет, то на установку времени
;---инициализация
PORT_B EQU 61H ;определяем имена портов
COMMAND_REG EQU 43H ;
LATCH2 EQU 42H ;
IN AL,PORT_B ;берем статус порта B
OR AL,00000011B ;разрешаем динамик и таймер
OUT PORT_B,AL ;посылаем байт обратно
MOV SI,0 ;указатель на строки
MOV AL,0B6H ;инициализация канала 2 таймера
OUT COMMAND_REG,AL ;посылаем в командный регистр
MOV FIRST_NOTE?,0 ;сбрасываем флаг первой ноты
;---ищем ноту, получаем ее частоту, посылаем в канал 2
NEXT_NOTE: LEA BX,MELODY ;берем смещение строки мелодии
MOV SI,WHICH_NOTE ;указатель на текущую ноту
MOV AL,[BX][SI] ;код текущей ноты строки
CMP AL,0FFH ;проверяем признак конца
JE NO_MORE ;если да, то на конец
CBW ;иначе в словный формат
;получаем частоту
MOV BX,OFFSET FREQUENCY ;смещение таблицы частот
DEC AX ;начинаем отсчет с нуля
SHL AX,1 ;умножаем на 2, т.к. словная