用 200 行 Python 代碼掌握基本音樂理論
本文轉載自微信公眾號「Python中文社區」,作者Manohar 。轉載本文請聯系Python中文社區公眾號。
本文作者是一位多年自學成才的吉他手,但對西方樂理一無所知,因此決定編寫一些代碼來搞懂它。
本文用了大約200行Python代碼來幫助我們理解西方音樂理論的基礎知識。
我們將首先查看西方音樂理論中的音符,使用它們來導出給定鍵中的半音階,然后將其與音程公式結合起來以導出常見的音階和和弦。
最后,我們將研究模式,這些模式是從通用音階衍生出來的整個音階集合,可以用來喚起比主要音階和次要音階所提供的悲喜二分法更微妙的情緒和氣氛。
十二音符
西方音樂的音樂字母由字母A到G組成,它們代表不同的音高。
我們可以使用以下Python列表來表示音樂字母:
- alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
但是,這些音符的頻率分布不均。為了使音高之間的間距更均勻,我們有以下十二個音符:
- notes_basic = [
- ['A'],
- ['A#', 'Bb'],
- ['B'],
- ['C'],
- ['C#', 'Db'],
- ['D'],
- ['D#', 'Eb'],
- ['E'],
- ['F'],
- ['F#', 'Gb'],
- ['G'],
- ['G#', 'Ab'],
- ]
這里有四點要注意:首先,每個音符相距半步或半音,其次,它的表示方式是通過可選的尾隨符號(稱為偶然符號)來表示半步升高(尖銳, ?)或將基調降低半個音階(平坦,?),第三,上述音符只是循環并重新開始,但是音階較高。
最后,您會注意到,其中一些音符由包含多個名稱的列表表示:這些是諧音等效詞,這是一種奇特的說法,即同一音符可以具有不同的“拼寫”。因此,例如,音符在A上方半步是A?,但也可以認為是B下方半步的音符,因此可以稱為B?。由于歷史原因,音符 B/C 和 E/F 之間沒有尖銳或平坦的部分。
我們需要這些等效詞的重要原因是,當我們開始推導通用音階(大,小和模式)時,連續音符必須以連續字母開頭。具有不同字母的諧音等效詞使我們能夠正確得出這些音階。
實際上,對于某些鍵,上述諧音音符是不夠的。為了滿足“連續字母的不同字母規則”,我們最終不得不使用雙尖銳和雙扁平方式來提高或降低音符整步。這些音階通常具有不需要這些雙重偶然性的等效詞,但是為了完整起見,我們可以通過重寫我們的注釋來包括所有可能的諧音等效詞,如下所示:
- 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'],
- ]
半音音階
半音階是最簡單的音階,它僅由給定音調(音階中的主要音符,也稱為音調)的八度之間的所有(十二個)半音組成。
我們可以很容易地為任何給定的鍵生成一個半音階:(i)在我們的筆記列表中找到該音符的索引,(ii)向左旋轉音符列表多次。
查找給定音符的索引
讓我們編寫一個簡單的函數來在此列表中查找特定的音符:
- 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()函數將一系列音符(scale)和要搜索的音符(search_note)作為參數,并通過簡單的線性搜索返回索引。我們在循環中處理兩種情況:(i)提供的音階由單個音符組成(例如上面的字母列表),或(ii)由音階等效音列表組成(例如上面的note或notes_basic列表)。下面是該函數對于這兩種情況的示例:
- >>> find_note_index(notes, 'A') # notes is a list of lists
- 9
- >>> find_note_index(alphabet, 'A') # alphabet is a list of notes
- 0
向左旋轉音符
現在,我們可以編寫一個將給定scale旋轉n步的函數:
- def rotate(scale, n):
- ''' Left-rotate a scale by n positions. '''
- return scale[n:] + scale[:n]
我們在位置n處切割scale列表,并交換這兩半。這是將alphabet列表旋轉三個位置(將音符D放在前面)的示例:
- >>> alphabet
- ['A', 'B', 'C', 'D', 'E', 'F', 'G']
- >>> rotate(alphabet, 3)
- ['D', 'E', 'F', 'G', 'A', 'B', 'C']
在給定鍵中生成半音音階
現在,我們終于可以編寫我們的colour()函數了,該函數通過旋轉notes數組為給定的鍵生成一個半音音階:
- 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()函數在注釋列表中找到所提供鍵的索引(使用我們的find_note_index()函數),然后將其旋轉該量以使其移到最前面(使用我們的rotate()函數)。這是生成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']]
對于半音音階,通常在上升時使用銳利度,而在下降時使用平坦度。但是,就目前而言,我們將諧音等值保持不變。我們將看到如何選擇正確的音節以供以后使用。
間隔時間
間隔指定音符之間的相對距離。
因此,可以基于半音階音符與根音的相對距離來命名。以下是每個音節的標準名稱,其順序與音節列表中的索引相同:
- 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
- ]
同樣,同一音符可以具有不同的音程名稱。例如,根音可以被認為是完美的統一音色或減弱的第二音符。
從諧音等效中選取音符
給定鍵中的半音音階和上述數組中的間隔,我們可以指出要使用的確切音符(并從一組諧音等效項中過濾掉)。讓我們看一下執行此操作的基本方法。
舉例來說,讓我們看一下如何從D色階中找到與M3或主要的第三音階相對應的音符。
1、從區間數組中,我們可以看到找到M3的索引為4。即'M3' in intervals[4] == True。
2、現在,我們在D半音音階(以其長度為模)中查看相同的索引。我們發現colour('D')[4]是音符['E ##','F#','Gb']的列表。
3、M3中的數字(即3)表示我們需要使用的字母,其中1表示根字母。因此,例如,對于D的鍵,1 = D,2 = E,3 = F,4 = G,5 = A,6 = B,7 = C,8 = D…等等。因此,我們需要在包含字母F的音節列表(['E ##','F#','Gb'])中尋找一個音節。這就是音節F#。
4、結論:相對于D的三分之一(M3)是F#。
以編程方式標記給定鍵的間隔
我們可以編寫一個相對簡單的函數,以編程方式為我們應用此邏輯,并為我們提供一個字典,將給定鍵中的所有音程名稱映射到正確的音符名稱:
- 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#'}
間隔公式
現在,我們可以使用間隔名稱指定公式或音節組,并能夠將它們映射到我們想要的任何鍵:
- 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']
美化音階
我們還快速編寫一個更好的方法來打印音階的函數:
- 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
對公式使用大音階區間
公式命名的另一種方法是基于主要標準的音節。彈奏樂器時這會更容易,因為如果您熟悉其主要音階,則可以在給定的琴鍵中獲得音階和和弦。
以下是相對于給定鍵中主音階的音程名稱:
- 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'],
- ]
我還添加了用于更復雜的和弦(第9、11和13)的常用音程。這些本質上是圍繞模八進行包裝的。因此,例如,第9位只是第2位,但高了八度。
我們還可以修改我們的make_intervals()函數以使用此函數:
- 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()函數添加了一個新參數(interval_type),并在內部循環中以不同的方式計算degree度數。如果將interval_type指定為'major',則只需刪除所有b和#字符,然后再轉換為整數以獲取度數即可。
推導通用音階和和弦
這是一堆涵蓋最常見音階和和弦的公式:
- 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鍵中生成所有這些音階和和弦時的輸出:
- 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♭
模式
模式本質上是刻度的左旋。
- mode = rotate
需要注意的是,由于旋轉后的根音會發生變化,因此所得到的旋轉比例或模式處于不同的鍵中。
對于每個鍵,主要有七個主要音階模式,具體取決于所應用的左旋次數,每個模式都有一個特定的名稱:
- major_mode_rotations = {
- 'Ionian': 0,
- 'Dorian': 1,
- 'Phrygian': 2,
- 'Lydian': 3,
- 'Mixolydian': 4,
- 'Aeolian': 5,
- 'Locrian': 6,
- }
使用此方法,我們現在可以為任何給定鍵生成主要比例的模式。這是C大調的一個例子:
- 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)))
這就是結果。請記住,根音隨著每次旋轉而變化:
- 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
上面,我們正在研究從給定比例導出的模式。但是,實際上我們關心的是給定鍵的模式。因此,給定C的鍵,我們想知道C Ionian,C Dorian,C Mixolydian等。
另一種表達方式是,例如“ 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
上面,我們循環了每個鍵,并建立了一個字典,其中包含我們遇到每個鍵時所使用的模式(通過檢查模式的第一個音符)。
現在,例如,如果我們打印出模式['C'],則會得到以下內容:
總結
因此,我們研究了西方音樂理論中的基本音符。如何從這些音節中得出音階。如何利用間隔名稱從諧音等效項中選擇正確的音符。然后,我們研究了如何使用間隔公式(使用標準音程名稱和相對于大音階的音程)來生成各種音階和和弦。最后,我們看到模式只是音階的旋轉,對于給定的鍵可以用兩種方式查看:通過旋轉給定鍵的音階(將在另一個鍵中)得出的模式,以及從模式中得出的模式。從某些鍵開始,這樣第一個音符就是我們想要的鍵。