Smooth Parameter Automation

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:

Hi @mrbombmusic

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
define :fadeControl do |min,max,duration,type|
  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}"
  in_thread do
    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
    fadeControl(0,1,2,:up)  #fade up from 0 to 1 in 2 seconds
    sleep 2
    fadeControl(0.3,1,1,:down) #fade down from 1 to 0.3 in 1 second
    sleep 2
    fadeControl(0.3,1,1,:wave) #fade up and down from 0.3->1-> 0 in 1 second
    sleep 2
    fadeControl(0,0.3,1,:down) #fade down from 0.3 to 0 in 1 second
    sleep 2
    sample :loop_amen_full,beat_stretch: 8 #start another sample to give 16 second loop
    fadeControl(0,0.4,8,:wave) #fade up and down from 0->0.4->0 in 8 seconds
    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.

1 Like

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

The adjusted programs are below



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
define :fadeControl do |start,finish,duration,type|
  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}"
  in_thread do
    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
    fadeControl(0,1,2,:up)  #fade up from 0 to 1 in 2 seconds
    sleep 2
    fadeControl(1,0.3,1,:down) #fade down from 1 to 0.3 in 1 second
    sleep 2
    fadeControl(0.3,1,1,:wave) #fade up and down from 0.3->1-> 0in 1 second
    sleep 2
    fadeControl(0.3,0,1,:down) #fade down from 0.3 to 0 in 1 second
    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
    fadeControl(0,0.4,8,:wave) #fade up and down from 0->0.4->0 in 8 seconds
    sleep 8
  end
end

ADDENDUM EDIT
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|
      fadeControl param[0],param[1],param[2],param[3]
    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.
So fade control is now

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}"
  in_thread do
    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.

EDIT ADD
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

Hi @robin.newman,

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
define :fadeControl do |start,finish,duration,type|
  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}"
  in_thread do
    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
    fadeControl(0,1,2,:up)  #fade up from 0 to 1 in 2 seconds
    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 :wink: ), 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
  in_thread do
    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.

Will give fuller answer later.

@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

define :fadeControl do |start,finish,duration,pointer,opt|
  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}"
  in_thread do
    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|
  fadeControl get(:vol),value,duration,:lv,:amp
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

Hi @robin.newman,

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

More gold from Robin :ok_hand:
Lots to unpack here!

I guess my first question is, were the original aims achieved, eg “full real-time fader control” (if that’s accurate), including ability to change fade type

Another fascinating thread!