利用Python進(jìn)行音樂編曲
一、實(shí)現(xiàn)方法
1.Python的musicpy庫(簡單易行好操作)
Musicpy可以讓你用非常簡潔的語法來表達(dá)一段音樂的音符,和弦,旋律,節(jié)奏,力度等信息,可以通過樂理邏輯來生成曲子,并且進(jìn)行高級(jí)的樂理操作。你可以很容易地把musicpy代碼輸出為MIDI文件的格式,也可以很簡單地加載MIDI文件并且轉(zhuǎn)換為musicpy的數(shù)據(jù)結(jié)構(gòu)進(jìn)行高級(jí)樂理的操作。musicpy的語法設(shè)計(jì)非常地簡潔與靈活,因此musicpy的代碼的可讀性比較強(qiáng),并且musicpy和python完全兼容,因此可以寫python代碼和musicpy進(jìn)行互動(dòng)。
2.一些基礎(chǔ)的樂理知識(shí)
3.一個(gè)有趣的想法(bushi)
二、關(guān)于musicpy的說明
musicpy的內(nèi)容十分豐富,我們首先要了解它的數(shù)據(jù)結(jié)構(gòu)、基本語法,才能更加有效并且方便地使用它。
1.關(guān)于數(shù)據(jù)結(jié)構(gòu)
在這里僅介紹幾個(gè)常用的數(shù)據(jù)結(jié)構(gòu):
1)音符(note)
初始化一個(gè)note的實(shí)例只需要給一個(gè)音名(比如C, Eb, A#,也可以是小寫)和一個(gè)八度數(shù)(一個(gè)整數(shù)),現(xiàn)在你就可以使用這 個(gè)音符去做音樂里的任何事情了。你還可以設(shè)定音符的duration(音符長度)和volume(音符的音量大?。?。音符長度默認(rèn)為0.25 (1/4個(gè)小節(jié)),音量默認(rèn)為100。
note的格式如下:
note(name,
num=4,
duration=0.25,
volume=100,
channel=None)
·name:為一個(gè)表示音名的字符串(如C,D,E#等)
·num:八度數(shù),和音名一起確定一個(gè)音高
·duration: 音符的長度,單位為小節(jié),比如duration = 1 表示音符長度為1個(gè)小節(jié),默認(rèn)值為0.25
·volume: 音符的力度,對(duì)應(yīng)的是MIDI文件里的音符的力度,范圍從0到127,默認(rèn)值為100
·channel: 音符的MIDI通道編號(hào),在寫入MIDI文件時(shí)如果channel不為None,則寫入對(duì)應(yīng)的通道
2)和弦(chord)
在musicpy里,和弦類被定義為“一組音符的集合”,這個(gè)定義或許比樂理里面的和弦定義更為廣義化,因?yàn)榘凑者@個(gè)定義,一首完整的樂曲也可以完全裝進(jìn)和弦類里面,在這個(gè)庫里也確實(shí)可以。
初始化一個(gè)和弦實(shí)例,只需要給一個(gè)音符的列表即可。還可以設(shè)定duration(所有音符的長度設(shè)置),interval(音符之間的間隔,用列表表示)。在給定音符列表時(shí)無需先用note類初始化,而只需要直接寫音符的名字(字符串)就行了。
chord的格式如下:
chord(notes,
duration=None,
interval=None,
rootpitch=4,
other_messages=[],
start_time=None)
·notes: 音符列表,為一個(gè)記載著這個(gè)和弦(或者曲子)所有音符的列表
·duration: 和弦的每個(gè)音符各自的音符長度,默認(rèn)值為None,如果為None則按照音符本身的長度,如果為一個(gè)整數(shù),小數(shù)或者列表則對(duì)音符的長度進(jìn)行調(diào)整
·interval: 每兩個(gè)連續(xù)音符之間的間隔,單位為小節(jié),為一個(gè)記載著音符間隔的列表(如果在初始化時(shí)是整數(shù)或小數(shù),則設(shè)定為全部的間隔都為此整數(shù)或小數(shù)),默認(rèn)值為None,如果沒有特別指定間隔,則默認(rèn)全部的音符間隔為0
·rootpitch: 如果傳入的音符列表的元素不是音符類型,而是表示音符的字符串,則會(huì)嘗試用toNote函數(shù)轉(zhuǎn)化為音符類型,如果音符字符串沒有八度數(shù),只有音名的情況下,會(huì)使用rootpitch來當(dāng)做音符的八度數(shù),默認(rèn)值為4
·other_messages: 用來記錄其他的MIDI信息的列表,默認(rèn)值為空列表
·start_time: 和弦類型的開始時(shí)間,可以理解為從開頭到第一個(gè)音符開始之間的休止符,單位為小節(jié),默認(rèn)值為0
3)音階(scale)
這個(gè)類可以表示一個(gè)特定的音階,使用這個(gè)類可以快速按照音的間隔來構(gòu)建調(diào)式。
scale的格式如下:
scale(start=None,
mode=None,
interval=None,
name=None,
notels=None,
pitch=4)
·start: 音階的主音(起始音) ,為一個(gè)音符類
·mode: 音階的名字,比如major, minor, dorian, lydian等等
·interval: 音階內(nèi)的音程關(guān)系,1表示半音,2表示全音,3表示增二度,以此類推,為一個(gè)列表的形式
·name: 當(dāng)直接輸入interval而不輸入mode構(gòu)建音階的時(shí)候,作為音階的名字來使用
·notels: 音符的列表,可以參考和弦類的音符列表,一個(gè)音階本身是一組確定的音符,因此notels就是一個(gè)音階類里的所有音符
·pitch: 當(dāng)start(音階的主音)在構(gòu)建音階的時(shí)候只有音名,沒有八度數(shù),pitch就作為音階的主音的八度數(shù),默認(rèn)值為4
2.關(guān)于基本語法
實(shí)現(xiàn)musicpy編曲,我們需要了解一些實(shí)現(xiàn)樂曲代碼化的基本語法,主要介紹和弦和樂曲的重點(diǎn)部分。
1)和弦類型基本語法
Ⅰ.對(duì)一個(gè)和弦進(jìn)行音符間隔和音符長度的設(shè)置
比如現(xiàn)在有一個(gè)和弦A,想要設(shè)置其音符間隔都為1個(gè)小節(jié),音符長度都為1.5個(gè)小節(jié),那么可以使用和弦類的內(nèi)置方法set,set的第一個(gè)參數(shù)是音符長度duration,第二個(gè)參數(shù)是音符間隔interval
A.set(1.5, 1)
得到的是和弦A的音符長度全部設(shè)置為1.5,音符間隔全部設(shè)置為1的全新的和弦類型,請(qǐng)注意不是修改和弦A的內(nèi)部屬性,而是直接返回一個(gè)全新的和弦,這個(gè)和弦的內(nèi)部屬性為和弦A修改過后的值。如果對(duì)于每個(gè)音的音符長度或者每個(gè)音符間隔都有不同的設(shè)定,那么可以傳一個(gè)列表進(jìn)去,比如
A.set([0.5, 0.5, 1, 1], [1, 1, 2, 2])
返回的是一個(gè)音符長度分別為0.5, 0.5, 1, 1 (單位為小節(jié)),音符間隔分別為1, 1, 2, 2 (單位為小節(jié))的和弦。
進(jìn)階寫法:
A % (1.5, 1)
Ⅱ.根據(jù)和弦根音和和弦名獲得一個(gè)和弦
介紹一個(gè)很方便的函數(shù):getchord,這個(gè)函數(shù)可以讓你得到你想要的類型的和弦,只需給定和弦的根音和和弦類型即可。由于這個(gè)函數(shù)的名字相對(duì)有點(diǎn)長,所以用chd這個(gè)名字(chord的縮寫)也可以調(diào)用getchord。比如:
Am7 = chd('A', 'm7')
這樣我們就創(chuàng)建了一個(gè)A的小七和弦。其表示為
[A5, C6, E6, G6] with interval [0, 0, 0, 0]
當(dāng)一個(gè)和弦的interval全部為0的時(shí)候,這個(gè)和弦就是所有音符一起同時(shí)演奏。如果某個(gè)interval為0,表示的是兩個(gè)音同時(shí)一起開始彈。
Ⅲ.輸入音符名稱,音符長度和音符間距構(gòu)建和弦
比如我們想構(gòu)建一個(gè)大七和弦,根音為C5,音符間距為每個(gè)音都相差一個(gè)小節(jié),每個(gè)音符的長度都為兩個(gè)小節(jié),那么可以這么寫:
chord(['C5','E5', 'G5', 'B5'], interval=1, duration=2)
如果音符間距和音符長度不是一樣的,那么可以傳一個(gè)列表進(jìn)去,比如:
chord(['C5','E5', 'G5', 'B5'], interval=[0.5, 0.5, 0, 2], duration=[1, 2, 0.5, 1])
Ⅳ.和弦的表示
在musicpy里,一個(gè)和弦類型的表示為:
[音符1, 音符2, 音符3, ...] with interval [間隔1, 間隔2, 間隔3, ...]
比如A是一個(gè)C大七和弦,根音為C5,
A = chd('C5', 'maj7')
和弦A的表示為
[C5, E5, G5, B5] with interval [0, 0, 0, 0]
2)樂曲類型基本語法
Ⅰ.piece類型(樂曲類型),用來處理多個(gè)音軌以及每個(gè)音軌有自己的樂器的曲子
用piece函數(shù)構(gòu)建一個(gè)樂曲類型:
A1 = C('Cmaj7') % (1, 1/8)
B1 = chord('A2') % (1,)
C1 = chord('C,D,E,F,G') % (1/8, 1/8)
D1 = chord('C5')
new_piece = piece([A1, B1, C1, D1],
['Acoustic Grand Piano', 'Electric Bass (finger)', 'Orchestral Harp', 'Synth Drum'],
150,
[0, 2, 2, 4],
['piano', 'bass', 'harp', 'drum'])
使用build函數(shù),可以用另一種語法來構(gòu)建樂曲類型,這種語法是把一個(gè)音軌的所有信息放在一個(gè)列表作為第1個(gè)軌道,下一個(gè)音軌的所有信息放在下一個(gè)列表作為第2個(gè)軌道, 以此類推。
build函數(shù)可以接收任意多個(gè)音軌的列表,曲速(BPM)默認(rèn)值為120,指定曲速必須用關(guān)鍵字參數(shù)bpm,其他的樂曲類型的參數(shù)也可以通過關(guān)鍵字參數(shù)指定,比如可以寫成:
new_piece = build([A1, 'Acoustic Grand Piano', 0, 1, 'piano'],
[B1, 'Electric Bass (finger)', 2, 2, 'bass'],
[C1, 'Orchestral Harp', 2, 3, 'harp'],
[D1, 'Synth Drum', 4, 4, 'drum'],
bpm=150)
除此之外,build函數(shù)也可以接收音軌類型作為參數(shù)構(gòu)建樂曲類型。音軌類型和音軌信息的列表可以混合輸入。
你也可以傳入一個(gè)元素為音軌類型或者音軌信息的列表(可以混合)的列表,同樣也可以構(gòu)建樂曲類型。
# 使用多個(gè)音軌類型構(gòu)建樂曲類型
new_piece = build(track(A1, instrument='Acoustic Grand Piano', start_time=0, channel=1, track_name='piano'),
track(B1, instrument='Electric Bass (finger)', start_time=2, channel=2, track_name='bass'),
track(C1, instrument='Orchestral Harp', start_time=2, channel=3, track_name='harp'),
track(D1, instrument='Synth Drum', start_time=4, channel=4, track_name='drum'),
bpm=150)
# 使用一個(gè)音軌類型的列表構(gòu)建樂曲類型
new_piece = build([track(A1, instrument='Acoustic Grand Piano', start_time=0, channel=1, track_name='piano'),
track(B1, instrument='Electric Bass (finger)', start_time=2, channel=2, track_name='bass'),
track(C1, instrument='Orchestral Harp', start_time=2, channel=3, track_name='harp'),
track(D1, instrument='Synth Drum', start_time=4, channel=4, track_name='drum')],
bpm=150)
Ⅱ.piece類型的編輯操作一覽
# 首先構(gòu)建一個(gè)樂曲類型,A1, B1, C1, D1都是已經(jīng)寫好的和弦類型
a = piece(tracks=[A1, B1, C1, D1],
instruments_list=[1, 35, 1, 49],
bpm=150,
start_times=[0, 8, 8, 16],
channels=[0,1,9,2],
track_names=['piano', 'electric bass', 'drums', 'strings'])
# 如果想要完全重復(fù)這個(gè)樂曲類型n遍,那么可以寫
b = a | n
# 如果想要對(duì)這個(gè)樂曲類型的所有MIDI通道進(jìn)行復(fù)制粘貼n遍的操作,那么可以寫
b = a * n
b = a % n
# 分別對(duì)應(yīng)不同的add模式,可以參考和弦類型的對(duì)應(yīng)的符號(hào)化寫法的運(yùn)作邏輯
# 可以使用index值查看一個(gè)樂曲類型的某一個(gè)音軌的信息,以0作為第1個(gè)音軌
# 使用a[n]的語法可以得到第n個(gè)音軌類型
>>> a[0]
[track]
BPM: 150
channel 0 piano | instrument: Acoustic Grand Piano | start time: 0 | [C4, E4, G4, B4] with interval [0, 0, 0, 0]
# 使用a(n)的語法可以得到一個(gè)列表,元素依次為第n個(gè)MIDI通道的和弦類型,樂器類型,BPM,開始時(shí)間,
# 通道編號(hào),音軌名稱,通道聲相,通道音量
>>> a(0)
[[C4, E4, G4, B4] with interval [0, 0, 0, 0], 'Acoustic Grand Piano', 150, 0, 0, 'piano', [], []]
# 升高/降低整首曲子n個(gè)半音,同樣也是與音符類型,和弦類型相同的語法,可以使用up/down函數(shù)或者+/-的進(jìn)階語法
b = a.up()
b = +a
b = a + 2
b = a.down()
b = -a
b = a - 2
# 有時(shí)你不想在升高或降低樂曲類型時(shí)改變鼓的音軌的音符,當(dāng)使用樂曲類型的up/down功能時(shí),你可以把參數(shù)`mode`設(shè)置為1來升高或降低整個(gè)樂曲,但鼓的音軌除外,通道號(hào)為9的音軌將被視為鼓的音軌。
b = a.up(mode=1)
# 可以往指定的位置添加實(shí)時(shí)的速度變化
a.add_tempo_change(bpm=100, start_time=None, ind=None, track_ind=None)
# bpm: 想要變化到的速度,單位為BPM
# start_time: 速度變化發(fā)生的時(shí)間,單位為小節(jié),如果不設(shè)置則以ind為準(zhǔn)
# ind: 速度變化信息插入的位置,需要和track_ind配合使用
# track_ind: 可以選擇在第幾個(gè)MIDI通道插入速度的信息,以0作為第1個(gè)MIDI通道,ind是選擇的MIDI通道里放在第幾個(gè)位置
# 如果ind和track_ind沒有設(shè)置,那么就默認(rèn)往第一個(gè)MIDI通道的最后添加速度變化的信息
# 可以往指定的位置添加實(shí)時(shí)的彎音(pitch bend可以模擬出音符的彎音,滑音,顫音等效果)
a.add_pitch_bend(value, start_time=0, channel='all', track=0, mode='cents', ind=None)
# value: 音符的音高變化的量
# start_time: 音符的音高變化發(fā)生的時(shí)間,單位為小節(jié)
# channel: 選擇往第幾個(gè)通道插入pitch bend信息,以0作為第1個(gè)MIDI通道,如果為'all'則往所有的MIDI通道插入相同的pitch bend信息
# track: MIDI軌道,一般情況下不用設(shè)置
# mode: 彎音信息的單位,之前有詳細(xì)的說明
# ind: 彎音信息插入的位置,如果start_time有設(shè)置則以start_time的位置為準(zhǔn)
# 查看樂曲類型個(gè)MIDI通道數(shù)量
>>> len(a)
4
# 添加新的音軌
a.append(new_track)
# new_track: 新添加的音軌,必須為音軌類型
# 可以使用build函數(shù)通過多個(gè)音軌類型構(gòu)建樂曲類型
new_piece = build(track(A1, instrument=1, start_time=0, channel=0, track_name='piano'),
track(B1, instrument=35, start_time=1, channel=1, track_name='electric bass'),
track(C1, instrument=1, start_time=8, channel=9, track_name='drums'),
track(D1, instrument=49, start_time=16, channel=2, track_name='strings'),
bpm=150)
# 請(qǐng)注意,用build函數(shù)傳入音軌類型來構(gòu)建樂曲類型時(shí),得到的樂曲類型的BPM只取決于build函數(shù)的bpm這個(gè)參數(shù),
# 與傳入的音軌類型自帶的BPM無關(guān)
三、進(jìn)行編曲
一段前奏
m = S('E major', 3)
r = m%(64516458, 1/2, 0.3/4, 5)
r = [i('omit7')^2 for i in r]
play(r*2, bpm=70, instrument=4)
一曲輕松的氛圍音樂
m = C('CM9') @ [1,3,5,2.1,4.1,5.1] % (1, 1/8) | 1/4
n = C('CM9', 4) % (1, 0)
n |= n - 2
n %= 4
n.setvolume(50)
b = chord('C2, A#1') % ([1, 1], [1, 1]) * 4
s = ((~m) | (~m) -2) * 4
t = chord('B4[.16;.16], C5[.16;.16], D5[1;1], C5[.2;.2], E5[.2;.2], D5[1;1], C5[7/8;7/8]')
the_tune = P([s | s-4, b | b-4, n | n-4, t | t-4], [9, 34, 49, 'Violin'], 90,[0, 0, 0, 3+7/8])
play(the_tune)
實(shí)際演示效果見視頻(intro & relaxing)
四、總結(jié)
通過不斷的學(xué)習(xí),我也可以在短時(shí)間內(nèi)快速上手musicpy庫,編寫出比較簡單的音樂曲子。在當(dāng)下電腦普及,許多人若是想學(xué)習(xí)編曲而沒有足夠的硬件條件支持的話,僅僅通過一臺(tái)電腦就能實(shí)現(xiàn)音樂夢想。相較于復(fù)雜的FL Studio,通過編程實(shí)現(xiàn)編曲既沒有復(fù)雜的操作界面,又不需要去掌握各種樂器音色的分配模式,只要有想法,有努力,就能實(shí)現(xiàn)。這也是當(dāng)代科技(編程)與藝術(shù)(音樂)的一種有機(jī)結(jié)合。
五、鳴謝
請(qǐng)大家多多關(guān)注musicpy庫的作者:Rainbow_Dreamer
bilibili:Rainbow_Dreamer的個(gè)人空間嗶哩嗶哩bilibili