Polyphony and Mini-Notation

I'm somehow just realizing how powerful the mini-notation can be in terms of adding more voices. I'm trying to wrap my head around how to control the separate voices though.

Say I have a pattern:
d1 $ slice 8 "{0 1 2 7 0 6 8 [4 2]}%4" $ s "arp:0" # legato "1 2 1 0.5"

Now I want to add another voice an octave up:
d1 $ slice 8 "{0 1 2 7 0 6 8 [4 2]}%4" $ s "arp:0" # legato "1 2 1 0.5" # up "[0,12]"

Can I now apply functions to just that second voice? Say I want to nudge just that one, should this work?:
d1 $ slice 8 "{0 1 2 7 0 6 8 [4 2]}%4" $ s "arp:0" # legato "1 2 1 0.5" # up "[0,12]" # nudge "[0,0.5]"

Can I then continue specifying effects and functions per voice in this way, like pan "[0, 1]", etc?

Thanks.

1 Like

I'm not sure what final sound you were aiming for, but I think off might work in place of nudge if you wanted to alternate between the higher and lower segments of the sample.

d1 $ off 0.5 (# up 12) $ slice 8 "{0 1 2 7 0 6 8 [4 2]}%4" $ s "arp:0" # legato "1 2 1 0.5"

and other effects can be chained for the offset voice:

d1 $ off 0.25 ( (# up 12) . (# pan 1)) $ slice 8 "{0 1 2 7 0 6 8 [4 2]}%4" $ s "arp:0" # legato "1 2 1 0.5"

Yes, good point that off might work better in the example I gave, but what I'm really asking is if we can specify functions per voice by indexing the same values in a list. Like will the 2nd value in a list control the second voice if there is already one that's been created in the sound? Or does adding the nudge "[0,0.5]" create a third voice that ignores the second voice created by the up "[0,12]"?

Or put another way, could I create a second voice, then pan it and filter it like this:
d1 $ slice 8 "{0 1 2 7 0 6 8 [4 2]}%4" $ s "arp:0" # legato "1 2 1 0.5" # up "[0,12]" # pan "[0,1] # lpf [300,900]"

I hope it makes sense what I'm asking. It seems like this would be a powerful way to control polyphony. But maybe it doesn't work like that?

can specify functions per voice by indexing the same values in a list

No.

Operators # (etc.) work on the semantics (the contents) of patterns, not on their syntax (the shape of their source code)

The semantics of a pattern is: (function from time slice to) set of events. This loses all syntactical information.

If you want to keep/manipulate structure (e.g., for separation of voices) then you have to represent it semantically (in the types).

Example: uses pattern of pairs, and transforms first component:

let p =   (,) <$>  "foo" <*>  "bar"  :: Pattern (String,String)

p
(0>1)|("foo","bar")

p >>= (\ (x,y)-> stack[pure x,pure y])
(0>1)|"foo"
(0>1)|"bar"

(mapFst reverse <$> p) >>= (\ (x,y)-> stack[pure x,pure y])
(0>1)|"oof"
(0>1)|"bar"

You can't play these things directly (you can only play Pattern ControlMap), so need a transformer for output
(after >>= in the example)

Also, mini-notation might be harder to use for patterns over non-standard types, as it requires some constraints for parsing to work.

2 Likes

Thanks @jwaldmann.
Your Haskell solution is beyond me, but I think I get what you're saying about how the functions work over time chunks.

But I also still don't really understand how polyphony in mini-notation works.
Continuing on with my previous example:

d1 $ slice 8 "0 1 2 7" $ s "arp:0" # lpf "900" # up "[0,12]"

From the way it sounds to me, when I add the second voice using up "[0,12]" I'm getting a copy of the sound an octave up that includes the lowpass filter (and therefore I assume everything before the up). Is it right that if I then add another function after that, like a nudge "[0,0.5]" that the 0.5 won't effect the existing (second) voice but will in fact create an entirely new voice that is a copy of everything before it? So I would end up with 3 voices? So every time mini-notation polyphony is used a new voice is created with everything before that call?

I guess that leaves the 'correct way' to specify multiple functions for individual voices as:
superimpose
layer
stack
jux
Is that correct?

Thanks!

If I then add another function after that, like a nudge "[0,0.5]"
that the 0.5 won't effect the existing (second) voice
but will in fact create an entirely new voice that is a copy of everything before it?

will apply # nudge 0 and # nudge 0.5 and stack the results, yes.

s "arp" # up "[0,12]"
(0>1)|note: 0.0f, s: "arp"
(0>1)|note: 12.0f, s: "arp"

s "arp" # up "[0,12]" # nudge "[0, 0.5]"
(0>1)|note: 0.0f, nudge: 0.0f, s: "arp"
(0>1)|note: 0.0f, nudge: 0.5f, s: "arp"
(0>1)|note: 12.0f, nudge: 0.0f, s: "arp"
(0>1)|note: 12.0f, nudge: 0.5f, s: "arp"

It will also copy parameters for effects (filters, etc.) but there are some ("global") effects where a copy of the parameter does not mean that the effect is copied (instead the different parameters will be used by just one instance of the effect processor). There was a separate thread about this.

3 Likes

That definitely clears me up a bit, thank you!