I have a project that is playing several samples of the same length in sync and I want to control the volume of each “track” via OSC. I have a small project working now (see abbreviated version below) that has OSC selecting between different sets of tracks, but the changes don’t take effect until the samples loop, whereas I’d like the mix to happen in real time.
Is there a way to control the volume of individual simultaneous sample loops asynchronously?
Edit: to clarify, my problem is that I can change the volume of any sample, but that only takes effect at the loop point. I want the volume to change when the OSC message is received
# test program, loops below replacing my own external loops, all the same length
live_loop :main do
sample :loop_breakbeat, amp: get(:trackVol)
sleep sample_duration :loop_breakbeat
end
set :trackVol, 1.0
live_loop :vol do
use_real_time
##| note,duration,value = sync "/osc*/pers/purpose"
value = 1 # normally value from OSC
set :trackVol, get(:trackVol) - 0.1
if get(:trackVol) < 0
set :trackVol, 1.0
end
puts get(:trackVol)
sleep 0.5
end
You can wrap the playing sample in an fx :level and control the amp: setting in the fx.
Code below illustrates.
There is one gotcha. Each time you run the program a new fx :level is gnereated. However the sample is always controlled by the initial one. The bodge solution is to comment out the second line so that the control always refers to the first fx :level set up.
Of course if you leave the program running and dont re-run without pressing stop first then this doesn’t matter.
with_fx :level,amp: 0 do |v1|
set :v1,v1 #nb comment after first run if you want to rerun with same fx level:
live_loop :s1 do #sample being played
sample :loop_breakbeat
sleep sample_duration :loop_breakbeat
end
end
live_loop :trig do #wait for incoming osc and control level: amp:
use_real_time
val = sync "/osc*/s1vol"
puts val[0]
control get(:v1),amp: val[0]
end
#generate osc calls to send new required amp value
use_osc "localhost",4560
live_loop :ov do
osc "/s1vol",rand(2)
sleep [0.25,1,2].choose
end
I use this technique a lot usually in conjuction with TouchOSC to generate the osc calls.
EDIT you can add an amp_slide: value too if you want smooth volume changes
Hi Robin - Thanks for the response, it looks promising. But when I run your example, I get the below error. Do I need to set |v1| in a variable somewhere?
Runtime Error: [workspace_nine] - Thread death ±-> :live_loop_trig Unable to normalise argument with key nil and value {amp: 0.169677734375} (RuntimeError)
@pvadbx - that is commented out on the first line inside the level fx block at the top of Robin’s example - there, v1 is intended to be stored in the state system with set(…) to access later. It’s important to note the point Robin mentioned above about v1 continually being overwritten unless you use a workaround such as those he suggests, in order to successfully modify the volume of that initial level fx without losing it in a later run…
You need to have the set :v1,v1 uncommented on the first run. You only need to THEN comment it if you want to be able to rerun the program wiht other values changed WITHOUT first stopping it.
Each time you press stop everything resets and it is the fx :level created on the FIRST run that you want to control.
Ethan’s answer gives furthert explanation of this.
I have edited the program in my orginal psot as it should be for the first run.
and welcome! This may be a little naive for your needs, but it’s how I do real time fx control:
use_real_time
s = :loop_breakbeat
t = sample_duration s
with_fx :level, amp: 0 do |vol|
live_loop :test do
sample s
10.times do
tick
control vol, amp: line(0,1.0, steps: 10).mirror.look
# amp (or any other fx) control in here
sleep t/10.0
end
end
end