# Smooth Parameter Automation

yeah, sure. While editing most of the example got lost… I edited again and now it should be complete.

I am definitely interested in this topic. I have been playing around with sending OSC messages from p5.js to adjust parameters and was considering this very question. I’ve also run into it when playing around with micro-controllers to adjust parameters. I was curious if I could adjust the rate of a looped sample without having to wait until the loop had completed before changing, but making any real time adjustments without waiting out the completion of the loop is of interest to me. However, I’m looking at your code and trying to make some sense of how it is all working, so I don’t really feel like I have much to contribute at this point. but I am following this thread closely!

Here’s a video of what I have been playing with:

that’s actually not too complicated: I started with looking for a way to make fading of any paramater in a smooth way. Which means: make the fade somehow independant of the length of the `live_loop` it runs in. If I have a loop that runs for 8 beats I don’t want to wait that long until the parameter change and goes to the next value. So it was clear that I had to use `control`. My initial attempt goes like this (in the code above this is example 2):

• Provide a `ring` of values and step through it using `tick`
• use a control value and use `tick` again to advance within the `ring`
• if e. g. I have a `ring` like `(ring 0, 0.25, 0.5, 0.75, 1)` the initial value for (let’s say) `amp` should be `0`; then the `control` takes over and sets the `amp` to `0.25`. And here comes, what’s wrong about this: Now, in the next loop run, the 3rd `tick` does not start at `0.25` but with `0.5` and goes to `0.75` with the next `control`.
• so it was clear, I needed another `ring` for that (and I got input from Sam about that). The `ring` must look like: `(ring 0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1)`. That is, what the expression: `(line 60, 100, steps: 10, inclusive: true).stretch(2).drop(1).butlast` in example 1 is about. See the discussion in Manipualation of Rings to get further details about that.
• now the 1st `tick` (to stay with the example of the `amp` value) takes the `0`, the following `control` will take `0.25` with the 2nd `tick`, and then the 3rd `tick` will also have the `0.25` because the `ring` provides every value except the first one (`.drop(1)`) and the last one (`.butlast`) twice. (The `.reflect` in example 1 just let’s the filter value go back, when it has reached the maximum value.)

Hope this makes the background of my solution search more understandable

Now I would like 1. to have a function that provides me with a simple way to construct `rings` of that kind (which I sketched out here) and furthermore a way to be able to fade in/out a `live_loop` at any time, which is a bit more complicated because you will have to deal with the `tick` value to set it back. One way is to a) set the filter, amp or whatever value to the last reached position and then b) `reset` `tick`. After that you can use you `ring` again with the desired values (e. g. first you fade in the loop and then, after a while, you want to fade it out). So I know how, but I was wondering if it is somehow possible to cover that with one conveniant function …

Martin

1 Like

Hi Martin
Had a play this afternoon and came up with this:

``````define :fade do |min, max, len, type|
case type
when :up
b = (line min, max, steps: len, inclusive: true).stretch(2).drop(1).butlast.ramp
when :down
b = (line min, max, steps: len, inclusive: true).stretch(2).drop(1).butlast.reverse.ramp
when :wave
b = (line min, max, steps: len, inclusive: true).stretch(2).drop(1).butlast.mirror
end
end
l=fade min,max,11,type #11 ensures 20 steps for each up/down, 40 for wave
if type==:wave
dt=duration/40.0 #adjust step interval to give correct total time
else
dt=duration/20.0
end
#puts l.length,type #uncomment for debugging to check number of steps
puts "fadeControl #{min} #{max} #{duration} #{type}"
t=vt
tick_reset
l.length.times do
control get(:lv),amp: l.tick,amp_slide: dt
sleep dt
end
end
end

live_loop :test do
with_fx :level,amp: 0 do |lv|
set :lv,lv
sample :loop_amen_full,beat_stretch: 8
sleep 2
sleep 2
sleep 2
sleep 2
sample :loop_amen_full,beat_stretch: 8 #start another sample to give 16 second loop
sleep 8
end
end
``````

I put the fx within the live_loop :test so that you could alter and rerun the loop without stopping.

Basically I put the control section in a function inside a thread. I saved the control pointer lv using set :lv and retrieved it where necessary.
The function has 4 parameters:
the min and max amplitude values, to type of fade and the overall time. I set the len parameter internally passed on to your fade function as 11 which gives 20 steps for :up and :down and 40 for :wave. I adjust the step time dt appropriately to give the correct total time required for the fade. If you only want a 10 step face the vaule to use is 6 in which case adjust the divides for the dt calculation to 20.0 and 10.0 rather than 40.0 and 20.0
By interspersing calls to fadeControl (which doesn’t consume time in the live loop, as it runs in a thread) with suitable sleep commands you can place fades where you want them in the live_loop with whatever duration time you wish making sure they don’t overlap subsequent fades. for subsequent fades set the range so that they follow on seamlessly from the previous one. This doesn;t ahve to be so, but you will get a sharp transition if not.
If you want to start the live_loop at full volume adjust the initial amp setting in the fx_level line to amp: 1

Finally if you are not going to reurn the program once started you can then put the with_fx :level outside the loop, plus the line `set :lv,lv`

EDIT ADDENDUM you can put these two lines outside the live loop, and then comment them out (plus the associated `end` after the live_loop) once the program is running. You can then re-run and the fx call will still be in force until you actually stop the whole program. Then of course you will have to uncomment the lines so that a new fx is set up again when you next start the program once more.

I improved the parameter list for fadeControl to use start and finish, which makes it more logical when entering the data. So you get
`fadeControl(1,0.5,2,:down)` for example, which fades down from 1 to 0.5 in 2 seconds
and `fadeControl(0.3,0.8,4,:up)` which fades up from 0.3 to 0.8 in 4 seconds

``````

define :fade do |min, max, len, type|
case type
when :up
b = (line min, max, steps: len, inclusive: true).stretch(2).drop(1).butlast.ramp
when :down
b = (line min, max, steps: len, inclusive: true).stretch(2).drop(1).butlast.reverse.ramp
when :wave
b = (line min, max, steps: len, inclusive: true).stretch(2).drop(1).butlast.mirror
end
end
if type==:down
min=finish
max=start
else
min=start
max=finish
end
l=fade min,max,11,type #11 ensures 20 steps for each up/down, 40 for wave
if type==:wave
dt=duration/40.0 #adjust step interval to give correct total time
else
dt=duration/20.0
end
#puts l.length,type #uncomment for debugging to check number of steps
puts "fadeControl #{start} #{finish} #{duration} #{type}"
t=vt
tick_reset
l.length.times do
control get(:lv),amp: l.tick,amp_slide: dt
sleep dt
end
end
end

with_fx :level,amp: 0 do |lv|
set :lv,lv
live_loop :test do

sample :loop_amen_full,beat_stretch: 8
sleep 2
sleep 2
sleep 2
sleep 2 #there will be 1 second of silence here
sample :loop_amen_full,beat_stretch: 8 #start another sample to give 16 second loop
sleep 8
end
end
``````

Finally (!!)
here is a much more compact way of writing the live loop above, making use of the at command. It is shown below with the fx wrapper.
The remainder of the program in my previous post remains the same.
Here is a much more compact version of the live loop using the at command. This replaces the live_loop :test and fx wrapper above. The remainder remains as it is.

``````with_fx :level,amp: 0 do |lv|
set :lv,lv
live_loop :test do
at [0,2,4,6,8],[[0,1,2,:up],[1,0.3,1,:down],[0.3,1,1,:wave],[0.3,0,1,:down],[0,0.4,8,:wave]]do |param|
end
sample :loop_amen_full,beat_stretch: 8
sleep 8
sample :loop_amen_full,beat_stretch: 8
sleep 8
end
end
``````

Hi Robin,

this looks very promissing. I might have the opportunity at the weekend to have a closer look. Please give me some time for a feedback which I am eager to give. For now, thanks a lot that you took the time to look into it!

Martin

Fine Martin
I’m having fun playing with this. I’ve now changed things slightly adding the pointer :lv as a parameter inside the fadeControl function. This means that it is possible to have instances fo the function running in differnt live loops at the same time, each with their own fx :level wrapper.

``````define :fadeControl do |start,finish,duration,type,pointer|
if type==:down
min=finish
max=start
else
min=start
max=finish
end
l=fade min,max,11,type #11 ensures 20 steps for each up/down, 40 for wave
if type==:wave
dt=duration/40.0 #adjust step interval to give correct total time
else
dt=duration/20.0
end
#puts l.length,type #uncomment for debugging to check number of steps
puts "fadeControl #{start} #{finish} #{duration} #{type} #{pointer}"
t=vt
tick_reset
l.length.times do
control get(pointer),amp: l.tick,amp_slide: dt
sleep dt
end
end
end
``````

and a typical call would be
`fadeControl(0.3,0.8,2,:up,:lv2)`
which would control the fx :level reference by :lv2 fading it up from 0.3 to 0.8 over 2 seconds

So if you had three controlled loops one would be on :lv1 a second on :lv2 and the third on:lv3

Hi Robin, this sounds exactly like what I need. Will get back to you …

Great. I’ve Made further progress. Can now choose which type of parameter to alter so can deal for example with `with_fx :pan` or `with_fx ;reverb`. I now have an example with three nested fx for pan, level and reverb and all are controlled independently within the loop at different times.

I’m also working on a touchOSC input controller for this. Initial experiments working OK, and so far I’m controlling the amp of a live_loop with fades up and down and “wave” remotely. Now working to allow several controllers together.

I now have a complete working system with two large scale examples.
which I will post separately. One controls five with_fx wrappers associated with two live loops, with sliding sequences built into the code. Each slide starts from the end point of a previous one.

The second uses four wrappers, but this time they are controlled by aTouchOSC screen.

The two fundamental routines `fadeSteps` (essentially your `fade` function) and `fadeControl` are shown below, in a simple example program.

1 Like

Hi Robin, you are very industrious (hope that’s the correct word, never heard or used it but the dictionary told me …). I still need some time to process what you’ve written but will definitely get back to you. I have some questions and some remarks …

The TouchOSC version has a video

code is available at

1 Like

I finally had some time to look at your code in more detail. This is nice work! Thanks for looking into that and thanks for the inspiration!

There are however two obstacles which I find using this in the context of live coding:

1. Due to the nature of live coding I will (hopefully) never stop the code unless I have finished the tune. This means I will place an `fx` only very rarely outside of a `live_loop` because I almost always want it to be adjustable by reevaluation.
2. The other thing I found (unless I tested the wrong code or my SP installation behaves differently than yours) is: Once the fading has been done I want e. g. the volume to stay on the level of the finish value. As far as I checked your examples my own very simple ones behaves in this way: the fading will start again as soon as the `live_loop` starts again (which is obvious, because the sample’s `amp` is not set to the finish value and `tick_reset` is called every new run). Am I right? See this example:
``````define :fade do |min, max, len, type|
case type
when :up
b = (line min, max, steps: len, inclusive: true).stretch(2).drop(1).butlast.ramp
when :down
b = (line min, max, steps: len, inclusive: true).stretch(2).drop(1).butlast.reverse.ramp
when :wave
b = (line min, max, steps: len, inclusive: true).stretch(2).drop(1).butlast.mirror
end
end
if type==:down
min=finish
max=start
else
min=start
max=finish
end
l=fade min,max,11,type #11 ensures 20 steps for each up/down, 40 for wave
if type==:wave
dt=duration/40.0 #adjust step interval to give correct total time
else
dt=duration/20.0
end
#puts l.length,type #uncomment for debugging to check number of steps
puts "fadeControl #{start} #{finish} #{duration} #{type}"
t=vt
tick_reset
l.length.times do
control get(:lv),amp: l.tick,amp_slide: dt
sleep dt
end
end
end

with_fx :level,amp: 0 do |lv|
set :lv,lv
live_loop :test do
sample :loop_amen_full,beat_stretch: 8
sleep 8
sample :elec_beep
sleep 2
end
end
``````

Now I messed with your code, firstly to understand how it is working and secondly to try to build on it and find a solution to the outlined issues. I did not succeed yet (due to lack of knowledge and experience) but I have at least a better grasp of what’s going on as well as some ideas.

Here are some notes on my ideas (please read this with respect to the following code sketch):

• You are very right, that the direction of the fade is defined by the min and max values. I build on that and skipped the ‘type’ thing completely; at least for me the type `:wave` is not necessary or better: this should better be coded as a separate function (actually it could be serving as a sort of LFO).
• I’d rather would like to set the initial `amp` value within the `live_loop` and tried something like: `amp: (set :vol, 1)` but this does not work.
• Actually it is my idea that I have to set the initial value just once (hence, the name ), because
• I then would like to do something like: if [the current volume of s] != finish then continue with `faceControl` else set the [current volume of s] to finish (which then would be the starting point for the next fade)
• I think it migt be a solution to know the current value of `:s`'s `amp` because if it has reached the desired fading finish value `fadeControl` does not have to get called. Only if the current `:amp` differs from the finish value …

It might well be that I am thinking to complicated or into a very wrong direction. It’ll be nice if you had a look at that. I am definitely stuck.

Here is my unfinished framing which although not working might show what I am aming at:

``````define :fadeControl do | start, finish, duration, pointer |
l = (line start, finish, steps: 11, inclusive: true).stretch(2).drop(1).butlast.ramp
dt = duration / 20.0
tick_reset
l.length.times do
control get(pointer), amp: l.tick, amp_slide: dt
sleep dt
end
end
end

set :vol, 0

live_loop :test2 do
set :s, (sample :loop_amen, beat_stretch: 4, amp: get(:vol))
fadeControl(get(:vol), 1, 2, :s) # if current_vol of :s != finish go on
sleep 4
end
``````

Hi Martin
I am very busy this weekend so haven’t time to look at your answer in detail till Monday. However you can use it with the fx inside the loop. I have done do with three nested loops. Also you can do the control using at function or manually so it can happen independently of the loop and shouldn’t keep resetting.

@robin.newman, thanks, whenever you have time and leisure to do so …

Hi Martin
I have had another look at things and I think the code below does what you want. The fadeControl function can be used to control other opts, but here is set to control the volume (:amp) of the sample in loop :test

The fade is controlled by the function setVol which needs two parameters: the final amp level (normal range 0->1 (although you can go higher) It always starts from the current volume level (held in :vol) so the fade is smooth without any jumps. It runs independently of the live_loop :test duration. (8 beats) and can for example last for 16 beats, or 2 beats. The initial volume the first time the program runs is set to 0 in the defonce :setup code, although this could be set to 1 if you wish. Note for such a change to take place, you would have to quit and restart SP or use the override option for defonce.

To test, just run the program. (The initial fade setup 0->0.5 will happen. When it has finished, just set a new fade eg setVol 1,6 and press run again, and so on.

``````#function returns step values for the fade
define :fade do |start, finish, len|
b = (line start, finish, steps: len, inclusive: true).stretch(2).drop(1).butlast.ramp
return b #cam be omitted but makes explicit what happens
end

return if start==finish
l=fade start,finish,11 #11 ensures 20 steps for each up/down
dt=duration/20.0
puts "fadeControl #{start} #{finish} #{duration} #{pointer} #{opt}"
t=vt
tick_reset
l.length.times do
#note that amp: is equivalent to :amp=> This enables use of :amp stored in a variable
# similarly for pan: or phase: or any other similar
# note also how the corresponding _slide is created.
control get(pointer),opt=> l.tick,(opt.to_s+"_slide").to_sym => dt
sleep dt
set :vol,l.look #update current volume at end of each step
end
end
end

#set inital volume value on first run
defonce :setup do
set :vol,0
end

live_loop :test do
lv=sample :loop_amen_full,beat_stretch: 8,amp: get(:vol) #sample volume will be controlled
set :lv,lv
sleep 8
end

#defines setvol so that only final vol and duration need to be specified for each fade
define :setVol do |value,duration|
end

setVol 0.5,4 #This sets and carries out the fade
``````

EDIT I also have a version that will control volume and pan independently at the same time if you want that.

1 Like

Hi Robin, again thanks, did a quick check. Something seems to be wrong (might be my installation). I will need some time to check again an give some usefull feedback but will definitely look into this because it is on my live coding todo list.

Martin

yes! It does work. I am very greatful for this intelligent piece of code! As soon as possible I will try out some of my uses cases. This will most probably go into my startup library to be available as a standard extension.

To explain my earlier posting: Currently I have 3 SP versions, which I can start (3.1 and two 3.2x dev versions). There are some irregularities, which I am not surprised of that being developement versions. F. e. the midi fader control was not running stable (besides my stupid coding error). I also had some not expected issues with `control` when I first started building a fader function. In effect the same code ran one day and failed a few months later.

But this can be due to a lot of reasons. I did not have the time to investigate in a systematic way… Actually it was kind of the same with your example. So I still don’t know if it is just me, the SP version I am using or some reason rooted in my system environement or current state. Anyhow… time will show.

Martin

Glad it’s working for you. I’ve enjoyed playing with this. I’ve tried it with more complex setups using both fx calls (inside a live loop) and other parameters such as pan and echo. Can get some quite nice setups.

I just started using SP and was looking for a way to fade in a bass line. This seems to do the trick! I’ll def be playing around with it. Thanks