Lately i’v been playing more with hardware synths (got a virus ti2 for myself as a birthday present) and thought that midi learn functionality for recording knob movements would be fun thing for Ziffers and Sonic Pi.
Here’s a simple learn method you can use to listen midi CC or Sysex messages from your hardware or vst synths and playback the recorded result:
# Learn method for MIDI CC or SYSEX messages. Listens to given sync port and sends backs the recorded result.
define :learn do |**opts|
knob = opts[:knob]
raise "No knob name given!" if !knob
knob = knob.to_s
sync_path = opts[:sync]
raise "No sync path given!" if !sync_path
port = opts[:port]
raise "No port given!" if !port
loop = opts[:loop] # Sync to this if given
channel = opts[:channel] || 1
length = opts[:length] || 4
halt = opts[:stop] || false
resolution = opts[:resolution] || 0.05 # Default sleep and recording "resolution"
set :event_time, 0
set :event_last_time, 0
set (knob+"_recording").to_sym, false
set (knob+"_event_list").to_sym, []
# Timer loop for counting beats
live_loop knob+"_timer" do
use_real_time
stop if halt
cur_time = get(:event_time)
if cur_time>=length
# Restart counters and stop
set :event_time, 0
set :event_last_time, 0
set (knob+"_recording").to_sym, false
events = get((knob+"_event_list").to_sym)
print "Recorded events:"
print events
stop
end
# Wait for first event before starting counter
if get((knob+"_recording").to_sym)
print "REC "+knob+": "+cur_time.to_s
set :event_time, cur_time + resolution
end
sleep resolution
end
# Midi learn loop for recording events (CC or SYSEX)
live_loop knob+"_recorder" do
use_real_time
stop if halt
cur_time = get(:event_time)
stop if cur_time>=length
sync_data = sync sync_path
events = get((knob+"_event_list").to_sym)
# Start recording with first event
if events.length==0
cur_time = 0
set :event_time, 0
set (knob+"_recording").to_sym, true
end
last_time = get :event_last_time
set :event_last_time, cur_time
difference = cur_time-last_time
events = events+[[sync_data,difference<=0 ? resolution : difference ]]
set (knob+"_event_list").to_sym, events
sleep resolution
end
# Event playback loop
live_loop knob+"_playback", delay: length do
sync loop if loop
use_real_time
stop if halt
recording = get((knob+"_recording").to_sym)
if !recording # Wait until recording stops
event_list = get((knob+"_event_list").to_sym)
event = event_list.ring.tick
if event then
if sync_path.end_with? "/control_change"
midi_cc *event[0], channel: channel, port: port
elsif sync_path.end_with? "/sysex"
midi_sysex *event[0], port: port
else
raise "Invalid sync path: "+sync_path
end
sleep event[1]
else
sleep resolution
end
else
sleep resolution
end
end
end
Example using knobs with sysex:
learn knob: "vol", sync: "/midi:2-_virus_ti_synth_5/sysex", port: "2-_virus_ti_synth_6"
To use the learn method, wiggle some knobs first and Sonic Pi can then autosuggest correct midi out path for the knobs. Knobs will either send CC or Sysex messages, which you can see in the Cues log. Recording will start when you run the method and start turning the knobs.
Example using knobs with cc:
learn knob: "cutoffs", sync: "/midi:2-_virus_ti_synth_5/control_change", channel: 2, port: "2-_virus_ti_synth_6"
You can also stop the recorded cc/sysex loop using stop parameter:
learn knob: "cutoffs", sync: "/midi:2-_virus_ti_synth_5/control_change", channel: 2, port: "2-_virus_ti_synth_6", stop: true
You can also record multiple knobs at the same time or run multiple learn loops. When playing out with multiple learn methods commenting out the running loops is a good way to keep things running smoothly. When learn method is runned again it automatically stops the running loop and starts recording again.
Syncing the CC or Sysex loop to the melody is not the easiest thing to do and there may be some room to improve there. Current default for recording “resolution” is 0.05, which can be changed using resolution parameter. You can also adjust the length of the record using length parameter and optionally sync to some existing loop:
learn knob: "cutoffs", sync: "/midi:2-_virus_ti_synth_5/control_change", channel: 2, port: "2-_virus_ti_synth_6", resolution: 0.1, length: 6, loop: :my_loop_to_sync
That’s about it. Have fun tweaking those knobs.