Assembler - язык неограниченных возможностей

         

Команды DSP


E1h

Определить версию DSP. После посылки этой команды в 22Ch надо выполнить чтение двух байт из 22Ah. Первый байт — старший номер версии DSP:

1 — Sound Blaster

2 — Sound Blaster 2.0

3 — Sound Blaster Pro

4 — Sound Blaster 16

10h, N

8-битный непосредственный вывод байта N (число без знака) в DSP Команда впервые появилась на Sound Blaster.

14h, LL, LH

8-битный DMA-вывод блока байт без знака длиной L + 1 (LL — младший байт длины, LH — старший). По окончании вывода блока вызывается аппаратное прерывание. Команда впервые появилась на Sound Blaster.

1Ch

8-битный DMA-вывод с автоинициализацией. Размер блоков задается командой 48h, по окончании вывода каждого блока вызывается аппаратное прерывание. Чтобы остановить DMA-вывод с автоинициализацией, надо воспользоваться командами DAh или 14h. Команда впервые появилась на Sound Blaster 2.0.

90h

8-битный ускоренный DMA-вывод с автоинициализацией. По окончании каждого блока вызывается прерывание, но DSP не будет откликаться ни на какие другие команды. Чтобы выйти из этого режима, надо выполнить сброс и инициализацию DSP заново. Команда впервые появилась на Sound Blaster Pro.

D1h

Включить звук.

Команда впервые появилась на Sound Blaster и не действует, начиная с Sound Blaster 16 (за управление линиями ввода/вывода отвечает микшер).

D3h

Выключить звук.

Команда впервые появилась на Sound Blaster и не действует, начиная с Sound Blaster 16 (за управление линиями ввода/вывода отвечает микшер).

40h, ТС

Установить скорость передачи звука. ТС — старший байт величины 256 (1 000 000/rate), где rate — частота дискретизации (умножить на 2, если используется стереозвук).

Команда впервые появилась на Sound Blaster.

41h, RH, RL

Установить частоту дискретизации (RH — старший байт, RL — младший). Частоту не надо умножать на два в случае стереозвука (допустимые значения — от 5 000 до 45 000 Hz). Частота автоматически округляется до соответствующего целого ТС, как в команде 40h. Команда впервые появилась на Sound Blaster 16.


48h, SL, SH

Установить размер блока для DMA-вывода (SL — младший байт, SH — старший. SH:SL — число байт в блоке минус 1). По окончании каждого блока будет вызываться прерывание.

Команда впервые появилась на Sound Blaster 2.0.

D0h

Приостановить 8-битный DMA. Команда впервые появилась на Sound Blaster.

D4h

Продолжить 8-битный DMA после D0h. Команда впервые появилась на Sound Blaster.

DAh

Закончить 8-битный DMA (после окончания пересылки текущего блока). Команда впервые появилась на Sound Blaster 2.0.

B?h, BM, LL, LH

16-битный DMA-режим. Младшие четыре бита команды B?h выбирают тип режима:

бит 3: 1/0 — ввод/вывод

бит 2: 1/0 — обычный/с автоинициализацией

бит 1: 1/0 — FIFO включено/выключено

бит 0: 0

Команда ВМ выбирает вариант режима:

бит 5: 1/0 — стерео/моно

бит 4: 1/0 — данные рассматриваются как числа со знаком/без знака

LL — младший байт, LH — старший байт числа слов в блоке минус один.

Команда впервые появилась на Sound Blaster 16.

C?h, BM, LL, LH

8-битный DMA-режим.

Эти команды в точности совпадают с B?h, только они опиcывают 8-битную передачу данных и LH:LL — число байт, а не слов в блоке.

Команда впервые появилась на Sound Blaster 16.

D5h

Приостановить 16-битный DMA. Команда впервые появилась на Sound Blaster 16.

D6h

Продолжить 16-битный DMA после D5h. Команда впервые появилась на Sound Blaster 16

D9h

Закончить 16-битный DMA (по окончании пересылки текущего блока). Команда впервые появилась на Sound Blaster 16.

Итак, для вывода звука через звуковую плату может использоваться один из трех режимов. Прямой вывод (команда 10h), когда программа должна сама с нужной частотой посылать отдельные байты из оцифрованного звука в DSP; простой DMA-режим, когда выводится блок данных, после чего вызывается прерывание; и DMA с автоинициализацией, когда данные выводятся непрерывно и после вывода каждого блока вызывается прерывание. Именно в этом порядке увеличивается качество воспроизводимого звука. Так как мы пока не умеем работать с DMA, рассмотрим первый способ.



Чтобы вывести оцифрованные данные с нужной частотой в DSP, придется перепрограммировать канал 0 системного таймера на требуемую частоту и установить собственный обработчик прерывания 08h. При этом будет нарушена работа системных часов, хотя можно не выключать совсем старый обработчик, а передавать ему управление примерно 18,2 раза в секунду, то есть, в частности, при каждом 604-м вызове нашего обработчика на частоте 11 025 Hz. Покажем, как это сделать на примере простой программы, которая именно таким способом воспроизведет файл c:\windows\media\tada.wav (или c:\windows\tada.wav, если вы измените соответствующую директиву EQU в начале программы).

; wavdir.asm ; воспроизводит файл c:\windows\media\tada.wav, не используя DMA, ; нормально работает только под DOS в реальном режиме ; (то есть не в окне DOS (Windows) и не под EMM386, QEMM или другими ; подобными программами)

FILESPEC equ "c:\windows\media\tada.wav" ; имя файла tada.wav с ; полным путем (замените на c:\windows\tada.wav для ; старых версий Windows) SBPORT equ 220h ; базовый порт звуковой платы (замените, если у вас он ; отличается) .model tiny .code .186 ; для pusha/popa org 100h ; СОМ-программа start: call dsp_reset ; сброс и инициализация DSP jc no_blaster mov bl,0D1h ; команда DSP D1h call dsp_write ; включить звук call open_file ; открыть и прочитать tada.wav call hook_int8 ; перехватить прерывание таймера mov bx,5 ; делитель таймера для частоты 22 050 Hz ; (на самом деле соответствует 23 867 Hz) call reprogram_pit ; перепрограммировать таймер

main_loop: ; основной цикл cmp byte ptr finished_flag,0 je main_loop ; выполняется, пока finished_flag ; равен нулю mov bx,0FFFFh ; делитель таймера для частоты 18,2 Hz call reprogram_pit ; перепрограммировать таймер call restore_int8 ; восстановить IRQ0 no_blaster: ret

buffer_addr dw offset buffer ; адрес текущего играемого байта old_int08h dd ? ; старый обработчик INT 08h (IRQ0) finished_flag db 0 ; флаг завершения filename db FILESPEC,0 ; имя файла tada.wav с полным путем



; обработчик INT 08h (IRQ0) ; посылает байты из буфера в звуковую плату int08h_handler proc far pusha ; сохранить регистры, cmp byte ptr cs:finished_flag,1 ; если флаг уже 1, je exit_handler ; ничего не делать, mov di,word ptr cs:buffer_addr ; иначе: DI = адрес текущего байта mov bl,10h ; команда DSP 10h call dsp_write ; непосредственный 8-битный вывод mov bl,byte ptr cs:[di] ; BL = байт данных для вывода call dsp_write inc di ; DI = адрес следующего байта cmp di,offset buffer+27459 ; 27 459 - длина звука в tada.wav, jb not_finished ; если весь буфер пройден, mov byte ptr cs:finished_flag,1 ; установить флаг в 1, not_finished: ; иначе: mov word ptr cs:buffer_addr,di ; сохранить текущий адрес exit_handler: mov al,20h ; завершить обработчик аппаратного ; прерывания, out 20h,al ; послав неспецифичный EOI ; (см. гл. 5.10.10) рора ; восстановить регистры iret int08h_handler endp

; процедура dsp_reset ; сброс и инициализация DSP dsp_reset proc near mov dx,SBPORT+6 ; порт 226h - регистр сброса DSP mov al,1 ; запись единицы в него начинает out dx,al ; инициализацию mov cx,40 ; небольшая пауза dsploop: in al,dx loop dsploop mov al,0 ; запись нуля завершает инициализацию out dx,al ; теперь DSP готов к работе ; проверить, есть ли DSP вообще add dx,8 ; порт 22Eh - состояние буфера чтения DSP mov cx,100 check_port: in al,dx ; прочитать состояние буфера and al,80h ; проверить бит 7 jz port_not_ready ; если ноль - порт еще не готов, sub dx,4 ; иначе: порт 22Аh - чтение данных из DSP in al,dx add dx,4 ; и снова порт 22Eh, cmp al,0AAh ; если причиталось число AAh - ; DSP присутствует и действительно ; готов к работе, je good_reset port_not_ready: loop check_port ; если нет - повторить проверку 100 раз bad_reset: stc ; и сдаться ret ; выход с CF = 1 good_reset: clc ; если инициализация прошла успешно, ret ; выход с CF = 0 dsp_reset endp

; процедура dsp_write ; посылает байт из BL в DSP dsp_write proc near mov dx,SBPORT+0Ch ; порт 22Ch - ввод данных/команд DSP write_loop: ; подождать готовности буфера записи DSP in al,dx ; прочитать порт 22Ch and al,80h ; и проверить бит 7, jnz write_loop ; если он не ноль - подождать еще, mov al,bl ; иначе: out dx,al ; послать данные ret dsp_write endp



; процедура reprogram_pit ; перепрограммирует канал 0 системного таймера на новую частоту ; Ввод: ВХ = делитель частоты reprogram_pit proc near cli ; запретить прерывания mov al,00110110b ; канал 0, запись младшего и старшего байт ; режим работы 3, формат счетчика - двоичный out 43h,al ; послать это в регистр команд первого таймера mov al,bl ; младший байт делителя - out 40h,al ; в регистр данных канала 0 mov al,bh ; и старший байт - out 40h,al ; туда же sti ; теперь IRQO вызывается с частотой ; 1 193 180/ВХ Hz ret reprogram_pit endp

; процедура hook_int8 ; перехватывает прерывание INT 08h (IRQ0) hook_int8 proc near mov ax,3508h ; AH = 35h, AL = номер прерывания int 21h ; получить адрес старого обработчика mov word ptr old_int08h,bx ; сохранить его в old_int08h mov word ptr old_int08h+2,es mov ax,2508h ; AH = 25h, AL = номер прерывания mov dx,offset int08h_handler ; DS:DX - адрес обработчика int 21h ; установить обработчик ret hook_int8 endp

; процедура restore_int8 ; восстанавливает прерывание INT 08h (IRQ0) restore_int8 proc near mov ax,2508h ; AH = 25h, AL = номер прерывания lds dx,dword ptr old_int08h ; DS:DX - адрес обработчика int 21h ; установить старый обработчик ret restore_int8 endp

; процедура open_file ; открывает файл filename и копирует звуковые данные из него, ; считая его файлом tada.wav, в буфер buffer open_file proc near mov ax,3D00h ; AH = 3Dh, AL = 00 mov dx,offset filename ; DS:DX - ASCIZ-имя файла с путем int 21h ; открыть файл для чтения, jc error_exit ; если не удалось открыть файл - выйти mov bx,ax ; идентификатор файла в ВХ mov ax,4200h ; АН = 42h, AL = 0 mov сх,0 ; CX:DX - новое значение указателя mov dx,38h ; по этому адресу начинаются ; данные в tada.wav int 21h ; переместить файловый указатель mov ah,3Fh ; АН = 3Fh mov cx,27459 ; это - длина звуковых данных ; в файле tada.wav mov dx,offset buffer ; DS:DX - адрес буфера int 21h ; чтение файла ret error_exit: ; если не удалось открыть файл mov ah,9 ; АН = 09h mov dx,offset notopenmsg ; DS:DX = сообщение об ошибке int 21h ; открыть файл для чтения int 20h ; конец программы notopenmsg db "Ошибка при открытии файла",0Dh,0Ah,'$' open_file endp

buffer: ; здесь начинается буфер длиной 27 459 байт end start

Если вы скомпилировали программу latency.asm из главы 5.10.5 и попробовали запустить ее в разных условиях, то могли заметить, что под Windows 95, а также под EMM386 и в некоторых других ситуациях пауза между реальным срабатыванием прерывания таймера и запуском обработчика может оказаться весьма значительной и варьироваться с течением времени, так что качество звука, выводимого нашей программой wavdir.asm, окажется под EMM386 очень плохим, а в DOS-задаче под Windows 95 вообще получится протяжный хрип. Чтобы этого избежать, а также чтобы указывать точную скорость оцифровки звука и выводить 16-битный звук, нам надо обратиться к программированию контроллера DMA (пример программы, выводящей звук при помощи DMA, см. в конце следующей главы).


Содержание раздела