Глава 2. Таймеры и звук.
Раздел 2. Создание звука.
2.2.6 Генерация строки тонов, одновременно с другими операциями.
Хотя в Бейсике это делается очень просто, на самом деле это нетривиальный трюк программирования в реальном времени. Для решения этой задачи нужно использовать генерацию звука через микросхему 8253 {2.2.3}, так как метод, использующий микросхему 8255 {2.2.2}, занимает процессор. Соответственно, только строки чистых музыкальных тонов могут производиться таким методом - всякого рода звуковые эффекты при этом недоступны. Основная техника программирования в реальном времени показана в {2.1.7}. Программы, работающие в реальном времени, модифицируют прерывание таймера, которое останавливает процессор 18.2 раз в секунду, чтобы изменить показание счетчика времени суток. Расширение процедуры прерывания сравнивает новое значение счетчика времени суток со значением, показывающим время завершения генерации тона, и когда это значение достигнуто, прерывает звук, начинает генерацию другого тона и устанавливает время его окончания.
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, т.к. словная |
| MOV DI,AX | ;адресуемся через DI |
| MOV DX,[BX][DI] | ;получаем частоту из таблицы |
| ;начинаем исполнение ноты | |
| MOV AL,DL | ;готовим младший байт частоты |
| OUT LATCH2,AL | ;посылаем в регистр задвижки |
| MOV AL,DH | ;готовим старший байт |
| OUT LATCH2,AL | ;посылаем его |
| ;---пустой цикл, определяющий длительность нот | |
| TIME_IT: MOV AH,0 | ;фнукция чтения счетчика |
| INT 1AH | ;получаем значение счетчика |
| MOV BX,OFFSET BEAT | ;смещение строки длин нот |
| MOV CL,[BX][SI] | ;длительность текущей ноты |
| MOV CH,0 | ; |
| MOV BX,DX | ;младшее слово значения счетчика |
| ADD BX,CX | ;добавляем длину в импульсах |
| MOV END_NOTE,BX | ;запоминаем время окончания |
| TIME_CHECK: MOV AH,0 | ;функция чтения счетчика |
| INT 1AH | ;читаем счетчик |
| CMP DX,END_NOTE | ;сравниваем с нужным |
| JNE NOT_NOW | ;если неравно, то выходим |
| MOV SI,WHICH_NOTE | ;иначе, берем следующую ноту |
| INC SI | ;увеличиваем номер ноты |
| MOV WHICH_NOTE,SI | ;запоминаем его |
| JMP NEXT_NOTE | ;начинаем следующую ноту |
| ;---завершение процедуры | |
| NO_MORE: IN AL,PORT_B | ;берем статус порта B |
| AND AL,0FCH | ;выключаем динамик |
| OUT 61H,AL | ;возвращаем байт |
| MOV SOUND_NOW?,0 | ;восстанавливаем переменные |
| MOV FIRST_NOTE?,1 | ; |
| NOT_NOW: POP DS | ;восстанавливаем регистры |
| POP SI | ; |
| POP DI | ; |
| POP DX | ; |
| POP CX | ; |
| POP BX | ; |
| POP AX | ; |
| IRET | ;возврат из прерывания |
| MELODY2 ENDP |
<~-2.2.5 Генерация набора тонов.
Содержание
2.2.7 Создание плавного перехода тонов.-~>