【日常篇】002_五线谱调式推导
五線譜調(diào)式推導(dǎo)
??很早的時(shí)候就聽說過各種諸如“X大調(diào)”、“X小調(diào)”這樣的術(shù)語,但聽了這么多年也沒有搞明白這究竟是什么意思。
??直到最近兩個(gè)月,才有大佬提示,其實(shí)各種調(diào)式升降記號(hào)都是可以由鋼琴鍵位平移的方式得到的,這提供了一種推斷調(diào)式的方法:在最常規(guī)的C大調(diào)基礎(chǔ)上,每個(gè)音都提升一個(gè)音程,然后再看有哪些音跑到了黑鍵上,就可以知道有幾個(gè)升號(hào)或降號(hào)了。
??盡管這個(gè)過程用手算也是很容易完成的,但考慮到自己在C++公選結(jié)課后,已經(jīng)有兩年多的時(shí)間沒有碰過C++了。為了再次熟練這個(gè)比較重要的語言,本次推導(dǎo)過程將使用C++來完成。
基本思路
從C大調(diào)出發(fā)
??C大調(diào)在五線譜中,是沒有任何升降號(hào)的存在的:
??而如果將鍵位整體右移兩個(gè)半音,則可以得到兩個(gè)升號(hào)的調(diào)(D大調(diào)):
??因?yàn)橐粋€(gè)八度共有12個(gè)半音,所以這樣的平移一共可以得到十二種調(diào)式。只要對(duì)C大調(diào)的鍵位進(jìn)行12次平移,就可以得到全部的十二種調(diào)式。
調(diào)式在程序中的表示
??一個(gè)八度有12個(gè)半音,而在推導(dǎo)過程中需要不斷地將這些鍵往一個(gè)方向進(jìn)行平移,因此考慮到循環(huán)移位。使用12個(gè)bit分別對(duì)應(yīng)一個(gè)八度中的12個(gè)半音C、C#|Db、D、D#|Eb、E、F、F#|Gb、G、G#|Ab、A、A#|Bb、B,若鍵位在那個(gè)半音上則置1,否則置0。每一次往高音處移位時(shí),超出B的部分都將會(huì)回到C的位置。
??讓第0位表示C,第1位表示C#……以此類推,第11位表示B,如果向左移位的話,則第11位被移動(dòng)至第0位:
??例如,C大調(diào)對(duì)應(yīng)的七個(gè)鍵位分別為C、D、E、F、G、A、B,則對(duì)應(yīng)的bit序列為:101010110101。而向高音處平移兩個(gè)半音到D大調(diào),則對(duì)應(yīng)向左循環(huán)移位兩次,得到:101011010110,對(duì)應(yīng)C#、D、E、F#、G、A、B。
??有了比較明確的思路后,就可以開始寫代碼了。
代碼實(shí)現(xiàn)
初始狀態(tài):C大調(diào)
??前面已經(jīng)提到,C大調(diào)的bit表示方法,而初始狀態(tài)又確認(rèn)為C大調(diào),因此這個(gè)數(shù)值可以作為常量存放在頭文件中:
int MODE = 0xAB5; //101010110101 <- reverse(101011010101)位與音符的對(duì)應(yīng)關(guān)系
??類似于python的字典,C++的標(biāo)準(zhǔn)庫(kù)也有提供字典這個(gè)結(jié)構(gòu)。該結(jié)構(gòu)存放在map庫(kù)中,include這個(gè)庫(kù)即可用std::map進(jìn)行使用(這里已經(jīng)用了using std::map,所以在使用時(shí)直接寫的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個(gè)詞條,主要是因?yàn)橛行┱{(diào)式是使用降號(hào)的,對(duì)于這樣的調(diào)式,應(yīng)該使用降號(hào)表示法(對(duì)應(yīng)字典的第13-24條)。
循環(huán)移位
??循環(huán)移位的思路非常簡(jiǎn)單,就是向左移位一次,然后將最高位移動(dòng)到第一位。這里沒有對(duì)第12位及以上的bit進(jìn)行截?cái)?#xff0c;但也沒有影響,因?yàn)楹罄m(xù)的操作不會(huì)涉及到更高位的bit:
/** brief 對(duì)調(diào)式向左(高音方向)移動(dòng)一個(gè)單位** @param orgMode 原有的調(diào)式*/ void shiftMode(int &orgMode){orgMode <<= 1;orgMode = orgMode | !!((1 << 12) & orgMode); }打印調(diào)式信息
??調(diào)式有升號(hào)調(diào)的和降號(hào)調(diào)這兩種,為了區(qū)分開來,在這里使用降號(hào)調(diào)的flag標(biāo)記:dFlag,來將它們區(qū)分開來。如果有降號(hào)表示,則會(huì)在字典中查詢第13-24項(xiàng)的詞條:
/** brief 打印調(diào)式的相關(guān)信息** @param mode 調(diào)式* @param dFlag 降號(hào)表示,默認(rèn)為0,即升號(hào)*/ 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函數(shù)
??對(duì)每一次移位的結(jié)果,都進(jìn)行打印即可。由于先前已有經(jīng)驗(yàn),升號(hào)調(diào)和降號(hào)調(diào)是交替出現(xiàn)的,所以在這里就可以很容易地判斷出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; }運(yùn)行結(jié)果及結(jié)論
??運(yùn)行后得到如下輸出:
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??此即推導(dǎo)得到的12種調(diào)式,注意到升號(hào)和降號(hào)的數(shù)量是關(guān)于C大調(diào)對(duì)稱的,用圖像表示則更為直觀:
??沿著這個(gè)圖像順時(shí)針看,調(diào)式依次從C大調(diào)、降D大調(diào)、D大調(diào)……演變到B大調(diào),最后再回到C大調(diào)。而這些大調(diào),每一個(gè)都又有著相對(duì)應(yīng)的小調(diào),由于還沒有找出規(guī)律,就不在這里描述了。
??而對(duì)于C大調(diào)向上移動(dòng)6個(gè)半音的調(diào)式——升F大調(diào),則暫時(shí)難以在輸出結(jié)果中得以理解。
??而經(jīng)過在overture和網(wǎng)上的查詢,得知升F大調(diào)由六個(gè)升記號(hào)或六個(gè)降記號(hào)組成:
??并且,E#=F,因?yàn)镋和F之間只差一個(gè)半音。
??由此一來,調(diào)式這個(gè)問題也就得到了一個(gè)比較清晰的解答。
完整代碼
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 對(duì)調(diào)式向左(高音方向)移動(dòng)一個(gè)單位** @param orgMode 原有的調(diào)式*/ void shiftMode(int &orgMode){orgMode <<= 1;orgMode = orgMode | !!((1 << 12) & orgMode); }/** brief 打印調(diào)式的相關(guān)信息** @param mode 調(diào)式* @param dFlag 降號(hào)表示,默認(rèn)為0,即升號(hào)*/ 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; }總結(jié)
以上是生活随笔為你收集整理的【日常篇】002_五线谱调式推导的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Secure CRT 自动记录日志
- 下一篇: Linux内核奔溃分析