我们在前面编制的程序只能发出频率一定的"纯音",但有时我们希望喇叭里能发出"噪音",即将许多种不同频率的声音无规律地混杂在一起发出,这样我们可以使喇叭产生"音响效果",如枪炮声、爆炸声等。下面的程序可以产生"白噪声"。
code segment assume cs:code org 100h main proc far mov dx,140h ;设置随机数的种子 mov bx,0fffh ;设置外循环数 in al,61h ;取61H端口当前值 and al,11111100b ;低2位置0 sound: xor al,2 ;改变Bit1位 out 61h,al ;输出至61H端口 add dx,9248h ;随机数种子加9248H mov cl,3 ;准备右循环移位数 ror dx,cl ;随机数种子右循环移位 mov cx,dx ;所得随机数置入CX寄存器 and cx,0fffh ;保证CX中的随机数不大于0FFFH or cx,10h ;保证CX中的随机数不小于10H delay: loop delay ;以随机数为循环计数循环 dec bx ;外循环计数减1 jnz sound ;循环发声 and al,11111100b ;关闭定时器 out 61h,al int 20h ;结束程序 main endp code ends end main
① “白噪声”这个词源自光学名词“白光”,白光是由多种光组成的,相应的,由多种频率的声音组成的噪声就称为“白噪声”。
程序中多了一条指令--ROR,这是一个移位指令,但它和前面讲的SHL不同,它可以完成循环移位:
助记符:ROR(Rotate right)
用 途:将寄存器或内存中的数据循环右移
格 式:ROR 寄存器,1
ROR 寄存器,CL
ROR 存储单元,1
ROR 存储单元,CL
执 行:寄存器或内存中的数据各个位顺次右移,移出去的数位返回到寄存器或存储单元的最左端
图4-5(a)表示了这条指令的动作情况。与这条指令功能相反的指令是:
助记符:ROL(Rotate left)
用 途:将寄存器或内存中的数据循环左移
格 式:ROL 寄存器,1
ROL 寄存器,CL
ROL 存储单元,1
ROL 存储单元,CL
执 行:寄存器或内存中的数据各个位顺次左移,移遇去的数位返回到寄存器或存储单元的最右端
图4-5(b)表示了此指令的动作情况。
WHITE程序的原理十分简单,我们在前面编制过通过61H端口的bit1位发声的程序,那些程序都是用LOOP指令控制声音频率,其循环次数是固定的。如果用一个随机数作循环计数,则产生的声音频率也是随机的,这就相当于将许多频率无规律地混合,从而产生噪音。
如何产生一个随机数?WHITE程序采用的是"种子"算法。即首先在DX寄存器中放入初值140H,这个初值称为"种子"(SEED),而后每次需要一个随机数时就将"种子"加上9248H,并循环右移三位,这样就产生了一个随机数,保留这个随机数作为新的"种子"以备后用。至于指令AND CX,0FFFH和OR CX,10H是用于限制随机数的取值范围,从而使声音频率在一定范围内变化。程序开始设定在BX中的循环计数值用于控制噪音的长短,适当减小此值可以听到一声爆炸声,下面的程序产生连续的爆炸声,也就是枪声。
GUN.ASM code segment assume cs:code org 100h gun proc far mov cx,20 ;发出20声枪声 shot: call shoot ;调用噪声子过程 push cx ;枪声数存入堆栈 mov cx,0c000h ;设置循环次数 silent: loop silent ;空循环,产生枪声之间的间隔 pop cx ;取回枪声计数 loop shot ;发下一声枪声 int 20h ;结束程序 gun endp shoot proc near ;噪声子程序 push cx ;保存CX寄存器 mov dx,140h ;设置随机数的种子 mov bx,0ffh ;设置每声枪响的长度 in al,61h ;取61H端口当前值 and al,11111100b ;低2位置0 sound: xor al,2 ;改变Bit1位 out 61h,al ;输出至61H端口 add dx,9248h ;随机数种子加9248H mov cl,3 ;准备右循环移位数 ror dx,cl ;随机数种子右循环移位 mov cx,dx ;所得随机数置入CX寄存器 delay: and cx,0fffh ;保证CX中的随机数不大于0FFFH or cx,10h ;保证CX中的随机数不小于10H loop delay ;以随机数为循环计数循环 dec bx ;外循环计数减1 jnz sound ;循环发声 and al,11111100b ;关闭定时器 out 61h,al pop cx ;恢复CX寄存器 ret ;子过程返回 shoot endp code ends gun end
注意这两个程序中的循环都是用LOOP指令完成的,因此在不同的机器上循环的计数值要用不同的值。笔者使用一台装有80MHzCyrix486DX2CPU的电脑,如果使用其它的CPU,需要修改计数值才能获得最好的效果。
在这一章中,我们详细地谈了精确控制频率和时间的方法,至此我们已经将常用的发声程序设计技术介绍完了,希望读者能很好地掌握讲过的指令、硬件知识和程序设计技巧。不过直到现在止我们还没有掌握最高境界的发声程序设计技术,有一个名为《DARKSEED》的电子游戏,这个游戏可以从喇叭里发出很清楚的语言。设计这样的程序还需要其它一些知识,我们现在就不再详细介绍了。如果有兴趣可以找一些资料自己研究。