Using pitch_change and control_change from midi controller (ROLI Lightblock)

Recently got ahold of a ROLI lightblock and got it sending MIDI info to Sonic Pi.
This device is very touch sensitive and sends lots of info beyond just note_on and velocity namely pitch_bends, channel_pressure and control_change.
Curious if any one has played around with using pitch bends or mod wheels to send MIDI info into Sonic PI. I got it to do a bit of pitch bending but didn’t feel like I was getting much out of it.

Also when using it to trigger multiple notes, each note_on has a different number in the midi message:

One pad will give a message: "/midi/lightpad_block_gxcx_bluetooth/0/11/note_on"
Another pad will give: "/midi/lightpad_block_gxcx_bluetooth/0/14/note_on"
So it seems each pad has a different number prior to the “/note_on” part of the string. Is there a way to run that through a for loop or something similar without having to create a different variable for each pad message?

check out the magical get_event() function, but know its officially unsupported and subject to change/removal – give it a call in your external midi event handler right after sync

1 Like

I think teh number you refer to here is the midi channel. If you look at the string
then the 0 represents the midi port, the 11 the midi channel and the actual note midi value and velocity follow in the two pieces of data the event will carry.

If you want ALL then notes from the various pads to be processed in teh same way, you can put a wild card in for the channel eg
If you want the different pads to be processed differntly in the same loop leave the * in to do the match, and use the get_event() function mentioned above to extract the channel info eg 11 or 14.

an example loop might be

live_loop :test do
  b= "/midi/lightpad_block_gxcx_bluetooth/0/*/note_on"
  play b[0],release: 0.1,amp: b[1].to_f/127 #scale the velocity value 0->1 and use for amp setting
1 Like

Thanks. That worked in terms of getting all the pads to work. (See video below)

Is the get_event() fn different from set and get? Any suggestions how I can add pitch bends or get notes to sustain for as long as I hold the note down and stop when note off is sent? I had some minor success with pitch bends using a similar code from your OSC tutorial, but can’t get the note to stop with note off.
Here’s an example of the code I’m trying.

set :block, 0
set :off, 1
live_loop :bend do
  b = sync "/midi/lightpad_block_gxcx_bluetooth/0/5/pitch_bend"
  #puts "block is", b[0]
  set :block, b[0]

live_loop :off do
  off = sync "/midi/lightpad_block_gxcx_bluetooth/0/5/note_off"
  puts "note is", off
  set :off, off[1]

live_loop :midi_piano do
  note, on = sync "/midi/lightpad_block_gxcx_bluetooth/0/5/note_on"
  if on
    use_synth :tb303
    x = play (hz_to_midi get(:block)/10), sustain: 5
    set :x, x
    in_thread do
      loop do
        control get(:x), note: (hz_to_midi get(:block)/10)
    sleep 0.1
else if get(:off) == 0
  control get(:x),amp: 0,amp_slide: 0.1
  sleep 0.1
  kill get(:x)

I’ll look at this tomorrow if I have time. For the moment, take a look at the received cues in the cues log.
Do you get separate on and off messages sent from your midi controller when you tap it on and tap if again for off?
Also take a look at the raw pitchbend cues coming in. What range do you get?
some controllers send not_on when you press the pad and can use note_on with velocity 0 when you release. Others may send separate not_on when you press and note_off when you release.

There are quite a lot of issues in switching a note on and off again, but it canb be done.
I did a more complex set up to give an 8 note polyphonic synth which could be driven from a keyboard back in August. One of the issues is that you have to keep track of which note you are turning on and off if there is any chance that more than one note may be triggered at a time. You can see a detailed video of the resulting system on youtube.

This has links to the code and an accompanying article

It should be possible to build a simpler system using the same principles to fit your midi controller.

I’ll look further into this as time permits

I have done some work on this tonight and come up with a simpler program to give polyphonic sustained notes from a midi controller. I tested it with my midi keyboard, but by changing the sync strings to what your controller produces ti should work with that as well. eg note, on = "/midi/lightpad_block_gxcx_bluetooth/0/5/note_on"
pitchbend is a compromise. You can adjust the pitch of a note, but only before it starts playing, and not while it is playing. That can be done in the polyphonic synth I described in my previous post, but the program is much more involved. I’m not sure what info your pitchbend control produces. My keyboard gives a number in the range 0 to 163843. You will have to adjust the values in the live_loop :pb to suit your set up.

#polyphonic midi input program with sustained notes
#experimental program by Robin Newman, November 2017
#pirchbend can be applied to note AS 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 particlar pitch 1=on, 0 = 0ff

128.times do |i|
  ns[i]=("n"+i.to_s).to_sym #set up array of symbols :n0 ...:n127
#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
#puts sv(ns[64]) #for testing

live_loop :pb do #get current pitchbend value adjusted in range -12 to +12 (octave)
  b = sync "/midi/steinberg_ur22mkii__port1/3/1/pitch_bend" #change to match your controller
  set :pb,(b[0]-8192).to_f/8192*12

live_loop :midi_piano_on do #this loop starts 5 second notes for spcified pitches and stores reference
  note, on = sync "/midi/steinberg_ur22mkii__port1/3/1/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 :tb303
      #max duration of note set to 5 on next line. Can increase if you wish.
      x = play note+get(:pb), sustain: 5 #play note
      set ns[note],x #store reference in ns array
    if nv[note]==1 #check if this pitch is on
      nv[note]=0 #set this pitch off
      plist << get(ns[note])

live_loop :notekill,auto_cue: false,delay: 0.25 do
  if plist.length > 0 #check if notes to be killed
    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
  sleep 0.01

The first section of the program sets up three lists plist, ns and nv.
plist contains the references to notes which are currently active notes but need to be killed as the relevant midi input has changed to note_off (or in my case note_on with velocity 0)
ns containts an array containing the symbolic references to the notes. These are arranged as:n0 ,:n1… :n127 and are used with set and get commands to store and retriev the reference to playing notes
nv contains a list of the state of each note pitch , set to 1 if it is playing and 0 if it is not.

sv is a fucntion which retreives the numberic value of the note references, thus sv(:ny64) returns 64

live_loop :pb returns an number in teh range -12 to +12 depending upon the current pitchbend value (used to shift the note up or down an octave range)

live_loop :midi_piano_on detects inputs from the midi controller keyboard. If the velocity setting (the second paramter) is >0 then it checks the value of nv[note} and if it is 0 (ie the note is not already playing) it sets nv[note] to 1 (note playing) and starts 5 Second note with pitch note+pb value playing and stores a refereece to the note in ns[note] using a set command.
otherwise (on=0 and the key is released)
it checks to see if the note is playing (nv[note]==1) and if so resets nv[note]=0 and adss the reference to the note in ns[note} to the end of the plist.
The note is NOT stopped playing in this loop as that takes time, and wheh I tried it here, the loop missed processing some note releases if more than one note was released at a time. Doing this allows the references to the notes to be killed to be stored in plist without any being missed.

live_loop :notekill is used to actually stop notes playing.
It does so by checking if there are any notes to be killed (plist.length>0) and if so it pops a value from plist and uses it to first fade out the note
(control k,amp: 0,amp_slide: 0.02 )
and then to kill the note (kill k)
The very short sleep time at the end just ensures that there is a delay in teh loop when there are no notes waiting to be killed.
This loop is delayed from starting for a short period until the other loops are running, to eliminate some can’t keep up errors that occurred otherwise.

I enjoyed working at this, and will look at it further to see if it is possible to allow control of the pitchbend while the note is playing as in the much more involved polyphonic synth I developed last August, although I’m not sure if it can be done at present.

I’ve now managed to get it controlling note pitchbend while the notes are playing. I’ve found this a great project, and it has been good to produce a simpler version of my previous polyphonic gated synth. This one does not have limitations on polyphony either and can handle a whole armful of notes (pressing my am down on a keyboard!) without any problem.

I’m going to produce a separate writeup on my blog and will post a link here when its done.
I also want to test to see if it will run OK on a Raspberry Pi.

1 Like

I’ve now published my code for the gated polyphonic synth with pitchbend control in a separate thread here

1 Like