SC:FM/AM合成,相位调制,音序器,采样和持有

在前面的章节里,我用电压控制创制了一个颤音:用一个低频振荡器在听觉领域控制另一个振荡器。那时我曾警告你,控制频率不要超过20Hz。希望你没有听从我的建议并用高频做了实验。当然你可以用低于20Hz的控制率,但有些不期望的甚至是神奇的东西产生了:新的频率在被控制的频率上、下出现。用高于LFO范围控制源得到的声音与打开收音机相似。那是因为你已经步入频率和振幅调制的国度:用于你am和fm收音机隔空传递信号的技术(它听上去同样与鸟的啁啾惊人相似。有的鸟类可以产生以同样方式交互的两个频率。)。

16.1. 从LFO到FM

{SinOsc.ar(SinOsc.ar(MouseX.kr(1, 500), mul: 300, add: 800))}.play

am、fm与LFO的区别在于高次和低次的边频带(sideband,以下简称边带):承载频率(被调制的音频频率)与调制频率(控制着音频频率的频率)的总合与不同。承载频率500和调制频率112可以产生两个边带:612和388。如果在其中一条波(比如一条被正弦波控制的锯齿波)里有泛音,那么每个泛音都会有边带。

16.2. AM合成 (SinOsc, scope, mul, Saw)

{SinOsc.ar(500, mul: SinOsc.ar(50, mul: 0.5))}.scope(1)
{Saw.ar(500, mul: SinOsc.ar(50, mul: 0.5))}.scope(1)

上例中,外部SinOsc为承载器,内部SinOsc为调制器。边带应为550和450。将引数50改为其它值,看它如何改变声音。将50换为MouseX.kr(1, 500)。当你将鼠标慢慢由左向右移动的时候,你应该能听到两个边带(频率):一个上升,另一个下降。

频率调制是同样的道理。但在fm中可以生成更多的边带。确切的数字取决于调制指数(modulation index)。调制指数是调制频率偏离其载体距离的远近(即调制波的振幅)。比如SinOsc.ar(400 + SinOsc(124, mul: 100)),400是载体频率,124是调制器频率,100是调制指数。(指数越高,边带越多)

16.3. FM合成

{SinOsc.ar(SinOsc.ar(MouseX.kr(1, 500), mul: 300, add: 800))}.play

相位调制(Phase Modulation)

使用PMOsc对象可以更容易的看到载体,调制器和调制指数,因为它们就是PMOsc的前三个引数。相位调制控制载体的相位而非频率。差别是学术上的,而效果与FM几乎一样。试着用MouseXMouseY替换下边的每个引数(400是承载频率,124是调制频率,1是弧度的指数)以更好地了解它们如何影响边带。

16.4. PM合成

{PMOsc.ar(400, 124, 1, mul: 0.5)}.scope(1)

载体与控制频率的比率决定着边带并进一步决定音质,调制指数控制边带的量,比如说,波的亮度。不同的载体频率将使声音变得像一个变换音高的乐器。不同的调制器频率听起来将仅仅像是在演奏同一个音高的不同乐器。不同的调制指数听上去将像一个被滤波的乐器。当然,你可以改变所有三个引数。简而言之:载体=音高,调制器=音色,调制指数=滤波器。

16.5. 载体、调制器、调制指数控制

(
{PMOsc.ar(LFNoise0.kr(5, 300, 700),// 载体
   134, 4, mul: 0.4)
   }.scope(1)
)
(
{PMOsc.ar(700,
      LFNoise0.kr(5, 500, 700),//调制器
      12, mul: 0.4
   )}.scope(1)
)
(
{PMOsc.ar(700, 567,
      LFNoise0.kr(5, 6, 12), //调制指数
      mul: 0.4
   )}.scope(1)
)
(
// 三者同时控制
// 这是最初让我迷上合成的声音
{PMOsc.ar(LFNoise0.kr([9, 9], 300, 700),
      LFNoise0.kr([9, 9], 500, 700),
      LFNoise0.kr([9, 9], 6, 12),
      mul: 0.5
   )}.scope(1)
)

边带是载体波与调制波的总和与差值。如果给定一个400Hz的载体波以及一个50Hz的调制波,边带将为450, 500, 550, 600, 650等等,以及350, 300, 250, 200等等,取决于调制指数的大小。如果一个调制指数大到足以在低层光谱生成负数,它们会折回。一个在SC内快速计算高于、低于一个给定频率的公式是abs((-30..30)*m+c),其中m是调制器频率,c是载体频率。套用上边那个假设,公式为abs((-30..30)*50+400)

尽管了解边带是如何生成的以及它们与载体、调制器和指数的关系是很重要的,但我不得不说我从未在现实生活中做过那些计算。我所说的现实生活是指我的实验和创作。我始终记得的是这三件事,因为他们关系到声音是如何改变的:载体 = 基础音高,调制器 = 波的特征,指数 = 分音或者滤波器的个数。

同样的结果可以由加法合成做到:20个按上边整理出来的频率调音的正弦波。FM最大的好处是效能。它仅用两条正弦波生成数百个边带,这些边带等同于正弦波。对比以下两个patch,一个用相位调制,另一个用加法合成。注意观察两者的ugen使用数量及CPU占用率。

16.6. PM的效率

{PMOsc.ar(1000, 1367, 12, mul: EnvGen.kr(Env.perc(0, 0.5), Impulse.kr(1)))}.play
(
{
Mix.ar(
   SinOsc.ar(abs((-20..20)*1367 + 1000),
      mul: 0.1*EnvGen.kr(Env.perc(0, 0.5), Impulse.kr(1)))
)}.play
)

边带听上去像是一个单独的音高的弦外之音。如果边带与一个低的比率有关,听上去会更和谐。因此,将调制器频率计算为一个载体频率的比率是很有帮助的。在下边这个patch中,将比率改为整数比如1,2,3,4,而后改为3/2,5/4,8/7,然后再试试2.317,1.576等等。一个接近整数的小数,比如2.01,将使一个谐波频率轻微去谐。因为调制器频率比率始终如一,因此我们听到的将使一个演奏不同音高的音色。调制指数仍被MouseY控制。用MouseY改变指数只会改变亮度而不会改变音色,因为它仅增加或移除边带。另外一个基于调制器频率比率的载体的好处是,如果载体频率改变,调制器的也将相应改变,并且边带的集合也将与载体保持一致。实际的结果就是尽管音高改变了,但我们听到的仍然是相同的音色。注意我为频率使用的是MIDI数字。

16.7. 载体和调制器比率

(
{var freq, ratio;
freq = LFNoise0.kr(4, 20, 60).round(1).midicps;
ratio = 2/1;
PMOsc.ar(
   freq, //carrier
   freq*ratio, //modulator
   MouseY.kr(0.1, 10), //index
   Mul: [0.4, 0.4]
)}.play
)

接下来的例子添加了一个包络来控制PMOsc的振幅,但它同样控制着调制指数,这样的话边带的数量会随着声音的衰减而衰减。TRand生成36到86间的值,适用于MIDI。因为引数是整数而非浮点数,因此它将返回整数。再次,为比率尝试不寻常的值以改变乐器的特性。

16.8. 附加包络到振幅和调制指数

(
{var freq, ratio, env, rate = 5, trig;
trig = Impulse.kr(5);
freq = TRand.kr([36, 60], [72, 86], trig).midicps;
ratio = 2;
env = EnvGen.kr(Env.perc(0, 1/rate), gate: trig);
PMOsc.ar(
      freq,
      freq*ratio,
      3 + env*4,
      mul: env
)}.play
)

FM合成在1960年代由John Chowning发展,并在1980年代被广泛应用,尤其是雅马哈DX7系列。它的成功源于它的高效:仅用两条波便可在理论上生成和谐或不和谐频谱的无限边带。

音序器

每一章我都会介绍几个通用控制源。我们使用SinOsc作为LFO来控制另一个SinOsc的频率。我们将LFNoise,包络,Line和其他波形作为控制。我还有两个经典控制想要阐述。首先是音序器。音序器以给定的比率或触发器穿过一组值。这可以通过SelectStepper的结合来完成。

音序被定义为一个数组。音序器可以被用来控制声音的任何方面。在本例中,它通过使用MIDI音高数来控制频率。你可以使用任意频率设置或MIDI数字,或者甚至是随机的频率。你同样可以使用Array.fill, rand, series等来自动填充数组,或者用不同的方法改变数组。

16.9. 音序器 (array, midicps, SinOsc, Sequencer, Impulse, kr)

( 
var pitchArray; //申明一个变量以储存数组 
//用midi音装载数组 
pitchArray = [60, 62, 64, 65, 67, 69, 71, 72].midicps; 
{ 
   SinOsc.ar( 
      Select.kr( 
         Stepper.kr(Impulse.kr(8), max: pitchArray.size-1), 
         pitchArray), 
      mul: 0.5) 
}.play 
)
(
var pitchArray; //申明一个变量以储存数组
//用midi音高装载数组
pitchArray = Array.rand(24, 100, 2000);
{
   SinOsc.ar(
      Select.kr(
         Stepper.kr(Impulse.kr(8), max: pitchArray.size-1),
         pitchArray),
      mul: 0.5)
}.play
)

你可以使用reverse, scramble, 或 fill 修改数组。下列代码,首先,一个数组被从60~84的数字所填充。Array.rand有三个引数:第一个是数组元素的数量,接下来的两个限定随机数组元素的最小和最大值。接下来我们将数组打散并列印它。然后反向并列印它。最后为整个数组加上12并列印它。

16.10. 打散, 反向 (Array.rand, postln, scramble, reverse)

( 
var pitchArray; 
pitchArray = Array.rand(10, 60, 84); 
pitchArray.postln.scramble.postln.reverse.postln; 
(pitchArray + 12).postln 
)

以下是在一个实际的patch内各步运行的实例。试试改变音高的数组的值到其他比例度(scale degree)。

16.11. 音序器变化

( 
var pitchArray; 
pitchArray = [60, 62, 64, 65, 67, 69, 71, 72]; 
pitchArray = [ //选取以下之一 
   (pitchArray + rrand(1, 12)).midicps, //转换 
   pitchArray.reverse.midicps, //反向 
   pitchArray.scramble.midicps, //打散 
   Array.rand(12, 36, 72).midicps, //随机midi 
   Array.rand(12, 100, 1200) //随机频率 
].choose;
{
   SinOsc.ar(
      Select.kr(
         Stepper.kr(Impulse.kr(7), max: pitchArray.size-1),
         pitchArray),
      mul: 0.5)
}.play
)

使用PM的一个更有趣的例子:

( 
{ 
var freq, freqArray, ratioArray, indexArray, 
   env, rate = 5, trig; 
trig = Impulse.kr(rate); 
freqArray = [48, 50, 52, 53, 55, 57, 59, 60, 
          62, 64, 65, 67, 69, 71, 72].scramble.midicps; 
ratioArray = {rrand(1.0, 3.0)}.dup(20); 
indexArray = {rrand(1.0, 4.0)}.dup(20); 
env = EnvGen.kr(Env.perc(0, 1/rate), gate: trig); 
freq = Select.kr(Stepper.kr(trig, max: freqArray.size-1), freqArray); 
PMOsc.ar( 
      freq, 
      freq*Select.kr(Stepper.kr(trig, max: ratioArray.size-1), ratioArray), 
      Select.kr(Stepper.kr(trig, max: indexArray.size-1), indexArray) 
      + env*4, 
      mul: env 
)}.play 
)

Stepper有一个引数控制它在数组中的进程。值为1将一次前进一步。如果为-1,则一次后退一步。如果值为3,则移动三步。

采样和持有

采样(sample)和持有(hold)是另一种古旧模块化合成器所使用的经典的合成控制源。它在模块化到联合装置的过渡中并没幸存下来。但我认为这个效果是值得保留的。在SC中,采样和持有等于Latch.kr

采样和持有使用一个周期波作为输入。这个波依规律的间隔进行采样。结果值被用于一个控制。它对波进行“采样”并且“持有”那个值,直到一个新值被采样。你可以想象它是一个基于一个低频采样率的模拟-数字转换器(尽管从技术上来说,被采样的波同样是数字的)。这个效果类似于一个平滑进程的闪光灯或电影截屏快照。然后那个平滑进程被量化入离散的步骤内。

在合成中这有什么用呢?为什么你想要定格一个波形?因为如果你用一个周期律去采样一个周期波,结果是持续进化的模式(pattern)。被采样的波会生成别名(alias),与我们在第6章讨论的别名类似。继续使用观察钟表秒针的例子,在一个给定的“采样”率下睁开你的眼睛。如果你每秒准时睁眼,你将得到这些数字:[1, 2, 3, 4, 5, 6, 7, …]。并不十分有趣。每5秒“采样一次又会怎样呢?[5, 10, 15, 20, …]。恩,貌似好一点,但仍然很容易被预料。但当这个采样率接近时钟一圈的周期时,有趣的模式出现了。举个例子,以46作为采样率。结果会怎样?SC可以告诉我们答案:Array.fill(20, {arg i; i*46}).mod(60);结果是[ 0, 46, 32, 18, 4, 50, 36, 22, 8, 54, 40, 26, 12, 58, 44, 30, 16, 2, 48, 34 ]。它们是随机的吗?不,因为采样率和数字序列都是周期性的,因此,这可以被定义为一个模式。而且是一个相较音序器和随机来说,更有趣的模式。

这个点子是,尽管采样率低到无法精确表明一个波的真实形状,模式仍将显现,因为这个波是周期性的,并且采样率也是。现在想象一个每秒从0~1然后归0移动的锯齿波。如果采样率是每秒10次,实际返回的值将是0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.0, 0.1, 0.2….等等。这个锯齿波的形状将由每秒10次的采样率来呈现。但如果采样率是每秒0.9次,结果会是怎样的呢(每10秒采样9次)?这也就是说每次采样的时间总量,或睁开眼睛看秒针,是0.9秒。第一个值将是0,然后是0.9,但接下来的0.9秒之后,将是0.8,接下来0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0, 然后再次返回0.9,以此往复。它们看起来像乘以9,但因为锯齿波每秒都会重置到0,值将永远不会高于1(就像模(modulo)的概念)。因此尽管最初的波形是一个上行的斜线,但这些采样在10秒的空间内描述的却是一个下行的斜线。如果采样率是1.65,那么实际返回的值将是0, 0.65, 0.3, 0.95, 0.6, 0.25, 0.9, 0.55, 0.2, 0.85, 0.5, 0.15, 0.8, 0.45, 0.1, 0.75, 0.4, 0.05, 0.7….等等(乘以1.65但永远不高于1)。这个序列甚至都不靠近原始的波形,但却有一个清晰的模式,实际上,是三个交错的模式。以下数组的例子展示了从两个周期源呈现出的一些模式。100,本质上,是周期波,95, 124, 165是采样率(别想这个例子太久。这会令你感到迷惑。就看它们返回的模式就好。)。

16.12. Latch模式

Array.fill(100, {arg i; i*95}).mod(100);
Array.fill(100, {arg i; i*124}).mod(100);
Array.fill(100, {arg i; i*165}).mod(100);

我喜欢采样和持有,因为它在变化和重复间制造了一个漂亮的平衡。其呈现的音序既比一个单独的音序具有更多的变化,又比一个LFNoise控制更紧密。

尝试下例并倾听频率中的锯齿波形。在上文中,我改变采样率来制造一个不同的模式,但在这个patch中,我改变的是被采样波的频率(缩短波而不是延长采样时间)。在模式上的效果是一致的,但这个方法,采样率仍然是一样的。我使用一个MouseX.kr来控制被采样的LFSaw的频率。它从0.1开始,每10秒1次然后移动到20。当鼠标移动到荧幕最左边,你应该听到经过频率控制后的波形。当你移动到右边,你从本质上缩短了与采样率有关的被采样波,并且平滑的斜线将开始消失,但却将一直有一个模式存在着。第二例用Line.kr取代了鼠标控制,这精细地阐明了从一个模式到下一个模式的渐变。

16.13. Latch

(
{
SinOsc.ar(
   Latch.kr(
      LFSaw.kr(MouseX.kr(0.1, 20), 0, 500, 600),
      Impulse.kr(10)),
   mul: 0.3 //Volume of Blip
)
}.scope(1)
)
(
{
SinOsc.ar(
   Latch.kr(
      LFSaw.kr(Line.kr(0.1, 20, 60), 0, 500, 600),
      Impulse.kr(10)),
   mul: 0.3 //Volume of Blip
)
}.scope(1)
)

波的频率与采样率改变的关系使你听到不同的模式,有的很明显,其他的很惊人。但任何两个值都将产生某些模式,因为触发器和波都是周期性的。

Latch可以被用于控制调制器、载体或指数的振幅、频率。第四个例子使用了这三者的控制。右声道被设置为延迟2秒发声,以获得更好的多样性。

16.14. Latch

( 
{ // 控制调制器和载体频率 
f = Latch.kr( 
      LFSaw.kr(MouseX.kr(1.1, 30), 0, 1000, 1100), 
      Impulse.kr(10)); 
PMOsc.ar([f, f*3/2], f*2, 12, mul: 0.3) 
}.scope(2) 
)
( 
{ // 控制指数 
i = Latch.kr( 
      LFSaw.kr(MouseX.kr(1.1, 30), 0, 5, 5), 
      Impulse.kr(10)); 
PMOsc.ar([300, 350], 356, i, mul: 0.3) 
}.scope(2) 
)
( 
{ // 控制比率 
r = Latch.kr( 
      LFSaw.kr(MouseX.kr(1.1, 30), 0, 2.0, 3.0), 
      Impulse.kr(10)); 
PMOsc.ar(300, [300*r, 400*r], 12, mul: 0.3) 
}.scope(2) 
)
( 
{ // 当然, 界定(scale)以控制三者 
c = Latch.kr( 
      LFSaw.kr(MouseX.kr(1.1, 30), 0, 0.5, 0.5), 
      Impulse.kr(10)); 
f = c*1000+200; 
o = PMOsc.ar(f, f*(c*3+4), c*5+6, mul: 0.3); 
[o, CombL.ar(o, 2, 2)] 
}.scope(2) 
)

在之前关于间隔(interval)的章节,我们看到两个频率的比率决定着破坏性和建设性干涉图样(interference patterns,)的相对复杂与简单程度,进而决定和谐波与不和谐波的水平(level)。对于S&H(采样与持有)来说,也是这样。被采样波的频率与采样率之间的比率将决定模式。如果我们将两个值联系起来,当采样率改变时,同样的模式将持续。在下边的patch中,试着用确切的比率替换MouseX。我发现1.6~1.75之间的值比较赞(中庸之道,万一你是那类家伙,用1.61803399)。

第二个例子加入多一个控制,因此采样率将持续沿有趣的模式移动。

16.15. Latch采样和速率 (Blip, Latch, LFSaw, Impulse, mul)

( 
{ 
var rate, speed, ratio; 
rate = MouseX.kr(1, 24); 
ratio = 1.61803399; 
c = Latch.kr( 
      LFSaw.kr(rate*ratio, 0, 0.5, 0.5), 
      Impulse.kr(rate)); 
f = c*1000+200; 
o = PMOsc.ar(f, f*(c*3+4), c*5+6, mul: 0.3); 
[o, CombL.ar(o, 2, 2)] 
}.scope(1) 
)
( 
{ 
var rate, speed, ratio; 
rate = MouseX.kr(1, 24); 
ratio = SinOsc.ar(2/7, mul: 0.2, add: 1.75); 
c = Latch.kr( 
      LFSaw.kr(rate*ratio, 0, 0.5, 0.5), 
      Impulse.kr(rate)); 
f = c*1000+200; 
o = PMOsc.ar(f, f*(c*3+4), c*5+6, mul: 0.3); 
[o, CombL.ar(o, 2, 2)] 
}.scope(2) 
)

另一个生成趣味的方法是采样一个更复杂的波形。即使它复杂,如果它是周期性的,波里的模式将在控制值中变得明显。用下边的波作为一个例子。四个波混合创建一个更复杂的,但仍是周期性的,波。然后再将它放进Latch中。

16.16. 将复杂波形作为采样源 (Mix, SinOsc, Blip, Latch, Mix, Impulse)

{Mix.ar(SinOsc.ar([100, 200, 300, 550], mul: 0.1))}.scope(1)
( 
//在采样和持有中使用 
{ 
f = Latch.kr( 
      Mix.ar(SinOsc.ar([100, 200, 300, 550], mul: 100, add: 110)), 
      Impulse.kr(7)); 
e = EnvGen.kr(Env.perc(0, 0.2), Impulse.kr(7)); 
PMOsc.ar(f, f*1.25, e*5, mul: e*0.3) 
}.play 
)

采样和持有、FM练习

如果你不知道我真的非常喜欢S&H(采样和持有)的结果,那么我们更应该多加使用它。我想不到其他任何组织方法可以生成如此引人入胜的模式。在下例内尝试1/rate的音头和0的衰减。尝试替换的latchrates,它会逐一或阶梯式地越过1.53到1.63间的随机值。这个patch被用[freq, freq * 1.5]扩展为立体声。这在右声道创建一个五度(fifth)。尝试其他音程比如1.2, 1.7, 2, 1.25等。反注释门闩率(latch rate)、频率和比率,或者添加你自己的。

关键是找到可以让你保持关注的复杂度:你能持续跟随的涌现模式的甜蜜的点,并且具备能够让你长时间保持注意的足够多的变化。这难道那不是所有创作都应该是的样子吗:可预见性和惊喜的混合体。

第二例未使用S&H或音序器(也许你可以自己加一个?),但阐述了PM。这是James McCartney写的SC2例子其中之一的变体。James
Harkins 首先将它写为SC3代码,然后我进行了进一步改动。它用复杂的比率生成PM事件,结果是一堆具备包络的振幅和调制指数集合的不谐和频谱(像一条响尾蛇)。

实际生成响尾蛇事件的代码是一条使用随机函数的例程(routine),但要记住它从一个随机数生成器获取值。记住你可以重设随机数列的种子,同时精准地重制值的序列。第三例这么做了,并且那些偶然的重复会给人一种已被认知的即兴姿态的感觉。他周期性地发送“种子”,或者从时钟里选(时间的70%),或重复之前的种子(30%)。你可以把这个任务想做一个指挥者,引导表演者要么(从无数可能的动作中)进入一些新的东西或者重复你曾做过的事。

一次演奏全部例子。这时你可能想要调整一下第一例的全局音量。

16.17. Practice, Sample and Hold, FM

(
{var freq, latchrate, index, ratio, env, rate;
rate = 9;
latchrate = rate*1.61803399;
// latchrate = rate*LFNoise1.kr(1/7, mul: 0.03, add: 1.6);
// latchrate = rate*LFNoise0.kr(1/3, mul: 0.03, add: 1.6);

index = Latch.kr(
   LFSaw.kr(latchrate, mul: 4, add: 8),
   Impulse.kr(rate)
);
freq = Latch.kr(
   LFSaw.kr(latchrate, mul: 36, add: 60),
   Impulse.kr(rate)
   ).round(1).midicps;
// freq = 200; //uncomment this line to hear just the index

ratio = 2.01;
// ratio = LFNoise1.kr(1, mul: 2.3, add: 3.0);
// ratio = LFNoise0.kr(1, mul: 2.3, add: 3.0);
// ratio = LFNoise1.kr(1/5, mul: 2.0, add: 5.0);

env = EnvGen.kr(Env.perc(0, 2/rate), gate: Impulse.kr(rate));

PMOsc.ar([freq, freq * 1.5],
      [freq*ratio, freq*1.5*ratio],
      index,
      mul: env*0.5
)}.play
)
// 变体。我爱这一例。
(
{var freq, latchrate, index, ratio, env, rate;
rate = 9;
latchrate = rate*LFNoise0.kr(1/10, mul: 0.03, add: 1.6);
index = Latch.kr(
   LFSaw.kr(latchrate, mul: 5, add: 6),
   Impulse.kr(rate)
   );
freq = Latch.kr(
   LFSaw.kr(latchrate,
      mul: max(0, LFNoise1.kr(1/5, 24, 10)),
      add: LFNoise0.kr(1/7, 12, 60)),
   Impulse.kr(rate)
   ).round(1).midicps;
ratio = LFNoise1.kr(1/10, mul: 2.0, add: 5.0);

env = EnvGen.kr(Env.perc(0, LFNoise0.kr(rate, 1, 1.5)/rate), Impulse.kr(rate),
LFNoise1.kr([5, 5], 2, 1).max(0).min(0.8));
PMOsc.ar(
   [freq, freq * 1.5],
   freq*ratio,
   index,
   mul: env //overall volume
)}.play
)

响尾蛇。先运行这个例子以定义合成器,然后执行下边的“task”,次数由你决定。

(
SynthDef("crotale", {
arg param = #[500, 3, 2, 0, 6, 5, 0, 0.9];
var factor, env, out, freq, index, dur;
var bus, ratioa, ratiob, attack, decay, panCont;
freq = param.at(0); index = param.at(1); dur = param.at(2);
bus = param.at(3); ratioa = param.at(4); ratiob = param.at(5);
attack = param.at(6); decay = param.at(7);

env = Env.perc(attack, decay);
factor = gcd(ratioa, ratiob);
ratioa = div(ratioa, factor);
ratiob = div(ratiob, factor);

panCont = (EnvGen.kr(env, timeScale: dur*1.1,
   levelBias: -1, levelScale: 2))
        * (IRand(0, 1) * 2 - 1); // 0*2-1 = -1, 1*2-1 = 1

out = PMOsc.ar(
   ratioa*freq, //or try ratioa*freqCont,
   ratiob*freq, //or try ratioa*freqCont,
   pmindex: EnvGen.kr(env, timeScale: dur,
      levelBias: 1, levelScale: index),
   mul: EnvGen.kr(env, timeScale: dur, levelScale: 0.3));

out = Pan2.ar(out, panCont);
out = out * EnvGen.kr(env, timeScale: 1.3*dur,
   levelScale: Rand(0.1, 0.5), doneAction:2);
Out.ar(0, out); //Out.ar(bus, out);
}).play;
)

//运行上边的代码以定义乐器
//然后运行这个task(任务)以实际演奏乐器

(
r = Task({
   var freq, indexDepth, indexRange, synthIndex, dur, repeat;
   var next, count, countDown, offset, ratioa, ratiob, envs, env;
   var range = 60, outBus = 0;
   count = 0; countDown = 0; offset = 0;
   envs = [[0, 0.9], [0.01, 0.9], [0.1, 0.8], [0.8, 0.01]];
   repeat = Array.fill(10,
      {[rrand(range, range+24).round(1).midicps, 3,
         2.1 - exprand(0.1, 2.0), 0, 1, 1, 0, 0.9]});
   next = Array.fill(10, {[3, 0.75, 0.5, 0.25, 0.125].choose});
   freq = rrand(range, range*2); // these two are just starting points
   indexDepth = 1;

   inf.do({
      if(countDown <= 0,
         {
         env = envs.choose;
         next.put(count%10, [3, 0.5, 0.25, 0.125, 0.125].choose);
         repeat.put(count%10, [
            rrand(range, range + 24).round(1).midicps,
            rrand(0.1, 12.0),
            2.1 - exprand(0.1, 2.0), outBus, rrand(1, 12),
            rrand(1, 12), env.at(0), env.at(1)]);
         });

         Synth("crotale").setn(\param, repeat.wrapAt(count));
         next.wrapAt(count).wait;
         if((count > 10).and(countDown <= 0),
            {offset = countDown = [0, 3.rand, 6.rand].choose;
            count = count - offset});
   count = count + 1;
   countDown = countDown - 1;
   });
}).play(SystemClock);
)

作者: ww1way

http://about.me/ww1way

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.