下面给出的程序例是一个音乐程序,与第四章中的程序例不同,这个程序可以产生出"背景音乐",即音乐的播放不会影响到机器进行其它工作。你可以一面听音乐一面格式化磁盘,或者用WPS写文章。总之,音乐的产生与用户的工作是同时的,而且互不干扰。
BKMUSIC.ASM
code segment
assume cs:code,ds:code
org 100h
main proc far
jmp install ;跳转至安装部分
freq dw 0,4552,4029,3617,3415 ;各音符频率表
dw 322,330,422,871
note db 3,4,2,4,1,4,2,4,3,4,3,4, 3,8,2,4,2,4,2,8 ;乐谱
db 3,4,6,4,6,8,3,4,2,4,1,4, 2,4,3,4,3,4,3,4
db 3,4,2,4,2,4,3,4,2,4,1,16
dw -1
nxtnote dw ? ;将要演奏的音符在乐谱中的位置
delay db ? ;当前音符应持续的时间
new_1ch: ;新的1CH中断服务程序
push ax ;保存寄存器
push bx
push si
cmp byte ptr cs:[delay],0 ;当前的音符演奏完了吗?
jz play_next ;若当前的音符已奏完,转PLAY_NEXT
dec byte ptr cs:[delay] ;将当前音符的持续时间减1
jmp return ;退出中断服务程序
play_next:
in al,61h ;关闭时间
and al,0fch
out 61h,al
mov bx,cs:[nxtnote] ;取得将要演奏的音符在乐谱中的位置
shl bx,1
mov ax,word ptr cs:note[bx] ;从乐谱中取出音符及时间
cmp ax,-1 ;乐谱已经演奏完了吗?
jz play_end ;若乐谱已结束,转PLAY_END
mov cs:[delay],ah ;将这个音符的时间送入DELAY
inc word ptr cs:[nxtnote] ;NXTNOTE指向下一个音符
xor ah,ah ;将AL中的音符转成16位数据
shl ax,1 ;将音符乘2,准备在频率表中取N值
mov si,offset freq ;SI寄存器指向频率表首
add si,ax ;加上AX中的偏移量
mov ax,word ptr cs:[si] ;取出N值并输出至定时器
out 42h,al
mov al,ah
out 42h,al
in al,61h ;启动定时器发声
or al,03h
out 61h,al
return:
pop si ;恢复寄存器
pop bx
pop ax
iret ;退出中断服务程序
play_end:
mov cs:[curnote],0 ;若乐谱已奏完,从头开始演奏
jmp return ;退出中断服务程序
install:
mov dx,offset new_1ch ;设置新的1CH中断向量
mov ax,251ch
int 21h
mov dx,offset install ;结束程序并驻留
int 27h
main endp
code ends
end main
CPU之所以能够一边放音乐一边做其它工作,还有一个重要的原因就是定时器的发声通道可以独立工作。只要CPU给定时器设定了参数N并打开了定时器,则声音就会连续不断的发出,此时CPU就可以进行其它工作而不必再参与声音的产生。剩下的工作就是每隔55MS关照一下正在播放的音符,看看是否应该播放下一个音符了。
至此有关内存驻留技术的讨论可以告一段落,注意内存驻留技术最关键的两点--程序驻留与中断驱动。本书给出的程序例利用了系统一些很有特色的中断,实际上驻留内存的程序可以由任意的中断来驱动,比如DOS系统中的磁盘高速缓存程序SMARTDRV就是重编了"13H"中断服务程序来工作的。