(Another) Looper

Hello everyone,

I’m a beginner on sonic pi, this seem to be a very interesting and fun approach of making music / coding mixture. I would like to share my current looper project : it is a live looping device intended to be reactive and avoid complicated trigs and live fails by an explicit way of performing musical actions (basically one button for one action) and eliminated non musical actions (as well as erase or tap tempo). It comes from experiencing live looping with acoustic duet project (me on the guitar + singer). So it is voice looping oriented as it is trigged with a pad.

In fact I’ve already coded a proto version on ableton live + m4l (link) but it is not suitable for live performance.

So here is my code so far, it trigs standard sonic pi samples for the moment in order to simulate loops (to avoid having to really continuously loop during code constuction) :

define :initmidi1 do |r11, s11, p11, r12, s12, p12, r13, s13, p13|
  midi_note_on 0, velocity = r11 if r11!=99
  midi_note_on 2, velocity = s11 if s11!=99
  midi_note_on 4, velocity = p11 if p11!=99
  midi_note_on 12, velocity = r12 if r12!=99
  midi_note_on 14, velocity = s12 if s12!=99
  midi_note_on 16, velocity = p12 if p12!=99
  midi_note_on 24, velocity = r13 if r13!=99
  midi_note_on 26, velocity = s13 if s13!=99
  midi_note_on 28, velocity = p13 if p13!=99
end
define :initvar1 do |r11, s11, p11, r12, s12, p12, r13, s13, p13|
  set :rec11, r11
  set :stop11, s11
  set :play11, p11
  set :rec12, r12
  set :stop12, s12
  set :play12, p12
  set :rec13, r13
  set :stop13, s13
  set :play13, p13
  cue :ready if r11!=1 and s11!=1 and s12!=1 and s13!=1
end
define :timer1 do |t|
  set :duration1, t - get(:rec11_t)
end
define :clumsyloop1 do |n|
  if (get(:play11) == 1 or get(:rec12) == 1) and get(:_c1) == n
    sample :ambi_lunar_land
    sleep get(:duration1)
    if get(:rec12) == 1 and get(:_c1) == n
      set :autorec13, 1
      cue :auto
      stop
    end
    if get(:stop12) == 1 and get(:_c1) == n
      set :autoplay11, 1
      cue :auto
      stop
    end
  else
    if (get(:play12) == 1 or get(:rec13) == 1) and get(:_c1) == n
      sample :ambi_lunar_land
      sample :ambi_glass_hum if get(:stop12) == 0
      sleep get(:duration1)
      if get(:rec13) == 1 and get(:_c1) == n
        set :autoplay13, 1
        cue :auto
        stop
      end
      if get(:stop13) == 1 and get(:_c1) == n
        set :autoplay11, 1 if get(:stop12) == 1
        set :autoplay12, 1 if get(:stop12) == 0
        cue :auto
        stop
      end
    else
      if get(:play13) == 1 and get(:_c1) == n
        sample :ambi_lunar_land
        sample :ambi_glass_hum if get(:stop12) == 0
        sample :ambi_dark_woosh if get(:stop13) == 0
        sleep get(:duration1)
      else
        stop
      end
    end
  end
end

initmidi1 0, 0, 0, 0, 0, 0, 0, 0, 0
initvar1 0, 0, 0, 0, 0, 0, 0, 0, 0
set :duration1, 0
set :_c1, 0
set :autorec13, 0
set :autolpay11, 0
set :autolpay12, 0
set :autolpay13, 0

live_loop :manualtrig1 do # Trig with touch osc midi pad
  use_real_time
  note, velocity = sync "/midi:samsung_android_samsung_android_midi_1_28_0:1/note_on"
  if note == 0 and get(:rec11) == 0 # rec11 double click ignored  ======== looper1 master loop
    sample_free_all
    sample :ambi_choir
    set :rec11_t, Time.now.to_f # t0 for :duration1 further calculation
    initvar1 1, 0, 0, 0, 0, 0, 0, 0, 0
    initmidi1 99,0, 0, 0, 60, 0, 0, 60, 0
  end
  if note == 2 and get(:stop11) == 0# stop11
    sample_free_all
    timer1 Time.now.to_f if get(:rec11) == 1 # means that previous action = rec11
    initvar1 0, 1, 0, 0, 0, 0, 0, 0, 0
    initmidi1 0, 99, 0, 0, 60, 0, 0, 60, 0
  end
  if note == 4 # play11
    sample_free_all
    timer1 Time.now.to_f if get(:rec11) == 1
    initvar1 0, 0, 1, 0, 0, 0, 0, 0, 0
    initmidi1 0, 0, 99, 0, 60, 0, 0, 60, 0
  end
  if note == 12 and get(:rec12) == 0 # rec12 ======== looper1 first overdub
    sample_free_all
    timer1 Time.now.to_f if get(:rec11) == 1
    initvar1 0, 0, 0, 1, 0, 0, 0, 0, 0
    initmidi1 0, 0, 60, 99, 0, 0, 0, 60, 0
  end
  if note == 14 # stop12
    sample_free :ambi_glass_hum
    set :rec12, 0
    set :stop12, 1
    initmidi1 99, 99, 99, 0, 99, 0, 99, 99, 99
  end
  if note == 16 # play12
    sample_free_all
    initvar1 0, 0, 0, 0, 0, 1, 0, 0, 0
    initmidi1 0, 0, 60, 0, 0, 99, 0, 60, 0
  end
  if note == 24 and get(:rec13) == 0 # rec13 ======== looper1 second overdub
    sample_free_all
    initvar1 0, 0, 0, 0, 0, 0, 1, 0, 0
    initmidi1 0, 0, 60, 0, 0, 60, 99, 0, 0
  end
  if note == 26 # stop13
    sample_free :ambi_dark_woosh
    set :rec13, 0
    set :stop13, 1
    initmidi1 99, 99, 99, 99, 99, 99, 0, 99, 0
  end
  if note == 28 # play13
    sample_free_all
    initvar1 0, 0, 0, 0, 0, 0, 0, 0, 1
    initmidi1 0, 0, 60, 0, 0, 60, 0, 0, 99
  end
end
live_loop :autotrig1 do 
  use_real_time
  sync "/cue/auto"
  sleep 0.03 # stabilization delay
  if get(:autorec13) == 1
    sample_free_all
    initvar1 0, 0, 0, 0, 0, 0, 1, 0, 0
    set :autorec13, 0
    initmidi1 0, 0, 60, 0, 0, 60, 127, 0, 0
  end
  if get(:autoplay11) == 1
    sample_free_all
    initvar1 0, 0, 1, 0, 0, 0, 0, 0, 0
    set :autoplay11, 0
    initmidi1 0, 0, 127, 0, 60, 0, 0, 60, 0
  end
  if get(:autoplay12) == 1
    sample_free_all
    initvar1 0, 0, 0, 0, 0, 1, 0, 0, 0
    set :autoplay12, 0
    initmidi1 0, 0, 60, 0, 0, 127, 0, 60, 0
  end
  if get(:autoplay13) == 1
    sample_free_all
    initvar1 0, 0, 0, 0, 0, 0, 0, 0, 1
    set :autoplay13, 0
    initmidi1 0, 0, 60, 0, 0, 60, 0, 0, 127
  end
end
live_loop :timemanagement1 do
  use_real_time
  sync "/cue/ready"
  if get(:stop11) == 1 or get(:rec11) == 1 # stop11 and rec11 kill the live_loop
    sample_free_all
    stop
  end
  if get(:play11) == 1 or get(:play12) == 1 or get(:play13) == 1 or get(:rec12) == 1 or get(:rec13) == 1 # floating counter to avoid waiting till end of sleep time
    set :_c1, get(:_c1) + 1
    if get(:_c1) == 11
      set :_c1, 1
    end
  end
  live_loop :loop101 do
    clumsyloop1 1
  end
  live_loop :loop102 do
    clumsyloop1 2
  end
  live_loop :loop103 do
    clumsyloop1 3
  end
  live_loop :loop104 do
    clumsyloop1 4
  end
  live_loop :loop105 do
    clumsyloop1 5
  end
  live_loop :loop106 do
    clumsyloop1 6
  end
  live_loop :loop107 do
    clumsyloop1 7
  end
  live_loop :loop108 do
    clumsyloop1 8
  end
  live_loop :loop109 do
    clumsyloop1 9
  end
  live_loop :loop110 do
    clumsyloop1 10
  end
end

Some explanation of main blocks :

define :initmidi1 do |r11, s11, p11, r12, s12, p12, r13, s13, p13|
  # Midi ctrl init and enlightening feedback
end
define :initvar1 do |r11, s11, p11, r12, s12, p12, r13, s13, p13|
  # Status var init
end
define :timer1 do |t|
  # Measure of main loop duration
end
define :clumsyloop1 do |n|
  # Sample/loop parallel plays depending on situation
end

live_loop :manualtrig1 do # Trig with touch osc midi pad
  # Midi pad to trig actions 3x3 pad with below midi notes (C D E of different octaves) :
  #  [ 24 ] [ 26 ] [ 28 ]  =>  [ rec13 ] [ stop13 ] [ play13 ] overdub2
  #  [ 12 ] [ 14 ] [ 16 ]  =>  [ rec12 ] [ stop12 ] [ play12 ] overdub1
  #  [ 00 ] [ 02 ] [ 04 ]  =>  [ rec11 ] [ stop11 ] [ play11 ] main loop
end
live_loop :autotrig1 do
  # Automatic actions
end
live_loop :timemanagement1 do
  # Making loops alive
end

One of trickiest part for me was finding how to keep reactivity and avoid waiting for end of sleep time. It is the aim of :timemamagement1 live loop : it increments a floating counter that skip current sample and make it possible to perform on the fly actions, by bypassing current sample sleep in a way. It works but there has to be a smarter way of doing it…

To do list before having a real functioning version :

  • To find how to automatically connect and configure soundcard on boot
  • Replace sonic pi sample by real live looping
  • Optimize :timemanagement1 an :clumsyloop1 sections
  • Duplicate code (two parallel loopers)

Thanks for reading :nerd_face: