Live loops Sync questions :-)

Hi everybody,

I always have troubles with sync in sonic pi…
if somebody can explain why this following code do run as it runs, it would be kind !

use_bpm 120
beats_per_bar = 4

############################### TOOLS ###############################
live_loop :metronome do
  use_synth :beep
  play :a4, release: 0.5
  sleep 1
end

live_loop :_1_bar do
  use_synth :beep
  play :as5, release: 0.5
  sleep beats_per_bar
  # the cue is sent ONLY NOW /:( so sad no ?
end

live_loop :_4_bars do
  sleep beats_per_bar*4
end

live_loop :_8_bars do
  sleep beats_per_bar*8
end

live_loop :_16_bars do
  sleep beats_per_bar*16
end

############################### live_l_o(U)pssssssssss ###############################

# this loop will start once _4_bars has finished once and become free as as bird no synchronicity
live_loop :loop_sans_sync, sync: :_4_bars do
  sample :drum_cymbal_hard
  sleep 4
end

live_loop :loop_8_beats do
  # après la durée de la boucle _1_bar 4 beats
  sync :_1_bar
  use_synth :sine
  riff_8_notes = (ring :c3, :d3, :e3, :f3, :g3, :a3, :b3, :c4)
  # this pattern last 8 beats
  play_pattern_timed riff_8_notes, 1
  # oh it's strange i "miss" the cue "/live_loop/_1_bar" why have i wait the next one ?????
  # 
end

live_loop :loop_7_beats do
  #  _1_bar last 4 beats
  sync :_1_bar
  use_synth :pretty_bell
  riff_7_notes = (ring :c6, :d6, :e6, :f6, :g6, :a6, :b6)
  # this pattern last 7 beats
  play_pattern_timed riff_7_notes, 1
end
``
cheers

Hi @nlb. Here’s an explanation for what you are observing. In summary though - this change was made in order to avoid non-deterministic behaviour.

thank you @ethancrawford for your answer. I’m aware of this behaviour.
as a French, i am not sure to understand perfectly the github @samaaron answer…

But i may understand that the cue is sent at the end of his first execution but in my code what about the fact the :loop_8_beats does not restart after its first execution, why do we have to wait for 4 beats ?
sorry but i can’t understand. must miss something …

cheers

You can also check out this thread which goes into different ways to use sync and how it works.

I had similar issues trying to figure it out. :disappointed_relieved:

1 Like

hi @mrbombmusic

i have read this post before and as you may see, i posted a question…
but sorry to insist, is there anybody who can try my code and give some explanations of its behaviour.
cheers

Hi @nlb,

see, if my comments help to clarify the situation:

use_bpm 120
beats_per_bar = 4

############################### TOOLS ###############################
live_loop :metronome do
  use_synth :beep
  play :a4, release: 0.5
  sleep 1
end

live_loop :_1_bar do
  use_synth :beep
  play :as5, release: 0.5
  sleep beats_per_bar
  # the cue is sent ONLY NOW /:( so sad no ?
end

live_loop :_4_bars do
  sleep beats_per_bar*4
end

live_loop :_8_bars do
  sleep beats_per_bar*8
end

live_loop :_16_bars do
  sleep beats_per_bar*16
end
  
############################### live_l_o(U)pssssssssss ###############################
  
  # this loop will start once _4_bars has finished once and become free as as bird no synchronicity
  # comment by MB: if you write sync: :_4_bars into the loop line this loop will initially
  # meaning _once_ synchronised with :_4_bars; if you want it to be synchronised
  # permanently or every run of :_4_bars, write sync :_4_bars into the loop body
  live_loop :loop_sans_sync do
    sync :_4_bars
    sample :drum_cymbal_hard
    sleep 4
  end
  
  # comment by MB: Here it is the other way round:
  # :loop_8_beats waits until the sync comes from :_1_bar;
  # See: https://github.com/samaaron/sonic-pi/issues/1730#issuecomment-353114957
  # It will sync only _after_ the cue has been done. So the sync you can hear (:a5 from :_1_bar)
  # _together_ with the start of :loop_8_beat _is_not_ the sync of the current run.
  # A bit difficult to explain but easy to understand if you imagine that you send a cue
  # respectively receive a sync and expect both happening _at_the_same_ time.
  # In effect you will have to wait until the cue comes and next time your :loop_8_beats
  # will start.
  # Put sync command in the live loop line and it probably runs as you want it to.
  live_loop :loop_8_beats, sync: :_1_bar do
    # après la durée de la boucle _1_bar 4 beats
    
    use_synth :sine
    riff_8_notes = (ring :c3, :d3, :e3, :f3, :g3, :a3, :b3, :c4)
    #riff_8_notes = (ring :c3, :d3, :e3, :f3, :g3, :a3, :b3)
    # this pattern last 8 beats
    play_pattern_timed riff_8_notes, 1
    # oh it's strange i "miss" the cue "/live_loop/_1_bar" why have i wait the next one ?????
    #
  end
  
  live_loop :loop_7_beats do
    stop
    #  _1_bar last 4 beats
    sync :_1_bar
    use_synth :pretty_bell
    riff_7_notes = (ring :c6, :d6, :e6, :f6, :g6, :a6, :b6)
    # this pattern last 7 beats
    play_pattern_timed riff_7_notes, 1
  end

Thank you @Martin for your answer but this is not the question :slight_smile:

this is the original code which causes trouble.

use_bpm 120
beats_per_bar = 4

############################### TOOLS ###############################
live_loop :metronome do
  use_synth :beep
  play :a4, release: 0.5
  sleep 1
end

live_loop :_1_bar do
  use_synth :beep
  play :as5, release: 0.5
  sleep beats_per_bar
  # the cue is sent ONLY NOW this is the normal behavior
end

live_loop :loop_8_beats do
  # at the end of the first _1_bar this loop will start
  sync :_1_bar
  use_synth :sine
  riff_8_notes = (ring :c3, :d3, :e3, :f3, :g3, :a3, :b3, :c4)
  play_pattern_timed riff_8_notes, 1
  # oh it's strange i "miss" the cue "/live_loop/_1_bar" why have i wait the next one ?????
  # 
end

live_loop :loop_7_beats do
  #  _1_bar last 4 beats
  sync :_1_bar
  use_synth :pretty_bell
  riff_7_notes = (ring :c6, :d6, :e6, :f6, :g6, :a6, :b6)
  # this pattern last 7 beats and it works as expected, this loop will restart on the 9 beats.
  play_pattern_timed riff_7_notes, 1
end
``

May @robin.newman have an idea and time to maybe explain this piece of code.
i don't understand why nobody if sync is so obvious can explain the result of this code...
cheers

About the :loop_8_beats:

The loop basically sleeps for 8 (in the play_pattern_timed).
As the loop repeats and goes to the sync :_1_bar the corresponding cue already came, so it waits for the next one (similar to a race condition.)

One way to see what’s going on is to use cue in the timing loop and send the tick value.
Then read this value in the syncing loop and print it.

I have found that a lot of timing problems can be traced back to slight errors in timing, though most of my testing was done in 2.11. I might do some experiments later.
I use either sleep or sync in my code, rarely both together. If I do, I make sure the sleep adds up to less than I expect from the sync, so the loop is already waiting when the cue comes.

thank you @Davids-Music-Lab for your answer. I guess there is a change between version 2 and version 3 see post above.

Would you have some code example to show us using your tick log ?

in fact we need to know when the cue is really sent. On which beat or at what time ?
It is after the fourth beat or when the first beat of the _1_bar ?

 1 2 3 4                     1 2 3 4 
 _ _ _ _  cue is sent ?      _ _ _ _   

 1 2 3 4                     5 6 7 8
 _ _ _ _                     _ _ _ _

 1 2 3 4                     5 6 7 
_ _ _ _                      _ _ _ _

Cheers

Some info about cue and sync:

Live_loops always send a cue with their name when they repeat, not on creation. This causes the delay at the start of your code.
The cue command sends a cue with a name when the interpreter reaches it.
Sync makes the interpreter stop and wait for the next cue with matching name. The protocoll window shows a yellow ‘sync’.
When the cue comes, the interpreter continues and prints ‘synced’ in blue.

Check the help window > lang > live_loop for more details.

If you insert
print '1_bar'
after line 12, at the start of the loop, you can see in the protocoll window that the 1_bar loop has already repeated (and triggered the print command) when the 8_bar loop reaches the sync command.

As a general workaround I suggest to use play and tick instead of play_pattern_timed.

1 Like

@Davids-Music-Lab nice and clever code to clear the situation. big thanks.

So a "solution " a “workaround”

uncomment do
  #:syn is one behind clock
  live_loop :clock do
    puts "clock:  " + tick.to_s
    sleep 1
  end
  
  live_loop :syn_1 do
    sync :clock
    puts "syn_1:  " + tick.to_s  #gets called every beat
  end
  
  live_loop :syn_4 do
    sync :clock
    puts "syn_4:  " + tick.to_s  #only gets called every 5 beats
    sleep 4
  end
  
  live_loop :syn_3 do
    sync :clock
    3.times do
      play :c3
      sleep 1
    end
    puts "syn_3:  " + tick.to_s  #only gets called every 4 beats
    
  end
  
  live_loop :syn_almost_4_beats do
    sync :clock
    play :c8
    sleep 3.99
    
    puts "syn_almost_4_beats:  " + tick.to_s  # gets called every 4 beats :-)    
  end  
end

it’s a bit weird to use no ?

It is, but it works.

You can also use

4.times do
sync :clock
end

I think it is worth reading Sam Aaron’s comments in a previous thread
My solution would be to delay the start of the :metronome loop to give the other loops a chance to start and to be waiting for their initial sync, which ONLY applies the first time round (set in the define line of the live loop). Subsequently none of the loops need a sync, apart from the one with the 7 beat pattern. This needs to be delayed on each pass to the start of the next bar.
So applying this to your original code I get this. (Note ALL loops which are an exact number of bars can be synced directly to metronome. You done really need the loops for 8 beats, but you do ned the loop for 1_bar so that you can restart the 7 beat loop correctly.

Your code modified is below

use_bpm 120
beats_per_bar = 4

############################### TOOLS ###############################
live_loop :metronome,delay: 0.01 do #delay start of this by 0.01 to allow other loops to be ready
  use_synth :beep
  play :a4, release: 0.5
  sleep 1
end

live_loop :_1_bar,sync: :metronome do
  use_synth :beep
  play :as5, release: 0.5
  sleep beats_per_bar
  # the cue is sent ONLY NOW /:( so sad no ? NOT ANY MORE!!!
end

live_loop :_4_bars,sync: :metronome do #not required here
  sleep beats_per_bar*4
end

live_loop :_8_bars,sync: :metronome do #you don't really need this loop here
  sleep beats_per_bar*8
end

live_loop :_16_bars,sync: :metronome do #not used here
  sleep beats_per_bar*16
end

############################### live_l_o(U)pssssssssss ###############################

# this loop will start once _4_bars has finished once and become free as as bird no synchronicity
live_loop :loop_sans_sync, sync: :metronome do #:_4_bars
  sample :drum_cymbal_hard
  sleep 4
end

live_loop :loop_8_beats,sync: :metronome do # sync to :metronome not :_1_bar
  # après la durée de la boucle _1_bar 4 beats
  #sync :_1_bar
  use_synth :sine
  riff_8_notes = (ring :c3, :d3, :e3, :f3, :g3, :a3, :b3, :c4)
  # this pattern last 8 beats
  play_pattern_timed riff_8_notes, 1
  # oh it's strange i "miss" the cue "/live_loop/_1_bar" why have i wait the next one ?????
  #
end

live_loop :loop_7_beats do
  #  _1_bar last 4 beats
  sync :_1_bar #this loop needs to wait EVERY time for the start of the bar
  use_synth :pretty_bell
  riff_7_notes = (ring :c6, :d6, :e6, :f6, :g6, :a6, :b6)
  # this pattern last 7 beats
  play_pattern_timed riff_7_notes, 1
end

To summarise.
You need to delay the start of the main metronome to give other time to be ready to receive sync cues.

All loops that last a whole number of bars can be synced to :metronome

You only need a 1_bar loop if (as in this case) you have a loop which is NOT an integral number of bars. In this case it needs to be delayed until the start of the next bar. Here the sync appears INSIDE the loop and applies on each pass.

1 Like

It’s lovely to see such a great discussion about this. However, please could I ask that you refrain from using negative and potentially confrontational language in post titles such as “X sucks” instead preferring constructive language.

1 Like

@samaaron i change the subject you’re right !
it was with a smiley :slight_smile:

Thanks :slight_smile:

I totally didn’t take any offence. I just think it’s inportant to keep the right cultural tone :slight_smile:

2 Likes

my english is not perfect as it is not my natural language, i’m a “frog” so i thought the term was not so violent.

may i add just a piece of advice for your health during your set live coding, mind your back !
cheers

1 Like

Hi again,

as the issue of syncing live_loops quite constantly pops up (e. g. in my courses) I made a little sketch, trying to visualise the logic behind it. I am aware of the fact that technical accuracy is not always on good terms with easy-to-understand-clarity; the sketch tries to tackle practical problems of what you can experience while syncing live loops. Anyhow, please let me know if I did missunderstand anything, if I can be technical more precise without making it more complicated or if the sketch could be improved otherwise:

(If this is to small to read please let me know, I’ll upload a A4 PDF and link it here.)

@nlb: Yes, it’s a typo. Fixed that. Thanks for the hint!

8 Likes

@robin.newman, the delay: 0.01 is a nifty trick: Somehow like a conductor raised its stick (allthough timewise just the opposite happens) and everybody knows, now a downstroke will follow and then I will have to start playing my instrument :wink:

good idea but isn’t there a typo sync: :red no ?