⼤⼀也能看懂的原神风物之诗⾃动演奏程序
前⾔
应朋友之邀,写⼀篇分析原神风物之诗⾃动弹奏原理的博客,并附带⼀个样例程序。
朋友的要求是:只⽤⼤⼀的C语⾔知识,写⼀个类似C语⾔⼤项⽬的程序,来实现原神风物之诗⾃动弹奏的功能。
注1:阅读本⽂需具有前置知识:C语⾔数组、⽂件、指针的知识。
注2:样例程序对编译器⽆限制,虽然dev-c++也能编译,但建议使⽤Visual Studio。
注3:本类程序使⽤C并不是最优解,相⽐来说,java更适合本类程序。
注4:⽂中的分析与注释基于初学者的C语⾔⽔平,实现简单,⽤语⽩话,程序结构没有进⾏精简。
正⽂
先来分析⼀下常见的风物之诗⾃动弹奏程序(pc端)。
(图源⽹络,侵权必删)
不难看出,这些程序本质上都由配置、选择乐谱、进⾏播放这⼏部分组成。再拆分⼀下进⾏模块化,我们将程序⼤体分为以下⼏个模块:主菜单模块menu(),调节曲速模块adjust(),集成⽂件选择与乐谱播放的读谱演奏模块play()。
第⼀部分是menu模块,功能是列出程序功能并提供相应跳转。本部分涉及对输⼊的处理操作。menu模块演⽰如下:
habe
第⼆部分是adjust模块,模块功能是修改程序中的⼀个变量(⾳间间隔变量cd)。本部分涉及指针操作。adjust模块演⽰如下:
第三部分是play模块,模块主要提供两个功能。第⼀个功能是遍历特定⽂件夹⾥的特定乐谱⽂件,列出乐谱⽂件供⽤户选择。本部分涉及⼀维、⼆维数组操作和⽂件操作。
第⼆个功能是根据⽤户选择的乐谱进⾏读谱弹奏。本部分涉及数组、⽂件以及模拟按键操作。
代码实现
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <windows.h>
4
5int cd=300; //⾳间间隔
6char temp; //1号临时变量
7int temp2; //2号临时变量
8int *p= &cd;//指向⾳间间隔的指针,需要对⼤⼀朋友强调的是,⼀定要有初始化指针的习惯,以免出现野指针
9
10//play函数内的变量
11char c;//临时变量,储存模拟按下的键的键名
12int tempcd;// tempcd为临时的⾳间间隔,⽅便调整。
13char musicPath[20]="乐谱\\*.txt"; //乐谱⽂件的地址的通⽤格式
14 WIN32_FIND_DATA findFileData;//⽂件操作常⽤的⼀种数据结构
15 HANDLE hFind;//句柄
16char str[50];//记录乐谱名字的字符串, 歌名最长50字符,汉字占两个
17char path[55][50]; //记录乐谱名字的字符串数组,⽤⼆维数组实现,最多55⾸歌
18
19void adjust(int *p);//调节曲速(⾳间间隔)功能
20void menu();//主菜单
21void play();//弹奏乐谱功能
22void me();//项⽬背景
esperanza spalding23void bye();//退出菜单
24
25void 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会读取下⼀个键⼊的字符,⽆需回车
36if(temp<'1' || temp>'4')//规范输⼊
37 {
38 printf(" 请输⼊1-4之间的整数 \n\n");
39 menu();
40 }
41if(temp == '1')//页⾯跳转
42 {
43 adjust(&cd);
44 }
45if(temp == '2')
46 {
prosperous的名词
47 play();
48 }
49if(temp == '3')
50 {
51 me();
52 }
53if(temp == '4')
54 {
55 bye();
56 }
57 }
58
59void 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
69while('\n'!=getchar()); //对可能残留在输⼊流中的数据进⾏清理。
70if(temp2>0 && temp2<5000)//将新曲速限制在0~5s
71 {
72 *p = temp2;//符合要求则将新曲速赋给⾳间间隔cd
73 printf(" 新曲速( %d ms)设置成功 \n\n",cd);
74 menu();//跳转
75 }el
76 {
77 printf(" 请输⼊1-5000之间的整数 \n",cd);
78 printf(" 单位 ms ,1000 ms = 1 s \n\n",cd);
79 adjust(&cd); //规范输⼊
80 }
81 }
82
83void play()//遍历乐谱⽂件夹,列出乐谱,使⽤者选择后,读相应乐谱,然后模拟按键弹奏。此部分可进⼀步模块化
84 {
85int i=0;//记录乐谱序号
86int j=0;//记录选择的乐谱的序号的临时变量
87char str[50] = "乐谱/";//初始化乐谱地址字符串,初始化为乐谱地址的⽬录
88char path[55][50]={};//初始化乐谱名字符串数组,初始化为空
89 hFind=FindFirstFile(musicPath,&findFileData);//找到⽬录下第⼀个格式为 musicPath(即乐谱\\*.txt)的⽂件
90if(hFind==INVALID_HANDLE_VALUE) //如果⽬录下没有(第⼀个)⽂件
91 {
92 printf("乐谱⽂件夹中没有⽂件,请将txt格式的乐谱放⼊乐谱⽂件夹中!\n");
93 menu();
94 }
95el//如果⽬录下存在(第⼀个)⽂件
96 {
97 printf("当前可演奏的乐谱有:\n");
98while(1)//开始遍历乐谱,并将每个乐谱的地址放进path数组中。
99 {
100if(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
101 {//优化搜索结果,可以注释掉这个ifel看看
102 }
103el//输出符合结果⽂件名
104 {
105char 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 }
112if(!FindNextFile(hFind,&findFileData)) //没有下⼀个⽂件了就退出遍历
113 {
114break;
115 }
116 }FindClo(hFind); //关掉句柄
117 printf(" 输⼊乐谱前序号开始演奏:\n");
118 scanf(" %d",&j);//读者可以⾃⾏对⽐这⾥的读取输⼊与主菜单的读取输⼊的区别
119while('\n'!=getchar()); //对可能残留在输⼊流中的数据进⾏清理。
120if(j<1 || j>i)
121 {covid 19英文全称怎么读
122 printf(" 请输⼊1-4之间的整数 \n\n");
123 play();
124 }el
125 {
126 FILE *p = fopen(path[j-1], "r");//打开⽂件流,因为给⽤户看的序号⽐数组的下标⼤1,所以这⾥j-1
127if(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)
dumbass
138// while(fscanf(p, "%c,%d", &c, &a) != EOF)//可以对⽐⼀下不同循环的区别
139for(;fscanf(p, "%c%d", &c, &tempcd) != EOF;tempcd=cd)//第⼆个参数:格式化读取到⽂件结束;第三个参数:没有指定⾳间间隔的使⽤默认⾳间间隔tempcd 140 {
141if(c>='a'&&c<='z') c-=32;//⼩写转⼤写
142if(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 fclo(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);
164while('\n'!=getchar()); //对可能残留在输⼊流中的数据进⾏清理。
165if(j<1 || j>3)
166 {
167 printf(" 请输⼊1-3之间的整数 \n\n");
168 play();
169 }
170if(j == 1)//跳转
171 play();
172if(j == 2)
173 menu();
174if(j == 3)
175 bye();
176 }
177
mba统考
178void me()
179 {
180 printf("\n");
181 FILE *file = fopen("项⽬相关.txt", "r");
182if(file == NULL)
183 printf("⽂件缺失!\n");
184char b;
185while((b = fgetc(file)) != EOF)
186 printf("%c", b);
187 fclo(file);
188 printf("\n");
189 menu();
190 }
191
192void 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");
gc
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("pau");
215 }
216
217void main()
218 {
219 menu();
shinewrite220 }
原理讲解到此就差不多了,如果有没讲清楚的地⽅欢迎提出。另外,程序的具体的使⽤⽅法我会和源码、乐谱库⼀起放到百度云中,有需要的朋友⾃取。
补充
如果你觉得程序太简陋(确实太简陋了),那么可改进的地⽅还有很多,⽐如使⽤变长数组,再增加暂停、录谱、和⾳等功能;或者改进程序使之不仅能读键盘谱,还能读数字谱,并增加数据库操作来处理两种谱;也可以使⽤图形化api来写个窗⼝程序,同时让程序⾃动获得管理员权限等。难度逐渐上升,但都是⼤⼀经过不复杂的学习或百度就能实现的功能,但是像分析⾳频⽂件⾃动⽣成乐谱⽂件等功能这⾥就不推荐了,因为这种功能往往涉及⽅向特化的知识,有兴趣的朋友可以⾃⾏实现。
最后,我的UID是116752259,我的世界还蛮⼤的,资源都可以随便拿,欢迎你来我家玩。