Declaring one-line Live-Loops

Hey,

Is there a way to create a more dense version of the live_loop and other loop structures? One may be tempted to do something like this:

live_loop :test do ; play 50 ; sleep 1 ; end

What I would like to do is to define a type of loop that is limited to one line only and that can accept any number of arguments and things nested inside. Something like this:

ll :test play 50 ; sleep 1 

The idea behind this would be to quickly create a large number of loops synchronised to each other, like this:

ll :test play 50 ; sleep 1
ll :test2, sync: :test ; play 50 ; sleep 0.5
ll :test3, sync:: test ; et caetera

It may not be the best idea for what concerns code organization and language design, but I would still enjoy to play with something like this.

2 Likes

I think that this is where a more dense pattern language like Ixi Lang makes sense…

I’m working on just that :slight_smile:

Given that sonic pi is just ruby under the hood you can create your own functions. For example

use_bpm 120

def lsample(name, sample_name, sleep_time)
  live_loop name do
    sync :zync
    sample sample_name, beat_stretch:sleep_time
    sleep sleep_time-0.001
  end
end

lsample :a, :loop_amen, 2
lsample :b, :ambi_piano, 2
lsample :c, :ambi_piano, 4
lsample :d, :ambi_piano, 8
lsample :e, :bass_woodsy_c, 16

live_loop :zync do
  sleep 0.5
end

But you do lose the intellisense of the sonic pi editor. That is you will have to remember all the sample names rather than get that little popup menu

For the record, the Sonic Pi way of defining functions is using define.

define :lsample |name, sample_name, sleep_time| do
  live_loop name do
    sync :zync
    sample sample_name, beat_stretch:sleep_time
    sleep sleep_time-0.001
  end
end

Whilst def does currently work, it’s not supported and may break in a future version without warning.

@bradgonesurfing : yes but you lost the possibility of adding more parameters than those that you defined when writing the function in the first place.

@samaaron : Cool! I just have to wait then! Thanks!

@bubo Within live_loop you can do pretty much anything so if you want a more compact DSL you have to decide on what you want. However you can pass through abitrary keyword value maps using splats to the inner code making it more flexible. @samaaron thanks for the headsup on def vs define.

use_bpm 120

define :lsample do |name, sample_name, sleep_time, fx_name, **fx_parts|
  live_loop name do
    sync :zync
    with_fx fx_name, fx_parts do
      sample sample_name, beat_stretch:sleep_time
    end
    
    sleep sleep_time-0.001
  end
end

lsample :a, :loop_amen, 2, :wobble, mix:0.1
lsample :b, :ambi_piano, 20, :wobble
lsample :c, :ambi_piano, 4, :flanger, decay:0.5, amp: 2
lsample :d, :ambi_piano, 8, :none
lsample :e, :bass_woodsy_c, 8, :none
lsample :e, :bass_woodsy_c, 12, :none


live_loop :zync do
  sleep 0.5
end

Can the same be done for threads?

I’d assume the following would play both parts at the same time, but it doesn’t.

use_bpm 130

define :run do |parts|
  parts.each { |part|
    in_thread do
      part
    end
  }
end

define :one do
  notes = (ring :a2, :a3, :a4, :a5)
  
  8.times do
    play notes.tick
    sleep 1
  end
end

define :two do
  notes = (ring :a2, :a3, :a4, :a5).reverse
  
  8.times do
    play notes.tick
    sleep 1
  end
end


run [one, two]

The problem is that you can’t pass the method via

run [one, two]

when you type

  one

or

  two

you actually are evaluating the functions rather than passing the method pointer. Ruby evaluates functions without brackets. Easy mistake to make. You could solve it by passing procs

use_bpm 130

define :run do |parts|
  parts.each { |part|
    in_thread do
      part.call
    end
  }
end

define :one do
  notes = (ring :a2, :a3, :a4, :a5)
  
  8.times do
    play notes.tick
    sleep 1
  end
end

define :two do
  notes = (ring :a2, :a3, :a4, :a5).reverse
  
  8.times do
    play notes.tick
    sleep 1
  end
end


run [->() {one}, ->(){two}]

or by passing symbols and using send to evaluate them

use_bpm 130

define :run do |parts|
  parts.each { |part|
    in_thread do
      send part
    end
  }
end

define :one do
  notes = (ring :a2, :a3, :a4, :a5)
  
  8.times do
    play notes.tick
    sleep 1
  end
end

define :two do
  notes = (ring :a2, :a3, :a4, :a5).reverse
  
  8.times do
    play notes.tick
    sleep 1
  end
end


run [:one, :two]
2 Likes