问题:

乐谱的 1、2、3、4、5、6、7,加上高低音可以谱出动听的曲子,请编写程

序,使计算机能够播放歌曲。

[分析]

播放歌曲意味着让计算机发声,声音从 PC 机内的扬声器发出,所以这个问题将与

硬件扬声器电路有关。

[解答]

解决这一编程问题,让我们首先简单了解一下计算机发声的原理。在PC 机的系统板上装有定时与计数器 8253 芯片,还有 8255 可编程并行接口芯片,由它们组成的硬件电路

可用来产生 PC 机内扬声器的声音,对于 286、386、486、586 等 PC 微机,由于采用了超大

规模集成电路,因而看不到这些芯片,它们均集成在外围电路芯片上了。

当我们操作计算机时,常常听到的发声,就是由软件控制这些电路而产生的。声音的

长短和音调的高低,均可由程序进行控制。在扬声器电路中,定时器的频率决定了扬声器发

音的频率,所以可通过设定定时器电路的频率来使扬声器发出不同的声音。对定时器电路进

行频率设定时,首先对其命令寄存器(口地址为 0x43)写命令字,如写入     0xb6,这可用

outporb(0x43,0xb6);来实现,则表示选择该定时器的第二个通道,计数频率先送低 8 位(二

进制),后送高 8 位。接着用口地址 0x42 送频率计数值,先送低 8 位,后送高 8 位,即用

outportb(0x42,低 8 位频率计数值)和 outportb(0x42,高 8 位频率计数值)来实现。通过这两

步使定时器电路产生一系列方波信号,此信号是否能推动扬声器发音,还要看由 8255 产生

的门控信号和送数信号是否为 1,而它们也可编程,口地址为 0x61。为了不影响 8255 口地

址 61H 中的其他高位,应先输入口地址 6lH 的现有值 bits,即用 bits= inportb(0x61)来实现,

然后就可用 outportb(0x61,bits|3)来允许发声,而用 outportb(0x61,bits&0xfc)来禁止发声,

且不改变 8255 其它位原来的值,关于这方面的详细内容可以参阅 IBM PC/XT 接口技术方面

书籍有关内容。

5.1 声音函数

编写音乐程序播放歌曲,最简单的方法是可以直接使用 TURBO C 在 dos.h 中提供的有

关发声的函数 sound()和 nosound()。sound()函数用于产生声音,其原型如下:

void sound(unsigned freqncy);

该函数的入口参数为扬声器要产生声音的频率。

与 sound()函数相反,nosound ()函数用于关闭扬声器,其原型为:

void nosound(void);

该函数没有入口和出口参数,它只是简单地把口地址 61H 中的低 2 位清 0。

在利用函数 sound 产生指定频率的声音后,一般要过一段时间后再调用函数 nosound 关

闭扬声器,这样我们才能清楚地听到一个声音。如果扬声器刚打开就关闭,我们是很难听到

一个声音的。某个频率的声音延续时间的长短是重要的,它将直接影响音响效果。这需要使

用 TURBOC 提供了专门的延时函数 delay,其原型说明如下:

void delay (unsigned milliseconds);

该函数中断程序的执行,中断的时间由 milliseconds 指定。

例程 3-36:该程序每间隔 10000 milliseconds pc 扬声器发出不同频率的声音,直到频率大于

5000hz。

#incl?<dos.h>

main()

{

int freq;

for(freq=50;freq<5000;freq+=50)

{

sound (freq);

delay(10000);

}

nosound();

}

如果不能使用上述现成的函数 sound()和 nosound(),当然我们也可以采用上节中的方法,

用  I/O  接口的输入输出函数,自己编写产生声音和关闭声音的函数。下面可供参考的函数

SOUND()与 TURBOC 提供的产生声音函数 sound()的算法类似:首先函数 SOUND()中使用

了一个由一个整数和两个字符组成的联合,其目的在于方便地把一个 16 位数分解成两个 8

位数。为了打开扬声器,需要把口地址 61H 的低 2 位置位,但又不能影响其他高位,为此,

先输入口地址 61H 中的现有值,与 3 逻辑或后再输出到口地址 61H。

void SOUND(unsigned freqncy)

{

union   {

unsigned divisor;

unsigned char c[2];

} tone;/*    定义由—个整数和两个字符组成的联合 */

tone.divisor=119328/freqncy; /* 计算该频率对应的定时器计数值  */

outportb(0x43,0xb6);  /*     通知定时器采用新的计数 */

outportb(0x42,tone.c[0]); /* 计数低字节先送到定时器 */

outportb(0x42,tone.c[1]);  /*  计数高字节后送到定时器 */

outportb(0x61, inportb(0x61) | 3 );   /*             使定时器到喇叭的输出有效             */

}

如下供参考的函数 NOSOUND(),为了不影响口地址 61H 中的其他高位,应先输入口地

址 6lH 的现有值.在屏蔽掉低 2 位后再输出到口地址 61H。

void NOSOUND(void)

{

outportb(0x61,inportb(0x61) & 0xfc));      /*  使定时器到喇叭的输出无效 */

}

5.2 计算机乐谱

表 3-18 是频率与音阶的对照表。我们可以通过该表编制出自己的驱动程序。编制乐谱

程序一般在原乐谱的基础上添加一些控制字符来完成。如:

。。。     。。。。。

_11176 3232 6 2

就是孟庭苇演唱的“羞答答的玫瑰静俏俏地开”的第一句歌词的乐谱。

在计算机中可以表述为:

600 H1 0.5 H1 0.5 H1 0.5 M7 0.5 M6 1 H3 1 H2 0.5 H3 0.5 H2 0.5 M6 0.5 H2 2

第一个为音长基准,一般为 300,600,900,1200。后面字 H1 表示高音的 1,音阶的

设置如下:

最高音:在音符前加“E”;

高音:在音符前加“计”;

中音:在音符前加“M”;

低音:在音符前加“L”;

再后面的字为节拍数,其中的 0.5 表示节拍数,每个音的音长=音长基数×\?X拍数,如

第一个“1”的音长为 600×0.5=300。

表 3-18 频率与音阶的对照表

低音

1

2

3

4

5

6

7

频率

131

147

165

176

196

220

247

中音

1

2

3

4

5

6

7

频率

262

296

330

349

392

440

494

高音

1

2

3

4

5

6

7

频率

523

587

659

699

784

880

988

最高音

1

2

3

4

5

6

7

频率

1047

1175

1319

1397

1568

1760

1976

知道了这些知识,就容易编制—个乐谱驱动程序。思路是将各个频率存储在一个二维数

组中,根据音阶字符、音符和节拍数,得到发音的音长,使用 sound 函数发音,使用 delay

函数控制。

5.3 问题实现

5.3.1  调用 sound()和 unsound()

下面的程序(例程 3-37)先开辟两个数组 freq[96]和 dely[96]分别用于存储声音的频

率和延时。采用图形方式,利用 printtext()函数在屏幕上分别显示字符串 Welcome !","Please

press any key to start ......","Enjoy yourself !”,"Press any key to end !"  和  "Thank you! Bye

Bye ......"

printtext()定义如下:

void printtext(unsigned char *temp[],int i)

{

setcolor(4);                            //设置颜色为 red

settextstyle(TRIPLEX_FONT,HORIZ_DIR,3); // 设置字符的字体,方向和大小

outtextxy(100,40+i*50,temp[i]);         // 显示字符串

}

另外,在此程序中还调用了 conio.h       中的函数 kbhit(),用于判断是否有键按下,当没有键按下

时返回 0。

/*例程 3-37*/

#incl?<dos.h>

#incl?<graphics.h>

void printtext(unsigned char *temp[],int i);     //   用于在屏幕上显示字符串

main()

{

int i,graphdriver,graphmode;

unsigned char *temp[4];

int freq[96]={784,660,588,660,523,523,

588,494,440,523,392,392,

330,392,440,523,784,440,523,392,

784,1048,880,784,660,784,588,588,

588,660,494,440,392,440,523,588,

330,523,440,392,440,523,392,392,

660,784,494,588,440,523,392,392,

330,392,330,392,392,440,494,588,440,440,392,440,

523,588,784,660,588,660,588,523,440,392,

330,523,440,523,440,392,330,392,440,523,

392,392,660,784,588,660,588,523,494,440,784,784};

int dely[96]={25,50,12,12,50,50,

25,50,12,12,50,50,

50,38,12,38,12,12,12,25,

38,12,12,12,12,12,50,50,

38,12,25,25,38,12,25,25,

25,25,12,12,12,12,50,50,

38,12,25,25,12,12,50,25,

12,12,12,12,12,12,12,12,50,25,12,12,

38,12,25,25,25,12,12,25,12,12,

50,50,12,12,12,12,12,12,12,12,

50,25,12,12,12,12,12,12,25,25,50,50};

graphdriver=DETECT;

graphmode=0;

temp[0]="Welcome !";

temp[1]="Please press any key to start ......";

temp[2]="Enjoy yourself !";

temp[3]="Press any key to end !";

temp[4]="Thank you! Bye Bye ......";

initgraph(&graphdriver,&graphmode,"");       //系统初始化

cleardevice();                                                          //清除屏幕

settextjustify(LEFT_TEXT,CENTER_TEXT);   //设置字符排列方式

for(i=0;i<2;i++)

printtext(temp,i);

getch();

for(i=2;i<4;i++)

printtext(temp,i);

i=0;

while(i<96&&!kbhit())

{

sound(freq[i]);                       //    扬声器根据频率发声

delay(1100*dely[i]);                   //  声音延时

i++;

}

nosound();                             //     关闭扬声器

printtext(temp,4);

getch();

closegraph();                                                      //  关闭图形模式

}

void printtext(unsigned char *temp[],int i)

{

setcolor(4);                                                          //  设置颜色

settextstyle(TRIPLEX_FONT,HORIZ_DIR,3); //       设置字符的字体,方向和大小

outtextxy(100,40+i*50,temp[i]);            // 在所指定的坐标出显示字符串

}

5.3.2  调用 inportb()和 outportb()

这个程序(例程 3-38)利用 3.5.2 节的 SOUND()和 UNSOUND()改写前一实现,播放一段不同

的曲目。

/*例程 3-38*/

#incl?<dos.h>

#incl?<graphics.h>

void printtext(unsigned char *temp[],int i);

void SOUND(unsigned freqncy);

void NOSOUND(void);

main()

{

int i,graphdriver,graphmode;

unsigned char *temp[4];

int freq[87]={196,262,262,262,330,294,262,294,330,294,262,

330,394,440,440,394,330,330,262,294,262,294,

330,294,262,230,230,196,262,440,394,330,330,

262,294,262,294,440,394,330,330,394,440,523,

394,330,330,262,294,262,294,330,294,262,230,

230,196,262,440,394,330,330,262,294,262,294,

440,394,330,330,394,440,523,394,330,330,262,

294,262,294,330,294,262,230,230,196,262};

int dely[87]={25,38,12,25,25,38,12,25,12,12,56,25,25,50,25,

38,12,12,12,38,12,25,12,12,38,12,25,25,100,25,

38,12,12,12,38,12,25,25,38,12,25,25,100,25,38,

12,12,12,38,12,25,12,12,38,12,25,25,100,25,38,

12,12,12,38,12,25,25,38,12,25,25,100,25,38,12,

12,12,38,12,25,12,12,38,12,25,25,100

}

graphdriver=DETECT;

graphmode=0;

temp[0]="Welcome !";

temp[1]="Please press any key to start ......";

temp[2]="Enjoy yourself !";

temp[3]="Press any key to end !";

temp[4]="Thank you! Bye Bye ......";

initgraph(&graphdriver,&graphmode,"");

cleardevice();

settextjustify(LEFT_TEXT,CENTER_TEXT);

for(i=0;i<2;i++)

printtext(temp,i);

getch();

for(i=2;i<4;i++)

printtext(temp,i);

i=0;

while(i<87&&!kbhit())

{

SOUND(freq[i]);

delay(1100*dely[i]);

i++;

}

NOSOUND();

printtext(temp,4);

getch();

closegraph();

}

void printtext(unsigned char *temp[],int i)

{

setcolor(4);

settextstyle(TRIPLEX_FONT,HORIZ_DIR,3);

outtextxy(100,40+i*50,temp[i]);

}

void SOUND(unsigned freqncy)

{

union  {     /*    定义由-个整数和两个字符组成的联合  */

unsigned divisor;

unsigned char c[2];

} tone;

tone.divisor=119328L/freqncy;  /*                                                      计算该频率对应的定时器计数值                                                    */

outportb(0x43,0xb6);   /*    通知定时器采用新的计数 */

outportb(0x42,tone.c[0]); /*  计数低字节先送到定时器 */

outportb(0x42,tone.c[1]);         /*      计数高字节后送到定时器 */

outportb(0x61,inportb(0x61) | 3 );  /*                                              使定时器到喇叭的输出有效                                                               */

}

void NOSOUND(void)

{

outportb(0x61,(inportb(0x61) & 0xfc)); /*           使定时器到喇叭的输出无效             */

}

c语言经典教程:发声技术相关推荐

  1. c语言教程文库,C语言经典教程

    C语言经典教程 C语言经典教程_谭浩强C语言_电子版 (2)本文由thuep贡献doc文档可能在WAP端浏览体验不佳.建议您优先选择TXT,或下载源文件到本机查看.靳润昭C 语言教程讲义2001 年 ...

  2. c语言经典解决实际程序,C语言经典教程1讲.ppt

    <C语言经典教程1讲.ppt>由会员分享,可在线阅读,更多相关<C语言经典教程1讲.ppt(48页珍藏版)>请在人人文库网上搜索. 1.C程序设计,主讲人:任祖华,2,本课程学 ...

  3. c语言程序设计实践教程编程题8.3,C语言程序设计教程(21世纪计算机科学与技术实践型教程)...

    <C语言程序设计教程>是根据 教育部高等学校计算机科学与技术教学指导委员会非 计算机专业计算机基础课程教学指导分委员会提出的 <非计算机专业计算机基础课程教学基本要求>和< ...

  4. 【C语言|菜鸟教程】100道进阶经典例题详细解答(实例一)

    [C语言|菜鸟教程]100道基础经典例题详解(实例一) 文章目录 前言 一.题目描述 二.题目分析 三.解题 (1)解题方法一 1. 思路 2. 程序运行 3. 程序运行结果 (2)解题方法二 1. ...

  5. 菜鸟教程-C语言经典100示例(后50)

    注:接上篇(菜鸟教程-C语言经典100示例(前50)) 51,学习使用按位与 &. 52,学习使用按位或 |. 53,学习使用按位异或 ^. 54,取一个整数a从右端开始的4-7位. 55,学 ...

  6. c语言好看的界面设计图片,C语言界面设计的经典教程.ppt

    C语言界面设计的经典教程 private void cmbDisplayStyle_SelectedIndexChanged(object sender, EventArgs e) { string ...

  7. 菜鸟教程-C语言经典100示例(前50)

    这两天正在看基础的算法,在做的时候肯定不能避免的接触了菜鸟上的经典100例,但是使用的时候发现并不是那么的方便,因为它只是给了序号,如果查找某一问题的话很不方便,所以做了这篇博客,希望给自己和其他人带 ...

  8. 《Adobe Illustrator CS4中文版经典教程》目录—导读

    版权声明 Adobe Illustrator CS4中文版经典教程 Authorized translation from the English language edition, entitled ...

  9. c语言超长编程程序,全国青少年软件编程等级考试C语言经典程序题10道五

    全国青少年软件编程等级考试C语言经典程序题10道五 [程序41] 题目:学习static定义静态变量的用法 1.程序分析: 2.程序源代码: #include "stdio.h" ...

最新文章

  1. 又一起“删库”:链家程序员怒删公司 9TB 数据,被判 7 年
  2. 用Java获取文件的MD5校验和
  3. 外圆内方与外方内圆的奇妙变换!
  4. nehe教程混合这一节需要注意的两个地方
  5. ceph操作相关命令整合
  6. C++模板剖析:函数模板、类模板解析
  7. 2019.4.26今日任务
  8. Android Studio使用jni、so库
  9. linux下.a/.so/.la目标库区别-转
  10. horizon服务主要模块_得助智能:智能外呼机器人有哪些模块功能层级?
  11. Java8日期类型常见用法总结
  12. CNN网络实现垃圾分类
  13. WxPython 界面利用pubsub与线程通讯
  14. 手机罗盘(指南针)校准方法
  15. QoE是什么?QoE对视频流供应商的重要性
  16. 如何让你的代码好维护
  17. CSS3_01_圆角_边框_渐变_字体
  18. 实现支付宝全额退款和部分退款功能
  19. Python爬虫实战(七):某讯较真辟谣小程序爬虫
  20. 轻松制作儿童趣味算术软件 - imsoft.cnblogs

热门文章

  1. node医保药品集中采购平台-采购系统的设计与实现 毕业设计-附源码271542
  2. oracle 表分区,根据时间按月分区做一次记录
  3. 获利委托和止损委托_将新的获利控制和见解扩展到发布者
  4. 几种常见硬件(PCB板子)开源协议具体应用
  5. 移动SIM卡:从迷你卡、微型卡到纳米卡的自然演化
  6. iPhone 3G/I代iPhone大PK
  7. 处理vue异步请求数据动态从父组件向子组件赋值时,子组件无法获取到值问题
  8. 2022-2028全球求职网站行业调研及趋势分析报告
  9. 如何设置微软拼音状态条上的图标
  10. 信息系统项目管理师(软考高项)备考经验与心得