Defining harmonic rules in polyphonic setting

Hi there, a question from a complete newbie (!), who’s just scratching the surface of supercollider’s apparently immense possibilities.
How would I go about defining harmonic rules in a polyphonic setting?
E.g. I imagine four voices, all using the chromatic scale (in different registers). The melody could be “random”, but the harmonies should always be seventh chords (in different inversions). How would I achieve this? Looking fotward to hearing your suggestions!

This really depends on how you want to mutate these voices later.

One ‘supercollidic’ way of doing this is with patterns:

s.waitForBoot {
	Pbind(
		\dur, 1,
		\degree, [5, 7, 9, 11] - 1, // 7th chord
		\scale, Scale.major,
		\root, Prand( (0..12), inf), 
		\octave, Ptuple(Prand( (3..5), inf)!4, inf) // random octave per voice
	).play
}

Here the voices do not exist in the traditional sense since the first ‘voice’ here might play the lowest pitch for one chord, then the highest for the next. If you wanted to have an idea of voice leading you would need a more complex system.

Thank you so much! Will dive into it asap.

A simple example for more voices, it employs a trick named data sharing. This is described in more detail in Chapter 06g of the Pattern Guide in SCdoc.

(
// Pattern a silently determines the sequence of harmonies
a = Pbind(
	\type, \rest,
	\dur, 3,
	\chord, Pseq([[0, 4, 8, 11], [1, 3, 7, 10], [2, 5, 6, 9]], inf).collect { arg x; ~chord = x }
);

// voices "look" at the harmony pattern
p = Pbind(	
	\note, Pfunc { ~chord.choose },
	\octave, Prand([3, 4], inf),
	\dur, Prand([0.4, 0.2, 0.2], inf)
);
		
q = Pbind(
	\note, Pfunc { ~chord.choose },
	\octave, Prand([5, 6], inf),
	\dur, Prand([0.4, 0.2, 0.2], inf)
);
	
Ppar([a, p, q]).play
)

@dkmayer - great example. A little slightly OT question and sorry for hijacking the thread: When I run you code I cannot stop the patterns - CMD + period does not bite and I have to reboot. In other cases CMD + period does stop a pattern. To me it appears random wether CMD + period stops a pattern or not. Are there any rules to this?

Wrapping it in a Pdef should do the trick.

I know, and I always use Pdefs in my own code - the reason I am asking is that there are a lot of examples in the help files which play pattern not assigned to any variable and thus cannot be stopped, or I guess sometimes they can and sometimes the cannot, at least on my M1 machine running OSX under Rosetta.

Calling .play on a pattern returns an EventStreamPlayer. You can stop this manually by calling .stop on the result of .play.

x = Pbind(...)
p = x.play; // EventStreamPlayer
p.stop // stop the player

Pdef registers the EventStreamPlayer with a variable and frees it with CmdPeriod (amongst other things).

x = Pbind(...)
p = x.play; 
CmdPeriod.add({p.stop})

I think Pdef isn’t used in the documentation because it came later with JITlib?

@jordan - Yes I know, but in the above example a free-standing Ppar was played without being assigned to any variable first. There are many such cases in the helpfiles which often mean I have to reboot the server - unless there is some secret trick I am unaware of.

Edit:

this is wrong - ignore

I don’t think there is. It doesn’t appear that EventStreamPlayer or subclasses thereof store their instances, so it isn’t possible for something like EventStreamPlayer.freeAll to exist.

Perhaps the docs could be improved by always assigning EventStreamPlayers to variables.

Yes, always assigning patterns to a variable in the docs and helpfiles would definitely be a great help. I am still wondering why CMD+period works sometimes and sometimes not. Are you able to stop the pattern from playing with CMD+period in dkmayer’s example above?

Okay I was wrong about that previous post.
I can’t get it to not free them, although I think I remember this being a thing in the past. Can you post an example where it doesn’t free?

I think the freeing is done by the Clock.

That’s the tricky thing, after rebooting I am able to stop the pattern with CMD + period so it seems it must be something related to the current state of the server. I am running OSX/M1 under Rosetta, I wonder if this could have something to do with it. I guess it is is time to update Supercollider to run natively on M1 and see:)

Hi Jordan and Daniel,
Many thanks for the great suggestions!
Lots to chew on, which I will do in the coming days.
Daniel, if I would want to choose custom scales for the different voices rather than chromatic ones, how would I define those within your example?

Hi Thor,

not being able to stop everything with Cmd-. is an issue I have never encountered and indeed, it’s a serious one !

As has become clear from the thread, you could do an assignment anyway and it’s recommeded. In my help files in miSCellaneous_lib I always do it like this.

x = Ppar([a, p, q]).play;

x.stop;

Just one hunch: do you have many quarks installed? Sometimes a quark might unintendedly change default behaviour. To go sure and exclude that possibility, I’d try with plain vanilla SC again.

Actually, this is already doing it. Any array assigned to the variable ~chord could be regarded as a representation of a scale (4 tones in this case, you are free to use 7 to form major, minor, phrygian etc.). The process of assignment and data sharing is completely independent from the way you define and use scales, tunings etc. in the Pbind. There are a lot of those options, descibed at the bottom of the Event help file. E.g., you could use degrees instead of notes

(
// Pattern a silently determines the sequence of degree selections
// (disjoint parts of a scale, the scales are defined in the other patterns though)

a = Pbind(
	\type, \rest,
	\dur, 3,
	\degrees, Pseq([[0, 2, 4, 5], [1, 3, 6]], inf).collect { arg x; ~degrees = x }
);

// tripled voices "look" at the degrees pattern
p = Pbind(	
	\scale, Scale.major,
	\degree, Pfunc { ~degrees.choose } + [0, 5, 7],
	\octave, Prand([3, 4], inf),
	\dur, Prand([0.4, 0.2, 0.2], inf)
);
		
q = Pbind(
	\scale, Scale.major,
	\degree, Pfunc { ~degrees.choose } + [0, 4, 9],
	\octave, Prand([5, 6], inf),
	\dur, Prand([0.4, 0.2, 0.2], inf)
);
	
x = Ppar([a, p, q]).play
)

Daniel, thank you! You have given me so much to work with. If it’s all right with you I’ll get back to you when I have a more thorough understanding of all the elements you show.

HI Daniel,

I don’t have many quarks installed as I recall this issue dates back to my very first SC experiences before installing any quarks. Could the issue be related to running SC under Rosetta on a M1 mac? I any case it is time for me to upgrade SC to run natively on the M1, I hope this will make the problem go away.