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.