你能在這里聽到它
用數(shù)學軟件演奏音樂早已不是什么新鮮事,很早就有人用Matlab彈奏卡農(nóng)或是最炫民族風,最近我知道還有人用無理數(shù)生成一段音樂,很有趣。而我選擇用Mathematica彈奏卡農(nóng)是因為卡農(nóng)這種譜曲方式很奇妙,體現(xiàn)在程序上也會是簡潔而優(yōu)美的??赡苡腥诉€不了解卡農(nóng)是什么,卡農(nóng)不是指某一首曲子,而是一種譜曲方式,它把幾段相同的旋律在不同的時刻依次展開,交錯的旋律又能相互配合,形成一首完整的卡農(nóng)。這用Mathematica演奏的卡農(nóng)就是大家最耳熟的“帕赫貝爾的卡農(nóng)”,你可以在它的樂譜中一窺其中的奇妙。

我們關(guān)注的重點是,這首卡農(nóng)有三個聲部(在樂譜上對應(yīng)著前三排)和一個背景旋律,但實際上三個聲部演奏的旋律是相同的,所以我們只需要輸入一個聲部的譜子,然后讓三個聲部在不同的時間進入就可以了。
手動輸入樂譜——失敗
起初我在網(wǎng)上找到的是巴赫的手稿,當我把一個聲部的旋律都輸入進去后,最終的效果并不是很理想,我想這個譜子更適合音樂會彈奏,需要樂手控制不同聲部的音量與節(jié)奏,但這在Mathematica上很難實現(xiàn)。

從Matlab到Mathematica
后來我在網(wǎng)上發(fā)現(xiàn)了一個用Matlab彈卡農(nóng)的程序,聽過之后,感覺他的譜子更適合用軟件演奏。就決定把Matlab代碼翻譯到Mathematica上。首先我們分析一下Matlab的代碼
Matlab代碼
Matlab中的代碼一共分三部分
1. 告訴電腦如何彈奏音符
2. 輸入一個聲部的旋律
3. 組合三個聲部
1.告訴電腦如何彈奏音符
我們需要告訴電腦
i.音符名稱(do,re,mi,fa...)
ii.音符持續(xù)的時間(音符時值t)
iii.音調(diào)(頻率f)
所以我們輸入的應(yīng)該是如下格式的信息:
????????????????????????????????????????????音符名稱=sin(2πft)
看看這在Matlab中是如何做到的
% 1/4 notes
do0f = mod4.*cos(2*pi*ScaleTable(21)*f0*t4);
re0f = mod4.*cos(2*pi*ScaleTable(22)*f0*t4);
mi0f = mod4.*cos(2*pi*ScaleTable(23)*f0*t4);
% 1/8 notes
fa0e = mod8.*cos(2*pi*ScaleTable(1)*f0*t8);
so0e = mod8.*cos(2*pi*ScaleTable(2)*f0*t8);
la0e = mod8.*cos(2*pi*ScaleTable(3)*f0*t8);
% 1/16 notes
fa0s = mod16.*cos(2*pi*ScaleTable(1)*f0*t16);
so0s = mod16.*cos(2*pi*ScaleTable(2)*f0*t16);
la0s = mod16.*cos(2*pi*ScaleTable(3)*f0*t16);
初看感覺很復雜,但其實它的形式和上面的公式是相同的
i.do0f,re0f,mi0f...代表音符名稱
ii.ScaleTable(\space)*f0代表頻率f,不同音符的ScaleTable()不同
iii.t4,t8,t16代表持續(xù)時間$t$,更具體的,t4=0.5s,t8=0.25s,t16=0.125s
但我們還發(fā)現(xiàn)他多出一個mod函數(shù),這其實是一個修正函數(shù)(modify),乘上他之后可以讓波形變得更柔和,有漸入漸出的效果。


mod后面的數(shù)字代表對應(yīng)音符的持續(xù)時間。比如mod4要和t4相乘。
2. 輸入一個聲部的音符
這一步就很好理解了,這首卡農(nóng)背景旋律(Base Melody)由大提琴演奏,主旋律(Long Melody )由三把小提琴演奏,我們分別命名cello和violin
% Base Melody
cello = [do1f do1f so0f so0f la0f la0f mi0f mi0f... fa0f fa0f do0f do0f fa0f fa0f so0f so0f];......
% Long Melody
violin = [mi2f mi2f re2f re2f do2f do2f ti1f ti1f... la1f la1f so1f so1f la1f la1f ti1f ti1f ......
3.組合三個聲部
正如我前面說的,我們只需要在不同的時間點加入相應(yīng)的旋律,就像下面這樣(blkblock代表空白音符)
% violin1
v1 = [blkblock violin blkblock blkblock];
% violin2
v2 = [blkblock blkblock violin blkblock];
% violin3
v3 = [blkblock blkblock blkblock violin];
% Combination
s = c1+v1+v2+v3;
sound(s,fs);
這樣就得到了一段能在Matlab上演奏卡農(nóng)的代碼,接下來就要把它翻譯到Mathematica上。
翻譯到Mathematica
我們當然要借助Mathematica來翻譯,思路跟Matlab上的思路一樣
1.建立音符名稱和聲音的關(guān)聯(lián)(Association)
最終的關(guān)聯(lián)應(yīng)該像下面這樣。我給這個關(guān)聯(lián)起名叫“asswecan”,意思是用這個關(guān)聯(lián)我們能做到「音符名稱到聲音的轉(zhuǎn)換」,記住這個名字,我們之后會用到。

那么要如何生成這個關(guān)聯(lián)呢?首先我們需要生成聲音,這里用到Play函數(shù):

根據(jù)Matlab代碼中的音符信息「ScaleTable(),f0,t4,t8,t16」改變Play函數(shù)中的參數(shù)來發(fā)出不同的音調(diào),替換的關(guān)系是這樣的:

有了這個思路,就可以用StringReplace函數(shù)進行替換了。
2.輸入一個聲部的旋律
Matlab中的旋律代碼有許多多余的字符,需要我們處理一下
i.輸入Matlab代碼中的旋律
cellonoteinfo =
"do1f do1f so0f so0f la0f%% la0f mi0f mi0f...fa0f%% fa0f do0f do0f fa0f \n
fa0f so0f so0f";
這里面有很多我們不希望得到的 ... %% 空格回車這樣的字符,需要把他們?nèi)サ簟?/p>
ii.定義去掉無用字符的函數(shù)
infotonote[info_] :=
StringPartition[StringJoin@StringCases[info, _?LetterQ | _?DigitQ],4];
iii.得到適用于Mathematica的旋律
In[1]:= cellonote = infotonote[cellonoteinfo]
Out[1]:= {"do1f", "do1f", "so0f", "so0f", "la0f", "la0f", "mi0f", "mi0f", \
"fa0f", "fa0f", "do0f", "do0f", "fa0f", "fa0f", "so0f", "so0f"}
這樣我們就得到了旋律中的音符名稱列表
3.用聲音替換旋律中的音符名稱
還記得那個"asswecan"嗎?它可以把音符名稱替換成聲音。用“asswecan”關(guān)聯(lián)完成對幾段旋律的轉(zhuǎn)換,并規(guī)定好他們開始彈奏的時間,比如vio1末尾的8就代表第一把小提琴在第8秒才開始彈奏。
cello = Sound[Flatten@Table[Replace[cellonote, notetosoundass, 1], 23], {0}];
vio1 = Sound[Replace[violinnote, notetosoundass, 1], {8}];
vio2 = Sound[Replace[violinnote, notetosoundass, 1], {16}];
vio3 = Sound[Replace[violinnote, notetosoundass, 1], {24}];
4.最后把他們組合起來

As we can see, 我們完成了!
你可以在B站聽到這段旋律
你也可以前往Wolfram社區(qū)下載Matlab及Mathematica代碼文件