Problem Solving Workflow

I thought it might be valuable to start a thread about workflows (and resources) for problem solving in Tidal; steps to take when you hit a problem.

My current approach has been feeling a bit fragmented and piecemeal, and a good workflow for problem solving seems key in terms of gaining independence, so please post any thoughts or tricks you have for figuring out what's going wrong.

2 Likes

I'm not sure how unique this will be, but my steps have been something like:

  • Look at the inline code editor help for examples and explanations.

  • Visit TidalCycles.org. If the answer is unclear or the documentation is missing, then...

  • Try to remember which club lesson it might've been covered in already (if it has been).
    (I've also noticed that I can search through VSCode files for keywords as well, which is sometimes helpful.)

  • Try evaluating the confusing part using :t so you can see what it's expecting and delivering.

If none of those work I'm generally pretty stuck and ask for help.

But I know that there's also looking at the source code but that's felt daunting so far. And searching for Haskell help, which has also felt daunting in general. Learning Haskell is very much something I'd like to know more of as I've already found a few things that could only be solved that way.

1 Like

Breaking down a line into chunks and using :t to see how they should be fitting together is my go-to. The more time I spend using :t, and generally getting familiar with Tidal/Haskell function signatures, the more fruitful this becomes. And frankly having the stomach to engage with Haskell's error messages instead of dismissing them as noise has started to pay off. Haskell's error messages are scary as hell, but often are telling you exactly what the problem is.

The other type of problem solving activity that I am faced with in Tidal is dealing with what gets called 'logic errors' in other contexts. Basically the code compiles, but what I'm hearing isn't what I was expecting to hear. I can easily print the first cycle to the console, which sometimes helps, but often I want to print more. I got this trick from jwaldmann on the TOPLAP forum for printing multiple cycles:

queryArc (s "<bd sn>/3") (Arc 0 4)

which will print the first 4 cycles. It's super helpful, but unfortunately will dump all of the output into a single unbroken string - not ideal for debugging. It does open the door to solving those 'logical errors'.

3 Likes

These are both great thank you.

A bit off-topic though:

@crashingbooth this is amazing!

Do you have any idea about the meaning of this?
[((2,1),(4,1)),((9,1),(10,1))]

I can understand this: (0>1)-3|s: "bd" and I like so much the way time is displayed.

@mattia.paterna, that was the same question I asked in the original TOPLAP forum thread.

Apparently, they refer to the relevant positions (column, line) in the Pattern String. So the ((2,1),(4,1)) refers to the interval from the 2nd position on the first line to the 4th position on the first line (i.e., bd) and the ((9,1),(10,1)) would then refer to the 3. Apparently there was/is some plan/exploration around highlighting the relevant bits of text as they get played. It would be a pretty cool feature.

2 Likes

Yes that's right, positions in the mininotation. It's used for event highlighting by the feedforward editor.

To get multiple cycles I tend to use fast, e.g. fast 4 $ sound "bd sn/3"

2 Likes

I guess it depends on the type of problem. A few disorganized thoughts:

  1. I am terrible at Haskell, so I rarely look at source code or function signatures. I usually look at code examples on tidalcycles.org, source code from someone else's work, or maybe a video recording of someone else using Tidal (e.g. I've been referring back to Yaxu's performance with Hellocatfood lately).

  2. Sometimes I try to use well-understood Tidal features to achieve a compositional goal, but I can't quite get it right - or maybe I just don't understand what my code is doing. In these moments, I often re-order or interchange my functions to see if they will behave differently. Maybe then I'll understand the interactions better, or I'll achieve my goal. For example, all of these could produce very different results:

d1 $ every 3 (|+ n 2) $ every 2 rev $  n (run 16) # s "arpy"

d1 $ every 2 rev $ every 3 (|+ n 2) $ n (run 16) # s "arpy"

d1 $ every 3 rev $ every 2 (|+ n 2) $  n (run 16) # s "arpy"

d1 $ every 2 (|+ n 2) $ every 3 rev $ n (run 16) # s "arpy"
  1. If I'm encountering a syntax error, then I put on my Haskell boots and try to understand the function signatures better. Using :t in the editor is my first choice:
:t every

:t bite

:t spreadr

Otherwise, I might also just find the function definitions on their own userbase page, or look in the source code.

  1. Been using the drawLine function lately if my problem has to do with not understanding how patterns line up or interfere with each other.
3 Likes

Thanks @kindohm.

Order is something that I've often wondered about but haven't spent enough time exploring. Is it right to assume that the ordering of functions does matter and we can think about things feeding into each other from right to left?

2 Likes

@ben I agree with you that order is important, but sometimes the order itself can be changed and yet you still get the same result:

d1 $ fast 2 $ rev $ note "0 1 2 3" # s "arpy"

d1 $ rev $ fast 2 $ note "0 1 2 3" # s "arpy"

I think what is important is to understand what output you are passing to another function.
In @kindohm's examples the order of the functions is important because every returns a Pattern a which is a modified version of your original pattern, and you are using that pattern to feed another every and so on. But in a case like the one above, doubling the notes before or after reversing pattern does not change the result:

"0 1 2 3" -(fast 2)> "0 1 2 3 0 1 2 3" -(rev)> "3 2 1 0 3 2 1 0"
"0 1 2 3" -(rev)> "3 2 1 0" -(fast 2)> "3 2 1 0 3 2 1 0"

This case is interesting tough:

-- four events per cycle
d1 $ note "0 1 2 3" # s "arpy"
-- same as above
d1 $ s "arpy" <| note "0 1 2 3"
-- only one event per cycle (note: 0.0f, sound: "arpy")
d1 $ s "arpy" # note "0 1 2 3"
-- same as above
d1 $ note "0 1 2 3" <| s "arpy"

IMHO what is really important here to think about is how you want to combine the two patterns because you might want take the left structure with #, or you might want to get the structure from the right (as in my case) when combining the two patterns.

Does it make sense?

2 Likes

Yes thanks. This is the info I was looking for.
Iā€™m going to go through your examples but it makes sense just from reading as well.

1 Like

@ben i have certainly found this to be the case. just like a signal chain for a pedalboard.

1 Like