Basic question about sliding controls

Hi all, getting back into Sonic Pi after a lengthy break. Pretty simple question here, I think. Let’s say I’ve got a basic sample kick pattern. I want to be able to sweep a filter cutoff over an arbitrary period of time. I assume _slide is a viable option — I’m close but not quite there. When I run the following code, the filter cutoff sweeps from note 40 to 100. Because this is a live_loop, I thought I’d be able to change 100 back to 40, run the code again, and hear the filter sweep back down. But it doesn’t, it just stays at 100. I suspect I’m overlooking something that is obvious to others. Can anyone point me in the right direction?

with_fx :lpf, cutoff: 40, cutoff_slide: 8 do |fx|
  live_loop :q do
    sample :bd_haus
    sleep 0.5
  end
  control fx, cutoff: 100
end

This is an issue with the way things are implemented, I forget exactly what’s happening, but I think it’s something like: when you rerun the code, the :lpf fx is recreated, but the live_loop is still executing inside the original copy of the fx, so you are not actually controlling the correct copy of it.

A work-around that seems to work for me is to set the fx reference into the time-state from inside the live_loop so that you can always access the copy of the fx as seen from inside the live_loop:

with_fx :lpf, cutoff: 40, cutoff_slide: 8 do |fx|
  live_loop :q do
    set :fx, fx
    sample :bd_haus
    sleep 0.5
  end
  control get(:fx, fx), cutoff: 100
end

Note that the first time the code is executed, the control will run before the live_loop has started, so I provide a default value to the get.

1 Like

Thanks, this is helpful. It sort of works, but not exactly the way I imagined it. If I run the code, the cutoff sweeps up to 100 over 8 seconds. If I replace 100 with 40 and re-run, the cutoff sweeps back down. But then, if I set cutoff to 100 again, there’s no sweep back up. Do you know why this happens?

I feel like I’m getting a little closer. In the following example, I can change the mix value on-the-fly, but the change is immediate…how would I modify this code to allow me to produce a gradual parameter change over a longer period of time? I’ve tried adding :mix_slide in various places, but no results.

live_loop :b do
  with_fx :reverb do |fx|
    control fx, :mix, 0.9
    sample :bd_haus
    sleep 0.5
  end
end

Have a look at this example building on your example above.
Note
1 the fx command is outside the live loop.
2 the pointer to the |fx| control is stored in the time state as :fx using the set command
3 the use of the ‘at’ command to specify new parameter lists to use at certain times
4 the retrieval of the control pointer fx using get(:fx)

#here is an example which starts a loop with fx_reverb but with mix: and room: set to 0
#so no reverb is heard
#the at statements then control the fx settings for mix: and room: with a given slide time
#changes occur after 5,10,15 and 20 seconds

set :fx,nil #intialise :fx
set :stop,false # intialise stop flag
with_fx :reverb, room: 0, mix: 0 do |fx|
  live_loop :b do
    stop if get(:stop) #stop the loop at the end
    set :fx,fx if get(:fx)==nil #update it once
    sample :bd_haus
    sleep 0.5
  end
end
#at has two parameter lists. The first is the time at which the event is processed
#the second is a list of lists. Eech entry contains the values for mix room and slide time
at [5,10,15,20],[[0.7,0.8,4],[0.7,0.4,2],[1,0.5,4],[0,0,5]]do |v|
  tick
  fx = get(:fx);puts"fx pointer retrieved. Next change to effect settings started"
  puts "Setting fx_reverb:,mix: #{v[0]},mix_slide: #{v[2]}, room: #{v[1]}, room_slide: #{v[2]}"
  
  #now control both mix and room opts with siding values
  # v will be a list containing mix value v[0], room value v[1] slide time v[2]
  #I am using teh same slide time for each but more parameters if you want
  control get(:fx),mix: v[0],mix_slide: v[2],room: v[1],room_slide: v[2]
end

at 25 do  #use this at command to stop the loop after 25 seconds
  set :stop ,true
end

This is an useful study, thank you for providing so much detailed information. I am starting to understand the value of things like set, get, and at — and how their context matters.

However, this example doesn’t do exactly what I want — sorry for not being clearer in my earlier posts. I don’t want a predetermined “score” that specifies timings of parameter changes in advance. I want to embrace the spirit of live coding and make these changes at an arbitrary point in the future, to be determined by me, while the sound is playing. Basically, I want to run some sound-making code, then edit and re-run it, to create a gradual parameter shift. Is this possible?

I think I’ve found a sort of (hacky) solution. Using @robin.newman’s idea of only setting :fx if it has not already been set, that allows you to keep on changing it.
The only real issue is that you have to remember to include the set :fx, nil line on the first run, then comment it out for all subsequent runs, until you stop.
Then when you run it again you have to do the same (include it only for the first run).
I also had to include a bit of a wait before controlling the fx outside the live_loop, as it seems that it is not set immediately (it took about 0.2 seconds on my machine), I suspect this is something to do with the sched_ahead_time.

# comment this out after the first run (when you stop running, enable it again for the next first run):
set :fx, nil #intialise :fx

with_fx :lpf, cutoff: 40, cutoff_slide: 8 do |fx|
  live_loop :b do
    set :fx, fx if get(:fx) == nil #update it once
    sample :bd_haus
    sleep 0.5
  end
end

# this can take a little while the first time, wait until it's ready:
while get(:fx) == nil; sleep 0.1; end

# you can change the cutoff here and press run, and it should start sliding it each time:
control get(:fx), cutoff: 100

Another possibility which I use quite frequently is to send information in via an external midi keyboard or controller, or using an osc command sent from a program in another buffer, or indeed another computer running sonic pi. These can both be used to provide inputs to a running program.
eg

live_loop :min do
  n = sync "/midi*/note_on"
  puts n[0]
  control get(:fx), cutoff: n[0],cutoff_slide: 2
  
end
live_loop :oin do
  n = sync "/osc*/t"
  puts n[0]
  control get(:fx), cutoff: n[0],cutoff_slide: 2
end

These could be added to the program
The first one you can use a midi keyboard to send different note values to the loop which can adjust the cutoff with a 2 second fade. The second you could run this in a second boffer.

use_osc "localhost",4560
osc "/t",100 #here 100 is the new target cutoff

You can have both inputs available and use either while the program is running. Just tried it and it works fine.

I was convinced I had reproduced your problem with the reverb mix not sliding, and it was fixed by the changed synth, but prompted by @robin.newman’s comment on the PR I had another try and it seems that the synth does actually slide fine.
I think the issue with that particular code is that you are creating a new synth each time around the live loop, so it’s not having time to hear the slide. You can make the synth run over a number of repeats with reps: n, then you can hear the slide:

live_loop :b do
  with_fx :reverb, reps: 8 do |fx|
    control fx, mix: 0.9, mix_slide: 4
    sample :bd_haus
    sleep 0.5
  end
end
1 Like

Thanks both for these interesting ideas. I think I’ll be able to use this information to come up with a solution that works for me.