Programmatically Creating Live Loops On The Fly

Is there a way to create a live_loop on the fly, programmatically?

The way I know how to make live_loops is like this:

live_loop :fixedname do
     # Do Something
     Sleep 1     
end

Is it possible to have “:fixedname” be something dynamic, that can be created during runtime? In other words, would it be possible to create a series of live_loops where the name is created on the fly, based on strings from an array? Ultimately, I’m hoping to create a bunch of loops that have unique names so that each can have their own timing.

Thanks for taking a look, community!! :slight_smile:

I figured it out! Thought I’d share for anybody else who might be trying to solve the same problem:

define :createloop do |loopname, looptiming|
	live_loop loopname.to_sym do
		sleep looptiming
		print loopname.upcase + ": " + looptiming.to_s
	end
end

looparray = ["loop1", "loop2", "loop3", "loop4"]
looptimes = [1, 3, 8, 2]

looparray.each do
	createloop looparray[tick], looptimes[look]
	print "BOOM!"
	sleep 1
end

Happy coding, friends! :slight_smile:

Thank you. I was thinking about doing that the other day and now I have a solution. Great work.

1 Like

Sweet! Glad I could help @Bubo :slight_smile:

I did something like this some time ago in a project

Here is a progam which creates 7 different live loops based on different loop samples. These can be chosem whenver you like and can be stopped again whebver you like.
In this demo each loop is played in sequence for 8 beats and then the next one starts,

live_loop :metro do #metronome to sync stuff together
  sleep 1
end


define :doLoop do |n,vol,sampleName,bs|
  set ("c"+n.to_s).to_sym,1
  set ("kill"+n.to_s).to_sym,false
  ln=("name"+n.to_s).to_sym
  in_thread do
    loop do
      if get( ("c"+n.to_s).to_sym)==0
        s= get( ("s"+n.to_s).to_sym)
        kill s
        set ("kill"+n.to_s).to_sym, true
        stop
      end
      sleep 0.1
    end
  end
  live_loop  ln, sync: :metro do
    s=sample sampleName,beat_stretch: bs,amp: vol
    set ("s"+n.to_s).to_sym,s
    k=(bs/0.1).to_i
    k.times do
      sleep bs.to_f/k
      stop if get( ("kill"+n.to_s).to_sym)
    end
  end
end

define :doCommandSelect do |n|
  puts n
  case n
  when 0
    doLoop 0,0.5,:loop_amen,4 #parameters: channel,vol,samplename,beatstrech value
  when 1
    doLoop 1,0.5,:loop_garzul,16
  when 2
    doLoop 2,0.8,:loop_compus,16
  when 3
    doLoop 3,0.9,:loop_mehackit1,4
  when 4
    doLoop 4,0.7,:loop_mika, 16
  when 5
    doLoop 5,0.7, :loop_weirdo, 4
  when 6
    doLoop 6,0.7, :loop_mehackit2,4
  else
    puts "Do Nothing"
  end
end



7.times do |n|
  doCommandSelect n
  sleep 16
  set ("c"+n.to_s).to_sym,0
end

You can see the original project in operation in this video.

1 Like

Nice work, @robin.newman! Your youtube videos have already inspired me to experiment with touchOSC, and now I can appreciate your solution to this as well! Thanks for sharing your hard work with me and the rest of the coding community! :slight_smile:

Just to add another solution: I also had to generate live_loops on the fly. In my case the number of loops depends on the user configuration so I had to access the live_loops name and several other variables (e. g. knobs of the Midi controller) by index. Thats why I created a config array which contains sub entries with variable names. This array I can access with Ruby’s size expression and an index. After the config array follows the actual binding of the values to the variables. If you are interested:

Five years later, but I’m looking at doing something similar. I would like to have a big file with a whole lot of functions that I can use to create live loops through specification of parameters, and then control them later on.

I didn’t know that it would be possible to start a live loop with a custom symbol in a function. I was hoping that the loops themselves would include some randomisation, but this could be done through a single random seed.

I attempted to create the loops as strings, and then use run_code to run them. While I’m nowhere near ready to write the real functions, I have this:

num_loops = 0
loops = []
descriptions = []

define :create do |seed1, seed2, samp, desc=""|
  
  use_random_seed seed2;
  pan = rrand( -1, 1 );
  amp = rrand( 0.3, 0.5 );
  
  loop = ":loop" + num_loops.to_s;
  
  code = " live_loop " + loop + " do \n" +
    " sync :timing\n "  +
    " use_random_seed " + seed1.to_s + "\n" +
    " 16.times do \n" +
    " sample " + samp.to_s + " if one_in(3)\n" +
    " sleep 0.25 \n" +
    " end \n" +
    " end \n";
  
  print code
  
  loops[num_loops] = loop;
  descriptions[num_loops] = desc;
  
  run_code code;
  num_loops = num_loops + 1;
end

define :stats do
  num_loops.times do |i|
    print loops[i] + " : " + descriptions[i];
  end
end

use_bpm 127

live_loop :timer do
  cue :timing
  sleep 4
end

That creates the function, as well as setting up a synchronisation loop. I can then use another buffer to call the function to create loops, and print out stats on the current loops.

# create 8354, 5233, ":bd_haus", "Drum Beat"

create 2831, 3563, ":sn_dolf", "Snare Bit"

stats

I do want to be able to control the loops once they are created, and would probably have a third array storing ‘status’ and additional functions (and code in the loops) to allow loops to be paused and controlled in other ways.

If anyone has any comments on the best way to do this sort of thing, that would be appreciated.

As other things got in the way, finally got around to this now.

use_bpm 127
use_debug false

num_loops = 0
stats = []
names = []

define :make do |seed,name,pan|
  set name.to_sym, "go"
  my_num = num_loops
  stats[ num_loops ] = "running"
  names[ num_loops ] = name;
  num_loops = num_loops + 1;
  
  live_loop ("loop_" + name).to_sym, sync: "/live_loop/timer" do
    
    if ( get( name.to_sym ) == "stop" )
      stats[my_num] = "stopped"
      stop
    end
    
    use_random_seed seed
    
    16.times do
      sample :drum_cymbal_closed, pan: pan if one_in(3)
      sleep 0.25
    end
  end
end

define :stats do
  num_loops.times do |i|
    print "Loop: " + names[i] + " status: " +
      stats[i]
  end
end


live_loop :timer do
  cue :time
  sleep 4.001
end

make( 3652, "one", -1 )
make( 7462, "two", 1 )
make( 8712, "three", 0 )

And, these loops can be stopped:

set  :one, "stop"

The method of creating a loop as a string then using “run_code()” might be slightly more flexible, but I think this method of starting live

Using this it’s possible to restart a live_loop, simply by creating a new, identical, one. I’ll need to refine my methods of tracking stats however.

Using @robin.newman recent example as inspiration, I’ve been interested having a set of utilities or “higher order components” (that’s a fancy programmer term) to create live loops with a more generalized or “templated” approach.

One goal of this approach is to separate out the music bits from the machinery. I like the readability of having all the musical ideas together without the live_loop boilerplate code around each one. I don’t love the convention of using method(:someMethod) to pass the block of code but that was the first incantation that worked for my goal. I was trying to find a way to also automatically call the block of code based on the name only but couldn’t crack it yet (treat the string as a method name and call it)
Standard Sonic Pi is a subset of Ruby and may change warnings apply :slight_smile:

use_synth :tri

define :lone do
  play scale(:c5,:minor_pentatonic).choose,release: 0.1
  sleep 0.1
end

define :ltwo do
  play scale(:g5,:minor_pentatonic).choose,release: 0.1
  sleep 0.1
end

define :lthree do
  play scale(:c6,:minor_pentatonic).tick,release: 0.1
  sleep 0.1
end

RUNSTATE_KEY = "ll_runstate_"
LIVE_LOOP_NAME_KEY = "ll_"

define :runLoop do |name, fn|
  loopsym = (RUNSTATE_KEY + name).to_sym
  set loopsym, true
  live_loop (LIVE_LOOP_NAME_KEY + name).to_sym do
    fn.call()
    stop if ! get(loopsym)
  end
end

define :stopLoop do |name|
  set (RUNSTATE_KEY + name).to_sym, false
end


live_loop :control do
  puts "start live_loop :lone"
  runLoop "lone", method(:lone)
  sleep 4
  
  puts "stop live_loop :lone"
  puts "start live_loop :ltwo"
  stopLoop "lone"
  runLoop "ltwo", method(:ltwo)
  sleep 4
  
  puts "stop live_loop :ltwo"
  puts "start live_loop :lthree"
  stopLoop "ltwo"
  runLoop "lthree", method(:lthree)
  sleep 4
  
  puts "ADD live_loop :lone"
  runLoop "lone", method(:lone)
  sleep 2
  
  puts "ADD live_loop :ltwo"
  runLoop "ltwo", method(:ltwo)
  sleep 4
  
  puts "stop live_loop :one"
  puts "stop live_loop :ltwo"
  puts "stop live_loop :lthree"
  stopLoop "lone"
  stopLoop "ltwo"
  stopLoop "lthree"
  sleep 2
end