【日常篇】002_五线谱调式推导
五線譜調式推導
??很早的時候就聽說過各種諸如“X大調”、“X小調”這樣的術語,但聽了這么多年也沒有搞明白這究竟是什么意思。
??直到最近兩個月,才有大佬提示,其實各種調式升降記號都是可以由鋼琴鍵位平移的方式得到的,這提供了一種推斷調式的方法:在最常規的C大調基礎上,每個音都提升一個音程,然后再看有哪些音跑到了黑鍵上,就可以知道有幾個升號或降號了。
??盡管這個過程用手算也是很容易完成的,但考慮到自己在C++公選結課后,已經有兩年多的時間沒有碰過C++了。為了再次熟練這個比較重要的語言,本次推導過程將使用C++來完成。
基本思路
從C大調出發
??C大調在五線譜中,是沒有任何升降號的存在的:
??而如果將鍵位整體右移兩個半音,則可以得到兩個升號的調(D大調):
??因為一個八度共有12個半音,所以這樣的平移一共可以得到十二種調式。只要對C大調的鍵位進行12次平移,就可以得到全部的十二種調式。
調式在程序中的表示
??一個八度有12個半音,而在推導過程中需要不斷地將這些鍵往一個方向進行平移,因此考慮到循環移位。使用12個bit分別對應一個八度中的12個半音C、C#|Db、D、D#|Eb、E、F、F#|Gb、G、G#|Ab、A、A#|Bb、B,若鍵位在那個半音上則置1,否則置0。每一次往高音處移位時,超出B的部分都將會回到C的位置。
??讓第0位表示C,第1位表示C#……以此類推,第11位表示B,如果向左移位的話,則第11位被移動至第0位:
??例如,C大調對應的七個鍵位分別為C、D、E、F、G、A、B,則對應的bit序列為:101010110101。而向高音處平移兩個半音到D大調,則對應向左循環移位兩次,得到:101011010110,對應C#、D、E、F#、G、A、B。
??有了比較明確的思路后,就可以開始寫代碼了。
代碼實現
初始狀態:C大調
??前面已經提到,C大調的bit表示方法,而初始狀態又確認為C大調,因此這個數值可以作為常量存放在頭文件中:
int MODE = 0xAB5; //101010110101 <- reverse(101011010101)位與音符的對應關系
??類似于python的字典,C++的標準庫也有提供字典這個結構。該結構存放在map庫中,include這個庫即可用std::map進行使用(這里已經用了using std::map,所以在使用時直接寫的map):
map<int, string> NOTE_MAP = {{0, "C",},{1, "C#",},{2, "D",},{3, "D#",},{4, "E",},{5, "F",},{6, "F#",},{7, "G",},{8, "G#",},{9, "A",},{10, "A#",},{11, "B",},{12, "C",},{13, "Db",},{14, "D",},{15, "Eb",},{16, "E",},{17, "F",},{18, "Gb",},{19, "G",},{20, "Ab",},{21, "A",},{22, "Bb",},{23, "B",}};??在這里給了24個詞條,主要是因為有些調式是使用降號的,對于這樣的調式,應該使用降號表示法(對應字典的第13-24條)。
循環移位
??循環移位的思路非常簡單,就是向左移位一次,然后將最高位移動到第一位。這里沒有對第12位及以上的bit進行截斷,但也沒有影響,因為后續的操作不會涉及到更高位的bit:
/** brief 對調式向左(高音方向)移動一個單位** @param orgMode 原有的調式*/ void shiftMode(int &orgMode){orgMode <<= 1;orgMode = orgMode | !!((1 << 12) & orgMode); }打印調式信息
??調式有升號調的和降號調這兩種,為了區分開來,在這里使用降號調的flag標記:dFlag,來將它們區分開來。如果有降號表示,則會在字典中查詢第13-24項的詞條:
/** brief 打印調式的相關信息** @param mode 調式* @param dFlag 降號表示,默認為0,即升號*/ void printModeInfo(int &mode, bool dFlag = false){for (int i = 0; i < 12; i++){int mask = (1 << i);if (mode & mask){std::cout << NOTE_MAP[i+12*dFlag] << "\t";}}std::cout << std::endl; }main函數
??對每一次移位的結果,都進行打印即可。由于先前已有經驗,升號調和降號調是交替出現的,所以在這里就可以很容易地判斷出dFlag的值是true還是false:
int main() {int mapSize = NOTE_MAP.size();int shift = 0;do{printModeInfo(MODE, (((shift <= 5) && (shift % 2)) || ((shift >= 8) && !(shift % 2))) ? true : false);shiftMode(MODE);shift += 1;}while (shift < 12);return 0; }運行結果及結論
??運行后得到如下輸出:
C D E F G A B C Db Eb F Gb Ab Bb C# D E F# G A B C D Eb F G Ab Bb C# D# E F# G# A B C D E F G A Bb C# D# F F# G# A# B C D E F# G A B C Db Eb F G Ab Bb C# D E F# G# A B C D Eb F G A Bb C# D# E F# G# A# B??此即推導得到的12種調式,注意到升號和降號的數量是關于C大調對稱的,用圖像表示則更為直觀:
??沿著這個圖像順時針看,調式依次從C大調、降D大調、D大調……演變到B大調,最后再回到C大調。而這些大調,每一個都又有著相對應的小調,由于還沒有找出規律,就不在這里描述了。
??而對于C大調向上移動6個半音的調式——升F大調,則暫時難以在輸出結果中得以理解。
??而經過在overture和網上的查詢,得知升F大調由六個升記號或六個降記號組成:
??并且,E#=F,因為E和F之間只差一個半音。
??由此一來,調式這個問題也就得到了一個比較清晰的解答。
完整代碼
main.h
#include <map> #include <string>using std::map; using std::string;map<int, string> NOTE_MAP = {{0, "C",},{1, "C#",},{2, "D",},{3, "D#",},{4, "E",},{5, "F",},{6, "F#",},{7, "G",},{8, "G#",},{9, "A",},{10, "A#",},{11, "B",},{12, "C",},{13, "Db",},{14, "D",},{15, "Eb",},{16, "E",},{17, "F",},{18, "Gb",},{19, "G",},{20, "Ab",},{21, "A",},{22, "Bb",},{23, "B",}};int MODE = 0xAB5; //101010110101 <- reverse(101011010101)main.cpp
#include <iostream> #include <string> #include "main.h"using std::map; using std::string;void shiftMode(int &orgMode); void printModeInfo(int &mode, bool dFlag);int main() {int mapSize = NOTE_MAP.size();int shift = 0;do{printModeInfo(MODE, (((shift <= 5) && (shift % 2)) || ((shift >= 8) && !(shift % 2))) ? true : false);shiftMode(MODE);shift += 1;}while (shift < 12);return 0; }/** brief 對調式向左(高音方向)移動一個單位** @param orgMode 原有的調式*/ void shiftMode(int &orgMode){orgMode <<= 1;orgMode = orgMode | !!((1 << 12) & orgMode); }/** brief 打印調式的相關信息** @param mode 調式* @param dFlag 降號表示,默認為0,即升號*/ void printModeInfo(int &mode, bool dFlag = false){for (int i = 0; i < 12; i++){int mask = (1 << i);if (mode & mask){std::cout << NOTE_MAP[i+12*dFlag] << "\t";}}std::cout << std::endl; }總結
以上是生活随笔為你收集整理的【日常篇】002_五线谱调式推导的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Secure CRT 自动记录日志
- 下一篇: 有意思的select~