下面给出的程序例是一个音乐程序,与第四章中的程序例不同,这个程序可以产生出"背景音乐",即音乐的播放不会影响到机器进行其它工作。你可以一面听音乐一面格式化磁盘,或者用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"中断服务程序来工作的。