前言
彎音輪,是在MIDI鍵盤或專業電子琴一旁安裝的一個裝置(如下圖)。
通過前後撥動滾輪,可以實現彎音和顫音的效果。這對於追求特殊電音效果的作曲者來說是必不可少的,而這兩個技巧也是吉他等樂器演奏時十分常用的技巧,故在編程中學會更加自然和協調的模擬彎音和顫音效果,是模擬吉他等樂器時必不可少的。
再談Message
我們第一篇文章已經簡單講過Message類是MIDI編曲中最爲重要的概念,地位和作用相當於人體的細胞。
再次參考 Mido官方文檔中的Message Type章節 ,我們可以看到在所有的Message種類中有pitchwheel,它便是用於模擬彎音輪效果的一種消息類別,也是實現滑音和顫音的關鍵。
Pitchwheel類Message的基本格式如下:
Message('pitchwheel', pitch, time, channel)
其中time和channel的意義同之前相同,而pitch參數是一個區間爲-8192到8192的整數,用於表示音高“彎曲”的程度,取正數時趨向於高音,取負數時趨向於低音。pitch取3000的時候效果是“彎曲”一個半音。
若要實現完整的滑音過程,我們還需要Aftertouch這個類型的Message類型,其基本格式如下:
Message('aftertouch', time, channel, ...)
這種Message是用於在音符按下且未結束的時候改變某些屬性,比如音量和頻道等,在此我們僅僅用它來維持我們的音高。
編程實現
我們的目標是通過Pitchwheel這一種Message類型實現兩種效果——滑音和顫音,故我們對這兩種效果分別編碼,將相關的代碼添加到改名後的 實踐(一)的play_note函數——add_note函數中:
def add_note(note, length, track, base_num=0, delay=0, velocity=1.0, channel=0, pitch_type=0, tremble_setting=None, bend_setting=None):
bpm = get_bpm(track)
meta_time = 60 * 60 * 10 / bpm
major_notes = [0, 2, 2, 1, 2, 2, 2, 1]
base_note = 60
if pitch_type == 0: # No Pitch Wheel Message
track.append(Message('note_on', note=base_note + base_num*12 + sum(major_notes[0:note]), velocity=round(64*velocity), time=round(delay*meta_time), channel=channel))
track.append(Message('note_off', note=base_note + base_num*12 + sum(major_notes[0:note]), velocity=round(64*velocity), time=round(meta_time*length), channel=channel))
elif pitch_type == 1: # Tremble
try:
pitch = tremble_setting['pitch']
wheel_times = tremble_setting['wheel_times']
track.append(Message('note_on', note=base_note + base_num * 12 + sum(major_notes[0:note]),
velocity=round(64 * velocity),
time=round(delay * meta_time), channel=channel))
for i in range(wheel_times):
track.append(Message('pitchwheel', pitch=pitch, time=round(meta_time * length / (2 * wheel_times)),
channel=channel))
track.append(Message('pitchwheel', pitch=0, time=0, channel=channel))
track.append(Message('pitchwheel', pitch=-pitch, time=round(meta_time * length / (2 * wheel_times)),
channel=channel))
track.append(Message('pitchwheel', pitch=0, time=0, channel=channel))
track.append(Message('note_off', note=base_note + base_num * 12 + sum(major_notes[0:note]),
velocity=round(64 * velocity), time=0, channel=channel))
except:
print(traceback.format_exc())
elif pitch_type == 2: # Bend
try:
pitch = bend_setting['pitch']
PASDA = bend_setting['PASDA'] # Prepare-Attack-Sustain-Decay-Aftermath (Taken the notion of ADSR)
prepare_rate = PASDA[0] / sum(PASDA)
attack_rate = PASDA[1] / sum(PASDA)
sustain_rate = PASDA[2] / sum(PASDA)
decay_rate = PASDA[3] / sum(PASDA)
aftermath_rate = PASDA[4] / sum(PASDA)
track.append(Message('note_on', note=base_note + base_num * 12 + sum(major_notes[0:note]),
velocity=round(64 * velocity), time=round(delay * meta_time), channel=channel))
track.append(Message('aftertouch', time=round(meta_time * length * prepare_rate), channel=channel))
track.append(Message('pitchwheel', pitch=pitch, time=round(meta_time * length * attack_rate), channel=channel))
track.append(Message('aftertouch', time=round(meta_time * length * sustain_rate), channel=channel))
track.append(Message('pitchwheel', pitch=0, time=round(meta_time * length * decay_rate), channel=channel))
track.append(Message('note_off', note=base_note + base_num * 12 + sum(major_notes[0:note]),
velocity=round(64 * velocity), time=round(meta_time * length * aftermath_rate), channel=channel))
except:
print(traceback.format_exc())
根據pitch_type的值,我們將函數分爲三部分:
- pitch_type爲0,代表沒有附加效果,同之前的play_note效果一樣。
- pitch_type爲1,代表添加顫音效果,即吉他中的揉弦。產生這一效果的兩個參數pitch和wheel_time通過tremble_setting傳入,分別表示顫音的幅度和顫音的次數。根據我的實踐來看,一個全音符跟隨3至4次顫音是比較自然的;而pitch的賦值也應適中,在1000左右比較合適,太小則看不出效果,太大則會跳動到另一個音符,很不自然。
- pitch_type爲2,代表滑音效果,即吉他中的推絃。由於這一效果的變化十分多樣,故我參考電子合成音樂中的 ADSR(Attack Decay Sustain Release) 屬性,自己設計了一個 PASDA(Prepare - Attack - Sustain - Decay - Aftermath) 屬性,即 初始音 - 向目標音行進過程中 - 滑到目標音後保持 - 向初始音行進過程中 - 初始音,這樣就可以比較好地表示滑音的屬性了,可以參考下圖來進行理解:
根據PASDA不同階段所佔比例的大小,我們就能很好地構建出心怡的滑音效果。
之後我們就可以對原始的音樂進行改進:
def verse(track):
add_note(1, 0.5, track) # 小
add_note(1, 0.5, track, pitch_type=2, bend_setting={'pitch': 6000, 'PASDA': [0.1, 0.3, 2, 0.3, 0]}) # 時
add_note(1, 1.5, track, pitch_type=1, tremble_setting={'pitch': 800, 'wheel_times': 10}) # 候
add_note(7, 0.25, track, -1) # 媽
add_note(6, 0.25, track, -1) # 媽
add_note(5, 0.5, track, -1, channel=1) # 對
add_note(2, 0.5, track, channel=1, pitch_type=2, bend_setting={'pitch': 6000, 'PASDA': [0.1, 0.8, 2, 0, 0]}) # 我
add_note(3, 2, track, channel=1, pitch_type=1, tremble_setting={'pitch': 640, 'wheel_times': 8}) # 講
add_note(3, 0.5, track) # 大
add_note(3, 0.5, track, pitch_type=2, bend_setting={'pitch': 3000, 'PASDA': [0.1, 0.8, 2, 0.3, 0]})
add_note(3, 1.5, track, pitch_type=1, tremble_setting={'pitch': 400, 'wheel_times': 6}) # 海
add_note(2, 0.25, track) # 就
add_note(1, 0.25, track) # 是
add_note(6, 0.5, track, -1, channel=1) # 我
add_note(1, 0.5, track, channel=1, pitch_type=2, bend_setting={'pitch': 6000, 'PASDA': [0.2, 0.8, 2, 0, 0]}) # 故
add_note(2, 2, track, channel=1, pitch_type=1, tremble_setting={'pitch': 600, 'wheel_times': 8}) # 鄉
add_note(7, 0.5, track, -1) # 海
add_note(1, 0.5, track)
add_note(7, 1.5, track, -1, tremble_setting={'pitch': 500, 'wheel_times': 6}) # 邊
add_note(6, 0.25, track, -1)
add_note(5, 0.25, track, -1)
add_note(5, 0.5, track, -1, channel=1) # 出
add_note(1, 0.5, track, channel=1, pitch_type=2, bend_setting={'pitch': 6000, 'PASDA': [0.2, 1.5, 3, 0, 0]})
add_note(2, 2, track, channel=1, pitch_type=1, tremble_setting={'pitch': 400, 'wheel_times': 8}) # 生
add_note(3, 1.5, track, pitch_type=2, bend_setting={'pitch': 3000, 'PASDA': [0, 0.3, 3, 0, 0]}) # 海
add_note(3, 0.5, track) # 裏
add_note(1, 0.5, track, channel=1) # 成
add_note(6, 0.5, track, -1, channel=1)
add_note(1, 3, track, channel=1, pitch_type=1, tremble_setting={'pitch': 800, 'wheel_times': 10}) # 長
完整代碼見 Github