用 200 行 Python 代碼掌握基本音樂理論
本文轉(zhuǎn)載自微信公眾號(hào)「Python中文社區(qū)」,作者M(jìn)anohar 。轉(zhuǎn)載本文請(qǐng)聯(lián)系Python中文社區(qū)公眾號(hào)。
本文作者是一位多年自學(xué)成才的吉他手,但對(duì)西方樂理一無所知,因此決定編寫一些代碼來搞懂它。
本文用了大約200行Python代碼來幫助我們理解西方音樂理論的基礎(chǔ)知識(shí)。
我們將首先查看西方音樂理論中的音符,使用它們來導(dǎo)出給定鍵中的半音階,然后將其與音程公式結(jié)合起來以導(dǎo)出常見的音階和和弦。
最后,我們將研究模式,這些模式是從通用音階衍生出來的整個(gè)音階集合,可以用來喚起比主要音階和次要音階所提供的悲喜二分法更微妙的情緒和氣氛。
十二音符
西方音樂的音樂字母由字母A到G組成,它們代表不同的音高。
我們可以使用以下Python列表來表示音樂字母:
- alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
但是,這些音符的頻率分布不均。為了使音高之間的間距更均勻,我們有以下十二個(gè)音符:
- notes_basic = [
- ['A'],
- ['A#', 'Bb'],
- ['B'],
- ['C'],
- ['C#', 'Db'],
- ['D'],
- ['D#', 'Eb'],
- ['E'],
- ['F'],
- ['F#', 'Gb'],
- ['G'],
- ['G#', 'Ab'],
- ]
這里有四點(diǎn)要注意:首先,每個(gè)音符相距半步或半音,其次,它的表示方式是通過可選的尾隨符號(hào)(稱為偶然符號(hào))來表示半步升高(尖銳, ?)或?qū)⒒{(diào)降低半個(gè)音階(平坦,?),第三,上述音符只是循環(huán)并重新開始,但是音階較高。
最后,您會(huì)注意到,其中一些音符由包含多個(gè)名稱的列表表示:這些是諧音等效詞,這是一種奇特的說法,即同一音符可以具有不同的“拼寫”。因此,例如,音符在A上方半步是A?,但也可以認(rèn)為是B下方半步的音符,因此可以稱為B?。由于歷史原因,音符 B/C 和 E/F 之間沒有尖銳或平坦的部分。
我們需要這些等效詞的重要原因是,當(dāng)我們開始推導(dǎo)通用音階(大,小和模式)時(shí),連續(xù)音符必須以連續(xù)字母開頭。具有不同字母的諧音等效詞使我們能夠正確得出這些音階。
實(shí)際上,對(duì)于某些鍵,上述諧音音符是不夠的。為了滿足“連續(xù)字母的不同字母規(guī)則”,我們最終不得不使用雙尖銳和雙扁平方式來提高或降低音符整步。這些音階通常具有不需要這些雙重偶然性的等效詞,但是為了完整起見,我們可以通過重寫我們的注釋來包括所有可能的諧音等效詞,如下所示:
- notes = [
- ['B#', 'C', 'Dbb'],
- ['B##', 'C#', 'Db'],
- ['C##', 'D', 'Ebb'],
- ['D#', 'Eb', 'Fbb'],
- ['D##', 'E', 'Fb'],
- ['E#', 'F', 'Gbb'],
- ['E##', 'F#', 'Gb'],
- ['F##', 'G', 'Abb'],
- ['G#', 'Ab'],
- ['G##', 'A', 'Bbb'],
- ['A#', 'Bb', 'Cbb'],
- ['A##', 'B', 'Cb'],
- ]
半音音階
半音階是最簡(jiǎn)單的音階,它僅由給定音調(diào)(音階中的主要音符,也稱為音調(diào))的八度之間的所有(十二個(gè))半音組成。
我們可以很容易地為任何給定的鍵生成一個(gè)半音階:(i)在我們的筆記列表中找到該音符的索引,(ii)向左旋轉(zhuǎn)音符列表多次。
查找給定音符的索引
讓我們編寫一個(gè)簡(jiǎn)單的函數(shù)來在此列表中查找特定的音符:
- def find_note_index(scale, search_note):
- ''' Given a scale, find the index of a particular note '''
- for index, note in enumerate(scale):
- # Deal with situations where we have a list of enharmonic
- # equivalents, as well as just a single note as and str.
- if type(note) == list:
- if search_note in note:
- return index
- elif type(note) == str:
- if search_note == note:
- return index
find_note_index()函數(shù)將一系列音符(scale)和要搜索的音符(search_note)作為參數(shù),并通過簡(jiǎn)單的線性搜索返回索引。我們?cè)谘h(huán)中處理兩種情況:(i)提供的音階由單個(gè)音符組成(例如上面的字母列表),或(ii)由音階等效音列表組成(例如上面的note或notes_basic列表)。下面是該函數(shù)對(duì)于這兩種情況的示例:
- >>> find_note_index(notes, 'A') # notes is a list of lists
- 9
- >>> find_note_index(alphabet, 'A') # alphabet is a list of notes
- 0
向左旋轉(zhuǎn)音符
現(xiàn)在,我們可以編寫一個(gè)將給定scale旋轉(zhuǎn)n步的函數(shù):
- def rotate(scale, n):
- ''' Left-rotate a scale by n positions. '''
- return scale[n:] + scale[:n]
我們?cè)谖恢胣處切割scale列表,并交換這兩半。這是將alphabet列表旋轉(zhuǎn)三個(gè)位置(將音符D放在前面)的示例:
- >>> alphabet
- ['A', 'B', 'C', 'D', 'E', 'F', 'G']
- >>> rotate(alphabet, 3)
- ['D', 'E', 'F', 'G', 'A', 'B', 'C']
在給定鍵中生成半音音階
現(xiàn)在,我們終于可以編寫我們的colour()函數(shù)了,該函數(shù)通過旋轉(zhuǎn)notes數(shù)組為給定的鍵生成一個(gè)半音音階:
- def chromatic(key):
- ''' Generate a chromatic scale in a given key. '''
- # Figure out how much to rotate the notes list by and return
- # the rotated version.
- num_rotations = find_note_index(notes, key)
- return rotate(notes, num_rotations)
上面的colour()函數(shù)在注釋列表中找到所提供鍵的索引(使用我們的find_note_index()函數(shù)),然后將其旋轉(zhuǎn)該量以使其移到最前面(使用我們的rotate()函數(shù))。這是生成D半音音階的示例:
- >>> import pprint
- >>> pprint.pprint(chromatic('D'))
- [['C##', 'D', 'Ebb'],
- ['D#', 'Eb', 'Fbb'],
- ['D##', 'E', 'Fb'],
- ['E#', 'F', 'Gbb'],
- ['E##', 'F#', 'Gb'],
- ['F##', 'G', 'Abb'],
- ['G#', 'Ab'],
- ['G##', 'A', 'Bbb'],
- ['A#', 'Bb', 'Cbb'],
- ['A##', 'B', 'Cb'],
- ['B#', 'C', 'Dbb'],
- ['B##', 'C#', 'Db']]
對(duì)于半音音階,通常在上升時(shí)使用銳利度,而在下降時(shí)使用平坦度。但是,就目前而言,我們將諧音等值保持不變。我們將看到如何選擇正確的音節(jié)以供以后使用。
間隔時(shí)間
間隔指定音符之間的相對(duì)距離。
因此,可以基于半音階音符與根音的相對(duì)距離來命名。以下是每個(gè)音節(jié)的標(biāo)準(zhǔn)名稱,其順序與音節(jié)列表中的索引相同:
- intervals = [
- ['P1', 'd2'], # Perfect unison Diminished second
- ['m2', 'A1'], # Minor second Augmented unison
- ['M2', 'd3'], # Major second Diminished third
- ['m3', 'A2'], # Minor third Augmented second
- ['M3', 'd4'], # Major third Diminished fourth
- ['P4', 'A3'], # Perfect fourth Augmented third
- ['d5', 'A4'], # Diminished fifth Augmented fourth
- ['P5', 'd6'], # Perfect fifth Diminished sixth
- ['m6', 'A5'], # Minor sixth Augmented fifth
- ['M6', 'd7'], # Major sixth Diminished seventh
- ['m7', 'A6'], # Minor seventh Augmented sixth
- ['M7', 'd8'], # Major seventh Diminished octave
- ['P8', 'A7'], # Perfect octave Augmented seventh
- ]
同樣,同一音符可以具有不同的音程名稱。例如,根音可以被認(rèn)為是完美的統(tǒng)一音色或減弱的第二音符。
從諧音等效中選取音符
給定鍵中的半音音階和上述數(shù)組中的間隔,我們可以指出要使用的確切音符(并從一組諧音等效項(xiàng)中過濾掉)。讓我們看一下執(zhí)行此操作的基本方法。
舉例來說,讓我們看一下如何從D色階中找到與M3或主要的第三音階相對(duì)應(yīng)的音符。
1、從區(qū)間數(shù)組中,我們可以看到找到M3的索引為4。即'M3' in intervals[4] == True。
2、現(xiàn)在,我們?cè)贒半音音階(以其長(zhǎng)度為模)中查看相同的索引。我們發(fā)現(xiàn)colour('D')[4]是音符['E ##','F#','Gb']的列表。
3、M3中的數(shù)字(即3)表示我們需要使用的字母,其中1表示根字母。因此,例如,對(duì)于D的鍵,1 = D,2 = E,3 = F,4 = G,5 = A,6 = B,7 = C,8 = D…等等。因此,我們需要在包含字母F的音節(jié)列表(['E ##','F#','Gb'])中尋找一個(gè)音節(jié)。這就是音節(jié)F#。
4、結(jié)論:相對(duì)于D的三分之一(M3)是F#。
以編程方式標(biāo)記給定鍵的間隔
我們可以編寫一個(gè)相對(duì)簡(jiǎn)單的函數(shù),以編程方式為我們應(yīng)用此邏輯,并為我們提供一個(gè)字典,將給定鍵中的所有音程名稱映射到正確的音符名稱:
- def make_intervals_standard(key):
- # Our labeled set of notes mapping interval names to notes
- labels = {}
- # Step 1: Generate a chromatic scale in our desired key
- chromatic_scale = chromatic(key)
- # The alphabets starting at provided key's alphabet
- alphabet_key = rotate(alphabet, find_note_index(alphabet, key[0]))
- # Iterate through all intervals (list of lists)
- for index, interval_list in enumerate(intervals):
- # Step 2: Find the notes to search through based on degree
- notes_to_search = chromatic_scale[index % len(chromatic_scale)]
- for interval_name in interval_list:
- # Get the interval degree
- degree = int(interval_name[1]) - 1 # e.g. M3 --> 3, m7 --> 7
- # Get the alphabet to look for
- alphabet_to_search = alphabet_key[degree % len(alphabet_key)]
- try:
- note = [x for x in notes_to_search if x[0] == alphabet_to_search][0]
- except:
- note = notes_to_search[0]
- labels[interval_name] = note
- return labels
這是我們返回C鍵的字典:
- >>> import pprint
- >>> pprint.pprint(make_intervals_standard('C'), sort_dicts=False)
- {'P1': 'C',
- 'd2': 'Dbb',
- 'm2': 'Db',
- 'A1': 'C#',
- 'M2': 'D',
- 'd3': 'Ebb',
- 'm3': 'Eb',
- 'A2': 'D#',
- 'M3': 'E',
- 'd4': 'Fb',
- 'P4': 'F',
- 'A3': 'E#',
- 'd5': 'Gb',
- 'A4': 'F#',
- 'P5': 'G',
- 'd6': 'Abb',
- 'm6': 'Ab',
- 'A5': 'G#',
- 'M6': 'A',
- 'd7': 'Bbb',
- 'm7': 'Bb',
- 'A6': 'A#',
- 'M7': 'B',
- 'd8': 'Cb',
- 'P8': 'C',
- 'A7': 'B#'}
間隔公式
現(xiàn)在,我們可以使用間隔名稱指定公式或音節(jié)組,并能夠?qū)⑺鼈冇成涞轿覀兿胍娜魏捂I:
- def make_formula(formula, labeled):
- '''
- Given a comma-separated interval formula, and a set of labeled
- notes in a key, return the notes of the formula.
- '''
- return [labeled[x] for x in formula.split(',')]
大音階公式
例如,大音階的公式為:
- formula = 'P1,M2,M3,P4,P5,M6,M7,P8'
我們可以使用它輕松地為不同的鍵生成主音階,如下所示:
- >>> for key in alphabet:
- >>> print(key, make_formula(formula, make_intervals_standard(key)))
- C ['C', 'D', 'E', 'F', 'G', 'A', 'B', 'C']
- D ['D', 'E', 'F#', 'G', 'A', 'B', 'C#', 'D']
- E ['E', 'F#', 'G#', 'A', 'B', 'C#', 'D#', 'E']
- F ['F', 'G', 'A', 'Bb', 'C', 'D', 'E', 'F']
- G ['G', 'A', 'B', 'C', 'D', 'E', 'F#', 'G']
- A ['A', 'B', 'C#', 'D', 'E', 'F#', 'G#', 'A']
- B ['B', 'C#', 'D#', 'E', 'F#', 'G#', 'A#', 'B']
美化音階
我們還快速編寫一個(gè)更好的方法來打印音階的函數(shù):
- def dump(scale, separator=' '):
- '''
- Pretty-print the notes of a scale. Replaces b and # characters
- for unicode flat and sharp symbols.
- '''
- return separator.join(['{:<3s}'.format(x) for x in scale]) \
- .replace('b', '\u266d') \
- .replace('#', '\u266f')
這是使用正確的unicode字符的更好輸出:
- >>> for key in alphabet:
- >>> scale = make_formula(formula, make_intervals_standard(key))
- >>> print('{}: {}'.format(key, dump(scale)))
- C: C D E F G A B C
- D: D E F♯ G A B C♯ D
- E: E F♯ G♯ A B C♯ D♯ E
- F: F G A B♭ C D E F
- G: G A B C D E F♯ G
- A: A B C♯ D E F♯ G♯ A
- B: B C♯ D♯ E F♯ G♯ A♯ B
對(duì)公式使用大音階區(qū)間
公式命名的另一種方法是基于主要標(biāo)準(zhǔn)的音節(jié)。彈奏樂器時(shí)這會(huì)更容易,因?yàn)槿绻煜て渲饕綦A,則可以在給定的琴鍵中獲得音階和和弦。
以下是相對(duì)于給定鍵中主音階的音程名稱:
- intervals_major = [
- [ '1', 'bb2'],
- ['b2', '#1'],
- [ '2', 'bb3', '9'],
- ['b3', '#2'],
- [ '3', 'b4'],
- [ '4', '#3', '11'],
- ['b5', '#4', '#11'],
- [ '5', 'bb6'],
- ['b6', '#5'],
- [ '6', 'bb7', '13'],
- ['b7', '#6'],
- [ '7', 'b8'],
- [ '8', '#7'],
- ]
我還添加了用于更復(fù)雜的和弦(第9、11和13)的常用音程。這些本質(zhì)上是圍繞模八進(jìn)行包裝的。因此,例如,第9位只是第2位,但高了八度。
我們還可以修改我們的make_intervals()函數(shù)以使用此函數(shù):
- def make_intervals(key, interval_type='standard'):
- ...
- for index, interval_list in enumerate(intervals):
- ...
- intervs = intervals if interval_type == 'standard' else intervals_major
- for interval_name in intervs:
- # Get the interval degree
- if interval_type == 'standard':
- degree = int(interval_name[1]) - 1 # e.g. M3 --> 3, m7 --> 7
- elif interval_type == 'major':
- degree = int(re.sub('[b#]', '', interval_name)) - 1
- ...
- return labels
上面,我們剛剛向make_intervals()函數(shù)添加了一個(gè)新參數(shù)(interval_type),并在內(nèi)部循環(huán)中以不同的方式計(jì)算degree度數(shù)。如果將interval_type指定為'major',則只需刪除所有b和#字符,然后再轉(zhuǎn)換為整數(shù)以獲取度數(shù)即可。
推導(dǎo)通用音階和和弦
這是一堆涵蓋最常見音階和和弦的公式:
- formulas = {
- # Scale formulas
- 'scales': {
- # Major scale, its modes, and minor scale
- 'major': '1,2,3,4,5,6,7',
- 'minor': '1,2,b3,4,5,b6,b7',
- # Melodic minor and its modes
- 'melodic_minor': '1,2,b3,4,5,6,7',
- # Harmonic minor and its modes
- 'harmonic_minor': '1,2,b3,4,5,b6,7',
- # Blues scales
- 'major_blues': '1,2,b3,3,5,6',
- 'minor_blues': '1,b3,4,b5,5,b7',
- # Penatatonic scales
- 'pentatonic_major': '1,2,3,5,6',
- 'pentatonic_minor': '1,b3,4,5,b7',
- 'pentatonic_blues': '1,b3,4,b5,5,b7',
- },
- 'chords': {
- # Major
- 'major': '1,3,5',
- 'major_6': '1,3,5,6',
- 'major_6_9': '1,3,5,6,9',
- 'major_7': '1,3,5,7',
- 'major_9': '1,3,5,7,9',
- 'major_13': '1,3,5,7,9,11,13',
- 'major_7_#11': '1,3,5,7,#11',
- # Minor
- 'minor': '1,b3,5',
- 'minor_6': '1,b3,5,6',
- 'minor_6_9': '1,b3,5,6,9',
- 'minor_7': '1,b3,5,b7',
- 'minor_9': '1,b3,5,b7,9',
- 'minor_11': '1,b3,5,b7,9,11',
- 'minor_7_b5': '1,b3,b5,b7',
- # Dominant
- 'dominant_7': '1,3,5,b7',
- 'dominant_9': '1,3,5,b7,9',
- 'dominant_11': '1,3,5,b7,9,11',
- 'dominant_13': '1,3,5,b7,9,11,13',
- 'dominant_7_#11': '1,3,5,b7,#11',
- # Diminished
- 'diminished': '1,b3,b5',
- 'diminished_7': '1,b3,b5,bb7',
- 'diminished_7_half': '1,b3,b5,b7',
- # Augmented
- 'augmented': '1,3,#5',
- # Suspended
- 'sus2': '1,2,5',
- 'sus4': '1,4,5',
- '7sus2': '1,2,5,b7',
- '7sus4': '1,4,5,b7',
- },
- }
這是在C鍵中生成所有這些音階和和弦時(shí)的輸出:
- intervs = make_intervals('C', 'major')
- for ftype in formulas:
- print(ftype)
- for name, formula in formulas[ftype].items():
- v = make_formula(formula, intervs)
- print('\t{}: {}'.format(name, dump(v)))
- scales
- major: C D E F G A B
- minor: C D E♭ F G A♭ B♭
- melodic_minor: C D E♭ F G A B
- harmonic_minor: C D E♭ F G A♭ B
- major_blues: C D E♭ E G A
- minor_blues: C E♭ F G♭ G B♭
- pentatonic_major: C D E G A
- pentatonic_minor: C E♭ F G B♭
- pentatonic_blues: C E♭ F G♭ G B♭
- chords
- major: C E G
- major_6: C E G A
- major_6_9: C E G A D
- major_7: C E G B
- major_9: C E G B D
- major_13: C E G B D F A
- major_7_#11: C E G B F♯
- minor: C E♭ G
- minor_6: C E♭ G A
- minor_6_9: C E♭ G A D
- minor_7: C E♭ G B♭
- minor_9: C E♭ G B♭ D
- minor_11: C E♭ G B♭ D F
- minor_7_b5: C E♭ G♭ B♭
- dominant_7: C E G B♭
- dominant_9: C E G B♭ D
- dominant_11: C E G B♭ D F
- dominant_13: C E G B♭ D F A
- dominant_7_#11: C E G B♭ F♯
- diminished: C E♭ G♭
- diminished_7: C E♭ G♭ B♭♭
- diminished_7_half: C E♭ G♭ B♭
- augmented: C E G♯
- sus2: C D G
- sus4: C F G
- 7sus2: C D G B♭
- 7sus4: C F G B♭
模式
模式本質(zhì)上是刻度的左旋。
- mode = rotate
需要注意的是,由于旋轉(zhuǎn)后的根音會(huì)發(fā)生變化,因此所得到的旋轉(zhuǎn)比例或模式處于不同的鍵中。
對(duì)于每個(gè)鍵,主要有七個(gè)主要音階模式,具體取決于所應(yīng)用的左旋次數(shù),每個(gè)模式都有一個(gè)特定的名稱:
- major_mode_rotations = {
- 'Ionian': 0,
- 'Dorian': 1,
- 'Phrygian': 2,
- 'Lydian': 3,
- 'Mixolydian': 4,
- 'Aeolian': 5,
- 'Locrian': 6,
- }
使用此方法,我們現(xiàn)在可以為任何給定鍵生成主要比例的模式。這是C大調(diào)的一個(gè)例子:
- intervs = make_intervals('C', 'major')
- c_major_scale = make_formula(formulas['scales']['major'], intervs)
- for m in major_mode_rotations:
- v = mode(c_major_scale, major_mode_rotations[m])
- print('{} {}: {}'.format(dump([v[0]]), m, dump(v)))
這就是結(jié)果。請(qǐng)記住,根音隨著每次旋轉(zhuǎn)而變化:
- C Ionian: C D E F G A B
- D Dorian: D E F G A B C
- E Phrygian: E F G A B C D
- F Lydian: F G A B C D E
- G Mixolydian: G A B C D E F
- A Aeolian: A B C D E F G
- B Locrian: B C D E F G A
上面,我們正在研究從給定比例導(dǎo)出的模式。但是,實(shí)際上我們關(guān)心的是給定鍵的模式。因此,給定C的鍵,我們想知道C Ionian,C Dorian,C Mixolydian等。
另一種表達(dá)方式是,例如“ C Mixolidian”與“the Mixolydian of C”不同。前者是指根音為C的混合音階,后者是指C大音階的混合音階(即上方的G混合音階)。
我們還可以非常輕松地在給定鍵中生成模式。
- keys = [
- 'B#', 'C', 'C#', 'Db', 'D', 'D#', 'Eb', 'E', 'Fb', 'E#', 'F',
- 'F#', 'Gb', 'G', 'G#', 'Ab', 'A', 'A#', 'Bb', 'B', 'Cb',
- ]
- modes = {}
- for key in keys:
- intervs = make_intervals(key, 'major')
- c_major_scale = make_formula(formulas['scales']['major'], intervs)
- for m in major_mode_rotations:
- v = mode(c_major_scale, major_mode_rotations[m])
- if v[0] not in modes:
- modes[v[0]] = {}
- modes[v[0]][m] = v
上面,我們循環(huán)了每個(gè)鍵,并建立了一個(gè)字典,其中包含我們遇到每個(gè)鍵時(shí)所使用的模式(通過檢查模式的第一個(gè)音符)。
現(xiàn)在,例如,如果我們打印出模式['C'],則會(huì)得到以下內(nèi)容:
總結(jié)
因此,我們研究了西方音樂理論中的基本音符。如何從這些音節(jié)中得出音階。如何利用間隔名稱從諧音等效項(xiàng)中選擇正確的音符。然后,我們研究了如何使用間隔公式(使用標(biāo)準(zhǔn)音程名稱和相對(duì)于大音階的音程)來生成各種音階和和弦。最后,我們看到模式只是音階的旋轉(zhuǎn),對(duì)于給定的鍵可以用兩種方式查看:通過旋轉(zhuǎn)給定鍵的音階(將在另一個(gè)鍵中)得出的模式,以及從模式中得出的模式。從某些鍵開始,這樣第一個(gè)音符就是我們想要的鍵。