看到Beep()就会想起上世纪90年代初在8086的机器或者稍后的286、386机器上用解释型Basic编简谱玩的情景,这便是那个声霸卡还没上市的年代里,几乎是人们在PC上唯一可编的声音了。
Beep的函数原型:
BOOL Beep(
DWORD dwFreq; /*指定要发出的频率(HZ)*/
DWORD dwDuration; /*指定发音的时长,以毫秒为单位*/
);
通常先把do re mi ...的频率预定义好,再照着简谱把频率和时长编入两个数组或一个结构体数组。
#define do 523
#define re 578
#define mi 659
#define fa 698
#define so 784
#define la 880
#define si 988
......
int freq[] = { do,re,mi,do,do,re,mi,do,...}
int duration[] = {300,300,300,300,300,300,300,300,...}
最后用Beep()循环输出。
对此我做了些小改进:把频率定入一个结构体数组便于反复调用;简谱放入一个vector容器就不用管谱子的长短不用管数组的下标,只要按顺序push_back()每一个音符,还能随时插入删除某个音符,重复的小节可以用循环多次输出以缩短代码长度;简谱转代码时“所见即所得”看到什么数字就写入什么数字;音符的时长也以“拍”为单位,每个小节后加个空行便于检查每节的总时长。源代码如下:
#include
#include
#include
#include
using namespace std;
#define PAI 300 //一拍的时长,可以自行调整
struct tone{
short a; short d; short g; //中音;低音;高音
short b; short e; short f; //半阶音
};
struct tune{
short t; //唱名
float l; //音长
short b; //音调
};
arraym;
short t(short a,short b)
{
switch(b){
case 0: return m[a].a;
case 1: return m[a].d;
case 2: return m[a].g;
case 3: return m[a].b;
case 4: return m[a].e;
case 5: return m[a].f;
}
}
short p(float p)
{
return (short)(PAI*p);
}
void initTone(void)
{ //把各音符的频率写入数组,一劳永逸可以随时调用
m.at(0)={0,0,0,0,0,0};
m.at(1)={523,262,1046,554,277,1109};
m.at(2)={578,294,1175,622,311,1245};
m.at(3)={659,330,1318,659,330,1318};
m.at(4)={698,349,1493,740,370,1556};
m.at(5)={784,392,1568,831,415,1661};
m.at(6)={880,440,1760,932,466,1865};
m.at(7)={988,494,1976,988,494,1976};
}
void initTune(vector&s)
{
for (int i=0;i<2;i++){
s.push_back({1,1,0});
s.push_back({2,1,0});
s.push_back({3,1,0});
s.push_back({1,1,0});
}
for (int i=0;i<2;i++){
s.push_back({3,1,0});
s.push_back({4,1,0});
s.push_back({5,2,0});
}
for (int i=0;i<2;i++){
s.push_back({5,0.75,0});
s.push_back({6,0.25,0});
s.push_back({5,0.75,0});
s.push_back({4,0.25,0});
s.push_back({3,1,0});
s.push_back({1,1,0});
}
for (int i=0;i<2;i++){
s.push_back({1,1,0});
s.push_back({5,1,1});
s.push_back({1,2,0});
}
//以上根据简谱上的拍子和音调编入容器,方法如下:
//第一个参数 1~7 对应do re mi fa so la si 0=休止符
//第二个参数 1拍=1;半拍=0.5 四分之一拍=0.25 以此类推
//第三个参数 一般就为0,低音=1 高音=2 对应的半阶音=3 4 5
//转简谱时看到什么数字就是什么,不用记频率数方便编辑和排错
}
int main()
{
vectormusic;
initTone();
initTune(music);
cout<<"开始演奏《两只老虎》"<
一个程序独占控制台CPU时间来演奏音乐,没有一点实用性。我们再来编一首《送别》并实现“后台演奏”,注意:碰到简谱中有重复的小节可多放几个子函数以供多次调用。
实际上,演奏的同时还要做其他工作,就是要创建多个线程来完成几个不同的工作:先调用
#include
#include
#include
#include
#include
using namespace std;
#define PAI 400 //一拍的时长,可以自行调整
struct tone{
short a; short d; short g; //中音;低音;高音
short b; short e; short f; //半阶音
};
struct tune{
short t; //唱名
float l; //音长
short b; //音调
};
arraym;
short t(short a,short b)
{
switch(b){
case 0: return m[a].a;
case 1: return m[a].d;
case 2: return m[a].g;
case 3: return m[a].b;
case 4: return m[a].e;
case 5: return m[a].f;
}
}
short p(float p)
{
return (short)(PAI*p);
}
void initTone(void)
{
m.at(0)={0,0,0,0,0,0};
m.at(1)={523,262,1046,554,277,1109};
m.at(2)={578,294,1175,622,311,1245};
m.at(3)={659,330,1318,659,330,1318};
m.at(4)={698,349,1493,740,370,1556};
m.at(5)={784,392,1568,831,415,1661};
m.at(6)={880,440,1760,932,466,1865};
m.at(7)={988,494,1976,988,494,1976};
}
void repeat1(vector&s)
{
s.push_back({5,1,0});
s.push_back({3,0.5,0});
s.push_back({5,0.5,0});
}
void repeat2(vector&s)
{
s.push_back({6,1,0});
s.push_back({1,1,2});
s.push_back({5,2,0});
}
void repeat3(vector&s, short i)
{
s.push_back({i,2,0});
s.push_back({0,1,0});
s.push_back({0,1,0});
}
void initTune(vector&s)
{
for (int i=0;i<2;i++){
repeat1(s);
s.push_back({1,2,2});
repeat2(s);
s.push_back({5,1,0});
s.push_back({1,0.5,0});
s.push_back({2,0.5,0});
s.push_back({3,1,0});
s.push_back({2,0.5,0});
s.push_back({1,0.5,0});
repeat3(s,2);
repeat1(s);
s.push_back({1,1.5,2});
s.push_back({7,0.5,0});
repeat2(s);
s.push_back({5,1,0});
s.push_back({2,0.5,0});
s.push_back({3,0.5,0});
s.push_back({4,1.5,0});
s.push_back({7,0.5,1});
repeat3(s,1);
s.push_back({6,1,0});
s.push_back({1,1,2});
s.push_back({1,2,2});
s.push_back({7,0.5,0});
s.push_back({6,0.5,0});
s.push_back({7,0.5,0});
s.push_back({1,2,2});
s.push_back({6,0.5,0});
s.push_back({7,0.5,0});
s.push_back({1,0.5,2});
s.push_back({6,0.5,0});
s.push_back({6,0.5,0});
s.push_back({5,0.5,0});
s.push_back({3,0.5,0});
s.push_back({1,0.5,0});
repeat3(s,2);
repeat1(s);
s.push_back({1,1.5,2});
s.push_back({7,0.5,0});
repeat2(s);
s.push_back({5,1,0});
s.push_back({2,0.5,0});
s.push_back({3,0.5,0});
s.push_back({4,1.5,0});
s.push_back({7,0.5,1});
repeat3(s,1);
}
}
void gotoXY(short x, short y)
{
COORD position = {x, y};
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(hConsole, position);
}
bool play_not_end=true; //设置演奏标记
void* play(void* args)
{
vectormusic;
initTone();
initTune(music);
gotoXY(15,8);
cout<<"背景音乐:《送别》";
for (auto b:music)
if (play_not_end) Beep(t(b.t,b.b),p(b.l));
else break;
play_not_end=false;
}
int main()
{
pthread_t pt;
int ret = pthread_create(&pt, NULL, play, NULL); //创建多线程,play()变相成为背景音乐
if (ret!=0) cout<<"pthread_create error: error_code="<
附:《两只老虎》、《送别》简谱