Support and Help

Ask your questions about using Sonic Pi here :slight_smile:

Ok, here’s a question: did someone (@robin.newman, maybe?) manage to get Sonic Pi to play as a polyphonic gated synth through a “normal” MIDI controller or MPE device?

Have yet to delve into Robin’s scripts from August, but they felt a bit involved and it sounded like they require an OSC source.

My goal is to use SPi as a sound generator with diverse controllers, the main one being the Eigenharp Pico. With monophonic input from a wind controller, my approach so far has been to use long notes (say, 1000s) with breath input modulating the cutoff frequency of a low-pass filter. The effect is pretty satisfying for a wind player like me. Though it’s technically a single note being played for the whole duration, you hear full phrasing with full control of the envelope.

Haven’t found a way to make this work polyphonically. In fact, still not sure what the proper way is to set up the loops to sync with the notes and the control changes, as they happen independently.

With MPE devices like the Eigenharp (or a ROLI device like the Lightpad Block), you can actually get the polyphony to apply to different channels. In other words, each note you play (up to 16) has its own MIDI channel, its own pitchbend, its own CC, etc. Sounds to me like it can open up really neat possibilities with Sonic Pi and might even solve part of the polyphony problem. It doesn’t solve the gating issue but since that controller also has a breath pipe, the same filtering trick can work (with breath controlling multiple notes at a time, which works well for me).

Oh, and part of this dream of my is to have this run on Raspberry Pi. Robin mentioned that his method was too taxing and my experience with a simple MIDI input on a Pi3 was that the latency can be too high. But, then, my hope is that the Blokas pisound HAT might magically shorten this latency to a reasonable level.

Anyhoo… Would be interested in hearing more from others who are experimenting with MIDI control to play multiple notes of different durations, all at the same time.

1 Like

You’re right - there is still some work to be done to bind external MIDI controllers to Sonic Pi’s internal synths. The major obstacle is that Sonic Pi currently requires you to define the envelope gate durations at trigger time which conflicts with a keyboard’s typical interaction of wanting to trigger gates with note on and note off.

I have some rough ideas but all of them involve quite substantial work to all of the synth designs to realise. However, I am very aware of the issue and I hope to resolve it soon. I think the next phase of work to tackle this is to introduce the equivalent of live_audio for synths.

2 Likes

Yes I did some work on this a while back. As Sam says, the main problme is that SP is not geared up to using a gated synth, as you have to define the envelope as the note starts, and can’t easily use note off signals. I did a system which works OK on a Mac, which intitiates a “long” note when a midi note-on signal is received and then waits for a note_off signal to terminate the running note. It is a bit kludgy and the main problem was in having a control loop for the running note which polled for the note-off signal and then killed the note. I also did a system to allocate the note_on signals to one of 8 channels, and then to filter the subsequent note_off signals so that the correct note was stopped.
The end result works fine with midi keyboard in, giving 8 note polyphony. OSC messages were used to select the synth to use, the volume, the cutoff, reverb and transpostion, but the basic idea will work with this all stripped out.
I spent some time modifying this in an attempt to get it to work on a Pi, but didn’t manage to get it running OK.
If you want to look at the code it is at https://gist.github.com/rbnpi/8446d5cd84ce63b3454ec28403a32d37
and there is an article at
https://rbnrpi.wordpress.com/2017/08/06/8-note-polyphonic-gated-synth-for-sonic-pi-3/ with a video link

PS thanks for reminding me of this work. I got a bit bogged down with it at the time, but taking another look I’ve just spent a happy hour playing it with a keyboard input, and enjoying the synth sounds that Sonic Pi make, especially with the reverb added. The code works well., and gives a nice flexible keyboard instrument.

3 Likes

Thanks! Will check things out.
Guess my main issue remains about an interaction between MIDI-sync loops. Will try to grasp what your code is doing to make it work. And will probably experiment with using multiple channels as it’s possibly easier to track than notes.

Since my musicking time has been somewhat limited recently and my personal bandwidth for coding is even more limited, pushed these issues aside for a while and focused on other methods to play with music in a very casual manner.

Thanks again for all your good work, @robin.newman! The Sonic Pi community benefits a lot from it.

1 Like

Nice to know. Unlike the MIDI/OSC/audio I/O stuff, won’t bug you to release ASAP. :wink:
Will surely find a workaround with the existing version. Just trying to figure out the best tactic to avoid “headaches”.

1 Like

Related issue: I have a Novation MIDI controller that has a number of knobs and pads. I’d love to use this to assign a particular control to each knob, e.g., cutoff of synth a, reverb mix value of live_loop b, etc., and use the pads to start and stop live_loops. Following the tutorial, I’m able to assign ALL knobs to one control, e.g., reverb mix value. Using MIDI, how can I parse and use the various incoming signals as separate channels. In Max I would use something like ctlin and specify the controller number.

The midi message when you turn a knob contains two numbers: the channel and the value of that knob. You can distinguish between knobs using the first one.

A basic example of how I do this is here: https://github.com/korfuri/tasty-pies/blob/master/tech/poc_midi2.txt

3 Likes

@korfuri - in your example you use $global_variables. It’s important to point out that these are not supported at all - instead please consider using get and set which are not only thread safe but also enable reproducible behaviour :slight_smile:

1 Like

Hi @samaaron,

what about the following case:

Working on my monome application I have 4 clips (representing samples). I do initialize e. g. lpf filter values applied to these samples like this:

set :lpf, [130, 130, 130, 130]

Filter will be open in the beginning. If I want to set lpf for clip no. 1 I will do that like that:

sample my_sample1, lpf: get(:lpf)[0]

Now I have some hardware interface (e. g. the monome) which delivers another lpf value for sample1. As I can’t do the following

live_loop :listen_to_monome do
   use real_time
   # some other code to keep track of the keys
   set :lpf[0], 40 # will of course throw an error
end

I am doing the following:

# initialization
lpf = [130, 130, 130, 130]
set :lpf, lpf

# and then
live_loop :listen_to_monome do
   use real_time
   # some other code to keep track of the keys
   lpf[0] = 40
   set :lpf, lpf
end

This kind of works as it is a way to change the immutable :lpf. The problem which I ran into is somehow an indirect consequence this: As I have too much code for one buffer (and would also like to separate library functions from configuration from program code) I have to rely on either several buffers or the run_code command; now of couse get/set works across files/buffers but my local variable lpf (which I initialize somewhere in the beginning of the buffer/file and will use later on) does - again of course - not as soon as it is in another buffer/file.

Maybe it is complete rubish what I am doing here (here is a more extensive example).

So, a bit more general: I do see the use of thread-save set/get but how can I access and change a list (or array) value, whithout using global (forbidden and/or not supported) or local (only valid within buffer) variables?

Martin you can effectively redefine the whole lpf list OK
consider this

set :lpf,[20,30,40,50]

newl0=[50] #new value you want to assign to the first element eg from your live_loop
set :lpf,newl0+get(:lpf)[1..-1] #you build a new list and reassign that to :lpf
newl2=[100] #new value to assign to the third element
set :lpf,get(:lpf)[0..1]+newl2+get(:lpf)[3..-1] #you build a new list and reassign that to :lpf
puts get(:lpf) #=> [50, 30, 100, 50]

I think you may be able to use this technique. You could write a function to do the build for the new list given the new value and the position as parameters

Oh, wow. Thanks Robin. I did not come up with this idea so I am glad you gave it to me! I’ll check it out at the weekend…

Just a question: Have you ever worked with classes and modules in Sonic Pi (or rather: thereby extending Sonic Pi)? In case you have, do you have some code I can look at?

Not really. Only some tentative attempts to use inbuilt modules in Sonic Pi to retrieve info.
I seem to remember someone posting code for Sonic Pi that did create a class, but it’s not something I have tried, and I think it begins to move away from the normal Sonic Pi environment maybe too much.

Hi @robin.newman,

I came up with the following, which seems to do the trick:

set :my_list, [1, 2, 3, 4, 5, 6, 7]
puts get(:my_list)

define :rewrite do | val, lst, pos |
  item = [val]
  rest = get(lst)[pos+1..-1]
  if pos == 0
    first = item
    set lst, first + rest
  elsif pos < lst.size
    first = get(lst)[0..pos-1]
    set lst, first + item + rest
  else
    puts "Error: Position is out of list bounds."
  end
end

rewrite(10, :my_list, 5)
puts get(:my_list)

EDIT: Well, that was too early… does fail with last position of a ring… but that’s solvable.

Thanks again!

EDIT 2: Just in case someone is interested in my solution:

set :my_list, (ring 1, 2, 3, 4, 5, 6, 7)
#set :my_list, [ 1, 2, 3, 4, 5, 6, 7]

define :rewrite do | val, lst, pos |
  item = [val]
  if pos + 1 == lst.size
    # position + 1 is out of bounds; just drop last element
    rest = []
  else
    rest = get(lst)[pos + 1..-1]
  end
  if pos == 0
    first = item
    set lst, first + rest
  elsif pos < lst.size
    first = get(lst)[0..pos - 1]
    set lst, first + item + rest
  elsif pos >= lst.size
    puts "Error: Position is out of list bounds."
  end
end

rewrite(10, :my_list, 6)
puts "Ergebnis: #{get(:my_list)}"

If this can be solved more elegantly I am very open to suggestions. But it now works with lists and rings.