Implementing somthing like SonicPi's rings

Hi there,

Recently, I’ve been toying around with SonicPi and loved the way the ring idea works in that language.

If you haven’t seen rings in sonic pi, they are basically infinite lists that you can continuously grab the next value from:

live_loop :foo do
  notes = [36,38,40,42].ring
  use_synth :tb303
  play 32 notes.tick
  sleep 1

In sonic pi the code above first plays note 36, then, on the next loop, plays 38 and so on. When it reaches the end of the ring, it starts over and you get 36 again.

I wanted to make something similar in tidal. I thought it might take the form of a function mapping a list of values over a pattern of events. Each event would get the next value in the list and when all the values were taken, it would start again at the beginning of the list.

Here is my attempt below:

  ring :: [a] -> Pattern b -> Pattern a
  ring xs structure = withEvents getRingValues structure
                      where getRingValues events = map (\(Event whole part v , x)-> Event whole part x) (zip events (cycle xs))

The problem is, when use it, it returns a pattern with first value of the list over and over again. Also, if I write the following:
d1 $ note (ring [0,1,2,3] $ "0*13") #s "gtr"
Tidal freaks out and can’t figure out if the “0*13” is a Pattern or a String.

So I have to write: d1 $ note (ring [0,1,2,3] $ euclidFull 11 16 0 0) #s "gtr"

(the euclidFull is just in there to give it some rhythmic structure as ("0*13"::Pattern String) bypasses the parser and gives me a pattern with only one event.

Does anyone know what I might be doing wrong?


What would be the difference between ring [1,2,3,4] and “<1 2 3 4>”?

Great question! cat and the angle braces get really close. The main difference too me seems to be that when using those functions the time until the next value in the ring is always the same size (1 cycle unless fast or slow is applied) and access is deterministic. In sonic pi, you could choose to fetch the next value randomly and that would affect the speed at which new values are gathered.

1 Like

Bumping because

  1. This would be incredibly useful
  2. As it stands, it’s almost impossible to achieve this behavior through user code.

Since Tidal works with cycles, it’s very difficult to implement behavior that strays from this logic. When dealing with polymetric / random melodies, ring is a very useful, basic building block that’s still missing.

I think the way to go is having ring implemented in the core library, probably as a monad whose bind returns the first value of a list and shifts it.

1 Like

I get that the proposal makes sense for sequences of consecutive events.

But what should the result be (and what will the suggested implementation

   ... (zip events (cycle xs)

do) if there are several events at the same time?

It seems I missed this thread for over a year!

fit can do this sort of thing.

The constraint is that you have to take the same number of values from the list every cycle. You give that as the first parameter. Then you give the list of values you’re reading from, and finally a pattern of numbers that indexes into that list.

So fit 3 [36,38,40,42] "0 1 2" would give "36 38 40" for the first cycle, and "42 36 38" for the second.