Fading live_loop in /out?

Hello everyone, I’m new to Sonic Pi and to this forum, from which I have already gathered some precious information.

Now, I would like to find a simple way to fade sample live_loops in and out.

Can you help me?

2 Likes

I normally fade in my synths using a ramp ring. These rings are different in that when you tick to the end, they don’t wrap around but stay at the last value. You can reverse the ring to fade out.

live_loop :fade_in do  
  synth :beep, note: (chord :c3, :min).tick, amp: (line 0,1,steps: 32, inclusive: true).ramp.look
  sleep 0.25
end

You can reset ticks using tick_reset or tick_reset_all, but I don’t know how to do that within a look once instead of on all iterations. Perhaps someone else can help out?

1 Like

Hi @Julien,

I am wondering how to do this in an easy way for quite a while (and I do not yet have that easy solution).

Have a look at https://in-thread.sonic-pi.net/t/smoothly-fading-samples-and-synths/528; towards the end @robin.newman proposes a smart solution, though it is not as concise as I would wish it to be for a live coding situation.

In case you want to fade the whole buffer (fade the whole track), you can always use something like:

set_mixer_control! amp: 0, amp_slide: 16

For fading single live_loops I currently also use the approach @chris.krakou mentions (of course you can control any parameter with it, such as a low pass filter); the problem with that is, that you will have a stepwise fade (in the above example each fading step takes 0.25 beats).

If your live_loop has a runtime of e. g. 8 beats you can apply control (see some ideas of how to do that in the mentioned thread).

This is something I’ve thought about in the past, but I haven’t really come up with a good solution. The method I’ve used is cludgy because I use set and get to get a reference to a with_fx block to control outside the block itself. And, I’d presume that Sam’s intention in the syntax of the with_fx block is to prevent this.

Here’s a bit of sample code using the methods I’ve used before. This assumes that all fading in/out occurs over a bar or multiple whole bars. The code could be changed to have more flexible level changes.

use_bpm 127
use_debug false

#
# First live loop to control
#

with_fx :level, amp: 0 do |level|
  
  set :leveller1, level
  
  live_loop :drums do
    use_random_seed 13237
    
    at [ 0, 1, 2, 3 ] do
      sample :bd_haus
    end
    
    at [ 1, 3 ] do
      sample :sn_dolf
    end
    
    16.times do |i|
      
      if one_in( 2 )
        
        at i / 4.0 do
          sample :drum_cymbal_closed, pan: -0.7
        end
      end
      
    end
    
    sleep 4
  end
end

# second live loop to control

tscale = (scale :e4, :minor_pentatonic )

with_fx :level, amp: 0 do |level|
  set :leveller2, level
  
  live_loop :melo do
    use_synth :dsaw
    use_synth_defaults cutoff: 80, res: 0.9, sustain: 0, release: 0.35
    use_random_seed 8123
    
    8.times do
      play tscale.choose if one_in(2)
      sleep 0.5
    end
    
    
  end
end

#
# level targets per bar for each loop. Note that
# each number is a target and this code pans
# smoothly throughout a bar to the target from the
# previous value.
#
# To have pans of different lengths, different code
# will be needed, but that wouldn't be too complicated
#
#

levels1 = [0.3, 0.6, 1, 1, 1, 1, 0.5, 0.5, 0.2, 1, 1, 1, 1, 0.75, 0.5, 0.25, 0]
last1 = 0

levels2 = [0.7, 1, 1, 1, 0.5, 0.5, 0.2, 1, 1, 1, 1, 1, 1, 0.75, 0.5, 0.25, 0]
last2 = 0

#
# both arrays must be the same length or this
# won't work. This print statement checks
#

print "levels1.length: ", levels1.length,
  "levels2.length: ", levels2.length

#
# Allow loops to get going
#

sleep 0.05

#
# Cludgy, but get references to the with_fx blocks
#

sn1 = get :leveller1
sn2 = get :leveller2

#
# For each bar, do the fade
#

levels1.length.times do |i|
  lev1 = levels1[i]
  lev2 = levels2[i]
  
  print "Targets 1: ", lev1, " 2: ", lev2
  
  incr1 = ( lev1 - last1 ) / 40.0
  incr2 = ( lev2 - last2 ) / 40.0
  
  #
  # Now fade in/out over 40 divisions of a bar
  #
  
  40.times do
    last1 = last1 + incr1
    
    # mathematical inaccuracies can put us out of
    # range
    
    if last1 < 0
      last1 = 0
    end
    
    if last1 > 1
      last1 = 1
    end
    
    # udate volume of loop
    
    control sn1, amp: last1
    
    last2 = last2 + incr2
    
    if last2 < 0
      last2 = 0
    end
    
    if last2 > 1
      last2 = 1
    end
    
    control sn2, amp: last2
    sleep 0.1
  end
  
  #
  # Prevent drift due to mathematical uncertainties
  #
  
  last1 = lev1
  last2 = lev2
end

#
# All panning has finished. Stop everything
#

print "Finished"

stop

Thank you all for your answers !

Chris, your solution is simple and works right way for synths.

Perhaps it is a stupid question but is there a way this simple to fade samples like this ? I mean gradually play on the amp of a sample.

Thanks again !

1 Like

After I post the above, I realise that it would be best to have three arrays per controlled loop. One being times at which automation occurs. The other being the target level, and the third being the slide time. That makes things more flexible.

Using at to schedule volume changes inside a function, this appears to work:

use_bpm 127
use_debug false


set :stop, false

#
# First live loop to control
#

with_fx :level, amp: 0 do |level|
  
  set :leveller1, level
  
  live_loop :drums do
    
    if get :stop
      stop
    end
    
    
    use_random_seed 13237
    
    at [ 0, 1, 2, 3 ] do
      sample :bd_haus
    end
    
    at [ 1, 3 ] do
      sample :sn_dolf
    end
    
    16.times do |i|
      
      if one_in( 2 )
        
        at i / 4.0 do
          sample :drum_cymbal_closed, pan: -0.7
        end
      end
      
    end
    
    sleep 4
  end
end

# second live loop to control

tscale = (scale :e4, :minor_pentatonic )

with_fx :level, amp: 0 do |level|
  set :leveller2, level
  
  live_loop :melo do
    
    if get :stop
      stop
    end
    
    use_synth :dsaw
    use_synth_defaults cutoff: 80, res: 0.9, sustain: 0, release: 0.35
    use_random_seed 8123
    
    8.times do
      play tscale.choose if one_in(2)
      sleep 0.5
    end
    
    
  end
end

#
# level targets per bar for each loop. Note that
# each number is a target and this code pans
# smoothly throughout a bar to the target from the
# previous value.
#
# To have pans of different lengths, different code
# will be needed, but that wouldn't be too complicated
#
#

times1 = [0, 4, 8, 12, 20, 24, 25 ]
levels1 = [0.3, 1, 0, 1, 0, 0.5, 0]
slides1 = [0.5, 0.5, 0.1, 0.1, 0.5, 1.5, 0.5]

times2 = [0, 2, 4, 8, 20, 25, 26]
levels2 = [0, 1, 0, 1, 0, 1, 0]
slides2 = [0.5, 0.5, 0.5, 1.5, 1.5, 2, 0.5]

#
# Allow loops to get going
#

sleep 0.05

#
# Cludgy, but get references to the with_fx blocks
#

sn1 = get :leveller1
sn2 = get :leveller2

define :setup do |fx, the_times, levels, slides|
  
  the_times.length.times do |i|
    print "time: ", the_times[i],
      " slide: ", slides[i],
      " level: ", levels[i]
    
    at the_times[i] do
      control fx, amp_slide: slides[i]
      control fx, amp: levels[i]
    end
    
  end
end

setup sn1, times1, levels1, slides1
setup sn2, times2, levels2, slides2

at 28 do
  set :stop, true
end

Since amp: is a parameter for samples as well, my method should work on those as well.

I think what we’re running into here is a limitation in Sonic Pi’s current approach.

What I’m leaning towards here is having a default mixer synth per live loop. This would allow amplitude, lpf, hpf, and other basic FX to be controlled dynamically by referencing the live loop’s name.

Would this provide a solution to what you’re looking for?

# :foo here is the name of a live loop
control :foo, amp_slide: 8, amp: 0
3 Likes

As far as I can see it would. Looks like a great feature!

That seems like a good approach. I see (and use) this type of boilerplate often within live_loop:

if get :stop
  stop
end

Could it work to also have a play/stop flag available?

control :foo, stopped: true

That would be combining the synth node semantics (which is what control is currently designed to do) with thread life-cycle semantics (which is what a live_loop is).

Would you be looking for an external way to stop a running live_loop? If so, would stopping a live loop at the loop boundary (i.e. when it repeats) be good enough? This appears to be the behaviour of your current boiler plate code.

Yes, I’d use that if it were available :slight_smile:

Hello Sam, thanks for your answer. It looks great but I can’t seem to make it work, could you give me an example in context ? I’d be very grateful.

Here’s what I have:

  sample :bd_fat, amp: 2
  sleep 0.5
  control :fade_in, amp: 0, amp_slide: 8
end```

Hi Julien,

it doesn’t work because I haven’t implemented it yet. I was just suggesting it as something that could be built so I could get your feedback. Sorry for the confusion.

Hi !

Oh OK, my bad !:smiley:

Being able to effectively mix live loops would be, I think, a big improvement to Sonic Pi. It would make programming live_loops easier as coders wouldn’t have to worry about mixing when they’re writing the loop. They could leave that to later.

use_bpm 127

bassd = live_loop :bdrum do
  sync :timer
  
  at [0, 1, 2, 3] do
    sample :bd_haus
  end
end

snared = live_loop :snare do
  sync :timer
  
  at [ 1, 3 ] do
    sample :sn_dolf
  end
end

hats = live_loop :hats do
  sync :timer
  use_random_seed 3125
  
  8.times do |i|
    
    if (i % 2) == 0
      if one_in(2)
        
        at i / 2.0 do
          sample :drum_cymbal_open, sustain: 0.4 if one_in(2)
        end
      end
    else
      2.times do |j|
        if one_in(2)
          at (i*2+j) / 4.0  do
            sample :drum_cymbal_closed
          end
        end
      end
    end
  end
end

live_loop :metro do
  cue :timer
  sleep 4
end  

control bassd, amp: 0.7, pan: 0, lpf: 70
control snared, amp: 0.5, pan: -0.3, hpf: 70
control hats, amp: 0.35, pan: 0.4, hpf: 70

Having with_fx return an object similar to how synth, sample, or play does would also help make mixing easier. But, what if someone wants to apply several effects in a chain to a live_loop. Something like:

object = with_fx :level do
    with_fx :reverb do
       sample :loop_amen_full
    end
end

then how could the internal reverb be accessed?

I’m wondering if there is a way to have simple and easy mixing of the type that would be possible to have easy and flexible mixing without making the language convoluted or unsuitable for beginners.

If ALL of synth, with_fx, and live_loop returned an object that could be controlled through control, then that would be simple and easy to learn.

I’m curious about more sophisticated mixing, and wonder if there is a way to fake it in Sonic Pi. I’m going to give it a go.

PS: I too would definitely use stopping a loop at a loop boundary. I do, anyway.

2 Likes

Hi Chris,

I have tried your solution with a sample but I must be doing it wrong.

Here’s what I got:

  sample :bd_808.tick, amp: (line 0,1,steps: 32, inclusive: true).ramp.look
  sleep 0.25
end```

Sorry if I’m sticking my nose in, but for @Julien, the following works

live_loop :fade_in do
  sample :bd_808, amp: (line 0,1,steps: 32, inclusive: true).tick
  sleep 0.25
end

Hi

It does ! Thank you very much. Now what if I wanted to keep it running at the same volume after the fade in ? Would it be possible ?

This works, but I think there will be a much easier way. I thought there was a data structure in Sonic Pi that returns the last value for indexes greater than the length of the array. But, I can’t remember what it’s called.

live_loop :fade_in do
  if look < 31
    sample :bd_808, amp: (line 0,1,steps: 32, inclusive: true).tick
  else
    sample :bd_808, amp: 1.0
  end
  
  sleep 0.25
end