L-System Dictionary Sublist Help

Hey,
Im quite new to Supercollider and trying to wrap my head around L-Systems.
The normal \A -> AB, \B -> A with Axiom of “A” is already working, but now I would like to implement a key modulation in my Program.
I think for this instance I have to create a Sublist inside the noteDictonary for \C, so “A” could be gradually replaced by “C” after looking up the List from A.
Can someone explain to me how these proto/parent variables ared used? Or am I on the wrong path?

(
    var dict = IdentityDictionary[
    	\A -> "AB",
    	\B -> "A",
    ];

    var word = "A";
    var string_temp = "";
    var iter = 10;

//noteDictionary
 var dictnotes = IdentityDictionary[
    \A -> [64, 71, 72, 67, 74],
    \B -> [71, 69, 76, 72, 78],
    \C -> [72, 78, 79, 88, 74],
    ];

var notes=[];

//Iteration
iter.do({

word.asArray.do({|i|
    string_temp = string_temp ++ dict[i.asSymbol];
});

word = string_temp;
string_temp = "";
});

word.postln;

word.do({|i| notes = notes ++ dictnotes[i.asSymbol];});

notes.postln;
)

thanks alot :slight_smile:

Hello and welcome to the forum!

IMO what you would like to do it’s not so clear, but maybe it’s just me not being familiar with L-system…

Do you want to substitute A chords with C chords, one A at a time? Or one note at a time, for all As at the same time?
It would be definitely clearer with a step by step example of what you want to achieve :slight_smile:

However, I would split your code in three functions: one to rewrite, one to get notes, and one to morph a chord into another (one note at a time, using a Routine).
Here is some untested code (sorry, I’m on a bus) that could work as an example of what I mean.

PS also make sure you check out existing work on L-systems in SC, I saw there’s already some stuff out there that could be interesting for you

~rewriteWord = {|word, rules, iter=10|
    iter.do{
        word = word.asArray.collect{|c|
            rules[c.asSymbol]
        }
    };
    word
};

~getNotes =  {|word, notesDict|
  word.asArray.collect{|c| notesDict[c.asSymbol] }
};

// now define rules
r = (A:"AB", B:"A");
~notesDict = (
    A: [64, 71, 72, 67, 74],
    B: [71, 69, 76, 72, 78],
    C: [72, 78, 79, 88, 74]
);
w = "A";
// 10 rewriting iteration
w = ~rewriteWord.(w,r,10);
n = ~getNotes.(w,~notesDict);

// substitute all A chords with C chords at once
~notesDict2 = ~notesDict.copy;
~notesDict2[\A] = ~notesDict2[\C];
n = ~getNotes.(w,~notesDict2);

// in this case you could also replace directly in the note array

// gradually morph A to C
// lets invent a simple strategy:
// first replace in random order, then resize if needed
~morphChord = {|a,b|
    var commonLen = min(a.size,b.size);
    var diffLen = b.size - a.size;
    Routine{
        // substitute
        (0..commonLen-1).scramble.do{|i|
            a[i] = b[i];
            a.yield;
        };
        // resize
       diffLen.abs.do{|i|
           if(diffLen > 0){
                a = a ++ b[commonLen+i]
           }{
                a.pop
           };
           a.yield;
       }
    }
};

g = ~morphChord.(~notesDict[\A],~notesDict[\C]);
~notesDict2 = ~notesDict.copy;

~notesDict2[\A] = g.next();
n = ~getNotes.(w,~notesDict2);

Hello,
many thanks for your help :slight_smile:
Im trying to make things more clear:

the basic example with
production Rules: \A -> AB, \B -> A
Axiom: “A”
Iteration count: 10

and these iteration steps:
0: A
1: AB
2: ABA
3: ABAAB
4: ABAABABA
5: tbc.

gives you an order of: ABAABABAABAABABAABABAABAABABAABAABABAABABAABAABABAABABAABAABABAABAABABAABABAABAABABAABAABABAABABAABAABABAABABAABAABABAABAABABAABABAABAABABAABABA

substituted with pitches from the dictionary you get the following linear sequence:
[ 64, 71, 72, 67, 74, 71, 69, 76, 72, 78, 64, 71, 72, 67, 74, 64, 71, 72, 67, 74, 71, 69, 76, 72, 78, 64, 71, 72, 67, 74, 71, 69, 76, 72, 78, 64, 71, 72, 67, 74, 64, 71, 72, 67, 74, 71, 69, 76, 72, 78, 64, 71, 72, 67, 74, 64, 71, 72, 67, 74, 71, 69, 76, 72, 78, 64, 71, 72, 67, 74, 71, 69, 76, 72, 78, 64, 71, 72, 67, 74, 64, 71, 72, 67, 74, 71, 69, 76, 72, 78, 64, 71, 72, 67, 74, 71, 69, 76, 72, 78, 64, 71, 72, 67, 74, 64, 71, 72, 67, 74, 71, 69, 76, 72, 78, 64, 71, 72, 67, 74, 64, 71, 72, 67, 74, 71, 69, 76…etc…

i would like to archieve an order of:
ABAABABAABACBABAABABCABAABABCABACBABACBABCABCCBABCABCBACBCABCBCABCCBCBCCB
where in the end all of the “A” are replaced by “C”.

For this fibonacci-based transition i think you need a sublist in the noteDictionary for “C”, so the Mainlist “A” is called at the beginning of the algorithm and at the end only the Sublist “C”.

Ok it’s getting clearer

The sublist is there since the beginning right?

Isn’t the question more like how to carry on the transition? It’s still not clear what you mean when you mention a “fibonacci based transition”… but anyway I think your process could look like this:

  1. Get a word from axiom and rules
  2. Substitute As with Cs through the word the way you like
  3. Get the notes

About point 2, a simple starting point could be this

// find all As indices in word
a = word.asArray.findAll('A');
a.do{|i,n|
 // increase probability of substitution as we go
 var prob = (n/a.size).sqrt;
 // substitute
 if(prob.coin){word[i]='C'}
}

Note: the sqrt there is meant to give higher probability to Cs before the end of the string…
You could also use linlin to make sure, for example, that Cs are selected every time after half the As showed up:

var prob = (n/a.size).linlin(0,0.5,0,1).sqrt

hey,

thanks a lot for your help.

“\C” is currently not a sublist. it is a list but on the same hierarchy level with “\A” and “\B” in the dictionary. thats the problem. i think it just has to be a sublist of “\A” and then its working.
how can I do this?

I need a bit of time to understand your additional code examples.

In my examples you can keep it on the same level as the others.
In general, if you want to make a multi-dimensional list, it is like this:

\A-> [ [ 1,2,3 ], [ 4,5,6 ] ] 

Now you would have dictnotes[\A][0] and dictnotes[\A][1]

this multidimensional Array gives me chords

and ufortunately i found another mistake.

with:
Productionrules \A -> “AB” and \B -> “A”, Axiom: A
and Pitch Mapping like this:
\A -> [1,2,3],
\B -> [4,5,6],

you always get the same order:
[ 1, 2, 3, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, 3, 1, 2, 3, 4, 5, 6 …etc…]

i would like to have the following:
when A is called its picking the first item in the Array not always sequencing the entire Array before moving on to B.
so when A is called it should pick the first item, when B is then called it should pick its first item, when now A is called again it should pick the second item …etc… otherwise you always get the same sequences in different chains but not a mutated sequence.

do you have a solution for this?

Yes, using a stream, in this case you can create one easily with Pseq:

http://doc.sccode.org/Classes/Pseq.html

a = Pseq([1,2,3],inf).asStream
a.next // 1
a.next // 2
a.next // 3
a.next // 1
// and so on

This is because you do this:

word.do({|i| notes = notes ++ dictnotes[i.asSymbol];});

This ++ is concatenating the previous selected notes with the whole content of dictnotes[i.asSymbol]. Here is where you need to implement a mechanism for choosing which sublist you want to use.

And if you are using a stream, like the one from Pseq, you will also need to call next on it!

thank you for being so patient with me. this has helped me alot :slight_smile:

1 Like

This ++ is concatenating the previous selected notes with the whole content of dictnotes[i.asSymbol] . Here is where you need to implement a mechanism for choosing which sublist you want to use.

And if you are using a stream, like the one from Pseq, you will also need to call next on it!

okay i think im not sure where to start then.
where should i call next? i was trying in the Pbind which plays the synth inside the routine like in the Pseq HelpFile. But this is not working. there is also something weird happening with 16.wait;
sometimes multiple notes are displayed at once in the post window.
the whole topic is a bit too advanced for me right now but its really importing for me to solve it. i hope you can help me.

(
var dict = IdentityDictionary[
\A -> “AB”,
\B -> “A”,
]; //production rules

var word = “A”; //Axiom word
var string_temp = “”;
var iter = 10;

//Dictionary for Pitches

var dictnotes = IdentityDictionary[
\A -> Pseq([64, 71, 72, 67, 74],inf).asStream,
\B -> Pseq([71, 69, 76, 72, 78],inf).asStream,
];

~notes=[];

//This iteration generates the system recursively

iter.do({

word.asArray.do({
arg i;
string_temp = string_temp ++ dict[i.asSymbol];
});

word = string_temp;
string_temp = “”;
});

word.postln;

//Here we map the final system to the parameters as above

word.do({
arg i;
~notes = ~notes ++ dictnotes[i.asSymbol];}
);

~notes.postln;

x = Synth.new(\dpo_1, [\amp, 0.5, \out, ~bus[\reverb]], ~mainGrp);

r=Routine({
~notes.do({
arg item, i;
var d = 8/(2**(i/2).round);
Pbind(
\type, \midi,
\dur, 1,
//\dur, d,
\midicmd, \noteOn,
\midiout, m,
\chan, 0,
\midinote, Pn(Plazy { Pseq(~notes.next) }).trace,
\amp, 0.5,
\sustain, 0,
\group, ~mainGrp,
\out, ~bus[\reverb]).play;
d.postln;
16.wait;
});
});
r.play;
)

You want to call next when translating from a symbol to a note:

word.do({
arg i;
~notes = ~notes ++ dictnotes[i.asSymbol].next;}
);

I checked the first examples I posted, and they had a major error: it’s not possible to call collect on Strings if you don’t return only single characters. @dietcv don’t worry about this for now :slight_smile:

Here is a corrected version that works, take a look at it. I insist on writing named functions because makes code so much clearer :slight_smile:

// first we define some functions
~rewriteWord = {|word, rules, iter=10|
    iter.do{
		word = Array.newFrom(word)
		.collect{|c| rules[c.asSymbol]}
		.join;
    };
    word
};

~getNotes =  {|word, notesDict|
  // streams are created inside this function
  // so every time we call ~getNotes, streams are resetted
  var streams = notesDict.collect{|seq|Pseq(seq,inf).asStream};
  Array.newFrom(word).collect{|c| streams[c.asSymbol].next }
};

~replace = {|word,from,to,start=0.1,end=0.6|
	// find all As indices in word
	var found = word.asArray.findAll(from);
	var newWord = Array.newFrom(word);
	found.do{|i,n|
		// increase probability of substitution as we go
		var prob = (n/found.size).linlin(start,end,0,1).sqrt;
		// replace
		if(prob.coin){newWord[i]=to}
	};
	newWord.join
};


// now define rules
r = (A:"AB", B:"A");
// define notes for each symbol
// NOTE: not using Pseqs here
~notesDict = (
    A: [64, 71, 72, 67, 74],
    B: [71, 69, 76, 72, 78],
    C: [72, 78, 79, 88, 74]
);

// Here we go:

w = "A";
// 10 rewriting iteration
w = ~rewriteWord.(w,r,10);
// getting notes
n = ~getNotes.(w,~notesDict);

// replace As with Cs.
// start:0.3 = Leave the first third of As unchanged
// end:0.6 = The last third of As will be replaced for sure
// Try to execute this repeatedly to see differences
~replace.(w,"A","C",start:0.3,end:0.6)
// getting replaced notes
n = ~getNotes.(w,~notesDict);

// now you can play your pattern

thank you so much for your effort building this bunch of beautiful and well structured code. will take my time to digest it. i really appreciate that.
i just tried it out for the AB example without the Cs and its working perfectly fine.

when it comes to the Pbind i would like to use the index of an iteration to have some time based dependency. so the whole pattern increases its speed with iteration counts. like this:

 r=Routine({
	n.collect({
		arg item, i;
		var d = 8/(2**(i/2).round);
		Pbind(\instrument, \synth,
			\dur, d,
			\midinote, Pn(Plazy { Pseq(n) },inf).trace,
			\atk, 0.01,
			\sus, 0.1,
			\rel, 1,
			\amp, 0.5
		).play;
		d.postln;
16.wait;

	});
});
r.play;

but the notes get stuck in the procedure and after some counts its not a linear sequence any more and i think the already sequenced notes are played again with an increase while iterating.
i also tried to sequence the item, then i get the error “ListPattern (Pseq) requires a non-empty collection; received 71”. i looked up this error and used Plazy for this reason. but it was not helping. when the wait time is decreased its playing all notes really fast not depending on the duration time. or is there maybe another way around it?
was also trying s.sync with .play(AppClock) but this isnt helping either.

In general i find it really difficult after investigating in a bunch of tutorials on SynthDefs, Patterns etc. to make a more sophisticated algorithmic composition. but im just starting. so thanks again for your help.

You can use collectAs to use one type of collection as the source, and return a different type: string.collectAs({ ... might return anything, not only characters... }, Array).

hjh

1 Like