I have a function that creates a live_loop dynamically
define :start_note do |note|
set :enabled,1
print("add", note)
live_loop "loop-#{note}" do
ena = get[:enabled]
print("Loop", ena)
synth :bass_foundation , note: note
sleep 0.1
ena = get[:enabled]
print("Loop test", ena)
if ena==0
print("Loop stop")
stop
end
print("loop next")
end
end
I trigger the function via midi in messages. There is also a stop function:
define :stop_note do |note|
set :enabled,0
print("remove")
end
Now when I trigger a midi note for a very short duration I still hear the synth playback about 4 times which means the loop is executed 4 times before it detects that :enabled is 0.
I have also tested using a global variable āenabledā directly without using set and get. Same behaviour.
How can I get the live_loop (or thread) to react faster?
A nice idea to create a named live_loop then stop to kill your note. However I think this is quite resource intensive. I had a play and prevented the multiple note triggering (which will also add to the resources) by testing to see if the note has already been triggered.
This sort of worked but is not very fast.
define :start_note do |note|
use_real_time
currentnote=0
set :enabled,1
live_loop "loop-#{note}" do
use_real_time
ena = get[:enabled]
stop if ena==0
synth :piano , note: note if currentnote !=note
currentnote=note
sleep 0.05
end
end
define :stop_note do |note|
use_real_time
set :enabled,0
print("remove")
end
#added net two live_loops to get input from my keyboard which uses note_on with velocity zero for note release
live_loop :mstart do
use_real_time
n,v=sync "/midi*/note_on"
puts n,v
start_note(n) if v>0
end
live_loop :mstop do
use_real_time
n,v=sync "/midi*/note_on"
stop_note n if v==0
end
My own approach to get keyboard input playing a synth directly (which is essentially what this is) is shown in the program below which I originally wrote in 2017.
It keeps a record of notes started playing (each in initialised to last 5 beats) and then when the key is released it sends a signal to kill the particular note.
If your keyboard uses note_on and note_off rather than note_on with velocity 0 for note_off then it needs tweaking slightly.
This program will play midi-files OK (no channel 10 in this version) eg just tried it with Scott Joplinās The Entertainer.
#polyphonic midi input program with sustained notes
#experimental program by Robin Newman, November 2017
#tweaked to accommodate changes to midi cue handling in Sonic Pi from version 3.2.0
#pitchbend can be applied to note BEFORE IT STARTS
set :pb,0 #pitchbend value
plist=[] #list to contains references to notes to be killed
ns=[] #array to store note playing references
nv=[0]*128 #array to store state of note for a particular pitch 1=on, 0 = 0ff
128.times do |i|
ns[i]=("n"+i.to_s).to_sym #set up array of symbols :n0 ...:n127
end
#puts ns #for testing
define :sv do |sym| #extract numeric value associated with symbol eg :n64 => 64
return sym.to_s[1..-1].to_i
end
#puts sv(ns[64]) #for testing
live_loop :pb do #get current pitchbend value adjusted in range -12 to +12 (octave)
b = sync "/midi*/pitch_bend" #change to match your controller
set :pb,(b[0]-8192).to_f/8192*12
end
live_loop :midi_piano_on do #this loop starts 5 second notes for specified pitches and stores reference
use_real_time
note, on = sync "/midi*/note_on" #change to match your controller
if on >0
puts note,nv[note]
if nv[note]==0 #check if new start for the note
nv[note]=1 #mark note as started for this pitch
use_synth :bass_foundation
#max duration of note set to 5 on next line. Can increase if you wish.
x = play note+get(:pb),amp: on/127.0, sustain: 5 #play note
set ns[note],x #store reference in ns array
end
else
if nv[note]==1 #check if this pitch is on
nv[note]=0 #set this pitch off
puts"set #{ns[note]} to be killed "
plist << get(ns[note])#add reference to note to kill list plist
end
end
end
live_loop :notekill,auto_cue: false,delay: 0.25 do
use_real_time
if plist.length > 0 #check if notes to be killed
k=plist.pop
control k,amp: 0,amp_slide: 0.02 #fade note out in 0.02 seconds
sleep 0.02
kill k #kill the note referred to in ns array
end
sleep 0.01
end
Itās likely that the delay youāre seeing is due to the default schedule ahead time of 500ms that Sonic Pi uses to ensure timing is accurate regardless of any timing drift in the language runtime. The supported way of circumnavigating this default latency is to use use_real_time in the threads you wish to run in ārealtime modeā - which is typically when you want a thread that responds immediately to external events.
This is so weird! Just tested on my system (Arch Linux, Pipewire), and I get essentially the same delay (~400ms) with and without the use_real_time statements. I also tried putting a single use_real_time statement at the top of the buffer, on the theory that the threads might somehow sync better when inheriting the scheduling parameter from a common source, but no luck. Also tried set_sched_ahead_time! 0 and 1 at the top of the buffer, and neither of those changed anything.
This is pretty contrary to my intuition about Time State. Can anyone explain why it seems not to be the same between the two threads, or how to get it to behave as a proper global store of āreal-timeā parameters?
Or is it somehow an issue with @dwjbosman1 's and my systems, and for other people it works as expected?