C++ Beep()演奏簡譜的改進以及實現背景音樂

看到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 <array>
#include <vector>
#include <iostream>
#include <windows.h>
 
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;	//音調 
};
 
array<tone,8>m;
 
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<tune>&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()
{
	vector<tune>music;
	initTone();
	initTune(music);
		
	cout<<"開始演奏《兩隻老虎》"<<endl;	
	for (auto b:music) Beep(t(b.t,b.b),p(b.l));
	
	return 0; 
}

 背景音樂的實現

一個程序獨佔控制檯CPU時間來演奏音樂,沒有一點實用性。我們再來編一首《送別》並實現“後臺演奏”,注意:碰到簡譜中有重複的小節可多放幾個子函數以供多次調用。

實際上,演奏的同時還要做其他工作,就是要創建多個線程來完成幾個不同的工作:先調用<pthread.h>庫函數pthread_create()創建一個線程來播放背景音樂,然後讓主程序開始做其它工作,並且可以按需要來選擇哪項任務先結束。源代碼如下:

#include <array>
#include <vector>
#include <iostream>
#include <windows.h>
#include <pthread.h>
 
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;	//音調 
};
 
array<tone,8>m;
 
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<tune>&s)
{
	s.push_back({5,1,0});
	s.push_back({3,0.5,0});
	s.push_back({5,0.5,0});
}
 
void repeat2(vector<tune>&s)
{
	s.push_back({6,1,0});
	s.push_back({1,1,2});
	s.push_back({5,2,0});
}
 
void repeat3(vector<tune>&s, short i)
{
	s.push_back({i,2,0});
	s.push_back({0,1,0});
	s.push_back({0,1,0});
}
 
void initTune(vector<tune>&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)
{
	vector<tune>music;
	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="<<ret<<endl;
	
	//在 pthread_create()與 pthread_exit()之間,便是程序工作的主場 
	Sleep(100);
	for(int i=1;play_not_end;i++){
		gotoXY(20,10);
		cout<<"工作開始:循環次數 "<<i;
		Sleep(300);
		if (i==100) play_not_end=false;  //工作在音樂演奏完之前結束,只要把標記設爲false即可 
	}
	gotoXY(20,12);
	cout<<"工作結束!"<<endl;
	
	pthread_exit(NULL);
	return 0; 
}

 附:《兩隻老虎》、《送別》簡譜

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章