ITEEDU

我们在前面编制的程序只能发出频率一定的"纯音",但有时我们希望喇叭里能发出"噪音",即将许多种不同频率的声音无规律地混杂在一起发出,这样我们可以使喇叭产生"音响效果",如枪炮声、爆炸声等。下面的程序可以产生"白噪声"。

WHITE.ASM
        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)表示了此指令的动作情况。


图4-5 循环移位指令的执行情况

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》的电子游戏,这个游戏可以从喇叭里发出很清楚的语言。设计这样的程序还需要其它一些知识,我们现在就不再详细介绍了。如果有兴趣可以找一些资料自己研究。