Pmono record MIDI to DAW

hey,
yet another question about Pmono…

short: how do one use Pmono and MIDI?

long:
For composing with patterns ive exchanged everything based on Pbind with Pmono lately because most of the SynthDefs im writing have continous LFOs which should not be retriggered on every new note event and it gives me the additional opportunity to modulate the triggers in the SynthDef and it is also a better way to transform functions to SynthDefs in the process of writing instruments in SC imo. Im doing multichannel expansion with Ppar and Pmono via Patterns and chaining all the different parameters with Pdefn and using external Control Envelopes or LFOs to modulate some of the parameters + additional Fx Synths using PbindFx from https://github.com/dkmayer/miSCellaneous_lib with a custom function for data sharing between source and fx which all in all works quite well.

like this for example:

(
Pdefn(\durs, Pbind(\dur, [0.5, 0.667]));
Pdefn(\notes, Pbind(\freq, [68, 68]));
Pdefn(\slopes, Pbind(\slope, [-5, -15]));
Pdefn(\fltRanges, Pbind(\fltRange, [4000, 8000]));
Pdefn(\inharmonics, Pbind(\inharmonic, [0.05, 0.05]));

//Pdefn(\sustains, Pbind(\sustain, Pfunc { |ev| ev[\gainEnv].duration }));
Pdefn(\times, Pbind(\time, Pfunc { |ev| ev.use { ~sustain.value } / thisThread.clock.tempo }));

Pdef(\gabor,
	Ppar({|i|
		Pmono(\gabor,

			\trig, Pwhite(0.5, 1.0),
			\dur, Pkey(\dur).collect(_[i]),
			\freq, Pkey(\freq).collect(_[i]),
			\inharmonic, Pkey(\inharmonic).collect(_[i]),

			\atk, 0.01,
			\rel, 0.5,
			\gainEnv, Pfunc{|e|
				Env([0,0,1,0], [0, e.atk, e.rel], \lin)
			},
			\sustain, Pfunc { |ev| ev[\gainEnv].duration },
			\time, Pkey(\time),

			// Bandpass Filter
			\slope, Pkey(\slope).collect(_[i]),
			\fltRange, Pkey(\fltRange).collect(_[i]),
			\cutoff, ~bus[\ctrl][i].asMap,

			\fAtk, 0.5,
			\fRel, 0.01,
			\freqEnv, Pfunc{|e|
				Env([0,1,2,1],[0, 0.5, 0.01], \lin)
			},

			\amp, 0.30,
			\out, i,
			\group, ~mainGrp,
			\cleanupDelay, Pkey(\sustain),
			\fxOrder, [1]
		)
	} !2) <> Pdefn(\notes) <> Pdefn(\durs) <> Pdefn(\fltRanges) <> Pdefn(\slopes) <> Pdefn(\times) <> Pdefn(\inharmonics);
);

Pdef(\comb_fx,
	Ppar({|i|
		Pbind(
			\fx, \combL,
			\mix, 0.5,
			\amp, 1,
			\delStereoRatio, 0.9,
			\delHz, 0.05,
			\delMin, 0.1,
			\delMax, Pkey(\delMin) + 0.01,
			\decay, 0.25,
			\cleanupDelay, Pkey(\decay)
		)
	} !2)
);

Pdef(\gabor_fx, ~pbindFx.(\gabor, \comb_fx));
)

///////////Control Envelope///////////

(
Pdef(\ctrlEnv,
	Ppar({|i|
		Pmono(\ctrlEnvMono,

			\trig, Pwhite(0.5, 1.0),
			\dur, Pkey(\dur).collect(_[i]),

			\ctrlEnv, Pfunc{|e|
				Env([0.0001, 0.0125, 2, 0.0125], [0.0001, 0.3, 0.2], \exp)
			},

			\sustain, Pfunc { |ev| ev[\ctrlEnv].duration },
			\time, Pkey(\time),

			\out, ~bus[\ctrl][i],
			\group, ~ctrlGrp,
		)
	} !2) <> Pdefn(\durs) <> Pdefn(\times);
);
)

(
Pdef(\gabor_ctrl,
	Ptpar([
		0, Pdef(\gabor),
		0.0001, Pdef(\ctrlEnv)
	])
).play(t, quant:1);
)

Pdef(\gabor_ctrl).stop;

///////////LFOs///////////

(
x = { Out.kr(~bus[\ctrl][0], LFTri.ar(1).linexp(-1, 1, 0.0125, 2)) }.play;
y = { Out.kr(~bus[\ctrl][1], LFTri.ar(2).linexp(-1, 1, 0.0125, 1)) }.play;
Pdef(\gabor).play(t, quant:1);
)

(
Pdef(\gabor).stop;
x.free;
y.free;
)

For a midi based composition im working on right now, i would like to record the rhythms for some Pdef(...(Ppar(...Pmono,...)!n); combinations created by a L-System based fractional System ive build and a somehow not really widely used Pulse Divider Pattern class PSPdiv from https://github.com/dkmayer/miSCellaneous_lib (yet some other issues) to the DAW. compose / arrange / edit everything in the DAW, send the MIDI Data back to SC for playing the SynthDefs and record the multichannel Audio in the DAW.

something like this (simplified version of my Pmono approach):


(
var ixa, shaper;

ixa = { |freq, in, index|
    var phase, waveform, sig;
    phase = Phasor.ar(Impulse.ar(0), freq / SampleRate.ir, 0, 1);
    waveform = (phase % 0.5 * 2pi).sin * (2 * (phase % 0.5 < 0.25) - 1);
    waveform = waveform + (2 * (phase % 0.5 >= 0.25));
    waveform = waveform + (2 * (phase >= 0.5));
    sig = (waveform + (in * index)).fold2;
    sig;
};

shaper = {|in, mix, amount|
	var k, shaped;
	k = 2 * amount / (1 - amount);
	shaped = (1 + k) * in / (1 + (k * in.abs));
	shaped = XFade2.ar(in, shaped, mix);
};

SynthDef(\bass, {
	arg index=1, iScale=5;

	var trig = \trig.tr(1);
	var trigMod = LFNoise0.ar(8).round;
	var sig, gainEnv, iEnv, mod;
	var low, mid, high;
    var lowFreq, highFreq;

	iEnv = EnvGen.ar(Env([0, index, index * iScale, index], [0.0001, 5, 1], \lin), trig);
	gainEnv = EnvGen.ar(Env([0, 1, 1, 0], [0, \atk.kr(0.2), \rel.kr(0.01)], \curve.kr(-5)), (trig * trigMod).abs);
	mod = Sweep.ar(trig * trigMod, [51.913, 726.783]);

	sig = ixa.(51.913, ((mod * 2pi) + (pi/3)).wrap(-pi, pi), iEnv);
	sig = (sig * [2, 0.05]).mean;

	lowFreq = 207.652;
    highFreq = 1038.26;
    low = LPF.ar(LPF.ar(sig, lowFreq), lowFreq);
    sig = sig - low;
    mid = LPF.ar(LPF.ar(sig, highFreq), highFreq);
    high = sig - mid;

	// lows
	low = low + PitchShift.ar(low, 0.2, 2);
	low = shaper.(low, 1, \amount.kr(0.2));

	// highs
	high = (high * \boost.kr(3).dbamp).tanh;

	sig = low + mid + high;

	sig = sig * gainEnv * \amp.kr(0.25);

	sig = MidEQ.ar(sig, 13289.75, 0.7, 8);
	sig = LeakDC.ar(sig);
	sig = Limiter.ar(sig);
	Out.ar(\out.kr(0), sig);
}).add;
)

///////////////////// chain Pdefn(\durs) to Pmono //////////////////////

(
Pdefn(\durs, Pbind(\dur, 0.2));

Pdef(\bass,
	Ppar({ |i|
		Pmono(\bass,

			\dur, Pkey(\dur).poll,

			\atk, 0.2,
			\rel, 0.01,
			\curve, -5,

			\amount, 0.2,
			\index, Pwhite(0.0,0.03),

			\boost, 3,

			\trig, Pwhite(0.5, 1.0, inf),
			\amp, 0.15,
			\out, i,
		)
	} !2 ) <> Pdefn(\durs)
);
)

Pdef(\bass).play;
Pdef(\bass).stop;


////////// PSPdiv ///////////////////////

(
~bass = { |durs|
	Ppar({ |i|
		Pmono(\bass,

			\dur, Pseq(durs).poll,

			\atk, 0.2,
			\rel, 0.01,
			\curve, -5,

			\smooth, 0.5,
			\index, Pwhite(0.0,0.03),

			\boost, 3,

			\trig, Pwhite(0.5, 1.0, inf),
			\amp, 0.15,
			\out, i,
		)
	} !2 )
};

~pulse = 1;
~div = 5;
~divBase = 1;
~divType = \seq;

Pdef(\rhythms,
	PSPdiv(
		PL(\pulse),
		~bass,
		PL(\div),
		PL(\divBase),
		PL(\divType)
	),
);
)

Pdef(\rhythms).play;
Pdef(\rhythms).stop;

Ive set up everything with JACKRouter (audio) and loopMIDI (midi). I can receive and send multichannel MIDI Data from / to Ableton Live using the MIDISynth class from this thread: Video Tutorials on MIDI based music production with Supercollider, jackd and DAW - #10 by droptableuser and also record multichannel audio in the DAW from SC.

But i dont know how i can use this setup based on Pmono + the MIDISynth Class to record / playback MIDI in the DAW.

Its working with the MIDISynth class setup playing and recording MIDI from a Pbind. But whats about Pmono and MIDI?
Do i have to exchange everything again?

Pbind example with the MIDISynth Class:

/////////////////////////////////////////////
// create a synth
(
SynthDef(\sine, {
    var freq = \freq.kr(220);
    var gate = \gate.kr(1);
    var sig = SinOsc.ar(freq);
    var aeg = Env.adsr(
        \atk.kr(0.01),
        \dec.kr(0.3),
        \suslevel.kr(0.5),
        \rel.kr(1),
        curve:\curve.kr(-4)
    ).ar(gate:gate, doneAction:Done.freeSelf);
    sig = Splay.ar(sig) * aeg * \amp.kr(0.3) * \vel.kr(1);
    Out.ar(\out.kr(0), sig);
}).add
)

/////////////////////////////////////////////
// show ui
n = NdefMixer(Server.default)
ProxyMeter.addMixer(n)

/////////////////////////////////////////////
// connect midi
MIDIClient.init()
MIDIIn.connectAll()

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

// midi synth setup (see class below)
MidiSynth(\m1).synth(\sine) // primes the ndef with specified synth
MidiSynth(\m1).note(noteChan:2).cc(ctrl:[\rel, \sat], ccNum:[0, 1], ccChan:0)

/////////////////////////////////////////////
// test with pattern
m = MIDIOut.newByName("IAC Driver", "Bus 1") // or whatever
(
Pdef(\midi,
    Pbind(
        \type, \midi,
        \midiout, m,
        \chan, 2,
        \degree, Pseq([0, 1, 3, 5].pyramid(2), inf),
        \scale, Scale.dorian,
        \dur, 0.5,
        \octave, Pseq([ 6, 5, 5, 6, 5, 5, 6, 5 ], inf),
        \legato, 0.2,
        \tempo, 1,
        \amp, 1)
)
)
Pdef(\midi).play
Pdef(\midi).stop

some frustrations:
sorry for the long read and the possibility of confusion. I really cant find a way to have an universal approach of composing music using SC for creating initial ideas with patterns and playing back SynthDefs but arrange everything based on midi notes in the DAW (i dont want to use recorded audio in the composing process, imo it is very unflexible). any ideas?

thanks a lot merry xmas

Pmono isn’t for MIDI, I’m afraid, and I think it won’t ever be.

Wouldn’t the easiest way be to use a Pbind with event type MIDI, and set up the synth in the DAW to play monophonically? That is, MIDI has no concept of polyphony or monophony. It only dumps the keystrokes to the synth, and leaves it to the instrument to make sense of them.

hjh

Also, if the idea is to be able to write the pattern one way, and play it either by a SynthDef (but mono-style) or by a mono-synth in a DAW, I’d suggest:

(
~noteData = Pbind(
	\degree, Pseries(0, Prand([-1, 1], inf), inf),
	\dur, 0.25,
	\legato, Pwrand([0.5, 1.01], [0.1, 0.9], inf)
);
)

// play Pmono-style
p = Pchain(PmonoArtic(\default, \dummy, 0), ~noteData).play;

p.stop;

// equivalent, shorter syntax
p = (PmonoArtic(\default, \dummy, 0) <> ~noteData).play;

p.stop;

// play MIDI
// you should set up a *mono* synth on the other end
p = Pchain(~noteData, (type: \midi, midiout: insertYourMIDIOutHere)).play;

// or
p = (~noteData <> (type: \midi, midiout: insertYourMIDIOutHere)).play;

That is – it’s usually assumed that everything for a Pmono must be contained within the Pmono – but this isn’t true. Pmono(Artic) should be the last thing to touch the event. As long as that condition is satisfied, then it doesn’t matter where the key-value pairs come from. It’s completely valid to chain the values in from a separate Pbind. Then the separate Pbind can play MIDI on its own.

hjh

1 Like

thanks a lot i think this will work.

ive now built a structure which is working for playing the SynthDefs with Pmono chaining Pbind(\data) and Pdefn(\durs) and also have the flexibility to record MIDI Data into the DAW as suggested above i guess.

///////////////////// Pmono(\instrument) <> Pdef(\data) <> Pdefn(\durs)  //////////////////////

(
Pdefn(\bassDurs, Pbind(\dur, 0.2));
Pdefn(\sineDurs, Pbind(\dur, 0.2));
Pdefn(\glitchDurs, Pbind(\dur, 0.1));

Pdef(\bassData,
	Pbind(
		\trig, Pwhite(0.5, 1.0, inf),
		\dur, Pkey(\dur),

		\atk, 0.2,
		\rel, 0.01,
		\curve, -5,

		\amount, 0.2,
		\index, Pwhite(0.0,0.03),

		\boost, 3,
		\amp, 0.15,
	) <> Pdefn(\bassDurs);
);

Pdef(\sineData,
	Pbind(
		\trig, Pwhite(0.5, 1.0, inf),
		\dur, Pkey(\dur),

		\amp, 0.15,
		\pan, Pwhite(-1.0,1.0),
	) <> Pdefn(\sineDurs);
);

Pdef(\glitchData,
	Pbind(
		\trig, Pwhite(0.5, 1.0),
		\dur, Pkey(\dur),

		\rel, Pwhite(0.05, 0.1),
		\pan, Pwhite(-1,1),
		\amp, Pwhite(0.1, 0.2),
	) <> Pdefn(\glitchDurs);
);

Pdef(\bass,
	Ppar({|i|
		Pmono(\bass,
			\out, i,
		)
	} !2 ) <> Pdef(\bassData);
);

Pdef(\sine,
	Pmono(\sine,
		\out, 4,
	) <> Pdef(\sineData);
);

Pdef(\glitch,
	Pmono(\glitch,
		\out, 10,
	) <> Pdef(\glitchData);
);
)

Pdef(\player, Ppar([Pdef(\bass), Pdef(\sine), Pdef(\glitch)], inf)).play(t, quant:1);
Pdef(\player).stop;

However im not sure how i can implement the PSPdiv in this chain. I know that its not commenly used but its basically a Pspawner wrapper class which controls the timing of one or several layers of event patterns by a single pulse pattern. In every layer single pulse durations or integer multiples (‘division bases’) of pulse durations can be divided by Integers or proportionally. PSPdiv | SuperCollider 3.12.1 Help
The \dur values inside the passed Event Patterns in my case ~instrumentData are calculated from ~pulse, ~div and ~divBase inside PSPdiv and in my case are multiplied with Pbjorklund2 inside the Event Pattern ~instrumentData. Changing the ~pulse for examples creates a kind of metric modulation for all Event Patterns which i would like to do with the L-System.

In the Setup below im putting the ~InstrumentData functions for bass, sine and glitch inside the PSPdiv so their \dur values get calculated from the instrument related ~pulse, ~div and ~divBase variables and are multiplied by Pbjorklund2 inside their specific ~instrumentData function. But now i cant chain the Pdef(\rhythm, PSPdiv...) holding an array of all three parallel rhyhtm patterns to their specific Pmono.

PSPdiv should be somehow at the end of the chain. i dont know how this could work in relation to my initial question of writing the Patterns Pmono-style and also be flexible with MIDI Data. For this use case Pmono should be at the end of the chain i guess.
I would be really greatful if someone has an idea. many thanks.

EDIT: more generally speaking maybe the question is about accessing individual patterns inside a Pspawner and chain them to their specific Pmono. But maybe the whole setup could also be done more easily or isnt possible at all i dont know.

///////////////////// PSPdiv and Pmono //////////////////////


// evPat arguments now given as functions
// this especially makes sense in combination with parallel embedding
// otherwise parallel patterns would poll event data from a single event stream

(
~bassData = { |durs|
	// use durs calculated from pulse, div and divBase inside PSPdiv and multiply by Pbjorklund2
	Pbind(
		\dur, (Pseq(durs) *
			Pbjorklund2(
				k: Pseq([~numberOfHits_bass.clip(0, ~div_bass)]),
				n: Pseq([~div_bass])
		)),

		\atk, 0.2,
		\rel, 0.01,
		\curve, -5,

		\amount, 0.2,
		\index, Pwhite(0.0,0.03),

		\boost, 3,
		\amp, 0.15,
	)
};

~sineData = { |durs|
	// use durs calculated from pulse, div and divBase inside PSPdiv and multiply by Pbjorklund2
	Pbind(
		\dur, (Pseq(durs) *
			Pbjorklund2(
				k: Pseq([~numberOfHits_sine.clip(0, ~div_sine)]),
				n: Pseq([~div_sine])
		)),

		\pan, Pwhite(-1.0,1.0),
		\amp, 0.15,
	)
};

~glitchData = { |durs|
	// use durs calculated from pulse, div and divBase inside PSPdiv and multiply by Pbjorklund2
	Pbind(
		\dur, (Pseq(durs) *
			Pbjorklund2(
				k: Pseq([~numberOfHits_glitch.clip(0, ~div_glitch)]),
				n: Pseq([~div_glitch])
		)),

		\rel, Pwhite(0.05, 0.1),
		\pan, Pwhite(-1,1),
		\amp, Pwhite(0.1, 0.2),
	)
};

// default values for PL proxies, want to replace later on
// and ~numberOfHits_instrument for Pbjorklund2

~pulse = 1;

// bass
~numberOfHits_bass = 5;
~div_bass = 5;

// sine
~numberOfHits_sine = 5;
~div_sine = 5;

// glitch
~numberOfHits_glitch = 10;
~div_glitch = 10;

~divBase = 1;
~divType = \seq;

// creating rhythm data with PSPdiv for all Pbinds

Pdef(\rhythm,
	PSPdiv(
		PL(\pulse),
		[~bassData, ~sineData, ~glitchData],
		[PL(\div_bass), PL(\div_sine), PL(\div_glitch)],
		PL(\divBase),
		PL(\divType)
	),
);

// Pmonos for all the three instruments and try to chain individual rhythm data.

Pdef(\bass,
	Ppar({|i|
		Pmono(\bass,
			\trig, Pwhite(0.5, 1.0, inf),
			\out, i,
		)
	} !2 ) <> Pdef(\rhythm); // try to chain individual rhythm data to Pmono 
);

Pdef(\sine,
	Pmono(\sine,
		\trig, Pwhite(0.5, 1.0),
		\out, 4,
	) <> Pdef(\rhythm); // try to chain individual rhythm data to Pmono
);

Pdef(\glitch,
	Pmono(\glitch,
		\trig, Pwhite(0.5, 1.0),
		\out, 10,
	) <> Pdef(\rhythm); // try to chain individual rhythm data to Pmono
);
)

// play all Pmonos

Pdef(\player, Ppar([Pdef(\bass), Pdef(\sine), Pdef(\glitch)], inf)).play(t, quant:1);
Pdef(\player).stop;

okay, i thought i could make the chain differently but the only way which handles the triggers right is putting ~bassData into PSPdiv and chain Pdef(\bass) with Pdef(\rhythm) and then play Pdef(\bass).
But when you then have more then one Event Pattern inside Pdef(\rhythm) (which is the purpose of using the Pulsedivider) you cant acces the individual Patterns to chain them with their specific Pmono. i give up…

// this works but only for one Event Pattern, not with an array of Event Patterns inside PSPdiv
(
~bassData = {|durs, div|
	Pbind(
		\dur, Pseq(durs) * Pbjorklund2(
			k: Pseq([~numberOfHits_bass.clip(0, div)]),
			n: Pseq([div])
		),
		\trig, Pwhite(0.5, 1.0),
		\atk, 0.2,
		\rel, 0.01,
		\curve, -5,
		\amount, 0.2,
		\index, Pwhite(0.0,0.03),
		\boost, 3,
		\amp, 0.15,
	)
};

~pulse = 1;

// bass
~numberOfHits_bass = 5;
~div_bass = 5;

~divBase = 1;
~divType = \seq;

Pdef(\rhythm,
	PSPdiv(
		PL(\pulse),
		~bassData,
		PL(\div_bass),
		PL(\divBase),
		PL(\divType)
	),
);

Pdef(\bass,
	Ppar({|i|
		Pmono(\bass,
			\out, i,
		)
	} !2 ) <> Pdef(\rhythm);
);
)

Pdef(\bass).play(t, quant:1);
Pdef(\bass).stop;

im still trying to find a way to understand whats happening here. its giving me sleepless nights.

why is it not possible to chain the Pmono(\bass) with the ~bassData function in the example above? this would solve all the problems for multiple Event Patterns inside PSPdiv. as i understand it the \dur values inside the function ~bassData are calculated from pulse, div and divBase inside Pspawner wrapper class PSPdiv and then passed as arguments to the function ~bassData.

If this is really not working is there another way to have one “master pulse” Pbind where its index only advances on phrase boundaries like described here:

which is then chained to several Pbinds which do their math to the master pulse for \dur (divide the pulse and multiply by Pbjorklund2, to have one master pulse equally divided by hits and divisions) and then chain these Pbinds to their specific Pmonos ?

im not sure if this is working correctly:
EDIT: (its not, the master pulse is going faster when divided by a specific number in the subpatterns).

(
~index = Pn(Pseq([0,1], inf)).asStream.trace(prefix: "measure index: ");

Pdef(\pulse,
	Pdup(2,
		Pn(Pbind(
			\dur, Pdict(~patDict, Pfin(1, PL(\index))).trace,
		), inf)
	).asStream
);

// bass
~numberOfHits_bass = 3;
~div_bass = 4;

// sine
~numberOfHits_sine = 5;
~div_sine = 6;

Pdef(\bass,
	Pbind(
		\dur, Pkey(\dur) / ~div_bass * Pbjorklund2(
			k: Pseq([~numberOfHits_bass.clip(0, ~div_bass)]),
			n: Pseq([~div_bass])
		),
		\degree, 3,
	) <> Pdef(\pulse);
);

Pdef(\sine,
	Pbind(
		\dur, Pkey(\dur) / ~div_sine * Pbjorklund2(
			k: Pseq([~numberOfHits_bass.clip(0, ~div_sine)]),
			n: Pseq([~div_sine])
		),
		\degree, 5,
	) <> Pdef(\pulse);
);
)

Pdef(\player, Ppar([Pdef(\bass), Pdef(\sine)], inf)).play;

I’m afraid I have no idea – I don’t know what PSPdiv is trying to do.

Pstep maybe? If the phrase durations are known in advance, this would hold an index value for that duration before allowing the next to be produced.

hjh

i got rid of PSPdiv and was trying something like this with PtimeClutch and the L-System from the other thread. Dictionary inside Pattern - #20 by jamshark70
maybe better to post it there…

(
~rewriteWord = {|word, rules, iter=6|
	iter.do{
		word = Array.newFrom(word)
		.collect{|c| rules[c.asSymbol]}
		.join;
	};
	word
};

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

// function for cropping L-System

~sumUpTo = {|array, total|
	var sum = 0, i = 0, result = Array.new;
	while { i < array.size and: { sum < total } } {
		sum = sum + array[i];
		if(sum > total) {
			result = result.add(array[i] - (sum - total));
		} {
			result = result.add(array[i]);
		};
		i = i + 1;
	};
	result
};

// creating abstractions with lesser density: reduce the density by joining two values

~low_dens = {|array|
	var ioi, min_index, neighbor_index, result = [];
	ioi = array.copy();

	while {ioi.size() > 1} {
		// in every step, we will reduce ioi until it has size 1
		var intermediate_result = [];
		min_index = ioi.minIndex;

		// if first element is minimum element, the neighor can only be to the right
		if (min_index == 0) {
			neighbor_index = 1;
		} {
			// else if last element is minimum element, the neighbor can only be to the left
			if (min_index == (ioi.size - 1)) {
				neighbor_index = (ioi.size - 2);
			} {
				// else we're in the middle, so use the neighbor which is smallest (arbitrary choice)
				var dir = 1;
				if (ioi[min_index - 1] < ioi[min_index + 1]) {
					dir = -1;
				};
				neighbor_index = min_index + dir;
			};
		};
		// swap neighbor and min_index so that neighbor is always bigger than min_index
		// - this makes copying easier in the next step
		if (neighbor_index < min_index) {
			var tmp = min_index;
			min_index = neighbor_index;
			neighbor_index = tmp;
		};

		// make a new list consisting of everything before min_index, sum of min el and its neighbor, everything after neighbor
		intermediate_result = ioi.copyRange(0, min_index-1) ++ (ioi[min_index] + ioi[neighbor_index]) ++ ioi.copyRange(neighbor_index+1, (ioi.size-1));

		if (intermediate_result.size >= ioi.size) {
			"ERROR!".postln;
		};

		// add it to the list of results
		result = result.add(intermediate_result);

		// and update our starting point with the reduced list
		ioi = intermediate_result.copy();
	};

	// return the complete list of reductions
	result;
};

// creating abstractions with higher density by replacing a value with:
// a smaller potential IOI value and the difference between it and the value being replaced

~high_dens = {|array|
	var ioi, ioi_sort, pot_ioi, index_max, result = [];
	ioi = array.copy();

	ioi_sort = ioi.as(Set).as(Array).sort;
	pot_ioi = ioi_sort.minItem;

	while { ioi.maxItem > pot_ioi } {
		var intermediate_result = [];

		index_max = ioi.findAll([ioi.maxItem]).choose;

		intermediate_result = intermediate_result.add(ioi);
		intermediate_result = intermediate_result.insert(index_max+1, [ioi[index_max] - pot_ioi, pot_ioi]).flat;
		intermediate_result.removeAt(index_max);

		// add it to the list of results
		result = result.add(intermediate_result);

		// and update our starting point with the reduced list
		ioi = intermediate_result.copy();

	};

	result;
};

~getAbs = {|rules, durations|
	var axiom, lsys, low_abs, high_abs, abstractions = [];

	// axiom
	axiom = "A";

	// rewrite iteration
	axiom = ~rewriteWord.(axiom, rules, 10);

	// get durations
	lsys = ~getDur.(axiom, durations);

	//crop initial L-System to a specific length
	lsys = ~sumUpTo.(lsys, 4);
	lsys.debug("L-System");

	//add abstractions with lower density to the array of abstractions
	low_abs = ~low_dens.(lsys);
	low_abs.do({
		arg el, elindex;
		abstractions = abstractions.add(el);
	});

	//add the initial L-System to the array of abstractions
	abstractions = abstractions.add(lsys);

	//add abstractions with higher density to the array of abstractions
	high_abs = ~high_dens.(lsys);
	high_abs.do({
		arg el, elindex;
		abstractions = abstractions.add(el);
	});

	//order array of abstractions by size: low -> high
    abstractions = abstractions.sort({|a, b| a.size < b.size});

	abstractions.indexOf(lsys).debug("L-System index");
	abstractions.debug("abstractions");
};

// define rules
~rules = (A:"AB", B:"A");

// define durations for each symbol
~durations = (A: [0.25, 0.5], B: [0.5, 0.125]);

~absArray = ~getAbs.(~rules, ~durations);

~absDict = ();
~patDict = ();

~putL = { |key, array|
	~absDict.put(key, array);
	~patDict.put(key, Pseq(array, 1));
};

// or, wipe out the whole system and start again with 'arrays'
~resetL = { |arrays|
	~absDict.clear;
	~patDict.clear;
	arrays.do { |array, i|
		~putL.(i, array);
	};
};

~resetL.(~absArray);
)

// test SynthDef

(
SynthDef(\testMono, {
	var trig = \trig.tr(1);
	var gainEnv = EnvGen.ar(Env.perc(0.01,0.4), trig);
	var sig = SinOscFB.ar(\freq.kr(150), 0.6);
	sig = sig * gainEnv * \amp.kr(0.25);
	Out.ar(\out.kr(0), sig);
}).add;
)

// Pattern Setup

(
~index = Pn(Pseq([0,1], inf)).asStream.trace(prefix: "measure index: ");

Pdef(\durs, Pn(Pbind(
	\dur, Pdict(~patDict, Pfin(1, PL(\index))).timeClutch
), inf));

// bass
~numberOfHits_bass = 4;
~div_bass = 12;

// sine
~numberOfHits_sine = 3;
~div_sine = 6;

Pdef(\bassData,
	Pbind(
		\midinote, 60,
		\dur, Pkey(\dur) / ~div_bass * Pbjorklund2(~numberOfHits_bass, ~div_bass),
	);
);

Pdef(\sineData,
	Pbind(
		\midinote, 71,
		\dur, Pkey(\dur) / ~div_sine * Pbjorklund2(~numberOfHits_sine, ~div_sine),
	);
);

Pdef(\bassMono,
    Ppar({|i|
        var pat = Pmono(\testMono,
            \out, i,
        );
        if(i == 0, {pat.trace(\dur, prefix: "\tbass dur: ")}, {pat});
	} !2) <> Pdef(\bassData)
);


Pdef(\sineMono,
	Ppar({|i|
        var pat = Pmono(\testMono,
            \out, i,
        );
        if(i == 0, {pat.trace(\dur, prefix: "\t\tsine dur: ")}, {pat});
		pat; // comment out this line to trace sine durs
	} !2) <> Pdef(\sineData)
);
)

// play Pattern

Pdef(\player, Ppar([Pdef(\bassMono), Pdef(\sineMono)], inf) <> Pdef(\durs)).play(quant:1);
Pdef(\player).stop;


// change index

~index = Pn(Pseq([2], inf)).asStream.trace(prefix: "measure index: ");

// update ~absArray on the fly:
(
~rules = (A:"AB", B:"AC", C:"BA");
~durations = (A: [0.25, 0.5], B: [0.5, 0.125], C: [0.333, 0.167]);
~absArray = ~getAbs.(~rules, ~durations);
~resetL.(~absArray);
)

unfortunately the system is running asynchronous after a while and when updating the ~index there is some discontinuity between the phrase durations from the L-System and the durations from the Pbjorklund2s immediately.

i think the sum of the hits from the Pbjorklund2 should equal the size of the first item in the ~patDict. to have them synchronized. for example with [ 2.125, 1.875 ] at index 1 of the ~patDict.

(
var durs = [ 2.125, 1.875 ];
var numberOfHits_bass = 4;
var div_bass = 12;

x = durs[0] / div_bass * Pbjorklund2(numberOfHits_bass, div_bass).asStream;

y = x.nextN(numberOfHits_bass);
)

[ 0.53125, 0.53125, 0.53125, 0.53125 ].sum;

-> 2.125 // equals first item in the ~patDict

1.) is there a way to have the setup 100% synced for n chained Patterns with their own sequences on a note level to the master L-System pulse which only advances on phrase durations. I think its even more difficult when ~numberOfHits_instrument is a Pattern.
2.) and when updating the ~index manually at a random time or updating the ~absArray it will wait until every duration from the chained patterns at the former index from the ~patDict has been passed.

thanks :slight_smile: