Week 2 lesson 3 - Combining patterns with arithmetic, plus the 'hurry' function

Here's another video as promised. To tell the truth, I'm super tired at the moment, so despite a couple of takes the video ended up a bit "non-linear", with an explanation of the 'hurry' function dropped in the middle of an exploration of the different ways of combining control patterns of the same type.

For the same reason of tiredness, I haven't managed to put together a worksheet for this tonight, but will get to that tomorrow, and hopefully some reference material with diagrams. Anyway, here we go!

  • 1:11 - Using # to combine two instances of the same control pattern, to take structure from the left, and values from the right
  • 3:39 - |> as an alias for '#', taking values from the right and structure from the left
  • 3:50 - |< - that takes both values and structure from the left
  • 5:04 - >| - that takes both values and structure from the right
  • 5:45 - <| - that takes values from the left and structure from the right.
  • 6:59 - Using |+ and +|, to take structure from the left or right respectively, but taking values from both sides and adding them together * 8:13 - Using |* and *|, to do the same but multiplying the values together
  • 8:27 - Making use of this with mininotation to make longer form patterns
  • 10:00 - Using 'hurry' to both speed up patterns, and sample playback rate ('pitching up' samples).
  • 11:48 - Using |*| and friends to take structure from both sides
  • 12:55 - Using these operators to combine number patterns, rather than control patterns

Here's a worksheet which should hopefully help get your head around this. Reference material with diagrams to follow soon!

-- Ok, so what happens when we specify a 'control' pattern (like e.g. n,
-- sound, speed, or squiz) more than once?

-- Lets start with the handy 'numbers' sounds:
d1 $ n "0 1 ~ 2" # sound "numbers"

-- lets put than 'n' again, but with a different number:
d1 $ n "0 1 ~ 2" # sound "numbers" # n "4"

-- So.. you can hear that we still have the rhythmic structure from
-- the left, but all the values have been replaced with the one on the
-- right. That's what `#` does!

-- lets make that right hand pattern more complicated:
d1 $ n "0 1 ~ 2" # sound "numbers" # n "4 5"

-- Now the 0 and 1 have been replaced with the 4, and the 2 has been
-- replace with the 5.

-- This is because tidal matches them up for you, based on where they
-- are in the cycle. The 0 and 1 start inside the first half, so are
-- replaced with '4'. The 2 starts inside the second half, so is
-- replace by '5'.

-- # is actually shorthand, for '|>'. There's a whole family of these:

-- |> is structure from the left, values from the right
-- <| is values from the left, structure from the right
-- |< is structure from the left, values from the left
-- >| is structure from the right, values from the right
-- |<| is values from the right, structure from both sides
-- |>| is values from the left, structure from both sides

-- < points to where the values come from, and | goes on the side where the
-- rhythmic structure comes from.

-- Everything from the left:
d1 $ n "0 1 2 3" # sound "numbers" |< n "4 5"

-- Everything from the right:
d1 $ n "0 1 2 3" # sound "numbers" >| n "4 5"

-- Rhythmic structure from left, values from the right:
d1 $ n "0 1 2 3" # sound "numbers" |> n "4 5"

-- Values from the left, rhythmic structure from right:
d1 $ n "0 1 2 3" # sound "numbers" <| n "4 5"

-- Values from the left, rhythmic structure from both sides:
d1 $ n "0 1 2 3" # sound "numbers" |<| n "4 5"

-- The above use of |<| sounds the same as |<, because the rhythmic
-- structures line up.

-- This changes 
d1 $ n "0 1 2" # sound "numbers" |>| n "4 5"

-- Some gotchas!

-- Even though you are taking everything from one side, something
-- still has to match up on the other side..
-- So this makes no sound:
d1 $ n "~" # sound "numbers" >| n "4 5"

-- Only the '4' sounds here:
d1 $ n "0 ~" # sound "numbers" >| n "4 5"

-- Most of the time you'll be fine forgetting all this, and just using
-- |> , and its alias # . 

-- However, there are other things you can do!

-- Instead of taking values from one side, you can add the values together, by
-- using '+' instead of '>' or '<'.

-- This:
d1 $ n "0 1 2 3" # sound "numbers" |+ n "4 5"

-- adds up to:
d1 $ n "4 5 7 8" # sound "numbers"

-- This:
d1 $ n "0 1 2 3" # sound "numbers" +| n "4 5"

-- adds up to:
d1 $ n "4 7" # sound "numbers"

-- This is because the rhythm comes from the left, from the "4 5", and
-- so we start from that. The start of 4 matches with 0, and the start
-- of 5 matches with 2, and adding them up, we end up with 4+0=4, and
-- 5+2 = 7.

-- This all gets complicated, especially when you work with patterns
-- with different numbers of steps..

d1 $ n "0 1 2 3" # sound "numbers" |+ n "4 5 6"

-- But don't worry too much. You just have to say what you want to
-- add together, let Tidal worry about working it out for you!

-- Ok that's enough numbers, lets put this into action with some
-- interesting patterns.

-- Here's one adding together 'n' patterns, using |+| to take
-- structure from both sides. On the right hand side, it uses the < >
-- mininotation syntax to pick a different subsequence per cycle.
-- The result is an interesting, longer form pattern:

d1 $ n "0 1 2 [3 5]" # sound "cpu"
  |+| n "<[4 5 3 2] [5 4 3] [6 5]>"
  # squiz 2

-- I just added a bit of squiz there to make it sound nice.

-- Here's a simpler example, cycling between three 12 note octaves, one per cycle:
d1 $ n "7 5 [2 7] 0" # sound "superpiano"
  |+ n "<-12 0 12>" 

-- It's actually possible to apply these to patterns of numbers
-- _before_ they become control patterns, like this:
d1 $ n ("7 5 [2 7] 0" |+ "<-12 0 12>") # sound "superpiano"
  
-- You have to use parenthesis to make sure the two patterns are added
-- together, before being passed to the 'n'.

-- To be clear, this is a pattern of numbers:
-- "7 5 [2 7] 0"

-- This is a control pattern, because 'n' turns numbers into synthesiser
-- control patterns:
-- n "7 5 [2 7] 0"

-- This all works for effects too:
d1 $ n "0(5,8) [4 1]" # sound "drum"
  # squiz "0 2 5"
  |+ squiz "<0 2 3>"

-- Or again, you can add the number patterns, rather than the control
-- patterns. This is the same:
d1 $ n "0(5,8) [4 1]" # sound "drum"
  # squiz ("0 2 5" |+ "<0 2 3>")

-- See which you prefer to do!

-- 'saw' is a pattern that slowly moves from 0 to 1 over a cycle. Here
-- I'm slowing it down so it lasts 4 cycles, slowing increasing the
-- speed over that time:
d1 $ n "[0 4 2] [4 1] 3 [2 0] 3 [3 1] 4 4" # sound "cpu"
  # squiz 3
  # speed "1 [2 3] 3"
  |+ speed (slow 4 saw)

Next lesson:

19 Likes

By the way, we've got some fun ahead next week, when we'll exploring working with longer samples, lots of slicing and chopping ahead!

I also plan to put more examples together, to consolidate what we've learned so far.

Plus very soon, we've got a creative task to get stuck into.. Have a good one !

10 Likes

For understanding this stuff I've found it helpful to read through the source code. I'm not a haskell coder so there are definitely some places where it disappears over the event horizon for me but in general it's pretty readable. Just seeing some of the data structures involved is clarifying. Like what does speed "1 2 3" |> speed "0.5 0.25" do? One side produces some maps with a key-value that controls sample playback rate and the other side sets a different value for that key. Only useful if you're already used to thinking about things in those terms but if you are it's worth the effort to check out the code.

2 Likes

Where do you look at the source? Unpack this?
~/.cabal/packages/hackage.haskell.org/tidal/1.4.8/tidal-1.4.8.tar.gz
Or on github?

You could do either, depending on whether you prefer to look in a web browser or text editor. Another place to look is here: https://hackage.haskell.org/package/tidal

1 Like

Thank you! I was wondering where/how @alec looked at the speed function in his example.

I have the source checked out from github. So I'm looking at it locally in a text editor.

Finding speed involves looking in a couple of places and tracing the code back and forth. The function itself is defined in src/Sound/Tidal/Params.hs but the underlying mechanism of creating and manipulating patterns of maps is Pattern.hs. I found Alex's NIME submission useful as a guide to how to think about the structure of the system.

2 Likes

Hi all,

I've added a worksheet to the above.

This is all getting a bit technical and ungrounded.. But don't worry if it's not sinking in. I promise the next lessons will be more 'hands on', and I'll refer back to and update this more technical side of patterning as we go.

2 Likes

There's a typo in the worksheet:

This should have |> instead of |<

1 Like

Thanks! Fixed.

Hi @yaxu, I'm sorry but I haven't time to tweak and follow the course, I've picked it back only today - and I've picked up my doubts also.
I'm trying to write code like that:
d1 $ n "[[0 ~]*2, 4*8, [~ 2]*2]" # s "cpu" # gain "[2, 1, 0.9]" # pan "[0.5, [0.25 0.75], 0.6]"
In other words since I'm playing different sound with a single line of code, I would also control some parameters/effects for the specific sample e.g kickdrum loud and centered, hats panned half left and half right etc. etc. but the code doesn't work. What am i doing wrong, and why?

Thank you <3

No need to apologise, there is no time limit on the lessons.

Lets take the gain effect as an example. If you have something like # gain "[2,1,0.9]", then that will apply that to every event it matches. So every event will be played three times at once, at different gain levels.
If you want to effect different parts of the pattern differently, then best to separate them out.

d1 $ n "[0 ~]*2" # s "cpu" # gain "2" # pan "0.5"
d2 $ n "4*8" # s "cpu" # gain "1" # pan "[0.25 0.75]"
d3 $ n "[~ 2]*2]" # s "cpu" # gain "0.9" # pan "0.6"

Or you can 'stack' them

d1 $ stack [n "[0 ~]*2" # gain "2" # pan "0.5", n "4*8" # gain "1" # pan "[0.25 0.75]", n "[~ 2]*2]" # gain "0.9" # pan "0.6"]
   # s "cpu"

thanks @yaxu!

i might just have missed something out - but is there a slow equivalent of hurry ?
(:

No - what would it be? dawdle? You could define it like this:

dawdle t = hurry (1/t)
4 Likes

Hi Alex, I just started making my way through the course, so I'm just coming across this.

I'm having trouble using your dawdle example in Atom.

My (function ?) definition => dawdle t = hurry (1/t)

My attempted use => d1 $ dawdle 2 $ n "0 1 ~ 2" # sound "numbers" # n "4"
returns this error:

"Variable not in scope:
dawdle :: Integer -> Pattern ControlMap -> Pattern ControlMap"

What am I doing wrong?

Hi @goodeggny, are you running this first?

1 Like

Oops, totally forgot to run the line. It works now! Thanks.

1 Like