kitchen studies source code of the corresponding fixed media piece


Part of: miSCellaneous


See also: PbindFx, Buffer Granulation, Live Granulation, VarGui, DX suite, DXMix, DXMixIn, DXEnvFan, DXEnvFanOut, DXFan, DXFanOut



In 2016 my interest in granular synthesis was focussed on the potential of sequencing arbitrary effects and effect graphs on single grains. This is an application of the class PbindFx, which I added with miSCellaneous v0.14. As the possibilities are countless – an arbitrary number of effects can be applied on each grain in an arbitrary way: in sequence, in parallel or in any graph order – I started experiments with combinations of at most two effects and sequencing their parameters. At the same time I wanted to share my experiences and document what I was encountering while composing a piece of music. I thought the best approach for that twofold need would be a sequence of small pieces in once, each of them using different effect processings per grain. A piece of such form would not be totally typical for my usual compositional practice, as I tend to favour longer forms with a few contrasting types of material combined in a developing relation, but nevertheless an interesting challenge. As a sound source I took the kitchen sound of five seconds which is already contained in miSCellaneous lib since version 0.7 and used for examples in the Buffer Granulation tutorial. 

For the fixed media piece kitchen studies the audio, resulting from the six sections of code below, has only been cut and slightly mastered with a bit of equalization (as many random components are included, the output will of course vary from one evaluation to the next). Each code section delivers a mixed control by gui (VarGui) and code snippets (Patterns), which reflects my personal experimental preferences with the concerned sounds and might serve as a starting point for the reader's experiments and modifications. Compressed versions of the original piece as a whole and its parts can be found on my website http://daniel-mayer.at, a further documentation of the compositional process will follow as publication in the artistic research database Research Catalogue (https://www.researchcatalogue.net/profile/show-exposition?exposition=324609).



WARNING: 


1.) Be careful with amplitudes, especially with buffers you haven't granulated before !

Also keep in mind that a granular cloud moving through a buffer can suddenly become louder – this is especially the case with the one percussive sound contained in the used kitchen source sound. Moreover other controls than amp (e.g. buffer position, trigger rate, effect parameters) can cause a raise of amplitude too.


2.) I haven't used below setups for live performances. Although all of them work stable for me as they are, in general hangs can occasionally happen with pattern-driven setups. Often this can be tracked down to sequences of extremely short event durations (and/or long grain durations). Where this can happen as a side-effect, thresholds can be built in (e.g. a parameter maxTrigRate). 

Another possible source of hangs is careless deep nesting of Patterns where mistakes can easily occur. Starting with clear Pattern structures is recommended – and if more complications are involved: testing without sound first after saving your patch (generating data with a single Stream derived from a Pattern), might be a good idea.



NOTE: 


Running the code examples: First run the code from chapter 'Preparations' - i.e. start server with extended ressources and evaluate the following block of code. Then you can run the example code of each part (but not in parallel). See the comments of 'Part 1', many of them explain principles, which are used in all other parts too. Similary, gui conventions are pretty much the same for all six parts, see the note on common VarGui features below.

Types of variables: For employing the VarGui interface environmental variables and PLx patterns are used. With VarGui every EventStreamPlayer derived from a passed Pattern is run in a separate newly generated Environment, where variables are being set and Streams from Pfuncs and PLx patterns are reading from. See Event patterns and Functions, PLx suite and VarGui. For certain live replacements though, environmental variables in topEnvironment are chosen and for the sake of clarity some interpreter variables are used too. As a result of these two exceptions the six code blocks, as they are, cannot be run in parallel. As each one is producing dense and rich sound structures it was supposed that combining them in realtime would not be the first option anyway, but it could of course be done by rewriting these variables.

Buffers: All examples below expect mono buffers like the delivered kitchen sound. Buffer paths refering to the included sample suppose that you have installed via quarks or moved miSCellaneous lib into the user or system extensions directory directly (not into a subfolder), if not so or you have moved the sample file somewhere else you'd have to change paths accordingly.

Versions: For the published version of kitchen studies I worked on OS 10.6 / SC 3.6.5 and OS 10.8.5 / SC 3.8.0, slight differences in sound might occur with other hardware, operating systems and SC versions.




Preparations


// start server with extended ressources


(

s.options.numPrivateAudioBusChannels = 2048;

s.options.memSize = 8192 * 32;

s.options.maxNodes = 1024 * 4;


s.reboot;

)


// loading the buffer (maybe you need to use Platform.systemExtensionDir instead)


// defining SynthDefs:

// 'pos' for single grains

// 'posPlay' for moving buffer position

// fx SynthDefs for PbindFx

// 'leakDC' for leaking DC from summed grains and limiting


(

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

// This searches the most likely extension places for the miSCellaneous folder.

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.



// \posPlay is the synthdef for one grain

// args pos and posDev are to be read from a bus, played by a LFO


// arg pos is relative between 0 and 1

// arg posDev is absolute (max absolute deviation in seconds from pos)


SynthDef(\posPlay, { |out = 0, sndBuf = 0, granDur = 0.1, pos = 0,

posDev = 0, rate = 1, att = 0.002, rel = 0.005, pan = 0, amp = 1|

var env, src;

pos = Rand(posDev.neg, posDev) / BufDur.kr(sndBuf) + pos;

src = PlayBuf.ar(1, sndBuf, BufRateScale.kr(sndBuf) * rate,

1, round(pos * BufFrames.kr(sndBuf)), 1, 2);

env = EnvGen.ar(

Env([0, amp, amp, 0], [att, granDur - att - rel, rel], 0),

doneAction: 2

);

OffsetOut.ar(out, Pan2.ar(src, pan) * env);

}, \ir!10).add;


// posRate of 1 (given by posRateE and posRateM) means

// pos movement through the buffer with original velocity.

// buffer segment is determined by posLo and posHi.

// posDev and pos movement do not depend on buffer length and size of buffer segment !

// (posRate does, but pos is then mapped to the buffer segment by .linlin)


// there are four types of movement through the buffer, determined by the arg 'type':

// O: forward

// 1: backward

// 2: forward and backward

// 3: randomly with cubic interpolation


SynthDef(\pos, { |out = 0, sndBuf = 0, posRateE = 0, posRateM = 1, type = 0,

posLo = 0, posHi = 1, posDevE = 1, posDevM = 0|

var pos, posDev = 10 ** posDevE * posDevM, posRate = 10 ** posRateE * posRateM;


posRate = posRate / BufDur.kr(sndBuf) / max(0.001, (posHi - posLo));

pos = Select.kr(type, [

LFSaw.kr(posRate, 1),

LFSaw.kr(posRate, 1).neg,

LFTri.kr(posRate, 1).neg,

LFDNoise3.kr(posRate)

]);

pos = pos.linlin(-1, 1, posLo, posHi);

Out.kr(out, [pos, posDev])

}).add;



// leaking DC and limiting summed audio of all grains


SynthDef(\leakDC, { |out = 0, in|

var sig = In.ar(in, 2);

Out.ar(out, Limiter.ar(LeakDC.ar(sig)));

}).add;



// this Function determines enveloping convention:

// absEnv = 1 (true): attack and release times in milliseconds are taken anyway,

// 'overlap' is possibly overriden.

// absEnv = 0 (false): attack and release times in milliseconds are taken only if

// their sum is smaller than grain duration (derived from 'overlap' and 'trigRate'),

// otherwise they are shrunk and their relation is kept.


d = ();


d.attRel = { |dict, granDur, att, rel, absEnv = 1|

// times in secs

var sum;

att = att * 0.001;

rel = rel * 0.001;

sum = att + rel;

(sum <= granDur).if {

[att, rel]

}{

(absEnv == 1).if {

[att, rel]

}{

[granDur * att, granDur * rel] / sum

}

}

};

)




NOTE: 


Common features of dedicated VarGuis 


The slider section always contains two blocks, the lower one is for parameters of the buffer position synth and the upper one is for parameters of the PbindFx. Within the lower block the relative buffer position is determined by parameters 'posLo' and 'posHi'. As the percussive event within the the loaded kitchen sound appears around position 0.3, most position defaults describe a relatively small section around this value. The deviation of position as well as the rate of movement through the buffer are determined by a mantissa-exponent representation each. Finally four types of movement are defined: 0 = forward, 1 = backward, 2 = forward and backward, 3 = random with cubic interpolation.

Within the upper block there are two or three differently colored sections, separating controls of source and effects (one or two). PbindFx parameters can be directly passed to the source grain player synths (such a 'amp'), but they might also be used to process the actual synth args, e.g. 'absEnv' determines if parameters 'att' and 'rel' are to be taken literally or relative with respect to an "absolute" 'overlap' value (see pseudo method 'attRel' above), this kind of processing is defined within the PbindFx. Not all parameters need to occur in the VarGui instance, they might also be controlled by PL pattern proxies, in that case the pattern sources are defined in topEnvironment later on.

Amplitude parameters 'amp' occur with grain synths as well as with effect synths, in the latter case they appear with the fx name as suffix in the gui. Their meaning is not the same in all cases, mostly they are understood as amplitudes of the fx-processed signal ("pre-mix"), but they can be applied to the mix too ("post-mix"). In the case of a combined fx/no-fx sequencing a more fine-tuned amplitude control can be achieved with applying a separate gain fx, which is used instead of no effect. For the the sake of clarity this is only done within the last part.

The EventStreamPlayer derived from the PbindFx (e0 on the right side) and the buffer position synth can be started separately by pressing the green buttons. Note that patches can also be controlled by setting certain PL proxy pattern sources as shown at the bottom of part 1. Regarding the compositional process of kitchen studies, the final sounding result very much depends on the decision which parameters are controlled by pattern sequencing and which ones are controlled by gui-tunable parameters. Such decisions happened during a long process of experimenting which usually started with gui control for all parameters. However for the final version no live-control with sliders has been recorded, parameters were either fixed or algorithmically controlled by patterns.





Part 1 – comb delay plus separate delay modulation


// This granulation combines two effects in sequence.

// The same effect chain (fxOrder = [1, 2]) is used for all grains but parameters change.


(

// comb delay


SynthDef(\comb, { |out = 0, in, mix = 0.5, amp = 1, maxDelayTime = 0.2, delayTime = 0.02,

decayTime = 1|

var sig, inSig = In.ar(in, 2);

sig = CombL.ar(inSig, maxDelayTime, delayTime, decayTime, amp);

OffsetOut.ar(out, (1 - mix) * inSig + (sig * mix));

}).add;


// modulation of delay

// modInd is a provisional measure for delay modulation in analogy to FM's index


SynthDef(\delayMod, { |out = 0, in, delayTime = 0.1, maxDelayTime = 2,

modFreq = 0, modIndM = 0, modIndE = -5, mix = 1, amp = 0.1|

var inSig = In.ar(in, 2), sig, modDev;

modDev = 10 ** modIndE * modIndM * modFreq;

sig = DelayC.ar(inSig, maxDelayTime, SinOsc.ar(modFreq, 0, modDev, delayTime), amp);

OffsetOut.ar(out, (1 - mix) * inSig + (sig * mix));

}).add;



// topEnvironment needed for pattern proxies

// the slider variables are set in dedicated environments by VarGui


t = topEnvironment.push;


// bus to DC leaker

a = Bus.audio(s, 2);


// bus for position control

c = Bus.control(s, 2);


// PLs with envir = t will read from topEnvironment Patterns/Streams (see below)

// other PLs will read slider values


// produce three minutes audio


p = Pfindur(180, PbindFx([

\instrument, \posPlay,

\sndBuf, b,

\out, a,


\dur, 1 / PL(\trigRate),

\granDur, Pkey(\dur) * PL(\overlap, envir: t),


\pos, c.subBus(0).asMap,

\posDev, c.subBus(1).asMap,


\rate, PL(\rate, envir: t),

\amp, PL(\amp),


\pan, PLseq([-1, 1]) * PL(\panMax),

// determining concrete envelope times (see Function attRel above)

[\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) },


\fxOrder, PL(\fxOrder, envir: t),

// comb delay time needed for lag in case of no comb

\delayTime_comb, PL(\delayTime_comb, envir: t).collect { |x| d.dt = x; x },

\lag, Pfunc { |ev|  (ev.fxOrder.asArray.includes(1)).if { 0 }{ ev.delayTime_comb } },

\cleanupDelay, 0.3

],[

\fx, \comb,

\decayTime, PL(\decayTime_comb),

\delayTime, Pfunc { d.dt },

\mix, PL(\mix_comb),

\amp, PL(\amp_comb),

\cleanupDelay, Pkey(\decayTime)

],[

\fx, \delayMod,

\mix, PL(\mix_delayMod),

\amp, PL(\amp_delayMod),

\modIndM, PL(\modIndM_delayMod),

\modIndE, PL(\modIndE_delayMod),

\modFreq, PL(\modFreq_delayMod, envir: t),

\cleanupDelay, 0.1

    ]

));


// slider and player control

v = VarGui([

\att, [1, 200, \lin, 0, 6.97],

    \rel, [1, 200, \lin, 0, 8.96],

     \absEnv, [0, 1, \lin, 1, 0.0],

\trigRate, [1, 200, \lin, 0, 66.67],

\panMax, [0, 1, \lin, 0, 0.89],

    \amp, [0.0, 3, \lin, 0, 1],


\decayTime_comb, [0.001, 1, \lin, 0, 0.14086],

    \mix_comb, [0, 1, \lin, 0, 0.7],

    \amp_comb, [0, 3, \lin, 0, 1],


    \modIndE_delayMod, [-6, -2, \lin, 1, -5.0],

    \modIndM_delayMod, [0, 10, \lin, 0, 1.5],

    \mix_delayMod, [0, 1, \lin, 0, 0.88],

    \amp_delayMod, [0, 3, \lin, 0, 1.0]

],[

\sndBuf, b.bufnum,

    \posLo, [0, 0.99, \lin, 0, 0.2673],

   \posHi, [0, 0.99, \lin, 0, 0.3267],

    \posDevE, [-6, 0, \lin, 1, -5],

    \posDevM, [0.0, 10, \lin, 0, 6.7],


    \posRateE, [-4, 2, \lin, 1, -1],

    \posRateM, [0.1, 10, \lin, 0, 0.694],

  \type, [0, 3, \lin, 1, 3],

\out, c.index

], stream: p, synth: \pos

);


// gui look, color grouping

w = (

varColorGroups: (0..12).clumps([6, 3, 4]),

synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]),

tryColumnNum: 1,

labelWidth: 130,

sliderWidth: 350,

sliderHeight: 18,

playerHeight: 18

);

)




// running the patch: evaluate this, 

// then in gui start position synth and PbindFx stream by pressing both green buttons


// Psegs are very practical for defining LFO-like behaviour in SC lang


(

x = Synth(\leakDC, [\out, 0, \in, a]);


~overlap = Pseg(PLwrand([0.05, 0.5, 1], [4, 1, 1].normalizeSum), Pwhite(0.5, 1), \sine);

~rate = Pseg(Pwhite(0.05, 1), Pexprand(0.01, 0.5), \sine);

~delayTime_comb = Pseg(PLseq([0.02, 0.002]), Pwhite(8, 15));

~fxOrder = [1, 2];

~modFreq_delayMod = Pseg(Pwhite(10, 150), Pwhite(2, 5), \sine);


v.performWithEnvir(\gui, w)

)


// while running the patch you can play with sliders and

// exchange the control patterns in top envir on the fly:


~delayTime_comb = 0.002;

~rate = 0.5;


~modFreq_delayMod = 70;

~modFreq_delayMod = 50;


~modFreq_delayMod = Pseg(Pwhite(10, 150), Pwhite(2, 5), \sine);



// experiment with fx sequencing: no - comb - (comb > delay modulation)


~fxOrder = PLseq([0, 1, [1, 2]]);

~fxOrder = Pstutter(Pwhite(1, 10), PLseq([0, 1, [1, 2]]));



// after stopping free leakDC synth


x.free




Part 2 – rectangular comb (FFT)


(

// rectangular comb, FFT processing per grain


SynthDef(\pv_rectComb, { |out = 0, in, numTeeth = 0, width = 0.5, phase = 0, mix = 1,

amp = 0.1|

    var chain, inSig = In.ar(in, 2), sig, bufSize = 512, inSigDelayed;


    chain = FFT({ LocalBuf(bufSize) } ! 2, inSig);

    chain = PV_RectComb(chain, numTeeth, phase, width);

sig = IFFT(chain) * amp;

// for mix we have to take into account FFT delay

inSigDelayed = DelayL.ar(

inSig,

0.1,

bufSize / s.sampleRate - (s.options.blockSize / s.sampleRate)

);

OffsetOut.ar(out, mix * sig + ((1 - mix) * inSigDelayed));

}).add;



t = topEnvironment.push;


a = Bus.audio(s, 2);

c = Bus.control(s, 2);


// play two loops with three "interludes" (see Task below)


p = Pfindur(178, PbindFx([

\instrument, \posPlay,

\sndBuf, b,

\out, a,


\dur, 1 / PL(\trigRate),

\granDur, Pkey(\dur) * PL(\overlap),


\pos, c.subBus(0).asMap,

\posDev, c.subBus(1).asMap,


\rate, PL(\rate, envir: t),

\amp, PL(\amp),


\pan, PLseq([-1, 1]) * PL(\panMax),

[\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) },


\fxOrder, PLseq(\fxOrder, envir: t),

\cleanupDelay, 0.3

],[

\fx, \pv_rectComb,

\mix, PL(\mix_rectComb, envir: t),

\amp, PL(\amp_rectComb),


\numTeeth, PL(\numTeeth_rectComb, envir: t),

\width, PL(\width_rectComb, envir: t),

\phase, PL(\phase_rectComb, envir: t),


\cleanupDelay, 0.2

    ]

));


// timed pattern control for interludes, done with a Task


u = Task({

var times_1 = PLseq([10, 10, 20, 30]).iter;

var times_2 = PLseq([7, 8, 4, 0]).iter;


loop {

var tm;

~numTeeth_rectComb = 10;

~width_rectComb = 0.05;

~rate = 0.2;


tm = times_1.next;

tm.wait;


~numTeeth_rectComb = Pstutter(Pwhite(5, 30), Pwhite(3, 15));

~rate = Pstutter(Pwhite(50, 100), Pwhite(0.3, 0.5));

~width_rectComb = Pbrown(0.2, 0.5, 0.02);


tm = times_2.next;

tm.wait;

}

}.inEnvir(t));



v = VarGui([

// separate Environments for Task and PbindFx player

[],[

    \att, [1, 200, \lin, 0, 7],

    \rel, [1, 200, \lin, 0, 7],

     \absEnv, [0, 1, \lin, 1, 0],

\overlap, [0.05, 15, \lin, 0, 3.3],

\trigRate, [1, 200, \lin, 0, 54.73],


\panMax, [0, 1, \lin, 0, 0.99],

    \amp, [0.0, 3, \lin, 0, 0.15],


\amp_rectComb, [0, 10, \lin, 0, 5.6]

]],[

\sndBuf, b.bufnum,

    \posLo, [0, 0.99, \lin, 0, 0.2574],

   \posHi, [0, 0.99, \lin, 0, 0.3267],

    \posDevE, [-6, 0, \lin, 1, -5],

    \posDevM, [0.0, 10, \lin, 0, 1.9],


    \posRateE, [-4, 2, \lin, 1, -1],

    \posRateM, [0.1, 10, \lin, 0, 5.05],

  \type, [0, 3, \lin, 1, 3],

\out, c.index

], stream: [u, p], synth: \pos

);


w = (

varColorGroups: (0..7).clumps([7, 1]),

synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]),

tryColumnNum: 1,

labelWidth: 130,

sliderWidth: 350,

sliderHeight: 18,

playerHeight: 18

);

)



// running the patch: evaluate this, 

// then in gui start position synth  

// start PbindFx / Task players together by pressing their green buttons with shift-click


// while running the patch check modified slider values and 

// consider pattern replacements as shown in part 1



(

x = Synth(\leakDC, [\out, 0, \in, a]);


~mix_rectComb = 0.75;

~phase_rectComb = Pstutter(Pwhite(1, 4), Pwhite(0.0, 0.6));

~fxOrder = [1];


v.performWithEnvir(\gui, w)

)



// after stopping free leakDC synth


x.free




Part 3 – resampling


(

// fx with "post-mix" amplitude


SynthDef(\resample, { |out = 0, in, mix = 0.5, amp = 1, resampleRate = 44100|

var sig, inSig = In.ar(in, 2);

sig = Latch.ar(inSig, Impulse.ar(resampleRate));

OffsetOut.ar(out, ((1 - mix) * inSig + (sig * mix)) * amp);

}).add;


t = topEnvironment.push;


a = Bus.audio(s, 2);

c = Bus.control(s, 2);


p = Pfindur(90, PbindFx([

\instrument, \posPlay,

\sndBuf, b,

\out, a,


\dur, 1 / PL(\trigRate, envir: t),

\granDur, Pkey(\dur) * PL(\overlap),


\pos, c.subBus(0).asMap,

\posDev, c.subBus(1).asMap,


\rate, PL(\rate, envir: t),

\amp, PL(\amp),


\pan, PLseq([-1, 1]) * PL(\panMax),

[\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) },


\fxOrder, PL(\fxOrder, envir: t),

\cleanupDelay, 0.3

],[

\fx, \resample,

\mix, PL(\mix_resample, envir: t),

\amp, PL(\amp_resample),

\resampleRate, PL(\resampleRate_resample, envir: t),

\cleanupDelay, 0.01

    ]

));


v = VarGui([

\att, [1, 200, \lin, 0, 4.98],

    \rel, [1, 200, \lin, 0, 4.98],

     \absEnv, [0, 1, \lin, 1, 1.0],

\overlap, [0.05, 15, \lin, 0, 3.5],


\panMax, [0, 1, \lin, 0, 0.93],

    \amp, [0.0, 3, \lin, 0, 0.15],


\amp_resample, [0, 3, \lin, 0, 2.7]

],[

\sndBuf, b.bufnum,

    \posLo, [0, 0.99, \lin, 0, 0.27],

   \posHi, [0, 0.99, \lin, 0, 0.34],

    \posDevE, [-6, 0, \lin, 1, -5],

    \posDevM, [0.0, 10, \lin, 0, 0.3],


    \posRateE, [-4, 2, \lin, 1, -1],

    \posRateM, [0.1, 10, \lin, 0, 3.5],

  \type, [0, 3, \lin, 1, 3],

\out, c.index

], stream: p, synth: \pos

);


w = (

varColorGroups: (0..6).clumps([6, 1]),

synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]),

tryColumnNum: 1,

labelWidth: 110,

sliderWidth: 350,

sliderHeight: 18,

playerHeight: 18

);

)



// running the patch: evaluate this, 

// then in gui start position synth and PbindFx stream by pressing both green buttons


// while running the patch check modified slider values and 

// consider pattern replacements as shown in part 1


(

Synth(\leakDC, [\out, 0, \in, a]);


~resampleRate_resample = Pn(Plazy {

var freqs = (1..rrand(8, 15)) * rrand(100, 600);

Pseq(freqs, rrand(3, 6))

});


~fxOrder = 1;


~mix_resample = Pseg(

PLseq([0.0, 0.9]),

// see PS help, examples for "counted embedding"

PLseq([

PS(Pwhite(2.0, 3), Pwhite(1, 2)),

PS(Pwhite(0.03, 0.05), Pwhite(50, 80))

]),

\step

);


// random quarter tone row

~rate = Pseg(PLshuf((-24..24)/2).midiratio, Pwhite(0.2, 2), \step);


~trigRate = Pseg(Pwhite(40, 120), Pwhite(0.5, 2), \sine);


v.performWithEnvir(\gui, w)

)


// after stopping free leakDC synth


x.free



Part 4 – spectral complements (FFT)


(

// emphasizes / supresses bin range and / or complement

// uses pseudo ugens PV_BinRange and PV_BinGap


// freqLo and freqHi define the range

// rangeMul is the multiplier for the selected band

// compMul is the multiplier for the rest of the spectrum


SynthDef(\pv_binRangeBal, { |out = 0, in, freqLo = 80, freqHi = 300, rangeMul = 1, compMul = 1, mix = 1|

var chain, chain_range, chain_comp, inSig = In.ar(in, 2), sig, bufSize = 2048,

lo, hi, binRange, binNum, inSigDelayed;


binRange = s.sampleRate / bufSize;

binNum = (freqHi - freqLo / binRange).round;

lo = (freqLo / binRange).round;

hi = (freqHi / binRange).round;


chain = FFT({ LocalBuf(bufSize) } ! 2, inSig);

chain_range = PV_BinRange(chain, lo, hi);

chain_comp = PV_BinGap(chain, lo, hi);


sig = IFFT(chain_range) * rangeMul + (IFFT(chain_comp) * compMul);

inSigDelayed = DelayL.ar(

inSig,

0.1,

bufSize / s.sampleRate - (s.options.blockSize / s.sampleRate)

);

OffsetOut.ar(out, mix * sig + ((1 - mix) * inSigDelayed));

}).add;



t = topEnvironment.push;


a = Bus.audio(s, 2);

c = Bus.control(s, 2);


p = Pfindur(200, PbindFx([

\instrument, \posPlay,

\sndBuf, b,

\out, a,


\dur, 1 / PL(\trigRate),

\granDur, Pkey(\dur) * PL(\overlap),


\pos, c.subBus(0).asMap,

\posDev, c.subBus(1).asMap,


\rate, PL(\rate, envir: t),

\amp, PL(\amp),


\pan, PLseq([-1, 1]) * PL(\panMax),

[\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) },


\fxOrder, PL(\fxOrder, envir: t),

\cleanupDelay, 0.3

],[

\fx, \pv_binRangeBal,

\mix, PL(\mix_pv_binRangeBal),


\freqLo, PL(\freqLo_pv_binRangeBal, envir: t),

\freqHi, Pkey(\freqLo) + PL(\freqRange_pv_binRangeBal, envir: t),


// multipliers for spectral band and complement are given as tupel 'muls'

\muls, PL(\muls_pv_binRangeBal, envir: t),

\rangeMul, Pkey(\muls).collect(_[0]),

\compMul, Pkey(\muls).collect(_[1]),


\cleanupDelay, 0.2

    ]

));


v = VarGui([

\att, [1, 200, \lin, 0, 3],

    \rel, [1, 200, \lin, 0, 3],

     \absEnv, [0, 1, \lin, 1, 0],


    \overlap, [0.1, 2, \lin, 0, 1.45],

\trigRate, [1, 200, \lin, 0, 118.41],


\panMax, [0, 1, \lin, 0, 0.88],

    \amp, [0.0, 3, \lin, 0, 0.9],


\mix_pv_binRangeBal, [0, 1, \lin, 0, 1]

],[

\sndBuf, b.bufnum,

    \posLo, [0, 0.99, \lin, 0, 0.21],

   \posHi, [0, 0.99, \lin, 0, 0.34],

    \posDevE, [-6, 0, \lin, 1, -5],

    \posDevM, [0.0, 10, \lin, 0, 1.0],


    \posRateE, [-4, 2, \lin, 1, -2],

    \posRateM, [0.1, 10, \lin, 0, 2.278],

  \type, [0, 3, \lin, 1, 3],


\out, c.index

], stream: p, synth: \pos

);


w = (

varColorGroups: (0..7).clumps([7, 1]),

synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]),

tryColumnNum: 1,

labelWidth: 130,

sliderWidth: 350,

sliderHeight: 18,

playerHeight: 18

);

)



// running the patch: evaluate this, 

// then in gui start position synth and PbindFx stream by pressing both green buttons


// while running the patch check modified slider values and 

// consider pattern replacements as shown in part 1


(

Synth(\leakDC, [\out, 0, \in, a]);


~fxOrder = 1;


// double source grain in octave distance

~rate = [0.15, 0.3];


// pattern for lower bound of spectral band

~freqLo_pv_binRangeBal = Pn(Plazy {

var x = { exprand(200, 4000)  } ! rrand(2, 4);

Pseq(x, rrand(5, 15));

});


~freqRange_pv_binRangeBal = 2500;


// this is a bit more complicated

// polyrhythm for multipliers of band and complement

// it becomes more clear when applying trace to the following patterns


~zeroOneSeqTupleStream = PLshufn([

[[0, 1, 1], [0, 0, 1, 1]],

[[0, 1, 1], [0, 1, 1, 1]],

[[0, 0, 1], [0, 0, 1, 1]],

[[0, 0, 1], [0, 1, 1, 1]],

]).iter;


~muls_pv_binRangeBal = Pn(Plazy ({

var tuple = ~zeroOneSeqTupleStream.next;

if (0.5.coin) { tuple = tuple.reverse };

if (0.5.coin) {

tuple[0] = tuple[0].reverse;

tuple[1] = tuple[1].reverse;

};

Pfinval([500, 400, 300].choose, Ptuple([

PLseq(tuple[0]),

PLseq(tuple[1])

]))

}.inEnvir));


v.performWithEnvir(\gui, w)

)


// after stopping free leakDC synth


x.free



Part 5 – frequency shift with feedback


(

// frequency shift with feedback

// "post-mix" fx amplitude


// fx with feedback obviously needs envelope !


SynthDef(\freqShift, { |out = 0, in, mix = 1, freq = 0, fbAmp = 0,

granDur = 0.1, att = 0.01, rel = 0.01, amp = 0.1|

var inSig = In.ar(in, 2), sig, fb, env;

sig = inSig + (fbAmp * LocalIn.ar(2));

sig = Limiter.ar(sig);

sig = FreqShift.ar(sig, freq);

LocalOut.ar(sig);


sig = LPF.ar(sig, 10000);

// delayed attack is functioning as additional feedback control

env = EnvGen.ar(Env([0, 0, 1, 1, 0], [0.01, att, granDur - att - rel, rel]), doneAction: 2);

OffsetOut.ar(out, (mix * sig + ((1 - mix) * inSig)) * amp * env);

}).add;


t = topEnvironment.push;


a = Bus.audio(s, 2);

c = Bus.control(s, 2);


p = PbindFx([

\instrument, \posPlay,

\sndBuf, b,

\out, a,


\dur, 1 / PL(\trigRate),

\granDur, Pkey(\dur) * PL(\overlap),


\pos, c.subBus(0).asMap,

\posDev, c.subBus(1).asMap,


\rate, PL(\rate, envir: t),

\amp, PL(\amp),


\pan, PLseq([-1, 1]) * PL(\panMax),

[\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) },


// envelope data to be shared with fx

\do, Ptuple([Pkey(\granDur), Pkey(\att), Pkey(\rel)])

.collect { |x|  t[\share] =  x },


\fxOrder, PL(\fxOrder, envir: t),

\cleanupDelay, Pfunc { |ev|  max(0.2, ev[\granDur] - ev[\dur]) }

],[

\fx, \freqShift,

\mix, PL(\mix_freqShift),

\amp, PL(\amp_freqShift),

[\granDur, \att, \rel], Pfunc { t[\share] },

\granDur, Pkey(\granDur) * 0.9,

\freq, PL(\freq_freqShift, envir: t),

\fbAmp, PL(\fbAmp_freqShift, envir: t),


\cleanupDelay, 0.5

    ]

);



v = VarGui([

\att, [1, 200, \lin, 0, 3],

    \rel, [1, 200, \lin, 0, 7],

     \absEnv, [0, 1, \lin, 1, 1.0],


    \overlap, [0.05, 15, \lin, 0, 6.927],

\trigRate, [1, 200, \lin, 0, 24.88],


\panMax, [0, 1, \lin, 0, 0.84],

    \amp, [0.0, 1, \lin, 0, 0.06],


    \mix_freqShift, [0, 1, \lin, 0, 0.54],

    \amp_freqShift, [0.1, 2, \lin, 0, 0.3]

],[

\sndBuf, b.bufnum,

    \posLo, [0, 0.99, \lin, 0, 0.21],

   \posHi, [0, 0.99, \lin, 0, 0.34],

    \posDevE, [-6, 0, \lin, 1, -5],

    \posDevM, [0.0, 10, \lin, 0, 0.5],


    \posRateE, [-4, 2, \lin, 1, 0],

    \posRateM, [0.1, 10, \lin, 0, 1.486],

  \type, [0, 3, \lin, 1, 3],


\out, c.index

], stream: p, synth: \pos

);



w = (

varColorGroups: (0..8).clumps([7, 2]),

synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]),

tryColumnNum: 1,

labelWidth: 130,

sliderWidth: 350,

sliderHeight: 18,

playerHeight: 18

);

)



// running the patch: evaluate this, 

// then in gui start position synth and PbindFx stream by pressing both green buttons


// while running the patch check modified slider values and 

// consider pattern replacements as shown in part 1


(

Synth(\leakDC, [\out, 0, \in, a]);


~freq_freqShift = Pstutter(Pwhite(10, 50), Pwhite(-200, 50, 1));

~fbAmp_freqShift = Pstutter(Pstutter(Pwhite(2, 5), Pwhite(1, 2)), PLseq([1, -2]));

~fxOrder = [1];


~rate = Pn(Plazy {

Pseries(exprand(0.3, 0.7), rrand(0.02, 0.1), rrand(5, 20));

}) * 0.8;  


v.performWithEnvir(\gui, w)

)


// after stopping free leakDC synth


x.free



Part 6 – band pass


(

// band pass

// "post-mix" fx amplitude


SynthDef(\bpf, { |out = 0, in, freq = 440, rq = 0.1, amp = 1, mix = 1|

var sig, inSig = In.ar(in, 2);

sig = BPF.ar(inSig, freq, rq);

OffsetOut.ar(out, (mix * sig + ((1 - mix) * inSig)) * amp);

}).add;


// gain as fx for better control of balancing when sequencing grains with and without band pass


SynthDef(\gain, { |out = 0, in, amp = 1|

var inSig = In.ar(in, 2);

OffsetOut.ar(out, inSig * amp);

}).add;



t = topEnvironment.push;


a = Bus.audio(s, 2);

c = Bus.control(s, 2);


// duration defined by 'rate' with Pfinval

p = PbindFx([

\instrument, \posPlay,

\sndBuf, b,

\out, a,


\dur, 1 / PL(\trigRate),

\granDur, Pkey(\dur) * PL(\overlap),


\pos, c.subBus(0).asMap,

\posDev, c.subBus(1).asMap,


\rate, PL(\rate, 1, envir: t),

\amp, PL(\amp),


\pan, PLseq([-1, 1]) * PL(\panMax),

[\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) },


\fxOrder, PL(\fxOrder, envir: t),

\cleanupDelay, 0.3

],[

\fx, \gain,

\amp, PL(\amp_gain),


\cleanupDelay, 0.01

],[

\fx, \bpf,

\freq, PL(\freq_bpf, envir: t),

\rq, PL(\rq_bpf),

\mix, PL(\mix_bpf),

\amp, PL(\amp_bpf),


\cleanupDelay, 0.01

]

);


v = VarGui([

    \att, [1, 200, \lin, 0, 4.98],

    \rel, [1, 200, \lin, 0, 4.98],

     \absEnv, [0, 1, \lin, 1, 1.0],

     \overlap, [0.05, 15, \lin, 0, 6.7775],


\trigRate, [1, 200, \lin, 0, 176.12],

\panMax, [0, 1, \lin, 0, 0.83],

    \amp, [0.0, 3, \lin, 0, 0.75],


    \amp_gain, [0.0, 3, \lin, 0, 0.36],


    \rq_bpf, [0.01, 1, \lin, 0, 0.0397],

    \mix_bpf, [0, 1, \lin, 0, 0.91],

    \amp_bpf, [0, 1, \lin, 0, 2.7],

],[

\sndBuf, b.bufnum,

    \posLo, [0, 0.99, \lin, 0, 0.2574],

   \posHi, [0, 0.99, \lin, 0, 0.4059],

    \posDevE, [-6, 0, \lin, 1, -4],

    \posDevM, [0.0, 10, \lin, 0, 1.0],


    \posRateE, [-4, 2, \lin, 1, -1],

    \posRateM, [0.1, 10, \lin, 0, 0.892],

  \type, [0, 3, \lin, 1, 3],

\out, c.index

], stream: p, synth: \pos

);


w = (

varColorGroups: (0..10).clumps([8, 3]),

synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]),

tryColumnNum: 1,

labelWidth: 130,

sliderWidth: 350,

sliderHeight: 18,

playerHeight: 18

);

)



// running the patch: evaluate this, 

// then in gui start position synth and PbindFx stream by pressing both green buttons


// while running the patch check modified slider values and 

// consider pattern replacements as shown in part 1


(

Synth(\leakDC, [\out, 0, \in, a]);


// "octave arpeggio" bandpass frequencies, interleaved (PS with repeats = 1)

// check other relations, repetition numbers and fxOrder sequences


~freq_bpf = PLseq([

PS(Pstutter(3, Pseg(Pwhite(200, 7000), Pwhite(0.5, 2))) * PLseq([0.5, 1, 2]), 1),

PS(Pstutter(2, Pseg(Pwhite(200, 7000), Pwhite(0.5, 2))) * PLseq([0.5, 1, 2]), 1)

]);


// start with 1 ensures that we don't have filter only in single channel

~fxOrder = PLseq([1, 2, 2, 1]);


// development by ascending rate "chords"


~rate = Pseq([

Pfinval(5582,

Pstutter(

PLseq([120, 80, 80]),

Pexprand(0.3, 1.5).clump(PLshufn((1..3)) * 2 + 1)

).flatten

),

Pfinval(5582,

Pstutter(

PLseq([120, 80, 80]),

Pexprand(0.3, 0.6).clump(PLshufn((1..3)) * 2 + 1)

).flatten

),

Pfinval(15000,

Pstutter(

PLseq([120, 80, 80]),

Pexprand(0.1, 0.2).clump(PLshufn((1..3)) * 2 + 1)

).flatten

)

]);


v.performWithEnvir(\gui, w)

)


// after stopping free leakDC synth


x.free