Problem with nested live_loop and variable visibility

overview

Quite new to the party, but not to coding in general. But async programming in SP is new to me :slight_smile:

Running SP 3.2.2 on Win and trying to understand the internals of thread communication.
With my latest test, I had created nested live_loops to be able:

  • to send a cue from the end of :intro (which then stops)
  • to the loop which waits for the sync :intro => to start the inner live_loop :main
  • the example is simplified :slight_smile:

goal

The goal I try to achieve, is to have a boolean flag stop_main, which I want to change in live-coding.

The issue is, that when I have two nested live_loop the live-coding change of stop_main true is NOT be visible in the inner :main loop.

So I’ve just found out that replacing live_loop :start_main do with in_thread do seems to work!

Question

  • Can someone pls explain to me what the difference here is?
  • Is there another pattern how to make live-code changes (i.e. to variables) visible to nested live_loops?

Code:

live_loop :intro do
  4.times do
    play :a3
    sleep 0.125
  end
  cue :intro
  stop
end

stop_main = false
live_loop :start_main do
##| in_thread do
  sync :intro
  live_loop :main do
    4.times do
      play :c4
      sleep 0.25
    end
    puts "stop_main: ", stop_main
    stop if stop_main
  end
end

addendum

the statement I’ve made is only partially true:

So I’ve just found out that replacing live_loop :start_main do with in_thread do seems to work!

It only picks up the live-code change stop_main true WHEN it runs through the live_loop :intro before:

  1. seems the additional trigger ofcue/sync makes a difference
  2. in case I out-comment the live_loop :intro when making the stop_main true change (as I don’t want to play the intro again), then it will NOT pick up the changed variable value
  3. just double-checked but using live_loop :start_main do instead of in_thread do will not work either way (with or w/o the :intro loop)

still confuses me

Hello,

May i suggest you to search examples on this forum about live_loop. use the search field.
read some code and copy paste in your sonic pi and change it to test.

the sync system in sonic pi is not as easy as you may suppose. so avoid you too much complex code keep simple
good luck

Firstly I should state that I’m still yet to see a good reason for nesting live_loops. Perhaps if you’re generating new live loops to listen to different incoming events, that might be a reason, but in general if you find you’re nesting live loops, you can probably simplify your code.

Secondly, your :intro live loop is explicitly calling cue. This isn’t needed. Live loops automatically call cue with the name of their live loop (you can see this in the cue log). However, you stop your live loop so the implicit cue isn’t ever caught by any other thread, because they are all started after :intro - ordering matters for deterministic behaviour.

If you’re just trying to send a cue to :intro just call cue in the outer scope, there’s no need for a live loop.

With respect to sharing changes across threads, you can use get and set which will work deterministically (i.e. repeatably the same) and give you the ability to share data across threads/live loops. Take a look at section 10 of the tutorial on Time State for more information: https://sonic-pi.net/tutorial.html#section-10

2 Likes

With respect to sharing changes across threads, you can use get and set which will work

Thx for your feedback and the pointer to get/set!
Sounds that they are exactly the cross / thread safe communication channel which I’ve missed.

Secondly, your :intro live loop is explicitly calling cue

Because I wanted to send the cue at the END of my live_loop :intro, just before the stop.
Which seems to work the way I wrote it. Even when I remove the stop the explicit cue :intro sends the signal at the END of the 1st loop and not at the START.

I’ve started with a custom name for the cue/sync but figured out that it works with the loop-name too.

If you’re just trying to send a cue to :intro just call cue in the outer scope, there’s no need for a live loop.

Yes in this reduced example it can be simplified, but I was fiddling to find a more general pattern how many live_loops can start/stop each other by sending messages around, like a jam-session in a pub/sub style calling out the next one for a solo :).

And to me cue/sync and get/set are for sure key elements for this.
Nested live_loop for the moment is my solution

  • to keep the outer communication-loop with the sync
  • independent from the inner music-loop

Most likely there is a smarter solution… still ramping up :slight_smile:

2 Likes

I’m not 100% sure I’ve fully understood how you want your jam session to function. However, in general:

  1. Put all your repeating code in live_loops at the top level
  2. Start each live_loop with a sync

Then all your code is ready to fire, waiting for the cue to come in. At this point, you can either have a single thread call out all the cue as needed or have different live_loop trigger them.

However, of you want to just trigger something once, I think use in_thread (possibly wrapped in a define if you want to do it multiple times).

For synchronizing other aspects than timing - especially among several threads at once - set/get as Sam suggested. Alternatively, cue can take a second argument that will be returned by the matching sync call - this can be convenient for sharing small pieces of data in a more targeted way.