Tidal
Tidal copied to clipboard
unexpected "skip" messages
Seeing undesirable skip
messages, late
messages, and sometimes audibly inaccurate sequencing. Given this pattern:
d1
$ (|* gain 1.2)
$ foldEvery [6,8] rev
$ foldEvery [7,11] (linger "3e")
$ sometimesBy 0.1 (stutWith "<3 4 5>" "1e" (|* gain 0.95))
$ sometimesBy 0.1 ((# s "{peri ifdrums feel}%12") . (# gain 0.9))
$ sometimesBy 0.1 (|* speed 0.5)
$ foldEvery [3,7] (|+ n "<2 3 4>")
$ sometimesBy 0.1 (off "1s" id) $ (1 ~>)
$ sometimesBy 0.1 (off "3s" id) $ (1 ~>)
$ struct "1(<2 [1 2 2]/3 2 [1 2]/2 [2 1]/2 3>,{8 8 4 8 16}%2,<0 1 5>)"
$ s "{bd drum}%11"
# n "{10 5 2}%14" # cut 1
# speed "{1 1 1 1 0.5}%9"
# cps (120/120/2)
I see skip messages:
tidal> skip: 11
skip: 11
skip: 11
skip: 11
tidal> kip: 12
skip: 11
:{
hush
:}
tidal>
My SuperCollider output is filled with late
messages, even though there are only a few events fired every second.
My pattern is not really dense at all (the rhythm in the struct
function is kind of sparse). However, there are many layers of conditional logic.
If I start to remove various conditional effects, then the skip messages go away. I haven't been able to pinpoint if there is a specific function that results in the skip
s... but in general it seems to get worse with "sometimesBy" and "while". If I replace them with "every" or "foldEvery" conditions, then the problems go away.
I guess in my opinion this logic isn't very complex and shouldn't result in so many skips/late messages. I'm probably leaning into some difficult performance problems, but it would be great a Tidal user could create a pattern with many more layers of conditional logic.
If I continue to add more layers of conditional logic, the sequencing is audibly "wrong" to my ears and the timing starts to get really off.
Replacing sometimesBy
with every
or foldEvery
conditions seems to help a little bit.
Using while
instead of sometimesBy
seems to make it a little worse.
Hey @kindohm which tidal version are you on?
Hey @kindohm which tidal version are you on?
1.7.2
I'll update to 1.7.4 and will see if I observe the same stuff.
@yaxu updated to 1.7.4 and I'm seeing the save behavior with skip
and late
messages.
I'm using kind of a weird Tidal bootup sequence so I'm going to also try this on a vanilla Atom package to make sure my window/latency params aren't causing the issue.
Observing the same stuff with a vanilla Atom install.
I also tried that pattern and I get the same skip messages on tidal 1.7.4
I tried to simplify the pattern to understand what's the problem... seems like the sometimesBy
has a central role in this.
This pattern skips also, and it's not dense at all
d1
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ sometimesBy 0.1 (|+ n 1)
$ "bd"
sometimesBy performance is also discussed here: https://github.com/tidalcycles/Tidal/issues/564
- further discussion here: https://github.com/tidalcycles/Tidal/issues/636
In summary, it's tricky.. Each sometimesBy
doubles up the patterns that have to be calculated, and as we know from the pandemic, doubling gets bad quickly.. @jwaldmann explains it here: https://github.com/tidalcycles/Tidal/issues/636#issuecomment-618404963
If you're chaining them with the same likelihood, note though that sometimesBy 0.1 x $ sometimesBy 0.1 y
is the same as sometimesBy 0.1 (x . y)
. So the above from @ndr-brt is the same as sometimesBy 0.1 (|+ n 16)
I thought this might be more efficient but it seems not..
import Prelude hiding ((<*),(*>))
sometimesBy r f pat = stack [a,b]
where randpat = (\x y -> (x,y)) <$> pat <* (rotR 1000 rand)
a = fst <$> filterValues (\(_,y) -> y <= r) randpat
b = f $ fst <$> filterValues (\(_,y) -> y > r) randpat
This is much more efficient (linear?):
sometimesBy r f pat = innerJoin $ (\x -> if x >= r then f pat else pat) <$> rand
However you end up joining inner discrete events with outer continuous ones, which means that you will find different answers at different sample rates.
I tried by myself a solution similar to the first you shared, and I noticed that was not so efficient. The second one is better but I noticed two things:
- the sign should be inverted (
x < r
) - the function is not applied as a whole, eg:
d1
$ sometimesBy 0.3 (stutWith 3 "s" (|* sp 2))
$ s "bd*4"
With this piece of code, not every event that should be played by stutWith
will be played, while it does with the current sometimesBy
implementation
@ndr-brt yes I hear the problem and I think it comes from the fact that it's calculating 'inside' a continuous pattern. On the other hand it does some kind of awesome like this.
Here's an interesting hack that samples from rand
according to the input pattern:
sometimesBy r f pat = innerJoin $ (\x -> if x >= r then f pat else pat) <$> (const <$> rand *> mono pat)
It seems to work but wouldn't work well for polyphonic patterns, and doesn't seem very performant either.