大一也能看懂的原神風物之詩自動演奏程序


前言

應朋友之邀,寫一篇分析原神風物之詩自動彈奏原理的博客,並附帶一個樣例程序。

朋友的要求是:只用大一的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