Python編曲實踐(三):如何模擬“彎音輪”實現滑音和顫音效果

前言

彎音輪,是在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
    根據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

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章