How to change different instruments' sleep rhythms live?

I have, for example, a kick and two synth sounds, each playing back in its own rhythm because each gets its own live_loop containing its own sleep pattern that comes from each instrument’s own array of sleep times. I want to modulate the rhythms live, so you’d normally be using functions so that you can change the function’s code while leaving the code inside the loop alone so that reloading doesn’t mess things up. I can’t figure out how to achieve that behavior here because functions would play sequentially, yet I need these loops to continue to play concurrently before, during, and after altering their sleep functions. I guess the problem is that I need multiple sleep times within the same loop to apply to multiple blocks of code within that loop, which doesn’t sound possible to me. Is there some way to achieve the desired behavior? Here’s my toy example:

ToneRhythm = [1.0, 0.333, 0.333, 0.333]
ToneRhythmLength = ToneRhythm.length

bdRhythm = [1, 1, 1, 1]
bdRhythmLength = bdRhythm.length

SynRhythm = [0.5, 0.5, 0.5, 0.5]
SynRhythmLength = SynRhythm.length

live_loop :tone_loop, sync: :metronome do
  use_synth (ring :tb303, :blade).tick(:timbre)
  play :e2, attack: 0, release: 0.5, cutoff: 100
  sleep ToneRhythm[tick(:tone_tick) % ToneRhythmLength]
end

live_loop :bd_loop, sync: :metronome do
  sample :bd_haus
  sleep bdRhythm[tick(:bd_tick) % bdRhythmLength]
end

live_loop :syn_loop, sync: :metronome do
  play (ring :d3, :e3).tick, attack: 0, release: 0.5, cutoff: 100
  sleep SynRhythm[tick(:syn_tick) % SynRhythmLength]
end

in_thread do
  sleep 1
  cue :metronome
end

This plays the kick in quarter notes, one synth in quarter then triplet eighths, and the other synth in eighth notes. I’d like to be able to do complicated algorithmic changes to both the length and cell content of the three rhythm arrays and then reload to achieve interesting (to me) n-tuplet based rhythmic patterns.

Hi @blipson,

Bear with me, I’m not sure I fully understand what your problem is here…

I want to modulate the rhythms live

other than just altering the contents of each array?

you’d normally be using functions

unless your function requires parameters, there’s no difference here between calling your function from the live loop or placing the function’s contents directly in the live_loop (perhaps you could clarify what you mean by ‘so that reloading doesn’t mess things up’)?

Perhaps you could elaborate a bit more about your desired goal with a rough algorithm written in plain language?

Thanks for taking a look, and please bear with me as I express things like a n00b (and probably understand that way, too!) Here’s a simple example of “messing things up”: I want to make the live change of turning ToneRythm’s triplet into straight quarters by changing its array from

ToneRhythm = [1.0, 0.333, 0.333, 0.333]

to

ToneRhythm = [1.0]

But when I do that live, ToneRhythm’s quarter notes are out of sync (usually), not sounding in unison with the bdRhythm. In other words, I do cmd-R, then alter the ToneRhythm array as above, then do cmd-R again, and it’s out of sync, as expected. If I do cmd-S, then cmd-R, it’s in sync, but that’s not a live change.

That’s ok! No problem at all.
It’s fairly late here, and I’m calling it a day. My initial thoughts are that possibly a solution might involve (amongst other things) using sync inside the live_loops? I’m sure there may well be others here that can help, but if no one answers in the mean time, I could perhaps come back to this some time tomorrow :slightly_smiling_face:

1 Like

Thanks. yeah, I tried sync stuff, but couldn’t figure something out. It has seemed to me that as soon as you have a sleep in your loop, the stuff after it simply will not happen until that sleep is finished. Yet I need multiple sleeps in the same loop where everything between sleeps just keeps playing, only respecting its own, designated sleep pattern.

My goal is to do matrix operations to select different rows as arrays of rhythms, while also modifying the contents and dimensions of the matrix, i.e., temporal modulation. I really hope it’s possible to do in Sonic Pi.

I think I’ve now boiled it down to this question: Is there some way to control at exactly what point after typing M-r the re-run actually takes place?

Here’s a somewhat reduced example:

ToneRhythm = [1, 1]  # two 1/4 notes
##| ToneRhythm = [1, 0.333, 0.333, 0.333]  # 1/4 note then triplet 1/8's
ToneRhythmLength = ToneRhythm.length

define :tone_fn do
  use_synth (ring :tb303, :blade).tick(:timbre)
  play :e2, attack: 0, release: 0.5, cutoff: 100
  sleep ToneRhythm[tick(:tone_tick) % ToneRhythmLength]
end

live_loop :tone_loop, sync: :metronome do
  tone_fn
end


##| bdRhythm = [1, 0.5, 0.5]  # 1/4 note then two 1/8 notes
bdRhythm = [1, 1] # two 1/4 notes
bdRhythmLength = bdRhythm.length

define :bd_fn do
  sample :bd_haus
  sleep bdRhythm[tick(:bd_tick) % bdRhythmLength]
end

live_loop :bd_loop, sync: :metronome do
  bd_fn
end

in_thread do
  sleep 1
  cue :metronome
end

There are two different rhythms, bd_rhythm and tone_rhythm, that bring about their two different sleep patterns. For each, there’s a commented one that I’d like to uncomment in real time then comment out the original, then do M-r to change the rhythm live without losing sync. The problem is that when I hit M-r to run the new code without stopping, there’s no way to know where in the rhythmic sequence the M-r has occurred. That’s what throws them out of sync unless I’m lucky. But notice that all four rhythms add up to the same total time (two quarter notes) so that if the M-r’s re-run would delay until the whole vector has been ticked through to effect the re-run, then everything would stay in sync when I change rhythmic patterns live.

So my question is now this: is there some way to control or know in advance at exactly what point after typing M-r the re-run actually takes place? That way, I could adjust each vector’s values or lengths (or whatever needs to be done) to ensure that after typing M-r, the re-run will always happen at a synced moment.

Sure. That’s where set_sched_ahead_time! comes in :slightly_smiling_face:
(Of course, the larger the value, the more latency there is between hitting Run and hearing the result).

1 Like

Thanks for helping with feature discovery. I hardly know the language beyond what the tutorial series covers.

The original problem continues: because my typing of the M-r is virtually always asynchronous, the jumbled sync problem is just offset by the parameter sent to set_sched_ahead_time!. The issue is that the parameter specifies absolute time, but sync is based on number of ticks.

My rhythm vectors—even if their values sum to the same number of beats—have different numbers of components (differing vector lengths), e.g. 2 eighth notes = 1 quarter note. Am I right that it would be unwieldy to track the current active location in a rhythm vector, particularly when there are several vectors active and changing at the same time?

The behavior I want is for the M-r to effect a reset of all ticks’ values to zero. It doesn’t matter to me to be precise as to when this change happens, everything staying in sync after the M-r being the primary musical thing.

A FEW MINUTES LATER :grinning::
After that last paragraph, I thought of scouring the reference for tick-related functions, and found tick_reset_all. A quick test in my toy example above shows that this small change will make M-r’s behave exactly the way I need:

in_thread do
  sleep 1
  cue :metronome
  tick_reset_all  # added
end

Thanks for encouraging self-initiated feature discovery. :+1: If you have any suggestions related to handling ticks in my context, please post.

Hi I’ve only got time this morning to scan read all this - I may not understand want you want (caveat, caveat…) but I’d point you at looking at doing the playing bit from within another thread, inside the live_loop I mean.

That allows you to kick off a sequence, however long, and it just runs in parallel with everything else and stops. Something I use a lot - you don’t have to do the mental maths to make sure your loops has the right combo of sleep lengths in.

Take a look and see if that does it for you :smile:

This kind of thing, that live loop :a is going to go round every 4 beats come what may…

live_loop :a, sync: :foo do
  in_thread do
     # play something in here, as long/short as you like!
  end
  sleep 4
end

I want to implement changing rhythms, which means changing sleep times. I don’t see where your sleep time changes or allows for changes that stay in sync. Perhaps fill in your sketch?

For example, my bdRhythm’s rhythm is sometimes all 1/4 notes and sometimes it’s 1/4 plus two 1/8 notes. I want to shift between the two patterns by live coding the difference, then reloading so that my kick loop’s associated sleep value is sometimes [1] and sometimes [1, 0.5, 0.5]. Where are you implementing the sleep pattern changes?

OK I did understand correctly. You see the bit where it says: play something in here, as long/short as you like? In there :smile: Whatever you put there and edit will get kicked off every four beats. Then you can have other loops sync’d off that live_loop :a, same format and they’ll all stay in sync.

I must say, I don’t exactly follow you. I do this:

##| ToneRhythm = [1, 1]
ToneRhythm = [1, 0.333, 0.333, 0.333]
ToneRhythmLength = ToneRhythm.length

live_loop :a, sync: :metronome do
  in_thread do
    play :e2, attack: 0, release: 0.5, cutoff: 100
    sleep ToneRhythm[tick(:tone_tick) % ToneRhythmLength]
  end
  sleep 4
end

in_thread do
  sleep 1
  cue :metronome
end

The tempo is rather slow, but I guess I could adjust for that by setting bpm. Then I uncomment the first ToneRhythm, comment out the second ToneRhythm, do M-r, and there’s no change in the rhythm. It also seems weird to me that there are nested sleeps.

That’s probably because you are currently playing one note from the melody per cycle of the loop, instead of a complete bar’s worth, and then waiting for 4 beats.

That’s just a quirk of nesting two threads - the live_loop on the outside and the in_thread on the inside.
The inner thread is playing your melody, but since that is a separate thread, as far as the live_loop’s thread is concerned, it has nothing causing it to wait/sync unless we add a further sleep/sync adjacent to the in_thread block, still inside the live_loop.

1 Like

This is the kind of thing, I’ve put in some percussion so you can hear what’s going on

##| ToneRhythm = [1, 1]
use_bpm 100
ToneRhythm = [1, 0.333, 0.333, 0.333, 1]
ToneRhythmLength = ToneRhythm.length

live_loop :a, sync: :metronome do
  sample :perc_snap, amp: 0.1
  in_thread do
    ToneRhythmLength.times do
      play :e2, attack: 0, release: 0.5, cutoff: 100
      sleep ToneRhythm[tick(:tone_tick) % ToneRhythmLength]
    end
  end
  sleep 4
end

live_loop :metronome do
  sample :drum_cymbal_pedal, amp: 0.1
  sleep 1
end
2 Likes

Simplyfing a bit, that tick is in its own thread so just needs…

Now you can live code the ToneRhythm to your heart’s content :smile:

##| ToneRhythm = [1, 1]
use_bpm 100
ToneRhythm = [1,0.75,0.5,1]
ToneRhythmLength = ToneRhythm.length

live_loop :a, sync: :metronome do
  sample :perc_snap, amp: 0.1
  in_thread do
    ToneRhythmLength.times do
      play :e2, attack: 0, release: 0.5, cutoff: 100
      sleep ToneRhythm.tick
    end
  end
  sleep 4
end

live_loop :metronome do
  sample :drum_cymbal_pedal, amp: 0.1
  sleep 1
end
1 Like

Sorry to go on and on but my meeting finished early and successfully so I’m in a good mood. You can put the other rhythm parts in the same loop, just a different thread…

##| ToneRhythm = [1, 1]
use_bpm 100
ToneRhythm = [0.5,1,0.5,0.5,0.25,0.25]
ToneRhythmLength = ToneRhythm.length

ToneRhythm2 = [1,1,0.5,0.5,0.75,0.5]
ToneRhythmLength2 = ToneRhythm2.length

live_loop :a, sync: :metronome do
  sample :perc_snap, amp: 0.1
  
  in_thread do
    ToneRhythmLength.times do
      play :e2, attack: 0, release: 0.5, cutoff: 100
      sleep ToneRhythm.tick
    end
  end
  
  in_thread do
    ToneRhythmLength2.times do
      play :b2, attack: 0, release: 0.5, cutoff: 100, amp: 0.5
      sleep ToneRhythm2.tick
    end
  end
  
  sleep 4
end

live_loop :metronome do
  sample :drum_cymbal_pedal, amp: 0.1
  sleep 1
end

Or in a different loop with different loop speed for some polyrhythm fun…

##| ToneRhythm = [1, 1]
use_bpm 100
ToneRhythm = [0.5,1,0.5,0.5,0.25,0.25]
ToneRhythmLength = ToneRhythm.length

ToneRhythm2 = [1,1,0.5,0.5,0.75,0.5]
ToneRhythmLength2 = ToneRhythm2.length

live_loop :a, sync: :metronome do
  sample :perc_snap, amp: 0.1
  
  in_thread do
    ToneRhythmLength.times do
      play :e2, attack: 0, release: 0.5, cutoff: 100
      sleep ToneRhythm.tick
    end
  end
  sleep 4
end

live_loop :b, sync: :metronome do
  in_thread do
    ToneRhythmLength2.times do
      play :b2, attack: 0, release: 0.5, cutoff: 100, amp: 0.5
      sleep ToneRhythm2.tick
    end
  end
  sleep 3
end

live_loop :metronome do
  sample :drum_cymbal_pedal, amp: 0.1
  sleep 1
end
1 Like

Thanks for spoonfeeding a n00b. I see my first and biggest problem is not comprehending the utility of in_thread, so I’ll study up on that. From the tutorial, I adopted the idea of writing complex functions, then calling those functions from within loops. I live code by making alterations in the function declarations, leaving the live loops alone. I’d totally forgotten about in_thread. Can you comment on any costs and benefits between the two paradigms? I guess I’m basically asking about the difference between a function and an in_thread called from inside a live_loop. Also, my messing with the tick flow seems a good way to lose visual track of what your doing, so maybe you have a comment on tick operations?

Putting the functions in loops requires me to be mindful of the functions’ order in the loop, which is sometimes useful. It can be constraining, though, so to keep individual functions independent, I was putting one function in each loop. That does seem like unnatural coding.

In the in_thread paradigm, which I take it is SPI’s basic way of doing things (before you try to get weird), I see that I must fill out my rhythm vectors to at least as many components as the sleep number in the live_loop, or else the rhythm gets padded out with rests. I’d gotten used to that automagically not happening, so that the rhythm vector [1] gave the same performance as [1, 1, 1, 1], a shorthand that may also have no real benefit. I’m actually a bit confused, though, as to what happens in your example when you have ToneRhythm2 = [1,1,0.5,0.5,0.75,0.5], whose six components add up to 4.25 beats, while needing to get ticked through six times to get to the end. When I squared it off to [1,1,0.5,0.5], it did what I expect, getting padded out to 4 with an invisible rest. Invisible can be confusing though; my original method always plays exactly what you see, and it’s up to you to ensure the durations add up to what you want.

I’m just trying to come up with a working method where I can keep track visually of the temporal modulations I’m working on in addition to keeping track aurally. Eventually, I want to put these rhythm vectors in matrices, then do matrix operations to not only transform a matrix’s values, but also to modulate its dimensions. It seems the number of columns should always match the loop’s sleep number.

Taking the easy one first…what is happening is that the rhythm defined by your sleep vectors is getting kicked off in a new thread at the start of each bar, and stops when it’s gone through all the elements - whenever they happen. So if you have sleeps adding up to more than a bar, you’ll get notes playing over each other - which can be a good or bad thing depending on what you want.

Think of each created thread as it’s own entitiy, which it is, and everything follows. Each journey through the live loop, a new thread is created and goes away on its own until it’s done its work.

If the total sleeps are less than a bar, then it will stop short of the bar and wait for the next bar. That’s what you call ‘padding with rests’. Again, you might want that or not.

If you want the rhythm to just go round and round regardless - that’s where we came in, and that’s the easy thing to do but it’ll get out of sync, depending on the specific pattern. Again, might be good.

If you want some kind of failsafe, so that you can code the sleeps to be whatever, but they cut off at the end of a bar, I’m sure a bit of logic would sort that out. Not tried it but I’d add up the sleeps and only play notes if they are inside the current bar.

The more complicated question - threads and functions. They’re not mutually exclusive, you can call a function from within a thread, so you could put all your ‘playing code’ in a function if you like. This is the joy and the pain of SPi - there’s lots of ways to acheive the same effects.

I feel we are moving towards a eureka moment. I had that when I started with Spi. Aaaah, now I see :smile:

1 Like

Yes, some aha’s over here already. The other issue is that I don’t precisely know what I want, so I’ll have to mess with different ways to see what they lead to, how they feel to live code, etc. I understand there are no mistakes, just sounds you either want or don’t want, but I do need to make sure that I’m not n00b’ly writing some sort of super-inefficient code that’s going to waste a lot of CPU compared to some other way of achieving the same thing more efficiently. As long as there’s no obtrusive performance hit, though, I’m happy to sacrifice CPU efficiency for a more visually obvious and trackable representation of what’s going on.

1 Like

Spi is pretty light on resources, the only problem I’ve had when using effects and with some of those you have to be careful about stucture. For the kind of thing we’re talking about here though, I doubt it’ll be a problem. Famous last words.