如同我们在之前章节中讨论的,纯音程即比率。一个八度是2:1。也就是说A440之上的一个八度即它的倍数,或880。再上一个八度是1760。音程倒置即倒数:往上一个八度是2:1,往下一个八度是1:2。西方音乐中使用的音程来自和声级数(harmonic series),是基准数的倍数。如果基准音是200(你听到的音),那么和声部分即200, 400, 600, 800, 1000 等等。基准音之上的音程即八度、五度、八度、三度、五度、七度、八度等等,如下图所示。

C2 是图表上最底部的音。我们碰到的第一个五度是G3,第三个和声。要算G2(C2之上第五个),你可以先向那个频率乘3(G30),然后再除以2向下移动一个八度到G2,或3/2。让我们说C2是60Hz(以便于计算),C3将是120,G3 180,G2将是这个的一半:60 * 3 / 2 = 90。
第五个和声是级数中的第一个大三度,实际上是两个八度和一个三度(E4)。欲计算C2之上的一个三度(E2),首先乘以5以得到E4,让后除以2降八度,即:E2 = 60 * 5 / 4 = 75。
记忆音程比率有一些技巧。一个八度是2:1,五度是3:2,四度是4:3,三度是5:4,小三度是6:5,二度是9:8。注意所有分子都比分母大1。因此如果你能记得序列(sequence)八度、五度、四度、三度、小三和二度,你通常便能重建比率(记得9:8是一秒)。
同样地,你可以参照上边那个和声级数以定位西方校音中的音程及对应的比率。和声1和2间的音程是一个八度(2:1),2和3间是一个五度(3:2),3和4间是一个四度(4:3),4和5间是一个大三度(5:4),5和6间是一个小三度(6:5)….但随后系统崩溃了。6和7间的音程是什么?7和8呢?我们为什么不用它们?我们应该用它们吗?8和9间是我们用作1秒(9:8)的音程。最后两个阐述了我们用于15和16间的半音(16:15)。还需注意和声5和8之间的音程是一个小六度(8:5)。一个大六度(5:3)可以在第六和第十个和声间找到,大七度(15:8),位于8和15之间,小七度(9:5)位于9和5之间。
下边这个patch允许你测试不同的音程。r = 2/1 代表了第二个频率的比率。如果 f = 400,r = 2:1(“二到一”),然后f*r 是 800,或者说 400乘2除以1。即使除以1是没有作用的,但我还是将它写出来,这样你便可以尝试不同的比率以证实它们确实代表普通的音程。将r的值改为3/2 (一个五度), 4/3 (一个四度), 5/4 等等。三全音的话,尝试64/45。
13.1. 音程
( { f = 400; //fundamental r = 2/1; //ratio for second note in interval FSinOsc.ar([f, f*r], mul: 0.6) }.scope(2) )
FSinOsc(快速正弦振荡器)的第一引数不是单独的值,而是一个数组。记得数组是位于方括号内的一串由逗号间隔开的值。当数组被在任何地方用作引数,SC会将整个patch为每个通道扩展为两个完全相同的patch。我们用数组中的第一个值给左声道,第二个给右声道。这个极好的功能被称为多通道扩展(multi-channel expansion)。多于两个值的数组将被扩展到更多通道:你声卡能处理多少就能扩展到多少。SC匹配所有数组引数,在需要的地方复制,以创建平行的patch。
13.2. 多通道扩展
//这段代码 ( {SinOsc.ar( LFNoise0.ar([10, 12, 14, 6], 400, [800, 1000]), mul: [0.3, 0.5])}.scope(4) ) //在每个通道内变成这个 ( //通道1 {SinOsc.ar( LFNoise0.ar(10, 400, 800), mul: 0.3)}.scope(1) ) ( //通道2 {SinOsc.ar( LFNoise0.ar(12, 400, 1000), mul: 0.5)}.scope(1) ) ( //通道3 {SinOsc.ar( LFNoise0.ar(14, 400, 800), mul: 0.3)}.scope(1) ) ( //通道4 {SinOsc.ar( LFNoise0.ar(6, 400, 1000), mul: 0.5)}.scope(1) )
再看一次那个音程的patch。因为所有频率都是周期性的,而且都是数学比率,相长(constructive)和相消(destructive)点变成一个聚合(aggregate),我们在波谷和波峰“听到”作为结果的模式。下面是同样的三通道扩展的例子。如果你在双声道设备上回放的话,你将仅能听到1和2。第三个将作为分析(尽管它同样会发声)。将r = 2/1 改为其他比率比如 3/2, 4/3, 5/4, 6/5, 9/8等等。
13.3. 音程
( { f = 400; r = 2/1; a = FSinOsc.ar(f, 0.3); b = FSinOsc.ar(f*r, 0.3); [a, b, a+b]*0.3 }.scope(3, zoom: 4) )
在之前的几个patch里你可能会注意到,我在申明变量前就可以使用它们。这是因为字母a-z是全局变量,SC运行后便已被申明,使用起来很方便。但是我不得不警告你:随着你的patch变得越来越复杂,你很可能会后悔当初使用了全局变量而不是一些更有意义的名字。
同样注意函数代码的最后一行是变量的数组。这最后一行被称作“返回(return)”。一个不管函数主体部分发生了什么仅“返回”最后一行代码的函数,如下所示。
13.4. 返回函数:最后一行
( { // 这段代码将仅演奏 "c" a = FSinOsc.ar(200); b = FSinOsc.ar(1000); c = Pulse.ar(45); d = FSinOsc.ar(400); c }.scope(1) )
( 1 + 25; 1000 * 13 / 4; 1 + 1; 7; // 将仅列印这一行,其他部分无效。 )
这里是一些多种音程波形的图示。第一个是超过1/10秒的五度。

在这个例子中有很多波峰和波谷。最高峰和最低谷是相长干涉最大量的地方。波们被叠加在一起以推拉更远。我们将整个模式听做一个五度。接着是四度(4/3), 三度 (5/4), 小六度 (8/5), 最后到三全音 (64/45)。


在较低的比率下会有更多的相长干涉,因此有较少的波峰和波谷。高比率下有更多相消干涉和更多的波峰与波谷。即使拥有复杂的波、谷,我们仍然能够听到模式(pattern)。谐和与不谐和可以与给定时间内模式的波峰数量直接相关,并能被其定义,或者说,定义模式波峰间的时间。更多的峰、谷,更大的不谐和。
我们的眼睛和大脑识别视觉模式,正如我们的双耳识别由相长和相消干涉造成的听觉模式。
下例实时阐明了这一概念。它允许你将两个低频移到人类的听觉范围内。这个方法下,你可以先听到一组滴答声(click)或低频脉冲的模式。伴随脉冲或锯齿波,你会在当扬声器返回初始位置的每个波的周期内听到一个单独的滴答声(我们像听3/2, 4/3等节奏模式一样听到单独的脉冲)。然后随着你将鼠标右移,将频率带进人类听觉范围,你便听到了音程。首先尝试运行下边这行代码以听到LFO和音频率之间的区别。将鼠标左移以得到低频,右移得到音频频率。
13.5. 音频频率
{Saw.ar(MouseX.kr(1, 1200, 1), 0.5)}.scope(1)
下边这个patch用两个频率定义一个音程。我用变量ratioNum 和 ratioDenum将比率设置到3:2,试着将之改为2:1, 4:3, 5:4等。同样可以试试超常规比率比如8:3, 7:2。注意,数字越大,一致的相长干涉波峰就会越少,我们的耳朵会感知到更多不和谐音程。
听其模式,视其范围。接下来缓慢将鼠标移至右侧直到频率成音。将它完全移到右边以证实那正是你在尝试的音程。
13.6. 从低频到音频率的比率(Ratios from LF to audio rate)
( { var freq, ratioNum, ratioDenum; //申明两个变量 ratioNum = 3; //赋值分子 ratioDenum = 2; //赋值分母 freq = MouseX.kr(1,440, warp: 1); //频率被鼠标控制 LFSaw.ar( [freq, freq*(ratioNum/ratioDenum)], 0, 0.3) }.scope(2) )
自然音程 vs. 平均音程(Just vs. Equal Intervals)
我们现在有一点点进退两难(或者说更多选择)。我们是使用纯的,自然的音程?还是使用平均乐律(equal temperament)?在之前章节中,我们使用 midicps 将 MIDI数转换为频率。但MIDI值是被平均调和(temper)的。我们之前实验的比率其结果是自然调谐(just tuning)。该用哪个呢?必须二择一吗?它们真的如此不同?
是的,它们真的如此不同。平均律音阶里的每个半音是100分。MIDI数用一个整数给每个半音,因此可以使用两个小数点的精准来描述分。C4和C#4间的四分音符,比如说,是60.5(60+50分)。下边的代码展示了如何用纯比率计算大调音阶,然后将那些频率转换为MIDI值,稍后可与平均律值做比较。
13.7. 平均律对比纯律
(261.6 * [1, 9/8, 5/4, 4/3, 3/2, 5/3, 16/15, 2/1]).round(0.01)
[261.6, 294.3, 327, 348.8, 392.4, 436, 279.04, 523.2].cpsmidi.round(0.01)
结果自然比率或称纯比率是60, 62.04, 63.86, 64.98, 67.02, 68.84, 61.12, 72,与之相对的结果是60, 62, 64, 65, 67, 69, 70, 72。大三度降了14分,大六度降了16分。
让我们用低频到音频率的实验来看它们是如何发声的。下边的例子中有两点非常重要的差别。首先,MouseX 不再有指数的变形(exponential warp)。这是因为MIDI数是线性的。频率被自动转化为midicps消息里的指数值。另外,音程(表述为半音(half step))被加到左麦克风的值上,并非像以往那样乘以比率。
13.8. 从低频到音频率的比率(Ratios from LF to audio rate)
( { var midiNum, interval; interval = 7; midiNum = MouseX.kr(0.1, 84); LFSaw.ar( [midiNum.midicps, (midiNum + interval).midicps], 0, 0.3) }.scope(2) )
当与其他平均律乐器(吉他,钢琴,MIDI键盘)协同工作时,你应使用MIDI值。当与非品格(fretless)弦乐或人声合作时,为什么不仅仅使用声调(intonation)?(为什么先前从没有人向你提出这个选择?因为在电脑出现前,我们没有足够精准的乐器,或者说足够灵活,来允许这个选择)或者四分音,中音,或汉朝淮南子的乐律(原文为Lu scale,我猜是指乐律,链接)?
如果你调音到持续的自然音程,你将碰到音调偏移的情况。如果你是冒险家,你会将这视为一个挑战,然后深入自由自然声调的世界探讨,在这个世界里,偏移也会被作为是正确的来接受。
下边的例子里,我在代码方面跳得远了一点。你不必弄懂每个细节。这个patch演奏了两个简单的音,一个在左边,一个在右边。左声道用自然调音操作(每个音程用毕达哥拉斯比率(http://wehner.org/pythag/ratios.htm)未作任何调整来计算)。右声道用平均律音程。毕达哥拉斯逗号(Pythagorean comma)将影响左声道,频率将偏移(与右声道相比)。它们演奏的应该是“同样的”音,如果它们被以同样的方式调音的话。当它们漂移开的时候,并非由于它们做出了不同的选择,而是由于调音方式,由于毕达哥拉斯调音的偏移。我再三检查了我的计算,我相信如果我错了你会纠正我,但是的,它们走音得如此之快,如此之多。
第一例选取随机音程,第二个仅是一个音阶(更好的展示了音调偏移)。当我们两者一起听的时候,可以听到它们是彼此走音的。但如果只听左声道的话,你能听出偏移来吗?
练习,自然律调音,平均律调音
13.9. 调音
( //双击以选取全部代码 SynthDef("PureTone", {arg justFreq = 440, equalFreq = 440; Out.ar(0, SinOsc.ar([justFreq, equalFreq], mul: 0.4) *EnvGen.kr(Env.perc(0, 1), doneAction:2)); }).load(s); Task({ var jfreq = 440, efreq = 69, next = 6, equalInt, justInt; equalInt = [-10, -8, -7, -5, -3, -1, 0, 2, 4, 5, 7, 9, 11]; justInt = [9/16, 5/8, 2/3, 3/4, 5/6, 15/16, 1/1, 9/8, 5/4, 4/3, 3/2, 5/3, 15/8]; { [equalInt[next], justInt.at(next).round(0.01)].post; Synth("PureTone", [\justFreq, jfreq.round(0.01), \equalFreq, efreq.midicps.round(0.01)].postln); next = 13.rand; jfreq = jfreq*justInt.at(next); efreq = efreq + equalInt.at(next); if(jfreq < 100, {jfreq = jfreq*2; efreq = efreq + 12}); if(jfreq > 1000, {jfreq = jfreq/2; efreq = efreq - 12}); [0.125, 0.125, 0.124, 0.25, 0.5, 1].choose.wait }.loop; }).play(SystemClock); )
// 只有一个音阶的同样的例子 ( //双击以选取全部代码 SynthDef("PureTone", {arg justFreq = 440, equalFreq = 440; Out.ar(0, SinOsc.ar([justFreq, equalFreq], mul: 0.4) *EnvGen.kr(Env.perc(0, 1), doneAction:2)); }).load(s); Task({ var jfreq = 440, efreq = 69, next = 0, equalInt, justInt; equalInt = [-12, 2, 2, 1, 2, 2, 3]; justInt = [1/2, 9/8, 9/8, 16/15, 9/8, 9/8, 6/5]; { [equalInt.wrapAt(next), justInt.wrapAt(next).round(0.01)].post; Synth("PureTone", [\justFreq, jfreq.round(0.01), \equalFreq, efreq.midicps.round(0.01)].postln); next = next + 1; jfreq = jfreq*justInt.wrapAt(next); efreq = efreq + equalInt.wrapAt(next); 0.25.wait }.loop; }).play(SystemClock); )
// 只有自然声调 ( //双击以选取全部代码 SynthDef("PureTone", {arg justFreq = 440, equalFreq = 440; Out.ar(0, SinOsc.ar([justFreq, equalFreq], mul: 0.4) *EnvGen.kr(Env.perc(0, 1), doneAction:2)); }).load(s); Task({ var jfreq = 440, efreq = 69, next = 0, equalInt, justInt; equalInt = [-12, 2, 2, 1, 2, 2, 3]; justInt = [1/2, 9/8, 9/8, 16/15, 9/8, 9/8, 6/5]; { [equalInt.wrapAt(next), justInt.wrapAt(next).round(0.01)].post; Synth("PureTone", [\justFreq, jfreq.round(0.01), \equalFreq, jfreq.round(0.01)].postln); next = next + 1; jfreq = jfreq*justInt.wrapAt(next); efreq = efreq + equalInt.wrapAt(next); 0.25.wait }.loop; }).play(SystemClock); )