Timing of different live_loops, syncing

Hi all -

MacBook Air M1 chip
Ventura 13.3
SPi 4.3.0

I am an Adv Beginner with Sonic Pi. And I’ve been working for the last five months at integrating SPi with my current workflow, including using Logic Pro. I am enamored with Live Coding. I am eager to sponsor some AlgoRave events in my area. But I recognize that my coding skills need to improve dramatically before I can realistically start something like that.

So here’s my latest challenge. I am using a live_loop to run chord changes. Using time state (set/get) to push chord changes out which can then be read by various instruments. I have live_loops for each of several instruments. They parse the current chord and play individual notes (via virtual MIDI in my DAW).

It’s the timing that is not quite right. And I feel like I need to deepen my understanding of how SPi prioritizes the various loops.

Problem: My chord changes are arriving late. When my song moves from Am to Dm, the first note of the measure is still playing :a1, from prior chord. By the second note of the measure, the chord change has been received. Everything plays fine, until the next chord change. Then again, the change is not received until the second note of the measure.

Note: to simulate the chord change, you have to go to the :chordChanger loop and change the reference to :chord1 to :chord2. Then refresh the code. This is by design. I want to control chord changes on the fly.

Can someone help me to understand the ‘why’ behind this issue? I have had similar problems crop up in different ways.

Thanks for everyone’s patience in helping me out with this. Y’all are the best!

use_bpm 120

# sequencer
live_loop :chordChanger do
  set :bassChord, get[:chord1]
  set :bass1Rhythm, get[:bassRhythm1]
  set :bass2Rhythm, get[:bassRhythm2]
  sleep 16
end

# kick
in_thread do
  sync :oneBar
  live_loop :kick do
    vel = 110
    kick vel if [1,1,0,0,1,0,0,0].tick == 1
    sleep 0.5
end end

in_thread do
  sync :oneBar
  live_loop :snare do
    snare 110 if [0,1].tick == 1
    sleep 1
end end

set :bassRhythm1, [1,1,2,1,0,1,0,1, 3,1,0,1,0,1,4,1]
set :bassRhythm2, [1,1,1,1]
set :bassNotes, [ 0,0,1,0, 0,0,2,0, 0,0,3,0]
set :chord1, [:a1,:d2,:c2,:g1]
set :chord2, [:d2,:f2,:e2,:c2]

# bass1
in_thread do
  sync :fourBar
  live_loop :bass1 do
    chordVar = get[:bassChord]
    noteX = :a1
    if get[:bassRhythm1].tick != 0 then
      noteX = chordVar[get[:bass1Rhythm].look - 1]
      bass1 noteX, 110
    end
    sleep 0.5
    tick_reset :noteTick if look(:noteTick) >= 11
end end

#bass2
in_thread do
  sync :fourBar
  live_loop :bass2 do
    chordVar = get[:bassChord]
    noteX = :a1
    if get[:bass2Rhythm].tick != 0 then
      noteX = chordVar[get[:bass2Rhythm].look - 1]
      bass2 noteX, 30
    end
    sleep 0.5
    tick_reset :noteTick if look(:noteTick) >= 11
end end

#hihat
in_thread do
  sync :oneBar
  live_loop :hat do
    hihat 100
    sleep 0.25
end end

define :bass1 do |note,amp|
  midi note, port: "logic_pro_virtual_in", channel: 2, vel: amp
end

define :bass2 do |note,amp|
  puts "bass2 amp = "+amp.to_s
  midi note, port: "logic_pro_virtual_in", channel: 3, vel: amp
end

define :kick do |vel|
  midi 36, port: "logic_pro_virtual_in", channel: 10, vel: vel
end

define :snare do |vel|
  midi 40, port: "logic_pro_virtual_in", channel: 10, vel: vel
end

define :hihat do |vel|
  midi 42, port: "logic_pro_virtual_in", channel: 10, vel: vel
end

define :fx1 do |vel|
  midi 57, port: "logic_pro_virtual_in", channel: 10, vel: vel
end

define :snare2 do |vel|
  midi 39, port: "logic_pro_virtual_in", channel: 10, vel: vel
end

in_thread do
  live_loop :counter do
    tick
    if look % 32 == 0 then
      puts "Eight bar"
      cue :eightBar
    end
    if look % 16 == 0 then
      puts "Four bar"
      cue :fourBar
    end
    if look % 4 == 0 then
      puts "One bar"
      cue :oneBar
    end
    sleep 1
  end
end
1 Like

I find it odd that you are putting live loops in in_threads. You can put all that in just a live loop as far as I know.

I had the same problem with sync, the loop is not done playing and the next cue arrives before it arrives to the sync to stop and listen for it. You can make your loop wait for a small amount of negative time like sleep - 0.05 to fix that.

Sidebar: those in_threads are left over from writing the code live. If I am composing live, once I start a loop and I want to begin the next loop in sync with the first, I write in_thread at the top of the loop, give it a cue, then put the loop inside. This ensures all added loops are synched loops that are currently playing. Youre right, after that initial session, the in_threads are unnecessary.

Wow. That really worked. I added a sleep -0.05 command to the loop that is my “chord changer”. The loop still has sleep 16 at the end of the loop. This seems to have solved the issue. One line of code… less than 15 characters… beautiful. I didn’t know negative sleep values were possible. This could be a game changer for me. Thanks R2L!