当前位置:   article > 正文

C++ Beep()演奏简谱的改进以及实现背景音乐_c++演奏音乐平凡之路

c++演奏音乐平凡之路

看到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()每一个音符,还能随时插入删除某个音符,重复的小节可以用循环多次输出以缩短代码长度;简谱转代码时“所见即所得”看到什么数字就写入什么数字;音符的时长也以“拍”为单位,每个小节后加个空行便于检查每节的总时长。源代码如下:

  1. #include <array>
  2. #include <vector>
  3. #include <iostream>
  4. #include <windows.h>
  5. using namespace std;
  6. #define PAI 300 //一拍的时长,可以自行调整
  7. struct tone{
  8. short a; short d; short g; //中音;低音;高音
  9. short b; short e; short f; //半阶音
  10. };
  11. struct tune{
  12. short t; //唱名
  13. float l; //音长
  14. short b; //音调
  15. };
  16. array<tone,8>m;
  17. short t(short a,short b)
  18. {
  19. switch(b){
  20. case 0: return m[a].a;
  21. case 1: return m[a].d;
  22. case 2: return m[a].g;
  23. case 3: return m[a].b;
  24. case 4: return m[a].e;
  25. case 5: return m[a].f;
  26. }
  27. }
  28. short p(float p)
  29. {
  30. return (short)(PAI*p);
  31. }
  32. void initTone(void)
  33. { //把各音符的频率写入数组,一劳永逸可以随时调用
  34. m.at(0)={0,0,0,0,0,0};
  35. m.at(1)={523,262,1046,554,277,1109};
  36. m.at(2)={578,294,1175,622,311,1245};
  37. m.at(3)={659,330,1318,659,330,1318};
  38. m.at(4)={698,349,1493,740,370,1556};
  39. m.at(5)={784,392,1568,831,415,1661};
  40. m.at(6)={880,440,1760,932,466,1865};
  41. m.at(7)={988,494,1976,988,494,1976};
  42. }
  43. void initTune(vector<tune>&s)
  44. {
  45. for (int i=0;i<2;i++){
  46. s.push_back({1,1,0});
  47. s.push_back({2,1,0});
  48. s.push_back({3,1,0});
  49. s.push_back({1,1,0});
  50. }
  51. for (int i=0;i<2;i++){
  52. s.push_back({3,1,0});
  53. s.push_back({4,1,0});
  54. s.push_back({5,2,0});
  55. }
  56. for (int i=0;i<2;i++){
  57. s.push_back({5,0.75,0});
  58. s.push_back({6,0.25,0});
  59. s.push_back({5,0.75,0});
  60. s.push_back({4,0.25,0});
  61. s.push_back({3,1,0});
  62. s.push_back({1,1,0});
  63. }
  64. for (int i=0;i<2;i++){
  65. s.push_back({1,1,0});
  66. s.push_back({5,1,1});
  67. s.push_back({1,2,0});
  68. }
  69. //以上根据简谱上的拍子和音调编入容器,方法如下:
  70. //第一个参数 1~7 对应do re mi fa so la si 0=休止符
  71. //第二个参数 1拍=1;半拍=0.5 四分之一拍=0.25 以此类推
  72. //第三个参数 一般就为0,低音=1 高音=2 对应的半阶音=3 4 5
  73. //转简谱时看到什么数字就是什么,不用记频率数方便编辑和排错
  74. }
  75. int main()
  76. {
  77. vector<tune>music;
  78. initTone();
  79. initTune(music);
  80. cout<<"开始演奏《两只老虎》"<<endl;
  81. for (auto m:music) Beep(t(m.t,m.b),p(m.l));
  82. return 0;
  83. }

 背景音乐的实现

一个程序独占控制台CPU时间来演奏音乐,没有一点实用性。我们再来编一首《送别》并实现“后台演奏”,注意:碰到简谱中有重复的小节可多放几个子函数以供多次调用。

实际上,演奏的同时还要做其他工作,就是要创建多个线程来完成几个不同的工作:先调用<pthread.h>库函数pthread_create()创建一个线程来播放背景音乐,然后让主程序开始做其它工作,并且可以按需要来选择哪项任务先结束。源代码如下:

  1. #include <array>
  2. #include <vector>
  3. #include <iostream>
  4. #include <ctime>
  5. #include <sstream>
  6. #include <windows.h>
  7. #include <pthread.h>
  8. using namespace std;
  9. #define PAI 400 //一拍的时长,可以自行调整
  10. struct tone{
  11. short a; short d; short g; //中音;低音;高音
  12. short b; short e; short f; //半阶音
  13. };
  14. struct tune{
  15. short t; //唱名
  16. float l; //音长
  17. short b; //音调
  18. };
  19. array<tone,8>m;
  20. short t(short a,short b)
  21. {
  22. switch(b){
  23. case 0: return m[a].a;
  24. case 1: return m[a].d;
  25. case 2: return m[a].g;
  26. case 3: return m[a].b;
  27. case 4: return m[a].e;
  28. case 5: return m[a].f;
  29. }
  30. }
  31. short p(float p)
  32. {
  33. return (short)(PAI*p);
  34. }
  35. void initTone(void)
  36. {
  37. m.at(0)={0,0,0,0,0,0};
  38. m.at(1)={523,262,1046,554,277,1109};
  39. m.at(2)={578,294,1175,622,311,1245};
  40. m.at(3)={659,330,1318,659,330,1318};
  41. m.at(4)={698,349,1493,740,370,1556};
  42. m.at(5)={784,392,1568,831,415,1661};
  43. m.at(6)={880,440,1760,932,466,1865};
  44. m.at(7)={988,494,1976,988,494,1976};
  45. }
  46. void repeat1(vector<tune>&s)
  47. {
  48. s.push_back({5,1,0});
  49. s.push_back({3,0.5,0});
  50. s.push_back({5,0.5,0});
  51. }
  52. void repeat2(vector<tune>&s)
  53. {
  54. s.push_back({6,1,0});
  55. s.push_back({1,1,2});
  56. s.push_back({5,2,0});
  57. }
  58. void repeat3(vector<tune>&s, short i)
  59. {
  60. s.push_back({i,2,0});
  61. s.push_back({0,1,0});
  62. s.push_back({0,1,0});
  63. }
  64. void initTune(vector<tune>&s)
  65. {
  66. for (int i=0;i<2;i++){
  67. repeat1(s);
  68. s.push_back({1,2,2});
  69. repeat2(s);
  70. s.push_back({5,1,0});
  71. s.push_back({1,0.5,0});
  72. s.push_back({2,0.5,0});
  73. s.push_back({3,1,0});
  74. s.push_back({2,0.5,0});
  75. s.push_back({1,0.5,0});
  76. repeat3(s,2);
  77. repeat1(s);
  78. s.push_back({1,1.5,2});
  79. s.push_back({7,0.5,0});
  80. repeat2(s);
  81. s.push_back({5,1,0});
  82. s.push_back({2,0.5,0});
  83. s.push_back({3,0.5,0});
  84. s.push_back({4,1.5,0});
  85. s.push_back({7,0.5,1});
  86. repeat3(s,1);
  87. s.push_back({6,1,0});
  88. s.push_back({1,1,2});
  89. s.push_back({1,2,2});
  90. s.push_back({7,0.5,0});
  91. s.push_back({6,0.5,0});
  92. s.push_back({7,0.5,0});
  93. s.push_back({1,2,2});
  94. s.push_back({6,0.5,0});
  95. s.push_back({7,0.5,0});
  96. s.push_back({1,0.5,2});
  97. s.push_back({6,0.5,0});
  98. s.push_back({6,0.5,0});
  99. s.push_back({5,0.5,0});
  100. s.push_back({3,0.5,0});
  101. s.push_back({1,0.5,0});
  102. repeat3(s,2);
  103. repeat1(s);
  104. s.push_back({1,1.5,2});
  105. s.push_back({7,0.5,0});
  106. repeat2(s);
  107. s.push_back({5,1,0});
  108. s.push_back({2,0.5,0});
  109. s.push_back({3,0.5,0});
  110. s.push_back({4,1.5,0});
  111. s.push_back({7,0.5,1});
  112. repeat3(s,1);
  113. }
  114. }
  115. void gotoXY(short x, short y)
  116. {
  117. COORD position = {x, y};
  118. HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
  119. SetConsoleCursorPosition(hConsole, position);
  120. }
  121. bool play_not_end=true; //设置演奏标记
  122. void* play(void* args)
  123. {
  124. vector<tune>music;
  125. initTone();
  126. initTune(music);
  127. gotoXY(28,8);
  128. cout<<"背景音乐:《送别》";
  129. for (auto v:music)
  130. if (play_not_end) Beep(t(v.t,v.b),p(v.l));
  131. else break;
  132. play_not_end=false;
  133. }
  134. char strTime[20];
  135. void* coutTime(void* args)
  136. {
  137. time_t lt;
  138. while (play_not_end){
  139. time(&lt);
  140. strftime(strTime,sizeof(strTime),"%Y-%m-%d %H:%M:%S",localtime(&lt));
  141. Sleep(300);
  142. }
  143. play_not_end=false;
  144. }
  145. int main()
  146. {
  147. pthread_t pt;
  148. int ret = pthread_create(&pt, NULL, play, NULL); //创建一个线程,play()变相成为背景音乐
  149. if (ret!=0) cout<<"create thread_1 error: error_code="<<ret<<endl;
  150. ret = pthread_create(&pt, NULL, coutTime, NULL); //再创建一个线程,用于显示当前系统时间
  151. if (ret!=0) cout<<"create thread_2 error: error_code="<<ret<<endl;
  152. //在 pthread_create()与 pthread_exit()之间,便是程序工作的主场
  153. Sleep(100);
  154. gotoXY(28,10);
  155. cout<<"工作开始:循环次数 ";
  156. for(int i=1;play_not_end;i++){
  157. gotoXY(48,10);
  158. cout<<i<<endl;
  159. gotoXY(60,0);
  160. cout<<strTime; //显示另外一个线程取回的时间
  161. Sleep(300);
  162. if (i==100) play_not_end=false; //工作在音乐演奏完之前结束,只要把标记设为false即可
  163. }
  164. gotoXY(28,12);
  165. cout<<"工作结束!"<<endl;
  166. pthread_exit(NULL);
  167. return 0;
  168. }

注意:本程序在Dev-C++ 5.11/编译器TDM-GCC 4.9.2 64-bit 上通过编译,若用VS编译找不到头文件<pthread.h>,请搜索“如何在vs2017上使用pthread.h”并自行安装库文件。

 

附:《两只老虎》、《送别》简谱

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/488414
推荐阅读
相关标签
  

闽ICP备14008679号