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; )
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.