Week 7 Lesson 3 - Composing tracks with the 'ur' function

Here's an introduction to the 'ur' function, which lets you make patterns out of patterns, to make a track. I also talk about issues with 'orbits' and global effects (e.g. reverb, delay) you might have when using ur, seqPLoop and stack. This is still an area of Tidal that could be developed, so I'd be happy to have your ideas about possible features/improvements.

Here's the pattern I deconstruct:

d1 $ ur 16 "[bdsd, ~ claps, ~ [bass bass:crunch] ~ bass]"
  [("bdsd", sound "bd [~ sd] bd sd" # squiz 2),
   ("claps", sound "clap:4*2 clap:4*3"
     # delay 0.8 # dt "t" # dfb 0.4
     # orbit 4 # speed 4
   ),
   ("bass", struct "t(3,8)" $ sound "dbass" # shape 0.7 # speed "[1, ~ 2]")
  ]
  [("crunch", (# crush 3))
  ]
13 Likes

thank you alex! that ur seems even more...neat than seqP :smiley:

alright...i'm german so i'll do a bit of germaning:
you're quite right, "ur-" means something that's higher than something else,
but in value-'cause-it's-older-so-to-say- A N C I E N T, not in height :slight_smile:

so...there's plenty o' fish in the sea, but there's only one UR F I S H left, see:
Quastenflosser

the austrians have a quite charming way to express the feeling of being well and cosy when all things align in space/time: leiwand
the superlative of leiwand, like when all that cosy space/time warps into a perfectly traversable wormhole of well-being is: UR L E I W A N D

which is how i feel here, and hopefully soon in tidalcycles as well, cheers

ps: oh dear, i only now realized:
this is just a form of appreciation of conservatism baked into and dragged along in language

9 Likes

@dtlvdoom Brilliant explanation,

small complement:
Ursprung -> Ur-sprung = origin, genesis, source;

3 Likes

When I called the orbits "orbits" I thought at first of the cause of tidalcycles of course. But the idea that caught on was:

things on different orbits are independent and drift past each other.

5 Likes

This is super useful even for live performance!
The fact that you're not able to hear what you're working immediately is a blessing I thin.
Sometimes it requires a lot of agility to turn every second channels off when you want to introduce a rapid change, sequencing it out in a pattern of patterns definitely helps.

Two things I'm curious about:

  • Is it possible to layer two effects on top of another inside of the metapattern?
    I've tried
ur "pat1:fx1:fx2"

in this case only fx1 seems to work, guess you can always define effects as composite functions separately.

  • I'm not sure if I correctly understand the syntax of the metapattern.
    When it comes to division of the number of cycles we give to the ur function, if we have a pattern ur 4 "pat1 pat2" it seems to divide it evenly between them (each gets two cycles out).
    For ur 8 "[pat1, ~ pat2] pat2 will only play on cycles 5-8.
    Not sure what happens if we slow the pattern down or speed it up though.
1 Like

You have no idea how much I've been waiting for that lesson, thank you so much !

I have one related issue (user error I bet ;)) : ur works perfectly, as expected... except for the fact that I can't find a way to have several cycle long patterns, say a 4 bar long bassline, to start at their respective beginning. I've tried using resetCycles, reset and trigger, but I can't get constant results. Any tip ? Cheers !

1 Like

No not currently. I could think about adding that.. Right now you'd have to make an fx3 that does both fx1 and fx2.

Yes pat1 and ~ pat2 are layered up as subpatterns. So pat2 is only active for the second half. There's lots on the mininotation in week 1: Week 1 - mini-notation - Tidal Club

2 Likes

Hm, here's a version of ur called ur' that plays each pattern from cycle 0, is that better?

import Data.Maybe (fromMaybe)

import Sound.Tidal.Utils (wordsBy)

ur' :: Time -> Pattern String -> [(String, Pattern a)] -> [(String, Pattern a -> Pattern a)] -> Pattern a
ur' t outer_p ps fs = _slow t $ unwrap $ adjust <$> timedValues (getPat . split <$> outer_p)
  where split = wordsBy (==':')
        getPat (s:xs) = (match s, transform xs)
        -- TODO - check this really can't happen..
        getPat _ = error "can't happen?"
        match s = fromMaybe silence $ lookup s ps'
        ps' = map (fmap (_fast t)) ps
        adjust (a, (pat, f)) = rotR (start a) $ f a pat
        transform (x:xs) a pat = transform xs a $ transform' x a pat
        transform _ _ pat = pat
        transform' str (Arc s e) p = inside (pure $ 1/(e-s)) (matchF str) p
        matchF str = fromMaybe id $ lookup str fs
        timedValues = withEvent (\(Event c (Just a) a' v) -> Event c (Just a) a' (a,v)) . filterDigital
2 Likes

Thanks Alex !

Unfortunately it doesn't seem to work either. I get a bit more of predictability / consistency if throwing a reset 1 in the mix, but then it seems like the bassline starts at its second cycle, rather than at its begining.

The incriminated melodic sequence is something like :

struct "[t(3,8,<0 2>)]!2" $ n (toScale [0,1,3,5,7,8,10] "<[0 1 2! 0 1 2] [3 4 5! 3 4 5]>") # s "midi"

Thanks again !

If you can make a simplified example, demonstrating what you expect vs what happens, I can look into it :slight_smile:

Hello @yaxu,

I have a question related to patterning chords but did't know if this lesson is the right place post it (I can change it if not).
The point is I am trying to use ur to create a melody (here a simplified example):

let parts =
[
("a", n "c'maj" # s "superpiano"),
("b", n "a'maj" # s "superpiano"),
("c", n "f'maj" # s "superpiano")
]
in
d1 $ ur 3 "a b c" parts

but I'd like that Tidal started playing at the beginning. I tried with "trigger" and "qtrigger" functions but doesn't seem to work. (d1 $ trigger $ ur 3 "a b c" parts ). Is there an alternative?

Additionally, is there a way to play it just once? (if you use once instead of d1 it plays just "a".

Thanks for your help!

Hi @yago,

I moved this to the lesson on ur in particular.

Here's some way towards this:


let parts = [
      ("a", n "c'maj" # s "superpiano"),
      ("b", n "a'maj" # s "superpiano"),
      ("c", n "f'maj" # s "superpiano")
      ]
    playFor c pat = seqP [(0, c, pat)]
    urOnce c pat parts fs = playFor c $ ur c pat parts fs
in
d1 $ qtrigger 1 $ urOnce 3 "a b c" parts []

You have to say which 'channel' you're triggering with qtrigger. I had to define a new function urOnce to only play the pattern through once. One problem is that if you've just missed the start of a cycle, qtrigger will miss off the start of the pattern.. Hmm.

You can also use trigger instead of qtrigger but that won't align with anything else that's playing.

2 Likes

Please bare the sheer absence of musicality of that example, hopefully it'll successfully illustrate what I aim for : I'd love to find a way to have the whole block starting with superpiano's lowest C note, with the slowest / simplest rhythm (that is, mel1). Can't make it work each and every time.

do
  let
    pat1 = "t*4"
    pat2 = "t([5 6],16,<0!! [0 2]>)"
    mel = "<[<-7 0!3> ~] [1 3 5 7]>"
    mel1 = struct pat1 $ n (toScale [0,1,3,5,7,8,10] mel) # s "superpiano" # amp 0.333
    mel2 = struct pat2 $ n (toScale [0,1,3,5,7,8,10] mel) # s "superpiano" # amp 0.333
    poomtchack = stack [struct "t(3,8)" $ s "808bd" , struct "[ft]*2" $ s "808sd"] #amp 0.75
    techno = stack [struct "t*4" $ s "808bd" , struct "[ft]!4" $ s "808hh"] #amp 0.75
  d1 $
    ur' 8 "a b"
      [ ("a", poomtchack),
        ("b", techno)]
    []
  d2 $
    ur' 8 "1 2"
        [ ("1", mel1),
          ("2", mel2)]
    []

Thanks !

This is amazing! I've always wondered if it was possible to use tidal in a more "offline" mode to make full tracks. Definitely going to explore this! :slight_smile:

1 Like

Interesting function, but it's a bit hard to sequence multiple patterns at once isn't it?

I know you can do it using a comma, but things gets complicated, at least in my head. On first glance seqPLoop seems more easy for this.
I'm pretty new at digital music, but I assume people wants to build up tunes by layering patterns and turn them off/solo. So for making tracks it's an important function, which I'll explore further.

Hello,

When I run the code from this lesson I get the following error. I had a look around the forum and it looks like either a Tidal package error or a Cabal error? In Atom everything seems up to date so I'm guessing it's the later?

Thanks in advance!

t> 
β€’ Variable not in scope: dt :: Double -> Pattern ControlMap
β€’ Perhaps you meant one of these:
    β€˜d1’ (line 43), β€˜qt’ (imported from Sound.Tidal.Context),
    β€˜det’ (imported from Sound.Tidal.Context)


Variable not in scope: dfb :: Double -> Pattern ControlMap

What's the specific code you're trying to run? It looks to me like a type dt - when in fact you meant do or d1

Hi cleary,

Thanks for getting back to me. I was trying to run @yaxu's code from above. I think dt is 'delay time' in this instance.

d1 $ ur 16 "[bdsd, ~ claps, ~ [bass bass:crunch] ~ bass]"
[("bdsd", sound "bd [~ sd] bd sd" # squiz 2),
("claps", sound "clap:4*2 clap:4*3"
# delay 0.8 # dt "t" # dfb 0.4
# orbit 4 # speed 4
),
("bass", struct "t(3,8)" $ sound "dbass" # shape 0.7 # speed "[1, ~ 2]")
]
[("crunch", (# crush 3))
]

Ah, yes if I recall he mentioned that it was a shortcut being added to newer versions of tidal (you may be running an older ver). You can also use delayt and delayfb I believe

Ah yes, for now you could make aliases by running e.g.:

let dt = delaytime
    dfb = delayfeedback
1 Like