大一也能看懂的原神风物之诗自动演奏程序


前言

应朋友之邀,写一篇分析原神风物之诗自动弹奏原理的博客,并附带一个样例程序。

朋友的要求是:只用大一的C语言知识,写一个类似C语言大项目的程序,来实现原神风物之诗自动弹奏的功能。

注1:阅读本文需具有前置知识:C语言数组、文件、指针的知识。

注2:样例程序对编译器无限制,虽然dev-c++也能编译,但建议使用Visual Studio。

注3:本类程序使用C并不是最优解,相比来说,java更适合本类程序。

注4:文中的分析与注释基于初学者的C语言水平,实现简单,用语白话,程序结构没有进行精简。

 

正文

先来分析一下常见的风物之诗自动弹奏程序(pc端)。

 

 (图源网络,侵权必删)

不难看出,这些程序本质上都由配置、选择乐谱、进行播放这几部分组成。再拆分一下进行模块化,我们将程序大体分为以下几个模块:主菜单模块menu(),调节曲速模块adjust(),集成文件选择与乐谱播放的读谱演奏模块play()。

第一部分是menu模块,功能是列出程序功能并提供相应跳转。本部分涉及对输入的处理操作。menu模块演示如下:

第二部分是adjust模块,模块功能是修改程序中的一个变量(音间间隔变量cd)。本部分涉及指针操作。adjust模块演示如下:

第三部分是play模块,模块主要提供两个功能。第一个功能是遍历特定文件夹里的特定乐谱文件,列出乐谱文件供用户选择。本部分涉及一维、二维数组操作和文件操作。

第二个功能是根据用户选择的乐谱进行读谱弹奏。本部分涉及数组、文件以及模拟按键操作。

 

 

 

代码实现

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <windows.h>
  4 
  5 int cd=300; //音间间隔 
  6 char temp; //1号临时变量 
  7 int temp2; //2号临时变量 
  8 int *p= &cd;//指向音间间隔的指针,需要对大一朋友强调的是,一定要有初始化指针的习惯,以免出现野指针 
  9 
 10 //play函数内的变量
 11 char c;//临时变量,储存模拟按下的键的键名
 12 int tempcd;// tempcd为临时的音间间隔,方便调整。
 13 char musicPath[20]="乐谱\\*.txt"; //乐谱文件的地址的通用格式 
 14 WIN32_FIND_DATA findFileData;//文件操作常用的一种数据结构 
 15 HANDLE hFind;//句柄 
 16 char str[50];//记录乐谱名字的字符串, 歌名最长50字符,汉字占两个 
 17 char path[55][50]; //记录乐谱名字的字符串数组,用二维数组实现,最多55首歌 
 18 
 19 void adjust(int *p);//调节曲速(音间间隔)功能 
 20 void menu();//主菜单 
 21 void play();//弹奏乐谱功能 
 22 void me();//项目背景 
 23 void bye();//退出菜单 
 24 
 25 void menu()//主菜单 
 26 {
 27     printf("\n --------------功能菜单--------------\n");
 28     printf(" |                                  |\n");
 29     printf(" |          1、调节曲速             |\n");
 30     printf(" |          2、读谱演奏             |\n");
 31     printf(" |          3、项目相关             |\n");
 32     printf(" |          4、退出程序             |\n");
 33     printf(" |                                  |\n");
 34     printf(" ------------------------------------\n");
 35     temp =  getch();//getch会读取下一个键入的字符,无需回车 
 36     if(temp<'1' || temp>'4')//规范输入 
 37     {
 38         printf("  请输入1-4之间的整数  \n\n");
 39         menu();
 40     }
 41     if(temp == '1')//页面跳转 
 42     {
 43         adjust(&cd);
 44     }
 45     if(temp == '2')
 46     {
 47         play();    
 48     }
 49     if(temp == '3')
 50     {
 51         me();
 52     }
 53     if(temp == '4')
 54     {
 55         bye();    
 56     }
 57 }
 58 
 59 void adjust(int *p)//使用指针可以将在函数内部改变的音间间隔传递出函数,当然,也可以用返回值解决这个问题 
 60 {
 61     printf("\n--------------调节曲速--------------\n");
 62     printf("|                                  |\n");
 63     printf("| 这里的曲速指两个音之间的时间间隔 |\n");
 64     printf("| 当前值%3ld,单位ms,值越小演奏越快  |\n",cd);
 65     printf("| 将新曲速调节为: __________ ms    |\n");
 66     printf("|                                  |\n");
 67     printf("------------------------------------\n  ");
 68     scanf("%d",&temp2);//将新曲速赋给临时变量temp2 
 69     while('\n'!=getchar());    //对可能残留在输入流中的数据进行清理。
 70     if(temp2>0 && temp2<5000)//将新曲速限制在0~5s 
 71     {
 72         *p = temp2;//符合要求则将新曲速赋给音间间隔cd 
 73         printf("  新曲速( %d ms)设置成功        \n\n",cd);
 74         menu();//跳转 
 75     }else
 76     {
 77         printf("  请输入1-5000之间的整数        \n",cd);
 78         printf("  单位 ms ,1000 ms = 1 s        \n\n",cd);
 79         adjust(&cd); //规范输入 
 80     }
 81  } 
 82  
 83 void play()//遍历乐谱文件夹,列出乐谱,使用者选择后,读相应乐谱,然后模拟按键弹奏。此部分可进一步模块化 
 84 {    
 85     int i=0;//记录乐谱序号 
 86     int j=0;//记录选择的乐谱的序号的临时变量 
 87     char str[50] = "乐谱/";//初始化乐谱地址字符串,初始化为乐谱地址的目录 
 88     char path[55][50]={};//初始化乐谱名字符串数组,初始化为空 
 89     hFind=FindFirstFile(musicPath,&findFileData);//找到目录下第一个格式为 musicPath(即乐谱\\*.txt)的文件 
 90     if(hFind==INVALID_HANDLE_VALUE) //如果目录下没有(第一个)文件 
 91     {
 92         printf("乐谱文件夹中没有文件,请将txt格式的乐谱放入乐谱文件夹中!\n");
 93         menu();
 94     }
 95     else //如果目录下存在(第一个)文件
 96     {
 97         printf("当前可演奏的乐谱有:\n");
 98         while(1)//开始遍历乐谱,并将每个乐谱的地址放进path数组中。 
 99         {
100             if(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 
101             {//优化搜索结果,可以注释掉这个ifelse看看 
102             }
103             else //输出符合结果文件名
104             {  
105                 char str[50] = "乐谱/";//新一轮循坏开始的时候重置str 
106                 memcpy(str+5, findFileData.cFileName, strlen(findFileData.cFileName));//补全乐谱名字符串str,添加目录后的乐谱名,memcpy会据参数进行字符串覆盖,详情可 
107                                                                                       //百度,第一个参数+5值覆盖的内容从str的第六个字符开始,目的是保留"乐谱/" 
108                 sprintf(path[i],str);//将得到的乐谱名str放入乐谱名数组path,也可以用memcpy(path[i], str, strlen(str))。path[i]可以看作path[i][]的数组名
109                 i++;//数组下标从0开始,供用户选择的乐谱序号从1开始,所以i++放到这个位置 
110                 printf("%3d : %s\n",i,findFileData.cFileName); //输出序号和与其对应的乐谱名 
111             }
112             if(!FindNextFile(hFind,&findFileData)) //没有下一个文件了就退出遍历 
113             {
114                 break;
115             }   
116         }FindClose(hFind); //关掉句柄 
117         printf(" 输入乐谱前序号开始演奏:\n");
118         scanf(" %d",&j);//读者可以自行对比这里的读取输入与主菜单的读取输入的区别 
119         while('\n'!=getchar());    //对可能残留在输入流中的数据进行清理。
120         if(j<1 || j>i)
121         {
122             printf("  请输入1-4之间的整数  \n\n");
123             play();
124         }else
125         {        
126             FILE *p = fopen(path[j-1], "r");//打开文件流,因为给用户看的序号比数组的下标大1,所以这里j-1 
127             if(p == NULL)
128             {
129                 printf(" 乐谱 %s 打开失败!\n",path[j-1]);
130                 play(); 
131             }
132             printf(" 5秒后开始演奏 %s ,请切到原神风物之诗界面...\n\n",path[j-1]);
133                Sleep(5000);//暂停5秒         
134             tempcd=cd;//tempcd为临时的音间间隔。 
135             printf(" 演奏开始...\n");
136             printf(" ...\n");
137             //while((c = fgetc(p)) != EOF)
138             // while(fscanf(p, "%c,%d", &c, &a) != EOF)//可以对比一下不同循环的区别 
139             for(;fscanf(p, "%c%d", &c, &tempcd) != EOF;tempcd=cd)//第二个参数:格式化读取到文件结束;第三个参数:没有指定音间间隔的使用默认音间间隔tempcd 
140             {
141                 if(c>='a'&&c<='z') c-=32;//小写转大写 
142                 if(c>='A'&&c<='Z')
143                 {
144                     keybd_event(c, 0, 0, 0); //模拟按下键盘 
145                     keybd_event(c, 0, 2, 0); //模拟松开键盘     
146                 }
147                 Sleep(tempcd);//等待指定时间:音间间隔tempcd毫秒。放到if外面,这样文档中打','来分隔的话也会延时默认音间间隔 
148                 //printf(" %c\n %d\n",c,tempcd);//可以解除注释观察读文件过程 
149             }
150             fclose(p);//关闭文件流 
151             printf("\n %s 演奏完毕!\n\n",path[j-1]);
152         }
153     }
154     printf(" -----------------------\n");
155     printf(" |                     |\n");
156     printf(" |     现在你可以:    |\n");
157     printf(" |     1、继续演奏     |\n");
158     printf(" |     2、返回首页     |\n");
159     printf(" |     3、退出程序     |\n");
160     printf(" |                     |\n");
161     printf(" -----------------------\n");
162     j = 0;//重置临时变量 
163     scanf(" %d",&j);
164     while('\n'!=getchar());    //对可能残留在输入流中的数据进行清理。
165     if(j<1 || j>3)
166     {
167         printf("  请输入1-3之间的整数  \n\n");
168         play(); 
169     }
170     if(j == 1)//跳转 
171         play();
172     if(j == 2)
173         menu();
174     if(j == 3)
175         bye();
176 }
177 
178 void me()
179 {
180     printf("\n");
181     FILE *file = fopen("项目相关.txt", "r");
182     if(file == NULL)
183         printf("文件缺失!\n");
184     char b;
185     while((b = fgetc(file)) != EOF)
186         printf("%c", b);
187     fclose(file);
188     printf("\n");
189     menu();
190 }
191 
192 void bye()
193 {
194     printf("\n                                     \n");
195     printf("        ----------------------       \n");
196     printf("        | 我们都有光明的未来 |       \n");
197     printf("        ----------------------       \n");
198     printf("                                     \n");
199     printf("  ------                      ------ \n");
200     printf("  | 我 |          /\\          | 你 |\n");
201     printf("  | 单 |         /福\\         | 十 |\n");
202     printf("  | 抽 |       /福福福\\       | 连 |\n");
203     printf("  | 奇 |     <福福福福福>     | 七 | \n");
204     printf("  | 迹 |       \\福福福/       | 金 |\n");
205     printf("  | 一 |         \\福/         | 从 |\n");
206     printf("  | 身 |          \\/          | 未 |\n");
207     printf("  | 欧 |                      | 保 | \n");
208     printf("  | 气 |                      | 底 | \n");
209     printf("  ------                      ------ \n");
210     printf("                                    \n");
211     printf("                                    \n");
212     printf("     希望你喜欢这个不完善的小程序  \n");
213     printf("     再见啦         /派蒙挥手.gif  \n\n");
214     system("pause");
215 }
216 
217 void main()
218 {
219     menu(); 
220  } 

原理讲解到此就差不多了,如果有没讲清楚的地方欢迎提出。另外,程序的具体的使用方法我会和源码、乐谱库一起放到百度云中,有需要的朋友自取。

 

补充

如果你觉得程序太简陋(确实太简陋了),那么可改进的地方还有很多,比如使用变长数组,再增加暂停、录谱、和音等功能;或者改进程序使之不仅能读键盘谱,还能读数字谱,并增加数据库操作来处理两种谱;也可以使用图形化api来写个窗口程序,同时让程序自动获得管理员权限等。难度逐渐上升,但都是大一经过不复杂的学习或百度就能实现的功能,但是像分析音频文件自动生成乐谱文件等功能这里就不推荐了,因为这种功能往往涉及方向特化的知识,有兴趣的朋友可以自行实现。

另外,我在米游社发布了一篇汇总对比风物之诗自动弹奏方法的帖子,有兴趣的朋友可以去看一下。链接:https://bbs.mihoyo.com/ys/article/5395350?create=1。

最后,我的UID是116752259,我的世界还蛮大的,资源都可以随便拿,欢迎你来我家玩。

百度网盘链接:https://pan.baidu.com/s/1Jl6F3sHnRXAkc00YXMtbkg
提取码:1234


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM