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?
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?
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?
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 http://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_loop
s 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 !
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
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
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 !
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.
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