Thinking time in addition vs division (Meshuggah-style prog metal in Tidal)

Hi everyone!

I'm trying to make prog metal music with live coding, which usually involves changes of time signatures and additive groupings of 16th notes. As an exercise, I'm trying to pattern the main riff of "Phantoms" by Meshuggah (here's a wonderful explanation of the riff by Yogev Gabay).

Part of the drums play the riff in 4/4 (e.g. s "hh hh sn:3 hh"). However, kick and guitars / bass play very long sequences of 16th notes (note number n means n 16th notes, ~ means one 16th note rest, and ! means repetition):
(3~ 3!2~ 3!4~ 3!3~)!3 = 34 x 3 = 102 16ths
(3~ 3!2~ 3!7~ 3!4~ 3!3~) = 56 16ths
(3~ 3!2~ 3!4~ 3!3~) = 34 16ths
Total length of the riff = 192 16th notes

I have 3 doubts:

Q1: How would you approach writing patterns like this in a compact way in Tidal?

Q2: How would you write a sequence of different time signatures, where the pulse duration (e.g. 16th) is maintained between bars, rather than the cycle duration? E.g. in a sequence like 4/4 5/8 3/4, my cycle would be divided into 16, 10 and 12 16th notes, but I want all of them to have the same duration.

Q3: How would you truncate a polymeter? E.g. say you have a 5 against 3 polymeter, but you wanna truncate the sequence in 5 after 4 repetitions of the sequence in 3, so when the sequence repeats, both start at the beginning of their sequence respectively:
5: 123451234512
3: 123123123123

2 Likes

Q1: How would you approach writing patterns like this in a compact way in Tidal?

I'm a bit confused as I think 3 ~ 3!2 ~ 3!4 ~ 3!3 ~ has 14 events not 34?

If you want to have 4 event per cycle you can use {}%4 e.g.

{3~ 3!2~ 3!4~ 3!3~}%4

Repeating these phrases beatwise is currently difficult though. This kind of beatwise concatenation is something I'm working on for version 2.0 of tidal, inspired by carnatic rhythms.

Q2: How would you write a sequence of different time signatures, where the pulse duration (e.g. 16th) is maintained between bars, rather than the cycle duration? E.g. in a sequence like 4/4 5/8 3/4, my cycle would be divided into 16, 10 and 12 16th notes, but I want all of them to have the same duration.

I think this hits the same issue unfortunately.

Q3: How would you truncate a polymeter? E.g. say you have a 5 against 3 polymeter, but you wanna truncate the sequence in 5 after 4 repetitions of the sequence in 3, so when the sequence repeats, both start at the beginning of their sequence respectively:
5: 123451234512
3: 123123123123

To restart a pattern every 4 cycles you can put restart "t/4" $ in front, e.g.

drawLine $ stack [restart "t/4" $ "{1 2 3 4 5}%3", "1 2 3"]

gives

|123|451|234|512|123|451|234|512|123|451|234|512|123|451|234|512|123|451|234
|123|123|123|123|123|123|123|123|123|123|123|123|123|123|123|123|123|123|123
4 Likes

Ok I made this handy 'minicat' function. You pass it a number of beats per cycle, and a list of mininotation strings, and it will add the mininotation together beatwise.

import Data.Either

minicat :: (Show a, Parseable a, Enumerable a) => Time -> [String] -> Pattern a
minicat beatsPerCycle minis = _fast (beatsPerCycle/total) $ timeCat sized
  where total = sum $ map fst sized
        sized = rights $ map (((\mini -> (fst $ steps_tpat mini, toPat mini)) <$>) . parseTPat) minis

So you can do this sort of thing:

drawLine $ minicat 4 ["a b", "c [d e] [~ f]"]
|a-b-c-de|.fa-b-c-|de.fa-b-|c-de.fa-|b-c-de.f|a-b-c-de|.fa-b-c-|de.fa-b-

The first string in the list it treated as having two beats, and the second as having three, and it fits it into a four-beat cycle..

Not sure if this gets you closer to what you want..

5 Likes

Oops I fixed the minicat type signature above, it should work now if you re-copy-paste.

restart is a new function in the latest tidal 1.9.4 that was released in March, probably you just need to upgrade tidal for that.

1 Like

I've worked with this in the past using a combination of cps maths and zoom - unfortunately cps is applied globally so it may or may not meet your needs:

-- zoom for time sig changes - every 4 bars changes from 4/4 to 3/4
let c = (140/240)
in
d1
    $ every' 4 1 (zoom (0, 0.75) . (# cps (c * (4/3))))
    $ s "bd cp"
    # cps c
1 Like

You can truncate polymeters with timeLoop

timeLoop 1 $ n "{1 2 3 4 5}%8" # s "arpy"

will give you |12345123|12345123|, 1 is a cycle here, so if you did something like timeLoop 16, you would get really long polymeters that would restart on the 1 after 16 cycles

2 Likes

I watched the (amazing!) video that you shared @alvaro.makes.music - thank you!

I think the notation you used ((3~ 3!2~ 3!4~ 3!3~)!3) is similar enough (but different!) to Tidal notation that it led us down a different path. That said, I love the rhythmic ideas that came up in the thread! For example, the restart function is totally new to me.

Here's my first attempt at the riff in Tidal:

d1 $ slow 2
$ struct "1!3 ~ 1!6 ~ 1!12 ~ 1!9 ~"
$ s "arpy"
# nTake "counterName" (concatMap (replicate 3) [0, 1, 3])
# octave 3 # cut 1

It uses nTake for the note pattern he describes here. It also converts the first sequence you notated as 3~ 3!2~ 3!4~ 3!3~ into Tidal's rhythm notation "1!3 ~ 1!6 ~ 1!12 ~ 1!9 ~".

The next step would be to concatenate the different sequences together while maintaining the equal length (your Q2). I imagine it would look something like this:

let cellBasic = "1!3 ~ 1!6 ~ 1!12 ~ 1!9 ~"
let cellVariation = "1!3 ~ 1!6 ~ 1!21 ~ 1!12 ~ 1!9"

d1 $ slow 2
$ struct (cat [cellBasic, cellBasic, cellBasic, cellVariation, cellBasic])
$ s "arpy"
# nTake "counterName" (concatMap (replicate 3) [0, 1, 3])
# octave 3 # cut 1

We are missing the part that ensures cellVariation has more time so that the notes have the same duration. Maybe someone on the forum can help us get the final part!

1 Like

(Edited) I realized it may be easier to create a custom function, SHUGGAH, like this:

  1. Given: INPUT, a list of integers
  2. Initialize: let RESULT be an empty string, ""
  3. Iterate: For each number i in INPUT, append to RESULT "1!3 " (a space at the end) i times, followed by "0 " (4 spaces at the end)
  4. Return: if RESULT != "", return "{" + RESULT + "}%16"

For example, SHUGGAH [1 2 4 3] should return "{1!3 0 1!3 1!3 0 1!3 1!3 1!3 1!3 0 1!3 1!3 1!3 0 "}%16

After a long heated argument with ChatGPT, I got it working (any suggestions to make it more elegant are welcome):

import Sound.Tidal.Context (parseBP_E)

shuggah :: [Int] -> Pattern Bool
shuggah input =
    if null input
        then fmap (/= 0) (parseBP_E "")
        else fmap (/= 0) (parseBP_E ("{" ++ unwords (map process input) ++ "}%16")) 
  where
    process i = unwords (replicate i "1!3") ++ " 0    "

do
    resetCycles
    d1 $ struct (shuggah [1, 2, 4, 3]) $ s "superpiano"
    d2 $ "[bd sn:1, hh!4]" #amp 0.9
    d3 $ "bass2:2/12"
    d4 $ "808cy:3/16"

Okay, next challenge.The pitch changes on every group of notes (the "1!3") between 0, 1 and 15 half steps. So for the abstraction [1,2,4,3], we get something like:
... #up "{0!3 0 2!3 15!3 0 0!3 2!3 15!3 0!3 0 2!3 15!3 0!3 0}%16"

To be used like
d1 $ struct (shuggah [1,2,4,3]) $ s "superpiano" #up (transpose [1,2,4,3] [0,1,15])

Except the pitches need to cycle until they align with the rhythm again. Example:

  • Rhythm: [1,2,4,3] => groups = 1+2+4+3 = 10 groups of ("1!3") notes
  • Pitch: [0,1,15] => notes = 3
  • How many times we have to run the pattern with cycling pitches until they align?
  • mcm(groups,notes) = mcm(10,3) = 30 groups of ("1!3") notes
  • Repetitions of the rhythm: mcm(groups,notes) / groups = 30/10 = 3 repetitions
0!3 0    2!3 15!3 0    0!3 2!3 15!3 0!3 0    2!3 15!3 0!3 0
2!3 0    15!3 0!3 0    2!3 15!3 0!3 2!3 0    15!3 0!3 2!3 0
15!3 0    0!3 2!3 0    15!3 0!3 2!3 15!3 0    0!3 2!3 15!3 0
0!3 ... (pitch and rhythm align again)

ChatGPT's solution won't even run. Any ideas? Sorry, Haskell is so foreign to me I have no idea on how to extend Tidal code myself.

import Data.Either (fromRight)

transpose :: [Int] -> Pattern Note
transpose input =
    if null input
        then silence
        else fromRight silence (parseBP ("{" ++ unwords (map process (zip input (cycle ["0", "1", "15"]))) ++ "}%16"))
  where
    process (i, num) = unwords (replicate i (num ++ "!3")) ++ " 0    "

Hey would love to look at this but have a bit of an aversion to chatgpt generated code.. Could you describe what you're looking to do without it?