Questions about PChain


I’m quite new to live coding (and coding in general), but I’m finding FoxDot to be incredibly intuitive.

As far as improvising goes, PChain seems very useful. I’d like to use it to string together a handful of different refrains that I can either play over, or have a player improvise behind me.

I can’t find much in the way of documentation outside the Pattern Generators intro doc, and I’m having trouble getting PChain to work with any sense of predictability. I also can’t seem to do much without consistently getting KeyErrors.

Would anyone be able to provide a bit more comprehensive explanation with some examples? I understand this question is a bit broad, but a general overview might help set me down the right path.


There’s a brief bit of info here: but I’m happy to explain in more detail. PChain is a very simple markov chain, which basically means that when the PChain holds a certain value, x, the next value in the sequence will be one of the values that x points to. The dictionary input for PChain are x values and the values that they point to. Take this for example:

PChain({0: 1, 1: [0, 2], 2: 0})

When it holds the value of 0 the next value will always be 1 because 0 points to 1 in the dictionary. When it holds the value of 1, there is a 50% chance that the next value in the sequence will be a 0 or 2 because 1 points to [0, 2]. When it holds the value of 2 then the next value will always be 0. Because each value points to another value in the dictionary, the loop continues forever.

The following PChain will throw a KeyError:

PChain({0: 1, 1: 2})

When it holds the value of 1, it points to 2, which isn’t in the dictionary so can’t go anywhere else after that. I can’t remember if the player stops making sound when this happens? Perhaps it needs to just print a warning and continue playing the sound event?

Hope that makes sense!

That’s super helpful, thank you!

After some additional poking:

Sound stops playing when it throws a KeyError, but the error only shows when I Clock.clear().

That said, the sound will continue to play if I am playing, then rewrite a bad PChain() and attempt to execute it. The error is thrown immediately, too, which is helpful.

I think I understand this much better now, though, so I’ll keep digging into it.

I’ve made some improvements to PChain that are in the newest version 0.8.10. You won’t get KeyErrors any more, it will just keep producing the value if it can’t find a new value to change to. It also can take generator patterns as inputs now. You can get the update by running pip install FoxDot -U from your terminal. Let me know how you get on with it :slight_smile:

1 Like

Very cool! Just upgraded and so far, so good.

I had another question. Are tuples, lists, or Pattern Functions not compatible with PChain? For the following, I get the corresponding errors.

  • For tuples, i.e. chords, I get:
    Error in Player s1: float() argument must be a string or a number, not 'tuple'

  • For lists:
    TypeError: unhashable type: 'list'

  • For generator patterns (PDur):
    TypeError: unhashable type: 'Pattern'

Like you included in your last message, a Pattern Generator, e.g. PRand, works just fine. I am unsure whether the above are simply not possible, which is totally fine, but I’m worried I’m doing something incorrect.

Here is a simple example of what I was attempting with tuples / chords.

thing_1 = (0,2)
thing_2 = (0,4)
thing_3 = (6,9)
thing_4 = (4,11)
markovd =PChain({thing_1: [thing_2, thing_4], thing_2: [thing_2, thing_1], thing_3: [thing_1, thing_3], thing_4: thing_3})
s1 >> sitar(markovd, dur = 0.5)

Thanks again. And knowing I won’t be able to do this will help me since I think I can find relatively straightforward workaround.

I had made a modification of ‘PChain’ to be able to specify probabilities for each element. If it can be useful to someone or if someone wants to improve it.

class PChain2(RandomGenerator):
    """ PChain Mod Markov Chain generator pattern with probability."""
    def __init__(self, mapping, **kwargs):
        assert isinstance(mapping, dict)
        RandomGenerator.__init__(self, **kwargs)
        self.args = (mapping)
        self.last_value = 0
        self.mapping = {}
        i = 0
        for key, value in mapping.items():
            self.mapping[key] = [asStream(value[0]), asStream(value[1])]
            if i == 0:
                self.last_value = key
                i += 1
    def func(self, index):
        key = list(self.mapping[self.last_value][0])
        prob = list(self.mapping[self.last_value][1])
        self.last_value = random.choices(key, prob)[0]
        return self.last_value

example :

print(PChain2({1: [[2,3,4],[10,80,10]], 
               2: [[1,3,4],[70,10,20]], 
               3: [[1,2,4],[50,20,30]], 
               4: [[1,2,3],[25,25,50]]})[0:10])

Cool! We could call it PMarkov and add it to the main codebase?

Any argument used will have to be hashable because it’s stored in a Python dict. That means lists etc won’t work because they are mutable and can change their contents at any point. You can use a PGroup in place of a tuple though by prepending it with a P:

p1 >> pluck(PChain({0: 1, 1: P(0, 2), P(0, 2): 0}), dur=4)

And this also works with modified PGroups such as P*

p1 >> pluck(PChain({0: 1, 1: P*(0,2), P*(0,2): 0}), dur=4)

I’ll look at making all tuples into PGroups in a future update

Absolutely no problem