SC:Pbind, 变异, Pfunc, Prand, Pwrand, Pseries, Pseq, 序列化

Pbind将音乐事件的不同参数联系在一起。它使用一个模式将那些值流化(stream)到当前的环境。环境(Environment)是另一个我们之前无需考虑的幕后结构。它包含了一组附属于符号(symbol)的全局默认值。下边是一个不完整的列表。你会发现它们常是以不同方式表述的同样的值:freq和midinote,amp和db。

\amp = 0.1, \db = -20, \degree = 0, \dur = 1, \freq = 261.62, \legato = 0.8, \midinote = 60, \note = 0, \octave = 5, \out = 0, \pan = 0, \root = 0, \scale = [0, 2, 4, 5, 7, 9, 11], \server =default, \velocity = 64, \instrument = default, \out = 0, \group = 0

Pblind通过匹配一个符号(比如 \freq)到一个值(比如400),或一个返回一个值的函数(比如rrand(400, 900)),可以将值传递到环境。如果什么也不提供,那么将使用默认值。默认值很有有,因为它们允许你集中于你创作的一个或两个元素上。如果你想在音高上做文章,便没有必要指定振幅、持续时间或乐器,等等:用默认值就行了。

在使用Pbind前,需要载入合成器描述库(synth description library)。因此对于本章的例子们来说,我假设你已运行过下边的代码。

26.1. 读取全局库

SynthDescLib.global.read

最普通的Pbind只设置一个值。比如下例中的频率。 符号是\freq,值是600。

26.2. 基础Pbind

Pbind(\freq, 600).play

当你使用其他控制流程后会变得更有趣。第一例用Pfunc评估任意函数,在这里是音高随机值。第二例增加持续时间。第三例用符号 \degree和 \octave的方式传递MIDI音。degree是音程度,octave是8度,5是C4八度。

26.3. Pbind与频率函数

Pbind(\freq, Pfunc({rrand(100, 900)})).play; 

Pbind( 
   \freq, Pfunc({rrand(100, 900)}), 
   \dur, Pfunc({rrand(0.1, 1.5)})).play 

Pbind( 
   \degree, Pfunc({8.rand}), 
   \oct, Pfunc({rrand(3, 7)}), //或者试试 \octave? 
   \dur, 0.2).play 

Pbind( 
   \scale, [0, 2, 4, 6, 8, 10], 
   \degree, Pfunc({6.rand}), 
   \oct, Pfunc({rrand(3, 7)}), //或者试试 \octave 
   \dur, 0.2).play 

Pbind( 
   \scale, [0, 2, 3, 5, 6, 8, 10, 11], 
   \degree, Pfunc({8.rand}), 
   \oct, Pfunc({rrand(3, 7)}), //或者试试 \octave 
   \dur, 0.2).play

目前为止,你设计的所有乐器在环境中都是可用的。所有与那些乐器结合创建的引数都是可以与值相搭配的符号。如果碰巧你使用了像midinote这样的引数,它们将正好与现存环境相合。如果你将它们命名为其他名字,比如midiPitch,为结合(articulation)使用art,你只需要使用那些符号。

26.4. Pbind与先前的乐器定义

Pbind( 
   \instrument, "KSpluck3", 
   \midiPitch, Pfunc({rrand(34, 72)}), 
   \art, Pfunc({rrand(1.0, 2.0)}), 
   \dur, 0.1 
).play

dur, legato, nextEnvent

我常将一个事件持续的时间与到下一个事件的持续时间加以区分。Pbind中默认的\dur被用于两者,但真正意味的是到下一个事件的持续时间。一个事件实际的持续时间由legato决定,它是dur的百分比。默认值是0.8。因此,如果两个事件间的持续时间为4,那这个事件的实际持续时间将是0.8×4 = 3.2。因此legato(连奏)可被用于实际的持续时间(2.5连奏 + 4 持续时间 = 10 实际持续时间)。

在我创建KSpluck3时,引数art(结合)真正意味持续时间,但也代表结合。在这个例子中,\dur意味着两个事件间的持续时间。嘿,它们只是变量和引数。随意命名它们就好。

你可以使用Pbind载入一个效果。但因为这是一个单一事件,所以实际没有必要:首先运行它(记住,先输入后输出),然后在Pbind里运行一个路由到它的合成器。

26.5. Pbind与之前的效果

Synth("delay1"); 

Pbind( 
   \instrument, "bells", 
   \freq, Pseq([100, 400, 1000, 1500, 2000]) 
).play;

Pbind在篡改(interplotion)中很有用:用少量的调整转化一个现存的风格(style)或模式(pattern)。我首先定义一个有着适于连续学习(serial studies)的简单乐器。记住,我们不关注乐器的特质。我们的焦点在音高、持续时间、振幅…上。记住你每新建一个乐器,必须重读DescLib

26.6. 简单串行乐器

( 
SynthDef("SimpleTone", 
{arg midinote = 60, amp = 0.9, dur = 1, pan = 0, legato = 0.8; 
   Out.ar(0, 
      Pan2.ar( 
         SinOsc.ar(midinote.midicps, mul: amp) 
         * 
         EnvGen.kr(Env.perc(0, dur*legato), doneAction: 2), 
         pan 
) 
) 
}).load(s); 

SynthDescLib.global.read 
)

接下来是在音高篡改研究中起作用的一个模型。MIDI音符由一个例程(routine)决定:一个能记录之前状态的函数,比如计数器。旋律是巴赫的。如果你熟悉原作的话,我想要指出以下几点:旋律被移调到一个八度上(当我们熟悉了这个转变,效果是一样的),我重复了一些音符以闪避节奏值。同样的,效果是一样的。

相比直接输入MIDI值的做法,我选择使用degree和scale数组,它们协同工作。Scale是和声小调的整/半音级方向。Degree是scale使用的音级(scale step)。二者结合使用的好处是,它允许我在不打断演奏重新输入所有音符的前提下改变模式(例如,从和声小调到里底安调式(Lydian))。数组degree保持原样,我可以改变音阶。

degree数组被-1调整,仅为迎合本人的口味。我常为音阶数组使用0,1,2,为音级使用1,2,3。但如果用乐理思考的话,改写旋律的音符将更简单,即,1是音阶的第一级,[1, 3, 5]是主音琶音,[1, 4, 6]是第一转位(inversion)。

26.7. 变化音高模型

( 
var degreeSeq, nextPitch, scale; 

scale = [0, 2, 3, 5, 7, 8, 11]; 

degreeSeq = [1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 
   3, 3, 5, 5, 1, 1, 4, 4, 7, 7, 2, 2] - 1; 

nextPitch = Routine({ 
   inf.do({arg count; 
      scale.wrapAt(degreeSeq.wrapAt(count)).yield; 
   }) 
}); 

Pbind( 
   \instrument, "SimpleTone", 
   \midinote, nextPitch + 60, 
   \dur, 0.1 
   ).play 
)

此处,你可以用实际回放快速满足你创作的好奇心。如果反向(count.neg)演奏的话会怎样呢?每隔一个音呢(count*2)?每隔两个,隔三个,等等。一个转位会怎样(12 — scale.wrapetc.)?均分间隔(scale = [etc.]/2),两倍间隔,不同的模式(scale = [0, 2, 3, 5, 7, 8, 9]))?

26.8. 实验

// 增加倍数 
   var mul = 1; 
   inf.do({arg count; 
      if(count%20 == 19, {mul = mul + 1}); 

// 偶发随机值 
   inf.do({arg count; 
      if(count%6 == 5, {12.rand.yield}, 
      {(scale.wrapAt(degreeSeq.wrapAt(count*mul))).yield}); 

// 偶发闪避音序 
   if(count%6 == 5, {scale.choose.yield}, 
   {(scale.wrapAt(degreeSeq.wrapAt(count*mul))).yield}); 

// 逐渐转变音阶 
   if(count%6 == 5, {scale.put(scale.size.rand, 12.rand)}); 
   (scale.wrapAt(degreeSeq.wrapAt(count))).yield;

Prand, Pseries, Pseq

其实有一堆有用的模式(看Streams的帮助文档)可用。我将在此展示三个。Prand从一个数组返回一个随机选取(另请参阅Pwrand,或者加权随机模式),Pseries返回一组具备引数start,step和length的值。Pseq看起来被使用的最多。它遍历数组的值。它们可以全部被嵌套,如下例所示。

26.9. 模式

( 
f = 100; 

Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pfunc({ 
      f = ([3/2, 4/3].choose) * f; 
      if(f > 1000, {f = f/8}); //.fold 或 .wrap 没有做到我想做的 
      f.cpsmidi 
      }), 
   \dur, 0.2 
   ).play 
) 

( 
Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Prand([60, 62, 64, 65, 67, 69, 70], inf), 
   \dur, 0.1 
   ).play 
) 

( 
// 数组dur由多个1和2组成, 代表八分音符. 
Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq([70, 58, 60, 62, 60, 58, 65, 62, 70, 
      65, 62, 65, 63, 62, 63, 65, 58, 62, 65, 69], inf), 
   \dur, Pseq([2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 
      2, 1, 1, 1, 1, 2, 2, 2, 2, 2] * 0.2, inf) 
   ).play 
)

当然,你可以运行若干个Pbind(巴赫可能会想要这么干)。为dur 2和3尝试以1或0.5替代。Pbind同样响应mute(静音)和unmute(取消静音)。聆听下例足够长时间,以听到相移:

26.10. 平行Pbind

( 
a = Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq([70, 58, 60, 62, 60, 58, 65, 62, 70, 
      65, 62, 65, 63, 62, 63, 65, 58, 62, 65, 69], inf), 
   \dur, Pseq([2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 
      2, 1, 1, 1, 1, 2, 2, 2, 2, 2] * 0.1, inf), 
   \pan, -1 
   ).play; 

b = Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq([70, 58, 60, 62, 60, 58, 65, 62, 70, 
      65, 62, 65, 63, 62, 63, 65, 58, 62, 65, 69, 0], inf), 
   \dur, Pseq([2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 
      2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2] * 0.1, inf), 
   \pan, 0 
   ).play; 

c = Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq([70, 58, 60, 62, 60, 58, 65, 62, 70, 
      65, 62, 65, 63, 62, 63, 65, 58, 62, 65, 69, 0, 0], inf), 
   \dur, Pseq([2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 
      2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2] * 0.1, inf), 
   \pan, 1 
   ).play; 
) 

a.mute; 
b.mute; 
a.unmute; 
c.mute; 
b.unmute; 
c.unmute;
// Riley? 
( 
p = Array.fill(20, {[0, 2, 4, 7, 9].choose + [60, 72].choose}).postln; 
q = p.copyRange(0, p.size - 2).postln; 
Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq([Pseq(p), Pseq(p), Pseq(p)], inf), 
   \dur, 0.1, 
   \pan, -1 
   ).play; 

Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq([Pseq(p), Pseq(p), Pseq(q)], inf), 
   \dur, 0.1, 
   \pan, 1 
   ).play; 
)
// 或平缓相位 
( 
p = Array.fill(20, {[0, 2, 4, 7, 9].choose + [60, 72].choose}).postln; 
Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq(p, inf), 
   \dur, 0.1, 
   \pan, -1 
   ).play; 

Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq(p, inf), 
   \dur, 0.101, 
   \pan, 1 
   ).play; 

Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq(p, inf), 
   \dur, 0.102, 
   \pan, 0 
   ).play; 
)

这儿是一些嵌套的模式。Pbind是一个做序列化(serialization)的优秀工具。下边就是一个部分节奏被序列化(即兴缩混)的12音例子,动态级(dynamic levels)同样是半序列化半随机的。

26.11. 序列主义(Serialism)

( 
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].scramble.postln; 
r = [0.1, 0.1, 1.0, 0.2, 0.3, 0.166, 0.166]; 
o = [48, 60, 72]; 

Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Prand( 
      [ //P, R, I, IR 
         Pseq(a) + o.choose, 
         Pseq(a.reverse) + o.choose, 
         Pseq(12 - a) + o.choose, 
         Pseq((12 - a).reverse) + o.choose 
      ], inf), 
   \dur, Pseq([Prand([0.1, 0.2, 0.5, 1.0], 7), 
            Prand([Pseq(r), Pseq(r.reverse)], 1)], inf), 
   \amp, Prand([ 
            Pseries(0.1, 0.1, 5), // 渐强 
            Pseries(0.9, -0.1, 6), // 减弱 
            Prand([0.1, 0.3, 0.5, 0.7], 5) 
            ], inf) 
).play; 
)
( 
// 恩,当然, 也可以一次三个 
// 如果是除0外的种子,那粒种子将被使用。 
// 如果是0, 一个随机的种子将被选取和列印。 
// 用它重复一个演奏 

var seed = 0; 

if(seed !=0, {thisThread.randSeed = seed}, 
   {thisThread.randSeed = Date.seed.postln}); 

a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].scramble.postln; 
r = [0.1, 0.1, 1.0, 0.2, 0.3, 0.166, 0.166]; 
o = [48, 60, 72]; 

Pbind( 
   \instrument, "SimpleTone", 
   \pan, -1, 
   \midinote, Prand( 
      [ //P, R, I, IR 
         Pseq(a) + o.choose, 
         Pseq(a.reverse) + o.choose, 
         Pseq(12 - a) + o.choose, 
         Pseq((12 - a).reverse) + o.choose 
      ], inf), 
   \dur, Pseq([Prand([0.1, 0.2, 0.5, 1.0], 7), 
            Prand([Pseq(r), Pseq(r.reverse)], 1)], inf), 
   \amp, Prand([ 
            Pseries(0.1, 0.1, 5), // 渐强 
            Pseries(0.9, -0.1, 6), // 减弱 
            Prand([0.1, 0.3, 0.5, 0.7], 5) 
            ], inf) 
).play; 

Pbind( 
   \instrument, "SimpleTone", 
   \pan, 0, 
   \midinote, Prand( 
      [ //P, R, I, IR 
         Pseq(a) + o.choose, 
         Pseq(a.reverse) + o.choose, 
         Pseq(12 - a) + o.choose, 
         Pseq((12 - a).reverse) + o.choose 
      ], inf), 
   \dur, Pseq([Prand([0.1, 0.2, 0.5, 1.0], 7), 
            Prand([Pseq(r), Pseq(r.reverse)], 1)], inf), 
   \amp, Prand([ 
            Pseries(0.1, 0.1, 5), // cresc 
            Pseries(0.9, -0.1, 6), // decresc 
            Prand([0.1, 0.3, 0.5, 0.7], 5) 
            ], inf) 
).play; 

Pbind( 
   \instrument, "SimpleTone", 
   \pan, 1, 
   \midinote, Prand( 
      [ //P, R, I, IR 
         Pseq(a) + o.choose, 
         Pseq(a.reverse) + o.choose, 
         Pseq(12 - a) + o.choose, 
         Pseq((12 - a).reverse) + o.choose 
      ], inf), 
   \dur, Pseq([Prand([0.1, 0.2, 0.5, 1.0], 7), 
            Prand([Pseq(r), Pseq(r.reverse)], 1)], inf), 
   \amp, Prand([ 
            Pseries(0.1, 0.1, 5), // cresc 
            Pseries(0.9, -0.1, 6), // decresc 
            Prand([0.1, 0.3, 0.5, 0.7], 5) 
            ], inf) 
).play; 
)

好吧,这不是韦伯恩(Webern)。但经过20分钟的代码输入后已经非常接近了。要点是这个模型具有一切你可以用手写出的序列作品的潜力。像我之前说过的,代码无需是作品本身(尽管这是我的偏爱)。它可以是一个测试场。一旦你找到一个你喜欢的变奏(为不同的变奏换随机的种子),将它拷贝出来然后交给演奏者。

使用MIDIout而不是合成与服务器做序列化

Pbind与服务器交流。我发现MIDI在序列化学习中更有用,因为那些创作通常是被指定给现场演出者的,用MIDI的话,我可以选择无数的乐器。MIDI也更高效,因为服务器不需要为乐器设计和回放进行任何计算。你可以保持服务器运行,或使用quit停止它们。或者直接无需开启它们。

下一个例子不是最严格意义上的完全序列化。仅仅是音高数组依据12音规范被序列化掉:在每个引用(12个音符,或者count%12)后,一个新行被选中。其他的序列设置被跟随一阵子然后被搅拌,大约10%的时间(0.1.coin)。我没有在每个系列中加入太多思考,但你懂的。

在r.stop之后,4.do在四条通道内停止我们正在使用的全部音符。

练习:仅使用MIDI的完全序列化

26.12. 巴比特(Babbitt): 完全序列化 (某种程度)

( 
MIDIClient.init; 
m = MIDIOut(0, MIDIClient.destinations.at(0).uid); 
) 

( 
var pitchSeries, octaveSeries, durationSeries, nextSeries, dynSeries, instSeries; 
pitchSeries = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].scramble.postln; 
octaveSeries = Array.fill(6, {[36, 48, 60, 72].choose}).postln; 
durationSeries = Array.fill(23, {rrand(0.1, 3.0).round(0.1)}).postln; 
nextSeries = Array.fill(30, {[0, 0, 0.1, 0.1, 0.1, 0.2, 0.2, 0.2, 0.4, 0.4, 
2.0].choose}).postln; 
dynSeries = Array.fill(24, {rrand(40, 120).round(0.1)}).postln; 
instSeries = Array.fill(20, {4.rand}).postln; 

r = Task({ 
   inf.do({arg count; 
   var note; 
   note = pitchSeries.wrapAt(count) + octaveSeries.wrapAt(count); 
   if(count%12 == 0, { 
      pitchSeries = // 选择一个新行 
         [pitchSeries.reverse, // 倒退 
         (12 - pitchSeries).reverse, // 反向倒退 
         12 - pitchSeries, // 反向 
         pitchSeries // 同等音(prime) 
         ].choose; 
      // 选择一个行移调(transposition of the row) 
      pitchSeries = (pitchSeries + 12.rand)%12; 
      pitchSeries.postln;}); 
   if(0.1.coin, { 
      durationSeries = durationSeries.scramble.postln; 
      nextSeries = nextSeries.scramble.postln; 
      dynSeries = dynSeries.scramble.postln; 
      instSeries = instSeries.scramble.postln; 
      }); 
   m.noteOn(instSeries.wrapAt(count), note, dynSeries.wrapAt(count)); 
   thisThread.clock.sched(durationSeries.wrapAt(count), 
      {m.noteOff(instSeries.wrapAt(count), note); nil}); 
   nextSeries.wrapAt(count).wait 
   }) 
}); 

r.start; 
) 

r.stop; 4.do({arg j; 127.do({arg i; m.noteOff(j, i, 0)})})

使用Pbind的MIDI

Pbind相较Task提供更多的功能。要与Pbind协同使用MIDI,你首先需要创制一个可以处理MIDI乐器的环境。这个模型由Julian Rohrhuber提供,我并未对其进行任何改动。

26.13. Pbind 和 MIDI, Julian Rohrhuber提供

(
var f;
f = (
   noteOn: #{ arg chan, midinote, amp;
               [chan, midinote, asInteger((amp * 255).clip(0, 255))]
                 },
   noteOff:#{ arg chan, midinote, amp;
               [ chan, midinote, asInteger((amp * 255).clip(0, 255))]
                 },
   polyTouch: #{ arg chan, midinote, polyTouch=125;
                         [ chan, midinote, polyTouch]
                 },
   control: #{ arg chan, ctlNum, control=125;
                         [chan, ctlNum, control]
                 },
   program: #{ arg chan, progNum=1;
                         [ chan, progNum]
                 }
     touch ( chan, val )
   bend ( chan, val )
   allNotesOff ( chan )
   smpte ( frames, seconds, minutes, hours, frameRate )
   songPtr ( songPtr )
   songSelect ( song )
   midiClock ( )
   startClock ( )
   continueClock ( )
   stopClock ( )
   reset ( )
   sysex ( uid, Int8Array )

   */
);

~midiEnv = (
            chan: 1,
            msgFuncs: f,
            hasGate: true,
            midicmd: \noteOn,
            play: #{
               var freqs, lag, dur, sustain, strum;
               var tempo, bndl, midiout, hasHate, midicmd;

               freqs = ~freq = ~freq.value + ~detune;

               tempo = ~tempo;
               if (tempo.notNil) {
                  thisThread.clock.tempo = tempo;
               };

               if (freqs.isKindOf(Symbol).not) {
                  ~finish.value;
                  ~amp = ~amp.value;
                  strum = ~strum;
                  lag = ~lag;
                  sustain = ~sustain = ~sustain.value;
                  midiout = ~midiout;
                  hasHate = ~hasGate;
                  midicmd = ~midicmd;
                  bndl = ~msgFuncs[midicmd].valueEnvir;

                  bndl = bndl.flop;

                  bndl.do {|msgArgs, i|
                        var latency;

                        latency = i * strum + lag;

                        midiout.performList(midicmd, msgArgs);
                        if(hasHate and: { midicmd === \noteOn }) {
                           thisThread.clock.sched(sustain) {
                              midiout.noteOff(*msgArgs)
                           };
                        };
                  };
                  }
               }
           ).putAll(
              Event.partialEvents.pitchEvent,
              Event.partialEvents.ampEvent,
              Event.partialEvents.durEvent
           )
);

// 初始化midiout
(
MIDIClient.init;
m = MIDIOut(0, MIDIClient.destinations.at(0).uid);
)

// 我修改了Pbind以显示更多的值 [它们不是非常好]
(
Pbind(
   \parent, ~midiEnv,
   \midiout, m,
   \chan, Pseq([0, 1, 2, 3], 60), //total number of events
   \amp, Prand([
      Pseq([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]),
      Pseq([0.8, 0.7, 0.5, 0.3, 0.1]),
      Prand([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], 10)
      ], inf),
   \dur, Prand([0.1, 0.1, 0.1, 0.2, 0.2,1.0, 2.0], inf),
   \sustain, Pfunc({rrand(0.1, 2.0)}),
   \midinote, Prand([36, 38, 40, 42, 43, 45, 47, 49, //synthetic scale
      50, 52, 54, 56, 57, 59, 61, 63, 64, 66, 68, 70], inf)
).play;
)
Be Sociable, Share!

Published by

ww1way

http://about.me/ww1way

One thought on “SC:Pbind, 变异, Pfunc, Prand, Pwrand, Pseries, Pseq, 序列化”

  1. Wow! This could be a definite of the most beneficial sites We’ve ever arrive throughout about this topic. Essentially Magnificent. We are additionally an expert on this topic so I can understand your effort.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *