看到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 <ctime>
#include <sstream>
#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(28,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;
}
char strTime[20];
void* coutTime(void* args)
{
time_t lt;
while (play_not_end){
time(<);
strftime(strTime,sizeof(strTime),"%Y-%m-%d %H:%M:%S",localtime(<));
Sleep(300);
}
play_not_end=false;
}
int main()
{
pthread_t pt;
int ret = pthread_create(&pt, NULL, play, NULL); //創建一個執行緒,play()變相成為背景音樂
if (ret!=0) cout<<"create thread_1 error: error_code="<<ret<<endl;
ret = pthread_create(&pt, NULL, coutTime, NULL); //再創建一個執行緒,用于顯示當前系統時間
if (ret!=0) cout<<"create thread_2 error: error_code="<<ret<<endl;
//在 pthread_create()與 pthread_exit()之間,便是程式作業的主場
Sleep(100);
gotoXY(28,10);
cout<<"作業開始:回圈次數 ";
for(int i=1;play_not_end;i++){
gotoXY(48,10);
cout<<i<<endl;
gotoXY(60,0);
cout<<strTime; //顯示另外一個執行緒取回的時間
Sleep(300);
if (i==100) play_not_end=false; //作業在音樂演奏完之前結束,只要把標記設為false即可
}
gotoXY(28,12);
cout<<"作業結束!"<<endl;
pthread_exit(NULL);
return 0;
}
注意:本程式在Dev-C++ V4.9.2 上通過編譯,若用VS編譯找不到頭檔案<pthread.h>,請搜索“如何在vs2017上使用pthread.h”并自行安裝庫檔案,
附:《兩只老虎》、《送別》簡譜


轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/257195.html
標籤:其他
