Limitations of Composition in SC

Recently i have been reading “Laura Tripaldi - Parallel Minds”, a great book.
She is discussing some concepts of nanotechnology for synthesizing complex compound structures and describes complexity as beeing made of two things: “All definitions, however agree on the fact that there are two fundamental characteristics of a complex system: a multiplicity of constituent elements, and the presence of non-negligible interactions between them”
and points out that in “Newtonian physics, infact, although able to study systems of bodies interacting with one another, through gravitational or electrostatic force for example, can provide exact predictions only when that interactionis limited to a maximum of two objects” known as the “three body problem”
so complex compound structures are made out of at least three individual parts interacting with each other.

I have also been listening to this podcast series called objecthood and read a lot about sound as an object. For example this thesis.

Coming from these ideas my main interest is to create complex compound structures of sound objects and gradually transform their timbre over time.
For this to work im trying to come up with SynthDefs which have a lot of degrees of freedom in terms of timbral transformation.

For this piece i used two SynthDefs. This squeaky FM instrument and a pulsarish microsound instrument (i say pulsarish because the pulsar synthesis implementation was not 100% correct at the time ive written the instrument).

Im using this setup for composing (ive also shared it in another post), which enables me to do transitions using ~runTrans between two Patterns. ~runTrans is using an Envelope and sends it to a bus. Im normally using Pdefs with Pmono.

// functions for beeing used to make transitions and organize rhythm
(
~pParTracks = { |...tracks|
	var fxTracks = tracks.collect { PatternProxy(()) };
	var evPatternFns = tracks.collect { |trackArgs, n|
		var dataPatternName = trackArgs[0];
		var durs = 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
(
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,
		\freq, 440,
		\pan, -1,
	);
);

Pdef(\patternB,
	Pmono(\test,
		\trig, 1,
		\freq, 880,
		\pan, 1,
	);
);
)

// compose a piece inside Pspawner

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

		var partA, partB;

		\partA.postln;

		// play both Pdefs with a different rhythm pattern
		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 for \freq over 8 secs for both Pdefs
		~runTrans.(partA.fxs[0], (
			freq: Env([440, 220], 1, \exp),
		), 8);

		~runTrans.(partA.fxs[1], (
			freq: Env([880, 1760], 1, \exp),
		), 8);

		sp.wait(8);

		\partB.postln;

		// play both Pdefs with a different rhythm pattern
		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));

		sp.wait(2);

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

This is the actual composition. The SynthDefs, Patterns and utility functions are stored somewhere else in the project:

(
Pdef(\player,
	Pspawner { |sp|

		// Part I
		var glisson_I_sequence;
		var pulsar_ornament;
		var vowel_A;

		// Part II
		var glisson_liquid_fx;
		var glisson_Interlude;

		// Part III
		var glisson_III_sequence;

		// Part IV
		var vowel_bass_A;
		var vowel_bass_B;
		var glisson_high_glitch;

		// Part V
		var vowel_trans, pulsar_rhythm;

		///////////////////////////////////////////////////////////

		\part1.postln;

		~absArray = ~getAbs.(~blend.(5, 8, 1), 8);
		~resetL.(~absArray);

		~index = 4;

		glisson_I_sequence = ~pParTracks.(
			[\glisson_Sieve, 0.1875 * Pdict(~patDict, PL(\index)) ],
		);
		sp.par(Pfindur(3.6, glisson_I_sequence[0]));
		sp.wait(3.6);

		glisson_I_sequence = ~pParTracks.(
			[\glisson_I_sequence_A, Pseg([10, 5.1913], [9], \exp, 1).reciprocal ],
		);
		sp.par(Pfindur(9, glisson_I_sequence[0]));

		sp.wait(3);

		vowel_A = ~pParTracks.(
			[\vowel_A, Pseg([51.913, 5.1913], [6], \exp, 1).reciprocal ],
		);
		sp.par(Pfindur(5.9, vowel_A[0]));
		sp.wait(6);
		sp.wait(0.2);

		vowel_A = ~pParTracks.(
			[\vowel_A2,  Pseg([5.55386, 55.5386], [2/3], \exp, 1).reciprocal ],
		);
		sp.par(Pfindur(2/3, vowel_A[0]));
		sp.wait(2/3);
		sp.wait(1/3);

		pulsar_ornament = ~pParTracks.(
			[\ornament, 0.0625 * Pdict(~patDict, PL(\index)) ],
		);
		sp.par(Pfindur(1.0, pulsar_ornament[0]));
		sp.wait(0.5);

		///////////////////////////////////////////////////////////

		\part2.postln;

		~absArray = ~getAbs.(~blend.(5, 8, 1), 8);
		~resetL.(~absArray);

		~index = Pseq(~fibTrans.(12, [4, 0]), inf);

		glisson_I_sequence = ~pParTracks.(
			[\glisson_I_sequence_A, 0.0625 * Pdict(~patDict, PL(\index))],
		);
		sp.par(Pfindur(18, glisson_I_sequence[0]));

		sp.wait(6);

		vowel_A = ~pParTracks.(
			[\vowel_A, Pseg([51.913, 5.1913], [12], \exp, 2).reciprocal ],
		);
		sp.par(Pfindur(25, vowel_A[0]));

		sp.wait(6);

		~runTrans.(vowel_A[1][0], (
			tFreqMF: Env([1.0, 5.00], 1, \sqr),
			tFreqEnvAmount: Env([0.0, 5.00], 1, \sqr),
		), 6);

		sp.wait(13);

		~runTrans.(vowel_A[1][0], (
			tFreqEnvAmount: Env([5.0, 3.00], 1, \sqr),
		), 5);

		sp.wait(5.6);

		vowel_A = ~pParTracks.(
			[\vowel_A2, Pseg([5.55386, 55.5386], [2/3], \exp, 1).reciprocal ],
		);
		sp.par(Pfindur(2/3, vowel_A[0]));
		sp.wait(2/3);
		sp.wait(1/3);

		pulsar_ornament = ~pParTracks.(
			[\ornament, 0.0625 * Pdict(~patDict, 4)],
		);
		sp.par(Pfindur(0.8, pulsar_ornament[0]));
		sp.wait(0.5);

		///////////////////////////////////////////////////////////

		\part3.postln;

		~absArray = ~getAbs.(~blend.(5, 8, 1), 8);
		~resetL.(~absArray);

		~index = 4;

		glisson_liquid_fx = ~pParTracks.(
			[\glisson_liquid_fx, 0.0625 * Pdict(~patDict, PL(\index)) ]
		);
		sp.par(Pfindur(16.25, glisson_liquid_fx[0]));

		sp.wait(4.0);

		glisson_Interlude = ~pParTracks.(
			[\glisson_Interlude, 0.25 * Polybjorklund2([[7,16], [9,16]], _|_, inf, false)],
		);

		sp.par(Pfindur(1.80, glisson_Interlude[0]));
		sp.wait(7.0);

		glisson_Interlude = ~pParTracks.(
			[\glisson_Interlude, 0.25 * Polybjorklund2([[7,16], [9,16]], _|_, inf, false)],
		);

		sp.par(Pfindur(5.00, glisson_Interlude[0]));
		sp.wait(5.25);

		pulsar_ornament = ~pParTracks.(
			[\ornament, 0.0625 * Pdict(~patDict, PL(\index))],
		);
		sp.par(Pfindur(0.8, pulsar_ornament[0]));
		sp.wait(0.8);

		///////////////////////////////////////////////////////////

		\part4.postln;

		~absArray = ~getAbs.(~blend.(5, 8, 1), 8);
		~resetL.(~absArray);

		~index = 4;

		~envBufIndex = 43;

		glisson_III_sequence = ~pParTracks.(
			[\glisson_III_sequence_A, 0.25 * Polybjorklund2([[7,16], [9,16]], _|_, inf, false)],
		);
		sp.par(Pfindur(3.90, glisson_III_sequence[0]));
		sp.wait(4.00);

		glisson_III_sequence = ~pParTracks.(
			[\glisson_III_sequence_B, 0.1875 * Pdict(~patDict, PL(\index))],
		);
		sp.par(Pfindur(41.60, glisson_III_sequence[0]));

		sp.wait(12.60);

		~runTrans.(glisson_III_sequence[1][0], (
			fmFreq: Env([5.0, 4.0, 3.00], [0.5, 0.5], \sqr),
		), 6);

		sp.wait(6);

		~index = Pseq(~fibTrans.(12, [4, 3]), inf);

		sp.wait(23);

		glisson_III_sequence = ~pParTracks.(
			[\glisson_III_sequence_B, 0.1875 * Pdict(~patDict, PL(\index))],
		);

		sp.par(Pfindur(4.20, glisson_III_sequence[0]));

		~envBufIndex = Pseq(~fibTrans.(12, [43, 12]), inf);

		~runTrans.(glisson_III_sequence[1][0], (
			freqEnvAmount: Env([1000.0, 8190.0], 1, \exp),
			fmFreq: Env([3.0, 4.0, 5.00], [0.5, 0.5], \sqr),
			iEnvAmount: Env([5.0, 0.0], 1, \exp),
			overlap: Env([1.0, 0.1], 1, \exp),
		), 4.20);

		sp.wait(4.20);

		///////////////////////////////////////////////////////////

		\part5.postln;

		~absArray = ~getAbs.(~blend.(5, 8, 1), 8);
		~resetL.(~absArray);

		~index = 4;

		vowel_bass_A = ~pParTracks.(
			[\vowel_bass_A, Pseq([51.913], inf).reciprocal],
		);
		sp.par(Pfindur(30, vowel_bass_A[0]));

		glisson_high_glitch = ~pParTracks.(
			[\glisson_high_glitch, 0.1875 * Pdict(~patDict, PL(\index))],
			[\grains, Pseq([0.5], inf)],
		);
		sp.par(Pfindur(30, glisson_high_glitch[0]));

		sp.wait(6);

		\glisson_transition.postln;

		~runTrans.(glisson_high_glitch[1][0], (
			freq: Env([25.957, 103.826], 1, \sqr),
			overlap: Env([0.1, 0.5], 1, \sqr),
			fmFreq: Env([5.0, 3.0], 1, \sqr),
			index: Env([1, 0.125], 1, \sqr),
			iEnvAmount: Env([0.0, 5.0], 1, \sqr),
			clipEnvAmount: Env([0.0, 0.5], 1, \sqr),
		), 24);

		sp.wait(12);

		\bass_transition_A.postln;

		~runTrans.(vowel_bass_A[1][0], (
			formIndex: Env([0.0, 1.0], 1, \wel),
			lpfCutoff: Env([51.913, 500.0], 1, \exp),
			lpfEnvAmount: Env([0.0001, 1000.0], 1, \exp),
		), 12);

		sp.wait(12);
		sp.wait(0.001);

		\bass_transition_B.postln;

		vowel_bass_B = ~pParTracks.(
			[\vowel_bass_B, Pseq([51.913], inf).reciprocal],
		);
		sp.par(Pfindur(20, vowel_bass_B[0]));

		sp.wait(2);

		~runTrans.(vowel_bass_B[1][0], (
			peak: Env([0.0, 1.0], 1, \sqr),
			combDensity: Env([0.0, 1.0], 1, \sqr),
			combEnvAmount: Env([0.0, 2.0], 1, \sqr),
		), 18);

		sp.wait(18);
		sp.wait(0.1);

		///////////////////////////////////////////////////////////

		\part6.postln;

		vowel_trans = ~pParTracks.(
			[\vowel_B, Pseg([51.913, 5.1913, 51.913], [8, 9], \exp, inf).reciprocal ],
		);
		sp.par(Pfindur(17, vowel_trans[0]));

		sp.wait(8);

		~runTrans.(vowel_trans[1][0], (
			overlap: Env([0.50, 1.00], 1, \exp),
			formEnvAmount: Env([0.00, 1.00], 1, \lin),
		), 9);

		sp.wait(9);
		sp.wait(0.1);

		///////////////////////////////////////////////////////////

		\part7.postln;

		~absArray = ~getAbs.(~blend.(5, 8, 1), 8);
		~resetL.(~absArray);

		~index = 4;

		pulsar_rhythm = ~pParTracks.(
			[\rhythm_A, (0.0625 * Pdict(~patDict, PL(\index)))]
		);

		sp.par(Pfindur(36, pulsar_rhythm[0]));
		sp.wait(27);

		\rhythm_transition.postln;

		~runTrans.(pulsar_rhythm[1][0], (
			overlap: Env([0.50, 0.75], 1, \exp),
			lpfCutoff: Env([2000, 500], 1, \exp),
			burst: Env([5, 0], 1, \lin),
			rest: Env([0, 3], 1, \lin),
		), 9);

		sp.wait(8.3);

		///////////////////////////////////////////////////////////

		\part8.postln;

		~absArray = ~getAbs.(~blend.(5, 8, 1), 8);
		~resetL.(~absArray);

		~index = Pseq(~fibTrans.(15, [4, 2, 3, 1], 0, 1), inf);

		pulsar_rhythm = ~pParTracks.(
			[\rhythm_B, 0.0625 * Pdict(~patDict, PL(\index))],
		);

		sp.par(Pfindur(17, pulsar_rhythm[0]));

		sp.wait(17);
		sp.wait(0.3);

		pulsar_ornament = ~pParTracks.(
			[\ornament, 0.0625 * Pdict(~patDict, 4)],
		);
		sp.par(Pfindur(0.8, pulsar_ornament[0]));
		sp.wait(0.8);

		///////////////////////////////////////////////////////////

		\done.postln;
		sp.suspendAll;
	}
).play(t, quant:1);
)

1.) Complexity / Scalability
Most of the time in the piece you just have two planes of tone, there is only one moment, beginning at 1:53 min after the transformation of the Squeaky FM instrument which has three different instruments playing at the same time. I was trying to develop this moment beginning at 1:53 to transform it into the last part but gave up on that. Thats the reason why two of the instruments are abruptly ending at 2:23 min. There would have been some potential to transform some of these sounds to accompany the ending of the piece but I lost track of the timeline and gave up on that.
When you scroll trough the code of the actual composition you can see that its already quite long.
The piece is only 4 min and there is not so much going on at the same time.
So i think this approach is not scalable to larger pieces with more instruments at the same time.
I also think that keeping track of all the individual parts on the timeline in one Pspawner is not possible.
I have multichannel recorded the output of SC into Ableton live. When you see the actual music layed out in the DAW you see how difficult and laborious it is to work like that in SC, it looks kind of cute in the DAW :slight_smile:

The reason to use this ~pParTracks function is to separate rhythm from all the other SynthDef arguments and be able to access them individually for transitions using ~makeTrans.
When i first started using ~pParTracks i thought of putting several Patterns in there and make individual transitions for each of them, which is working theoretically but it turns out that in music not everything starts and stops at the same time. Therefore Im having parallel ~pParTracks on the timeline which start and stop at different moments determined by Pfindur and also their transitions which you all have to track somehow.

2.) Programming Skills
Ive started using SC about three years ago and spent every free minute I have with it. I have no background in programming. My skill level in programming and DSP increased rapidly in the last three years but the time im spending with this is crazy.
For some of the functions i have been using to create the piece i got a lot of help from members of the community. Especially that ~runTrans function.
I think the structure im using is not beginner level SC and is really hard for maintenance with my intermediate SC skillset.
Ive run into some problems lately with my setup and not able to solve these on my own. see that post.
Both of the SynthDefs i have been using for the composition use the hybrid approach of Impulse.ar for audiorate triggering and \tFreq, 1 / Pkey(\dur) which im describing in the post above.This leads to timing inaccuracy between language and server and has a negative sideeffect when using ~makeTrans. I often times had to adjust the time of Pfindur by some small amount to make sure the Synth Node is still on the server as long as the transition lasts.
Beside the issues described in the post one cannot transition keys which are defined in the pattern itself:

\myFreq, 440,
\freq, Pkey(\myFreq),

you cannot make a transition for myFreq because it cannot be send to a bus?! dont know.

But in general i think when you compare it with a DAW approach to use automations to gradually change parameters over time is really basic stuff. My point here is that i think the skill level you need to work on a composition in SC is really high and i think the rare appereance of music made in SC underlines this.
Ive started studying composition two years ago and got really stuck because of programming. There are so many issues i have to fix right now, just pointed out a few which are keeping me away from composing.

Simplify it?

One could make an argument for having a 30 min jam, record the audio to the DAW and chop that.
I think using SC as a “noise box” like that is not really helping out when wanting to make gradual transitions between states of musical objects and compose longer forms out of these.
Especially when these transitions are the core of the composition and not foreign objects which are just used to fill in the gaps between other music material. There are some transitions in the piece one would not be able to do with this approach IMO.

Ive just seen these two other examples lately which are using the idea of transition between events How to create a popup menu whose menu options are dynamic - #4 by jordan
and alga
It seems beside these two attempts nobody is really missing this feature in SC, im really curious why that is?

This was a lot. If you have any other questions feel free to ask :slight_smile:

4 Likes