Independently control duration of two Synths within the same loop?

Hello all,

I am working on a Tdef again and am 90% completed with what I am trying to do. The last part of the puzzle is finding a way to modify the ‘duration’ value for each sub-array within the Routine independently. My goal is to essentially replicate the functionality of Pdefs, but without using patterns.

My current workaround is using one array within two Tdefs. But I am trying to do everything within one system.

Code overview (Tdef below):

I am using the ~synth_arg_funcs function to store the arguments that are then passed to the Synth. I have multiple arrays within this function that create multiple Synths when the loop is iterated.

The vars declared at the start of the Routine within the Tdef are used to calculate the duration. I add duration.wait after the Synth creation and set the gate = 0 so that n time elapses before the next Synth is created (got this technique from Nathan).

My question is- Is there any way to independently control the duration of each array?

Here is the Tdef:

// Tdef. Used for 'on the fly' changes and is (quant: 4)
(
Tdef(\test2,
	Routine {

		// Vars for duration
		var bpm, beat, duration;

		bpm = 137;
		beat = 60 / bpm;
		duration = beat / 2;

		loop {
			var i = 0;
			~synth_arg_funcs = {|i| [
				// Each of these sub-arrays acts like a standard Pdef. On the left is the arg and the right is the value. No limit on how many arrays you can add. Using wrapAt(i) to ensure the values loop to the start, like a Pseq.
				[
					patch: [~b1, ~b2].wrapAt(i),
					amp: [
						1, 0, 0, 0,  1, 0, 0, 1,  0, 0, 0, 0,  1, 0, 0, 0,
						1, 0, 0, 0,  1, 0, 0, 1,  0, 0, 1, 0,  1, 0, 0, 0,
						1, 0, 0, 0,  1, 0, 0, 1,  0, 1, 0, 0,  1, 0, 0, 0,
						1, 0, 0, 0,  1, 0, 0, 1,  0, 0, 0, 0,  1, 0, 0, 0,
					].wrapAt(i),
					buffer: [1].wrapAt(i),
					rate: [1].wrapAt(i),
					atk: [0.01].wrapAt(i),
					rel: [0.1].wrapAt(i),
				],
				[
					patch: [~b1, ~b2].wrapAt(i),
					amp: [
						1, 1, 1, 1,  0, 0, 1, 0,  1, 0, 1, 0,  0, 1, 0, 0,
						0, 1, 0, 1,  0, 0, 1, 0,  1, 0, 1, 0,  0, 1, 0, 0,
						0, 1, 0, 1,  0, 0, 1, 0,  1, 0, 1, 0,  0, 1, 0, 0,
						0, 1, 0, 1,  0, 0, 1, 0,  1, 0, 1, 0,  0, 1, 0, 0,
					].wrapAt(i),
					buffer: [1].wrapAt(i),
					rate: [1].wrapAt(i),
					atk: [0.01].wrapAt(i),
					rel: [0.1].wrapAt(i),
				],
			] };

			// Secondary loop. Using n = inf to cycle through infinitely
			inf.do { |synth, j|

				// Passing the args here as a collection
				var synthArgs = ~synth_arg_funcs.(i).collect({|args|

					// Creating the synth with args
					Synth(\mainbuf, args: args, target: ~sources);
				});
				s.makeBundle(s.latency, {

					// Once the synth plays, setting gate = 0
					synthArgs.do { |synth| synth.set(\gate, 0) };
				});

				// Duration value here. Can apply math to this and randomness
				duration.wait;

			};
		};
	};
).play(quant: 4);
)

Thank you

Why? This is all trivia with patterns.

1 Like

You can. It’s a little complex - the way to think about this is that when you schedule them you need to keep track of the wake-up time for each one. Then, you wait for the ahortest wake-up time (e.g the next event) - lets say for event A. When you resume, play the next event for A and then wait for the wake-up for the next one, B.
i think PriorityQueue is the data structure you want, to be able to insert things with a numerical “how far int he future” number and then pull the next one after you wait.

The Ppar pattern class does this exactly. There’s some subtlety to it… If you really want to do this outside the pattern ecosystem, you can learn a lot about doing this kind of scheduling by reading Ppar. In the end, whatever you implement should match this more or less exactly. It IS more straightforward to do what you’re talking about by just running three different Tdefs, so if you don’t have a personal goal to have a much deeper understanding of how to build event schedulers, that might solve the problem more straightforwardly :slight_smile:

1 Like

Hey @jordan,

Good question :sweat_smile:

Mainly proof of concept. I have been using patterns for years and was intrigued by Nathan Ho’s work. He uses a patternless design for all of his generative music. I wanted to see if it was possible to capture most of the benefits of patterns I used, without using patterns. Pretty stupid, I know, I shouldn’t overcomplicate things. But curiosity got the best of me!

Got it! Thanks for your insight @scztt I’ll read into the Ppar class and will keep using multiple Tdefs for now

Lol @jordans somewhat blunt point stands :slight_smile: - you will be re-inventing the wheel by doing there things without using Patterns. Re-inventing the wheel can be good because afterwards, you know how to build wheels really well. It can also take tons of time and effort and you might end up with some slightly broken wheels in between. I learned a lot about how to do more sophisticated kinds of scheduling by basically rebuilding Ppar from scratch, it’s a good engineering exercise. But it definitely won’t get you to making music faster.

2 Likes

Good! Just wanted to make sure you weren’t wasting time by using the wrong tool. Like what @scztt said, trying to copy is a great way to learn!

Have you considered moving the duration up into the arrays, and creating the routines with a collect statement? The tdef becomes irrelevant at this point.

It could be argued that the benefit in trying some new approach is in fully committing and forgetting about the old way, to see what the else this new approach musically affords (big academic word). It will have some disadvantages from patterns but a whole new terrain, it appears like you’re still thinking about the music through patterns, rather than considering what routines uniquely offers.

I won’t comment on the specifics of the technical questions here, but I’ll note that my music is often “linear” with not too many simultaneous voices. It’s a mixture of the influence of routines, but also stylistic. EPROM is a hero of mine and his music often builds complexity with numerous rapid vertical cuts with 1-2 elements at a time, instead of layering (eg the drop in Hope). That aesthetic works really well with imperative routine code.

When I do want parallel layers and I use a few different approaches. For this thing (source) there are nine independent routines running simultaneously. The first routine is a “conductor” routine that doesn’t play anything but periodically changes some variables that are outside the scope of the other routines. In this way the chord changes are broadcast to the three tonal elements. You can broadcast other things, like when voices are started or muted, and that would be my approach if I were to develop this patch into a more complete piece. (All this can certainly be done less verbosely using patterns, which is fine, I just can’t tolerate them.)

In other pieces I’ve done things like every 8 bars launching a bunch of parallel routines for the arrangement. A more extreme end, which I have also used sometimes, is using a single routine that loops through every beat and launches appropriate synths at each iteration.

I don’t see the challenges of parallel music entry as a patterns vs routines thing but a more fundamental issue that text is sequential, and music with many interacting layers is not easily captured in code without making significant assumptions.

4 Likes