# 2.5 Player Keys ## Introduction Player Keys are the easiest way to create relationships between your Player objects. They let you share data in a reactive and dynamic way and are especially useful if you are collaborating when using the [Troop interface](https://github.com/Qirky/Troop). They are accessed as you would any Python object attribute, by typing the name of the object, a dot, then the name of the attribute e.g. `p1.pitch`. The neat thing about Player Keys is that, if the original Player is updated, so is the Player Key, so you don’t need to worry about updating any values anywhere else. These work for any attribute of a Player e.g. `degree`, `amp`, `sus`, `pan` or anything else. It will even work for custom attributes on your own SynthDefs. It isn’t quite the same as copying and pasting the Pattern value as it will always use the most up-to-date value held by that attribute. Let’s look at an example: ``` python p1 >> pads([0, 4, 5, 3], dur=4) p2 >> pluck(p1.degree, dur=1/2) ``` The Pattern used by `p1` is `P[0, 4, 5, 3]` but the duration is 4 beats for each note. After using `p1.degree`, the `p2` player will play the same pitch, only changing the value when the pitch of `p1` changes. Essentially, `p2` will play each pitch 8 times. To access the Pattern directly, you need to read the data from the `attr` dictionary like so: ``` python p1 >> pads([0, 4, 5, 3], dur=4) p2 >> pluck(p1["degree"], dur=1/2) ``` _Note_: if `p1` is updated, then the pitch of `p2` will not be updated until the line of code is re-run when using the `attr` dictionary. ## Aliases The Player Key uses two aliases for retrieving a player's `degree` value. You can use `.pitch` for synths and `.char` for the `play` synth, which makes it easier to understand what value you are actually getting. ## Maths and Logic You can perform any mathematical operation on a Player Key that is available in Python: | Symbol | Name | Description | |--------|--------------------------|---------------------------------------------------------------------------------------------------------------| | `+` | Addition | | | `-` | Subtraction | | | `/` | Division | | | `*` | Multiplication | | | `**` | Power | | | `^` | Power | | | `//` | Floor division | Returns the integer result of the division | | `%` | Modulo division | Returns the remainder part of the division | | `==` | Equal to | Tests if the Player Key is equal to the given value, returns `1` if `True` and `0` if `False` | | `!=` | Not equal to | Tests if the Player Key is not equal to the given value, returns `1` if `True` and `0` if `False` | | `>` | Greater than | Tests if the Player Key is greater than the given value, returns `1` if `True` and `0` if `False` | | `<` | Less than | Tests if the Player Key is less than the given value, returns `1` if `True` and `0` if `False` | | `>=` | Greater than or equal to | Tests if the Player Key is greater than or equal to the given value, returns `1` if `True` and `0` if `False` | | `<=` | Less than or equal to | Tests if the Player Key is less than or equal to the given value, returns `1` if `True` and `0` if `False` | ## Methods ### `.transform(func)` The behaviour of a Player Key is similar to that of the TimeVar. It is essentially a number/string that changes over time but any transformation made to it, such as multiplying it by two, will also hold true whenever the value changes. ``` python-console >>> p1 >> pads([0, 4, 5, 3], dur=4) >>> a, b = p1.pitch, p1.pitch * 2 >>> print(a, b) 4 8 >>> print(a, b) 5 10 ``` As the time changes, so does the player’s pitch and performing any arithmetic operation on it will create a new Player Key that holds information about the original and the transformation. That way it always returns the transformation of the correct value. Things get tricky when we want to use a custom function on our player key. Let’s say we want to want transform our Player Key into a 5 when odd and a 3 when even. Here’s a function that will do that you: ``` python-console >>> def odd_test(num): ... return 5 if num % 2 == 1 else 3 >>> p1 >> pads([0, 1, 2, 3], dur=4) >>> a, b = p1.pitch, odd_test(p1.pitch) >>> print(a, b) 0 3 >>> print(a, b) 1 3 >>> print(a, b) 2 3 ``` Notice anything? We always got 3, even if the Player Key’s value was odd. To apply a function (as opposed to a mathematical operation) you need to use the `transform` method and supply the method with the function to transform it. It should take only one input argument: ``` python-console >>> p1 >> pads([0, 1, 2, 3], dur=4) >>> p2 >> pluck(p1.pitch.transform(odd_test), dur=1/2) >>> print(p1.pitch, p2.pitch) 0 5 >>> print(p1.pitch, p2.pitch) 1 3 ``` ### `.map(mapping_dict, default=0)` Instead of defining a function to return certain values as above, we can provide a mapping in the form of a Python dictionary. The mappings can be one-to-one values or functions. If a Player Key’s value is not in the dictionary, then the default value is returned. ``` python-console >>> p1 >> pads([0, 4, 5, 3], dur=4) >>> p2 >> pluck(p1.pitch.map({4: 1, 3: 0}, default=2)) >>> print(p1.pitch, p2.pitch) 0 2 >>> print(p1.pitch, p2.pitch) 4 1 >>> print(p1.pitch, p2.pitch) 5 2 >>> print(p1.pitch, p2.pitch) 3 0 ``` Here’s how you can implement the `odd_test` transformation using `map` and a `lambda` function: ``` python p1 >> pads([0, 4, 5, 3], dur=4) p2 >> pluck(p1.pitch.map({lambda x: x % 2 == 1: 5}, default=3) ``` ### `.accompany(rel=[0, 2, 4])` Returns a Player Key that, when the source Player Key changes value, moves to the closest value that is +/- the values in `rel` i.e. `[0, 2, 4]`. When used with `.pitch` this will move to the nearest pitch value that completes the third or fifth above or below the note. ``` python p1 >> pads([0, 4, 5, 3], dur=4) p2 >> pluck(p1.pitch.accompany()) ``` ## Useful Examples Create a chord sequence based on the pitch of a player: ``` python p1 >> bass([0, 4, 5, 3], dur=4) p2 >> pluck(p1.pitch + (0, 2, 4), dur=1/2) ``` Use the pitch of a player within a sequence: ``` python p1 >> bass([0, 4, 5, 3], dur=4) p2 >> pluck([p1.pitch, 7, 6, 7], dur=1/2) ``` Invert the amplitude of a player: ``` python p1 >> play("x-o-", amp=[1,1,0,1,0,1,0], dur=1/4) p2 >> play("*", amp=p1.amp != 1, dur=1/4) ``` Harmonise and pan two players to opposing channels: ``` python p1 >> pluck([0, 2, 6, 3, 2, 4, 1, -2], dur=1/2, pan=[-1, 0.5, 0.25, -0.5, 0]) p2 >> blip(p1.pitch + 2, dur=1/2, pan=p1.pan * -1) ``` Use indexing to accompany the root note of a chord sequence: ``` python p1 >> pluck([0, 4, 5, 3], dur=4) + (0, 2, 4) p2 >> blip(p1.pitch[0].accompany()) ```