Sequencing static compositions - too much code

Hey forum - I’ve been writing a writing a number of pieces using Pspawner. I have a pretty ignorant approach to using this class, and despite really liking the results of what I’m writing, I’m finding that managing the amount of code when actually writing out a full composition is surprisingly difficult. Currently, I treat it as a big, linear ‘mono’ routine with a lot of .par() + findur() methods, and giving each section a length (see below)

I’ve also tried splitting out sections and patterns into sub-patterns but IMO it has worse downsides: It is significantly more difficult to manage overlapping sections and transitions, and scrolling to where the pattern definitions are located is annoying.

I’ve actually switched to the vscode extension specifically to manage dealing with this amount of code by collapsing arbitary sections, but still finding it tedious to manage. Is there a better way?

Example piece (this uses a bunch of my own functions, as well as a couple of methods and synthdefs I’ve found off the forums):

(
t = TempoClock.new(180/60).permanent_(true).schedAbs(0, {t.beatsPerBar_(4)});
Pdef(\player,
    Pspawner({| sp |
        var sectionLength = 72;

        //fx const
        sp.par(
            Pbindf(
                Pdef(\miVerb),
                \time, 0.8,
                \damp, 0.6,
                \hp, 0.0,
                \freeze, 0,
                \diff, 0.9,
                \gain, -18,
                \out, ~mainout
            )
        );

        //pattern const
        Pdef(\mod, 
            Pmono(\west,
                \dec, Pkey(\dur) * 1,
                \freq, 30,
                \gate, 1,
                \glide, 0.01, 
                 
                \fm1Ratio, 4, 
                \fm2Ratio, 3,
                \fm1Amount, 0.1, 
                \fm2Amount, 0.1,
                
                \vel, 0.5, 
                \pressure, ~pmodenv.(Pkey(\groupdelta) * 5, Pkey(\dec)),
                \timbre, ~pmodenv.(Pwhite(0, 1), Pkey(\dec)),
                \waveShape, 0.25, 
                \waveFolds, ~pmodenv.(1 - Pkey(\groupdelta), Pkey(\dec)), 
                \envType, ~pmodenv.(Pseq([0, 1], inf), 4), 
                \peak, ~pmodenv.(Pwhite(250.0, 15000.0), Pkey(\dec)),
                \decay, Pwhite(1, 2),
                \pan, Pbrown(-0.5,0.5,0.001),
                \amp, 0.5,
                \lfoShape, 0,
                \lfoFreq, Pkey(\dur),
                \lfoToWaveShapeAmount, ~pmodenv.((1 - Pkey(\groupdelta)) * 0.25, Pkey(\dec)),
                \lfoToWaveFoldsAmount, ~pmodenv.(Pkey(\groupdelta) * 1, Pkey(\dec)),
                \lfoToReverbMixAmount, Pwhite(), 
                \drift, ~pmodenv.(Pwhite(0, 0.1), Pkey(\dec))
            )
        );
    
        Pdef(\fb1mod,
            Pbind(
                \amp, 0.7,
                \dec, Pkey(\dur) * 2,
                \feedback, ~pmodenv.(Pseq([0.2, 0.4, 0.8] * 0.5,inf), Pkey(\dec)),
                \time, ~pmodenv.(Pseq(([0.005, 0.001, 0.010] * 100),inf), Pkey(\dec)),
                \damp, ~pmodenv.(Pseq([0.1, 1],inf), Pkey(\dec)),
                \exciter, ~pmodenv.(Pwhite(1, 0, inf).lincurve(0, 1, 0, 1, -8), Pkey(\dec), 1, Pseq([\sine],inf)),
                \impulse, ~pmodenv.(Pwhite(20000, 200,inf), Pkey(\dec)),
                \spont, ~pmodenv.(Pseq([60,1000],inf), Pkey(\dec)),
                \boost,  ~pmodenv.(Pseq([20000,200],inf), Pkey(\dec)),
                \restore, 5,
                \dist,  ~pmodenv.(Pseq([16, 32],inf), Pkey(\dec)),
                \rev, ~pmodenv.(Pexprand(0.1, 4, inf), Pkey(\dec)),
                \pan, ~pmodenv.(Pwhite(-0.3, 0.3, inf), Pkey(\dec)),
            )
        );
    
        Pdef(\kickMod,
            Pbind(
                // \dur, 1,
                \freq, 40.0,
                \atk, 0.01,
                \dec, Pkey(\groupcount).wrap(1, 2).linlin(1, 2, 0.2, 0.5),
                \fb, Pkey(\eventcount) * 0.5,
                \index, 1.0,
                \ratio, 1.5,
                \drive, 5.0,
                \sweep, 32.0,
                \spread, 2,
                \lofreq, 500.0,
                \lodb, 10.0,
                \midfreq, 1200.0,
                \middb, -20.0,
                \hifreq, 7000.0,
                \hidb, 30.0,
                \gain, -15.0,
                \pan, 0.0,
                \noise, 0.5,
                \amp, Pkey(\groupcount).wrap(1, 2).linlin(1,2, 1, 0.7, 1),
            )
        );

        ////////////////////////////////////////
        \a.postln;      
        Pdef(\p1,
            ~makeSubdivision.(
                PlaceAll([2.5, 1.5, 1.5, Rest(1)], inf),
                PlaceAll([[8, 3], 4, 2, 1, 4], inf)
            )
        );
        
        sp.par(
            Pdef(\west,
                Pdef(\mod) <>
                ~pSkew.(Pdef(\p1), key: Pkey(\eventcount), group: [1, 2, 5], skew: [2, -1], curve: \exp) <> 
                Pdef(\p1)
                <> Pbind(
                    \instrument, \west,
                    \out, [~convolve_B]
                )
            ).finDur(sectionLength)
        );
    
        sp.par(
            Pbindf(
                Pdef(\seq3,
                    Pdef(\fb1mod) 
                    <> ~pSkew.(Pdef(\p1), key: Pkey(\eventcount), group: [1, 2, 5], skew: [2, -1], curve: \exp)
                    <> Pdef(\p1)
                    <> Pbind(\instrument, \fb1)
                ),
                \out, [~convolve_A, ~miVerb]
            ).finDur(sectionLength)
        );
        
        sp.par(
            Pmono(\fftStretch_magAbove_mono,
                \amp, 1,
                \gain, 0,
                \buf, ~specBuff2.at(\file),
                \analysis, [~specBuff2.at(\analysis)],
                \fftSize, ~specBuff2.at(\fftSize),
                \rate, 0.1,
                \pos, 0.3,
                // \pos, 0.8,
                \len, 0.1,
                \filter, ~pmodenv.(Pseq([1, 4],inf), Pseq([4, 8, 4], inf), curve: \sine),
                \out, [~convolve_B]
            ).finDur(sectionLength)
        );
    
        sp.par(
            Pbindf(
                Pmono(\pulsar_mono),
                \triggerRate, 16,
                \fluxMF, 1.5,
                \fluxMD, 0,
                \grainFreq, 15,
                \overlap, 0.5,
                \pmRatio, 100,
                \pmIndex, 0.5,
                \density, 0.1,
                \polarityMod, 0,
                \out, [~convolve_B],
                \lag, 8
            ).finDur(sectionLength)
        );
    
        sp.par(
            Pbind(\amp, 1, \freq, 30)
            <>~filterBeat.(key: Pkey(\eventcount), beat:[1], mod:4)
            <> ~filterBeat.(key: Pkey(\groupcount), beat:[4], mod: 2)
            <>Pdef(\p1)
            <>Pbind(\instrument, \simpleSub)
        );
    
        sp.par(
            Pbindf(
                Pdef(\morph),
                \gain, -6,
                \atk, 0,
                \rel, 100,
                // \swap, ~pmodenv.(Pseq([0, 1],inf), Pseq([1/2], inf), 1, \sin),
                // \swap, ~pmodenv.(Pseq([0, 1],inf), Pseq([1/3], inf), 1, \sin),
                \swap, ~pmodenv.(Pseq([0, 1],inf), Pseq([4], inf), 1, \sin),
                // \swap, 0,
                // \swap, 1,
                \out, [~mainout, ~miverb]
            ).finDur(sectionLength)
        );
    
        sp.wait(sectionLength);
        
        //////////////////////////////////////////
        \b.postln;
    
        sectionLength = 100;      
        Pdef(\p1,
            ~makeSubdivision.(
                PlaceAll([2.5, 1.5, 1.5, Rest(1)], inf),
                PlaceAll([[8, 3], 4, 2, 1, 4], inf)
            )
        );
        
        sp.par(
            Pdef(\west,
                Pbindf(Pdef(\mod), \pitchBendRatio, Pwhite(0.5, 2)) <>
                ~filterBeat.(key: Pkey(\eventcount), beat:[1, 2, 4]) <>
                ~pSkew.(Pdef(\p1), key: Pkey(\eventcount), group: [1, 2, 5], skew: [2, -1], curve: \exp) <> 
                Pdef(\p1)
                <> Pbind(
                    \instrument, \west,
                    \out, [~convolve_B]
                )
            ).finDur(sectionLength)
        );
    
        sp.par(
            Pbindf(
                Pdef(\seq3,
                    Pdef(\fb1mod) 
                    <> ~pSkew.(Pdef(\p1), key: Pkey(\eventcount), group: [1, 2, 5], skew: [2, -1], curve: \exp)
                    <> Pdef(\p1)
                    <> Pbind(\instrument, \fb1)
                ),
                \out, [~convolve_A, ~miVerb]
            ).finDur(sectionLength)
        );
        
        //fade out last section pad
        sp.par(
            PfadeOut(
            Pmono(\fftStretch_magAbove_mono,
                \amp, 1,
                \gain, 0,
                \buf, ~specBuff2.at(\file),
                \analysis, [~specBuff2.at(\analysis)],
                \fftSize, ~specBuff2.at(\fftSize),
                \rate, 0.1,
                \pos, 0.3,
                // \pos, 0.8,
                \len, 0.1,
                \filter, ~pmodenv.(Pseq([1, 4],inf), Pseq([4, 8, 4], inf), curve: \sine),
                \out, [~convolve_B]
            ).finDur(sectionLength)
            , 16)
        );

        sp.par(
            PfadeIn(
            Pmono(\fftStretch_magAbove_mono,
                \amp, 1,
                \gain, 0,
                \buf, ~specBuff.at(\file),
                \analysis, [~specBuff.at(\analysis)],
                \fftSize, ~specBuff.at(\fftSize),
                \rate, 1,
                \pos, 0.3,
                // \pos, 0.8,
                \len, 0.1,
                \filter, ~pmodenv.(Pseq([1, 4],inf), Pseq([4, 8, 4], inf), curve: \sine),
                \out, [~convolve_B, ~miverb]
            ).finDur(sectionLength)
            , 12)
        );
    
        sp.par(
            Pbindf(
                Pmono(\pulsar_mono),
                \triggerRate, 16,
                \fluxMF, 1.5,
                \fluxMD, 0,
                \grainFreq, 15,
                \overlap, 0.5,
                \pmRatio, 100,
                \pmIndex, 0.5,
                \density, 0.9,
                \polarityMod, 0,
                \out, [~convolve_B],
                \lag, 4
            ).finDur(sectionLength)
        );
    
        Pdef(\kickP,
            ~makeSubdivision.(
                PlaceAll([0.75, 1.5, 0.25, 2, 1.5, 2], inf),
                PlaceAll([4, 1, 1, 2], inf)
            )        
        );
        
        sp.par(
            Pdef(\kickMod)
            <> ~pSkew.(Pdef(\kickP), key: Pkey(\eventcount), group: [1,3,4], skew: [1], curve: \exp)
            <> Pdef(\kickP)
            <>Pbind(
                \instrument, \fmKick2,
                \out, [~convolve_B, ~grain]
            ).finDur(sectionLength)
        );
    
        sp.par(
            Pbindf(
                Pdef(\morph),
                \gain, -6,
                \atk, 0,
                \rel, 100,
                \swap, ~pmodenv.(Pseq([0, 1],inf), Pseq([4], inf), 1, \sin),
                \out, [~mainout, ~miverb]
            ).finDur(sectionLength)
        );
        
        sp.wait(sectionLength);
        
        //////////////////////////////////////////
        \c.postln; 
        \etc......
})
).play(t);
)

I have once used this setup, where you define a function ~pParTracks which is based on Ppar to be able to separate the duration for each pattern from its other keys. Which enables you to make individual transitions of these keys for each pattern using the ~runTrans function inside Pspawner. I also had the same problem with a lot of code for quite short pieces of music.

(
~getDurPattern = {|durs|
	case
	{ durs.isKindOf(SequenceableCollection) } {
		if (durs.rank > 1) {
			"pParTracks: flattening nested duration list %".format(durs).warn;
			durs = durs.flat.postln;
		};
		Pseq(durs, inf)
	}
	{ durs.isKindOf(Function) } { Pn(Plazy(durs), inf) }
	{ durs.isKindOf(Pattern) } { durs }
	{ Error("pParTracks: unsupported durations format: %".format(durs)).throw };
};

~pParTracks = {|...tracks|
	var fxTracks = tracks.collect { PatternProxy(()) };
	var evPatternFns = tracks.collect { |trackArgs, n|
		var dataPatternName = trackArgs[0];
		var durs = ~getDurPattern.value(trackArgs[1]);
		fxTracks[n] <> Pdef(dataPatternName) <> Pbind(\dur, durs)
	};
	var rhythmPars = Ppar(evPatternFns);
	(\rhythm: rhythmPars, \fxs: fxTracks);
};

~mapTrans = {|parEnvs, transDur= 1|
    var penvs = parEnvs.select{|v|v.class===Penv}.collect{|penv|
        penv.times = penv.times*transDur
    };
    var busses = parEnvs
    .select{|v,k| penvs.keys.includes(k).not}.collect{Bus.control(s,1)};

    {
        busses.collect{|bus, parName|
            Out.kr(bus, EnvGen.kr(parEnvs[parName],timeScale:transDur));
        };
        Line.kr(0,1, transDur, doneAction:2);
        Silent.ar;
    }.play.onFree{
        busses do: _.free
    };

    busses.collect(_.asMap) ++ penvs
};

~runTrans = {|transProxy, transDef, transDur|
	transProxy.source = Pbind(*~mapTrans.(transDef, transDur).asKeyValuePairs);
};
)

// test SynthDef for beeing played with Pmono
(
SynthDef(\test, {
	var trig = \trig.tr(0);
	var gainEnv = EnvGen.ar(Env.perc(\atk.kr(0.01), \rel.kr(0.5)), trig, doneAction: Done.none);
	var sig = SinOsc.ar(\freq.kr(440));
	sig = sig * gainEnv * \amp.kr(0.25);
	sig = Pan2.ar(sig, \pan.kr(0));
	Out.ar(\out.kr(0), sig);
}).add;
)

// Patterns

(
Pdef(\patternA,
	Pmono(\test,
		\trig, 1,
		\lowFreq, 440,
		\freq, Pfunc {|ev| ev[\lowFreq] },
		\pan, -1,
	);
);

Pdef(\patternB,
	Pmono(\test,
		\trig, 1,
		\lowFreq, 880,
		\freq, Pfunc {|ev| ev[\lowFreq] },
		\pan, 1,
	);
);
)

// compose a piece inside Pspawner

(
Pdef(\player,
	Pspawner({ |sp|

		\partA.postln;

		// play Pdef(\patternA) and Pdef(\patternB) with their sequence of durations
		~partA = ~pParTracks.(
			[\patternA, 0.25 * Pseq([3, 1, 2, 2], inf)],
			[\patternB, 0.25 * Pseq([2, 1, 2, 1, 2], inf)]
		);
		sp.par(Pfindur(8, ~partA.rhythm));

		// make individual transitions over 8 sec for Pdef(\patternA) with Env
		// pattern keys like legato must be transitioned via Penv (makes no sense here with Pmono though)
		// keys which have been defined in the pattern itself: \lowFreq cant be transitioned / sent to a bus
		~runTrans.(~partA.fxs[0], (
			freq: Env([440, 220], 1, \exp),
			pan: Env([-1.0, 0.0, 1.0], [0.5, 0.5], \lin),
			// lowFreq: Penv([440, 220], 1, \exp),
			// legato: Penv([1, 0.001], 1, \lin),
		), 8);

		// make individual  transitions over 8 sec for Pdef(\patternB) with Env
		~runTrans.(~partA.fxs[1], (
			freq: Env([880, 1760], 1, \exp),
			pan: Env([1.0, 0.0, -1.0], [0.5, 0.5], \lin),
			// lowFreq: Penv([880, 1760], 1, \exp),
			// legato: Penv([1, 0.001], 1, \lin),
		), 8);

		sp.wait(8);

		\partB.postln;

		// play Pdef(\patternA) and Pdef(\patternB) with a new sequence of durations
		~partB = ~pParTracks.(
			[\patternA, 0.25 * Pseq([3, 3, 2], inf)],
			[\patternB, 0.25 * Pseq([2, 1, 3, 2], inf)]
		);
		sp.par(Pfindur(2, ~partB.rhythm));

		// transition for both patterns over 2 secs
		~partB.fxs[0..1].do {|channel|
			~runTrans.(channel, (
				amp: Env([0.25, 0], 1, \lin),
			), 2)
		};

		sp.wait(2);

		\done.postln;
		sp.wait(2);
		sp.suspendAll;

	});
).play;
)
2 Likes

Yes, I studied that example you posted a while ago! I am using my own transition functions that are a little bit simpler. Do you use a different setup these days?

I have tested different approaches, but havent found a really good solution yet.

1 Like

One suggestion is to strictly separate Pbind / Pdef definitions and the Pspawner flow-of-control code, e.g. your main file might look like:

(thisProcess.nowExecutingPath.dirname +/+ "section1defs.scd").load;

(thisProcess.nowExecutingPath.dirname +/+ "section2defs.scd").load;

...

p = Pspawner({
    ...
});

… where the Pspawner just refers to resources by name.

It doesn’t solve the problem of transitions, but it does mean you have a zoomed-out view of the form in the Pspawner, and you can zoom in to detail in the auxiliary files.

hjh

1 Like