OSC + Loop machine + Makeymakey

Hello

I am doing a project for local burning man (Blazing Swan) where we have

On PI I have nodeJS server that captured the Keys (not so easy on Terminal but using iohook) this then send OCS to QLC+ and SonicPI

We have 10 Keys (triggers) on the mannequin which when you touch it should trigger sound (and lights, which is working OK)

Problem is I don’t know how to sync the LOOPS with each other and with the keys, and they should be only playing when pressed? (I also need to limit the amount of OSC I send btw)

I was gonan radnomize the each sound so it does not get soo boring, have most of them loops samples and then some SYNTH so the mannequin becomes instruments. But findint it hard to sync them.

Any help would be appreciated

I also watched many videos by @robin.newman and I am very inspired what the options are. but don’t have that much experience with it (yet)

Thanks

Petr

Hi Petr

Below is a possible solution. I have simulated your key triggers bu generating different OSC messages sent from Sonic Pi to itself. (This is done by sending the messages to localhost, the computer running Sonic Pi). I think you said you were able to generate the incoming messages using mode.js

Below that are 5 nearly identical live_loops, each of which responds to a different OSC address, and each of which will play a different note when it is triggered. (just used 5 keys, but you could exgtend to 10 easily).
Remember that Sonic Pi adds a /osc to the start of all received OSC triggers, to differentiate the source from midi or other sources of cues.

#simulate your key input by osc messages from sonic pi
#using 5 different addresses
use_osc "localhost",4559 #send to local host
live_loop :simulate1 do
  sleep rrand(0.2,1)
  key = rrand_i(1,5)
  puts "key triggerd is",key
  oscAddress="/trigger"+key.to_s
  osc oscAddress
end

#now have similar loops to receive the 5 possible input OSC cues
#each one plays a different note

live_loop :note1 do
  b = sync "/osc/trigger1" #waits for an osc message addressed to /trigger1 to be received
  use_synth :tri
  play :c3,release: 0.2
end

live_loop :note2 do
  b = sync "/osc/trigger2"
  use_synth :tri
  play :e3,release: 0.2
end


live_loop :note3 do
  b = sync "/osc/trigger3"
  use_synth :tri
  play :g3,release: 0.2
end


live_loop :note4 do
  b = sync "/osc/trigger4"
  use_synth :tri
  play :c4,release: 0.2
end


live_loop :note5 do
  b = sync "/osc/trigger5"
  use_synth :tri
  play :e4,release: 0.2
end

Hello @robin.newman,

thank you for quick reply.

The problem is that the sound shoudl be playing only when person is touch the area ( pressing the key) and it shoudl be all in sync, in normal world the loop woudl be only trigger on on 1st of or 3rd beat (Ableton)

So think about it as Band instrument, if you touch part of the body it woudl activete the loop which would be sync with the master BPM. Lets say 8 woudl be samples and 2 could be SYNTH. Also how do you stop the sample? I have code that runs the samples on PRESS but when user hits the button few times they all play over each other?

use_synth :saw
use_bpm 100
use_random_seed 3
use_debug true
use_real_time



duck_covers = "/Users/cerw/Music/Samples/Movie Dialogue/Movie Dialogue Vol 5/Movie_Dialogue_5_Wavs/Cat Woman/"

live_loop :sample do
  sync "/osc/sound/33"
  with_fx :reverb do
    sample  duck_covers, rrand_i(1,20),rate: rrand(0.9, 1.2),cutoff: rrand(80, 120)
  end
end

cymbals = "/Users/cerw/Music/Samples/Prime Loops - Soulful Hip Hop Samples MULTiFORMAT  MAC OS X - ReleaseLoad.Com/Drum One-Shots/Cymbals/"

live_loop :cymbals do
  sync "/osc/sound/31"
  with_fx :reverb do
    sample  cymbals, rrand_i(1,20),rate: rrand(0.9, 1.2),cutoff: rrand(80, 120)
  end
end

hats = "/Users/cerw/Music/Samples/99Sounds Cinematic Sound Effects/Hits/"

live_loop :hats do
  sync "/osc/sound/30"
  with_fx :reverb do
    sample  hats, rrand_i(1,10),rate: rrand(0.9, 1.2),cutoff: rrand(80, 120)
  end
end

spooky = "/Users/cerw/Music/Samples/99Sounds Cinematic Sound Effects/Atmos/"

live_loop :spooky do
  sync "/osc/sound/17"
  with_fx :reverb do
    sample spooky, rrand_i(1,10),rate: rrand(0.9, 1.2),cutoff: rrand(80, 120)
  end
end

Is this possible with Sonic Pi ?

Thank you!

P

This is an interesting problem. I played around with it a bit and came up with a solution that appears to work. I used three push buttons on TouchOSC to try it out, but it should work with your OSC inputs. TGhe code is shown below. It makes use of the kill function to stop the running sample. Also, to get a quicker response I split up the sleep command into 40 shorter segments so that poilling to stop the sample will respond more quickly.



set:c1,0 #initialise
set:c2,0
set:c3,0

#three input buttons catered for but can be extended
live_loop :p1 do
  b = sync "/osc/1/push1"
  set :c1,b[0] #1 if pushed 0 when released
  doLoopAmen if b[0]==1
end

live_loop :p2 do
  b = sync "/osc/1/push2"
  set :c2,b[0] #1 if pushed 0 when released
  doLoopGarzul if b[0]==1
end

live_loop :p3 do
  b = sync "/osc/1/push3"
  set :c3,b[0] #1 if pushed 0 when released
  doLoopCompus if b[0]==1
end

define :doLoopAmen do # function to deal with loop_amen sample
  set :kill1,false
  live_loop :controlLoopAmen do
    s1=sample :loop_amen #s1 stores reference to the running sample
    in_thread do #in a thread poll for button up cue (:C1=>0)
      loop do
        if get(:c1)==0 #if button up cue then
          kill s1 #kill the sample pointed to by the s1 reference
          set :kill1,true #set kill1 flag true
          stop # quit loop
        end
        sleep 0.1 #time between checks for button up cue
      end
    end
    40.times do # split sleep time to insert poll for kill1
      sleep (sample_duration :loop_amen) / 40.0
      stop if get(:kill1)
    end
  end #of live loop
end #of function




define :doLoopGarzul do
  set :kill2,false
  live_loop :controlLoopGarzul do
    s2=sample :loop_garzul
    in_thread do
      loop do
        if get(:c2)==0
          kill s2
          set :kill2,true
          stop
        end
        sleep 0.01
      end
    end
    40.times do #do this to get a quicker response time
      sleep (sample_duration :loop_garzul) / 40.0
      stop if get(:kill2)
    end
  end
end

define :doLoopCompus do
  set :kill3,false
  live_loop :controlLoopCompus do
    s3=sample :loop_compus,amp: 2
    in_thread do
      loop do
        if get(:c3)==0
          kill s3
          set :kill3,true
          stop
        end
        sleep 0.01
      end
    end
    40.times do #do this to get a quicker response time
      sleep (sample_duration :loop_compus) / 40.0
      stop if get(:kill3)
    end
  end
end

ADDED EDIT You could add a metronome live_loop and sync the sample live loops inside each function to that. Also you could add beat_stretch to the samples to get them to a known number of beats.

HI there

This works quite well, especially on the keyRelease it stops very quickly, however, how do I sync the beats? Let’s say I put the use_bpm 100, and then have drums, voice, bass how do I sync them?

And can I use same technique for just triggering one_off samples? (screems, FX) with no live_loops?

Thanks

Petr

Hi Petr
I have added a few more input, and adjusted the samples so that they all occupy a multiple of 4 beats. Each live loop is synced to start in time to the metro beat. The additional buttons control a single repeating note (whle the button is pushed), a sequence of notes, a single sample and a single voice sample. Each of them will start playing synced to the metro beat.


use_bpm 120
path="~/Desktop/samples/"
set:c1,0 #initialise
set:c2,0
set:c3,0
set:c4,0
set:c5,0
set:c6,0
set:c7,0

live_loop :p1 do
  b = sync "/osc/1/push1"
  set :c1,b[0] #1 if pushed 0 when released
  doLoopAmen if b[0]==1
end

live_loop :p2 do
  b = sync "/osc/1/push2"
  set :c2,b[0] #1 if pushed 0 when released
  doLoopGarzul if b[0]==1
end

live_loop :p3 do
  b = sync "/osc/1/push3"
  set :c3,b[0] #1 if pushed 0 when released
  doLoopCompus if b[0]==1
end

live_loop :p4 do
  b = sync "/osc/1/push4"
  set :c4,b[0] #1 if pushed 0 when released
  doLoopLongNote if b[0]==1
end

live_loop :p5 do
  b = sync "/osc/1/push5"
  set :c5,b[0] #1 if pushed 0 when released
  doLoopSequence if b[0]==1
end

live_loop :p6 do
  b = sync "/osc/1/push6"
  set :c6,b[0] #1 if pushed 0 when released
  doSingleSample if b[0]==1
end

live_loop :p7 do
  b = sync "/osc/1/push7"
  set :c7,b[0] #1 if pushed 0 when released
  doVoiceSample if b[0]==1
end

live_loop :metro do
  sleep 1
end

define :doLoopAmen do
  set :kill1,false
  live_loop :controlLoopAmen,sync: :metro do
    s1=sample :loop_amen,beat_stretch: 4,amp: 2
    in_thread do #in a thread poll for button up cue (:C1=>0)
      loop do
        if get(:c1)==0 #if button up cue then
          kill s1 #kill the sample
          set :kill1,true #set kill1 flag true
          stop # quit loop
        end
        sleep 0.1 #time between checks for button up cue
      end
    end
    40.times do # split sleep time to insert poll for kill1
      sleep 4/ 40.0
      stop if get(:kill1)
    end
  end #of live loop
end #of function





define :doLoopGarzul do
  set :kill2,false
  live_loop :controlLoopGarzul,sync: :metro do
    s2=sample :loop_garzul
    in_thread do
      loop do
        if get(:c2)==0
          kill s2
          set :kill2,true
          stop
        end
        sleep 0.01
      end
    end
    40.times do #do this to get a quicker response time
      sleep (sample_duration :loop_garzul) / 40.0
      stop if get(:kill2)
    end
  end
end

define :doLoopCompus do
  set :kill3,false
  live_loop :controlLoopCompus,sync: :metro do
    s3=sample :loop_compus,beat_stretch: 8,amp: 2
    in_thread do
      loop do
        if get(:c3)==0
          kill s3
          set :kill3,true
          stop
        end
        sleep 0.01
      end
    end
    40.times do #do this to get a quicker response time
      sleep 8 / 40.0
      stop if get(:kill3)
    end
  end
end

define :doLoopLongNote do #repeats a long note wile the button is pushed
  set :kill4,false
  live_loop :controlLongNote,sync: :metro do
    s4=play :c4,sustain: 3,release: 1
    in_thread do
      loop do
        if get(:c4)==0
          kill s4
          set :kill4,true
          stop
        end
        sleep 0.01
      end
    end
    40.times do #do this to get a quicker response time
      sleep 4 / 40.0
      stop if get(:kill4)
    end
  end
end

define :doLoopSequence do #plays a sequence of notes while the button is pushed
  set :kill5,false
  live_loop :controlSequence,sync: :metro do
    use_synth :tb303
    in_thread do
      loop do
        if get(:c5)==0
          set :kill5,true
          stop
        end
        sleep 0.01
      end
    end
    12.times do
      play scale(:c3,:minor_pentatonic,num_octaves: 2).choose,release: 0.25,amp: 0.5,cutoff: rrand_i(60,120)
      40.times do #do this to get a quicker response time
        sleep 0.25 / 40.0
        stop if get(:kill5)
      end
    end
    
  end
end

define :doSingleSample do #plays a single sample once when button pushed
  set :kill6,false
  sync :metro
  s6=sample :loop_amen_full,beat_stretch: 16
  in_thread do
    loop do
      if get(:c6)==0
        kill s6
        set :kill6,true
        stop
      end
      sleep 0.01
    end
  end
end

define :doVoiceSample do #plays a voice sample once when bugtton is pushed
  set :kill7,false
  sync :metro
  s7=sample "~/Desktop/samples/testsample.flac",amp: 2 #use your own voice sample here
  in_thread do
    loop do
      if get(:c7)==0
        kill s7
        set :kill7,true
        stop
      end
      sleep 0.01
    end
  end
  
end


live_loop :alwaysplaying,sync: :metro do #runs continuously playing
  use_synth :fm
  play :c2,release: 1,amp: 4
  play :c3,release: 2,amp: 0.2
  sleep 2
  play :c2,release: 2,amp: 4
  sleep 2
end

1 Like

Hi there.

OMG it’s getting there this is the best results I have had so far! Will spend more time on it tomorrow night.

I have friend helping me making more samples and will report soon!

Thanks!

Sometimes I get an error like this:

Runtime Error: [buffer 8, line 181] - SonicPi::Lang::Core::TimingError
Thread death!
 Timing Exception: thread got too far behind time
/Applications/Sonic Pi.app/app/server/ruby/lib/sonicpi/lang/core.rb:3971:in `sleep'
workspace_eight:181:in `block (5 levels) in __spider_eval'
/Applications/Sonic Pi.app/app/server/ruby/lib/sonicpi/lang/core.rb:2055:in `block (2 levels) in loop'
/Applications/Sonic Pi.app/app/server/ruby/lib/sonicpi/lang/core.rb:2276:in `block_duration'
/Applications/Sonic Pi.app/app/server/ruby/lib/sonicpi/lang/core.rb:2313:in `block_slept?'
/Applications/Sonic Pi.app/app/server/ruby/lib/sonicpi/lang/core.rb:2054:in `block in loop'
/Applications/Sonic Pi.app/app/server/ruby/lib/sonicpi/lang/core.rb:2052:in `loop'
/Applications/Sonic Pi.app/app/server/ruby/lib/sonicpi/lang/core.rb:2052:in `loop'
workspace_eight:175:in `block (4 levels) in __spider_eval'
/Applications/Sonic Pi.app/app/server/ruby/lib/sonicpi/runtime.rb:1043:in `block (2 levels) in __in_thread'

Errors like that occur if the loop is being asked to do too much before it has to repeat. eg:
This live loop is an extreme example which will crash. IT is being asked to replay the note repeatedly before the previous one has had time to finish.

live_loop :overload do
  play :c2,sustain:1,release: 5
  sleep 0.005
end

Other things can build up the resources being required in a loop, particularly creating large numbers of with_fx calls INSIDE the loop. That way the resource for the effect is created EVERY time the loop repeats. Better if you can set them up outside the loop, but containing the whole live_loop inside.

To try and remove such errors, look at the timings inside the loop.

Hello @robin.newman,

I have beeing adding more randomness to the code that i have 10 CUES

and each of them has DIR of samples link to it, I will make use of SET and GET TO make sure that it plays only one sample when user holds the button, clear the variable when released and pick a new sample. I think this should work. BUT

I am having a problem with the length of the samples, even if i Make metronome 10s long the sample number one gets played before the first finishes? but not with the prebuild sample? Why is that?

Again thank you so much for you help !

P

Ok I am gonna answer my self here (sorry ) I did find that I need to set the sleep to chosen sample and that way it works. But Since then I am getting lots of this:

{run: 37, time: 63.01}
 └─ Timing warning: running slightly behind...
 
{run: 37, time: 62.82}
 └─ Timing error: can't keep up...
 
{run: 37, time: 62.56}
 └─ Timing error: can't keep up...

Is there any recomende sample format or preload i can use to avoid this?

Hi Petr
I have done further work on improving things.
1 You get better response by using separate loops to detect push on and push off OSC messages
2 I had forgotten to add a use_real_time command. I have done so globally at the beginning
3 having used a copy and paste to set up the loops initially, I have now altered the timings in some of the loops to eliminate timing can’t keep up errors.
4 :kill6 and :kill7 are not required for the single sample play loops as no live loop to kill!
4 You can preload samples if you wish, but otherwise you may notice a slight delay the first time a new one is used. However now it shouldn’t affect the correct operation by doubling up the playing of samples.

The new updated program is below

use_real_time

use_bpm 120
path="~/Desktop/samples/"
#initialise c variables
set:c1,0;set:c2,0;set:c3,0;set:c4,0;set:c5,0;set:c6,0;set:c7,0;
#input on and off live_loops to detect inputs
live_loop :p1on do
  b = sync "/osc/1/push1"
  if b[0]==1
    set :c1,1
    doLoopAmen
  end
end

live_loop :p1off do
  b = sync "/osc/1/push1"
  set :c1,0 if b[0]==0
end

live_loop :p2on do
  b = sync "/osc/1/push2"
  if b[0]==1
    set :c2,1
    doLoopGarzul
  end
end

live_loop :p2off do
  b = sync "/osc/1/push2"
  set :c2,0 if b[0]==0
end

live_loop :p3 do
  b = sync "/osc/1/push3"
  if b[0]==1
    set :c3,1
    doLoopCompus
  end
end

live_loop :p3off do
  b = sync "/osc/1/push3"
  set :c3,0 if b[0]==0
end

live_loop :p4on do
  b = sync "/osc/1/push4"
  if b[0]==1
    set :c4,1
    doLoopLongNote
  end
end

live_loop :p4off do
  b = sync "/osc/1/push4"
  set :c4,0 if b[0]==0
end
live_loop :p5on do
  b = sync "/osc/1/push5"
  if b[0]==1
    set :c5,b[0]
    doLoopSequence
  end
end

live_loop :p5off do
  b = sync "/osc/1/push5"
  set :c5,0 if b[0]==0
end

live_loop :p6on do
  b = sync "/osc/1/push6"
  if b[0]==1
    set :c6,1
    doSingleSample
  end
end

live_loop :p6off do
  b = sync "/osc/1/push6"
  set :c6,0 if b[0]==0
end

live_loop :p7on do
  b = sync "/osc/1/push7"
  if b[0]==1
    set :c7,1
    doVoiceSample
  end
end

live_loop :p7off do
  b = sync "/osc/1/push7"
  set :c7,0 if b[0]==0
end

live_loop :metro do #metronome to sync stuff together
  sleep 1
end

define :doLoopAmen do
  set :kill1,false
  live_loop :controlLoopAmen,sync: :metro do
    s1=sample :loop_amen,beat_stretch: 4,amp: 2
    in_thread do #in a thread poll for button up cue (:C1=>0)
      loop do
        if get(:c1)==0 #if button up cue then
          kill s1 #kill the sample
          set :kill1,true #set kill1 flag true
          stop # quit loop
        end
        sleep 0.1 #time between checks for button up cue
      end
    end
    40.times do # split sleep time to insert poll for kill1
      sleep 4/ 40.0
      stop if get(:kill1)
    end
  end #of live loop
end #of function

define :doLoopGarzul do
  set :kill2,false
  live_loop :controlLoopGarzul,sync: :metro do
    s2=sample :loop_garzul
    in_thread do
      loop do
        if get(:c2)==0
          kill s2
          set :kill2,true
          stop
        end
        sleep 0.1
      end
    end
    40.times do #do this to get a quicker response time
      sleep (sample_duration :loop_garzul) / 40.0
      stop if get(:kill2)
    end
  end
end

define :doLoopCompus do
  set :kill3,false
  live_loop :controlLoopCompus,sync: :metro do
    s3=sample :loop_compus,beat_stretch: 8,amp: 2
    in_thread do
      loop do
        if get(:c3)==0
          kill s3
          set :kill3,true
          stop
        end
        sleep 0.1
      end
    end
    40.times do #do this to get a quicker response time
      sleep 8 / 40.0
      stop if get(:kill3)
    end
  end
end

define :doLoopLongNote do #repeats a long note wile the button is pushed
  set :kill4,false
  live_loop :controlLongNote,sync: :metro do
    s4=play :c4,sustain: 3,release: 1
    in_thread do
      loop do
        if get(:c4)==0
          kill s4
          set :kill4,true
          stop
        end
        sleep 0.1
      end
    end
    40.times do #do this to get a quicker response time
      sleep 4 / 40.0
      stop if get(:kill4)
    end
  end
end

define :doLoopSequence do #plays a sequence of notes while the button is pushed
  set :kill5,false
  live_loop :controlSequence,sync: :metro do
    use_synth :tb303
    in_thread do
      loop do
        if get(:c5)==0
          set :kill5,true
          stop
        end
        sleep 0.1
      end
    end
    12.times do
      play scale(:c3,:minor_pentatonic,num_octaves: 2).choose,release: 0.25,amp: 0.5,cutoff: rrand_i(60,120)
      10.times do #do this to get a quicker response time
        sleep 0.25 / 10
        stop if get(:kill5)
      end
    end
    
  end
end

define :doSingleSample do #plays a single sample once when button pushed
  sync :metro
  s6=sample :loop_amen_full,beat_stretch: 16
  in_thread do
    loop do
      if get(:c6)==0
        kill s6
        stop
      end
      sleep 0.1
    end
  end
end

define :doVoiceSample do #plays a voice sample once when button is pushed
  sync :metro
  s7=sample path,"testsample.flac",amp: 2 #use your own voice sample here
  in_thread do
    loop do
      if get(:c7)==0
        kill s7
        stop
      end
      sleep 0.1
    end
  end
end

live_loop :alwaysplaying,sync: :metro do #runs continuously playing
  use_synth :fm
  play :c2,release: 1,amp: 4
  play :c3,release: 2,amp: 0.2
  sleep 2
  play :c2,release: 2,amp: 4
  sleep 2
end
1 Like

I would love to hear more detail about how you are getting the Makey Makey to trigger events in Sonic Pi. Even just an explanation about how you implemented all this.

How do you get the nodeJS server to convert the key press events into OSC messages? Is that part of the iohook?

Any good references or tutorials on how to implement this on something besides a Rasp Pi?

I’m not really savvy with node so I’m not even sure I’m asking the right questions, but I am very intrigued. We have talked about using Makey Makey on this forum in the past. Sam has also alluded to adding something into a future version of SPi that will make this type of interaction a lot easier, but until then I am curious to hear how people are doing it.

Thanks

I did further work on my code, and got this final result.

2 Likes

Hello @mrbombmusic,

I am using simple NodeJS Script that combines few things together

  • WebServer
  • Socket Server
  • OCS Proxy
  • Catches Keyboard Events

I use this as core part of my projects with Music, Lights etc Here is an example of capturing the keys and then sending it to SonicPI via OSC

Atm, I am capturing the Key events in Terminal using IOHOOK, this hooks to keyboard and does not have to be focused! Another way I am doing this is via web browser and then sending it to socket back to nodejs and then to SonicPI (this seem more stable then ioHOOK but need more memory)

This is just copied/pasted from much larger code, but it should give you an idea.

var osc = require('osc-min')


const ioHook = require('iohook');
  touchHost = "10.1.1.106",
    touchPort = 9000,
let map = {}

ioHook.on("keydown", event => {
     weblog("Keydown: "+new Date()+JSON.stringify(event));
     if(map[event.keycode] !== true) {
       map[event.keycode] = true
       sonicSend({
         address: "/sound/"+event.keycode,
         args: {
           type: 'float',
           value: 1
         }
       })
     }

});

ioHook.on("keyup", event => {
  weblog(JSON.stringify(event));
  map[event.keycode] = false
  sonicSend({
    address: "/sound/"+event.keycode,
    args: {
      type: 'float',
      value: 0
    }
  })
});

//Register and start hook
ioHook.start();


function sonicSend(message) {
  var request = osc.toBuffer(message);
  udp.send(request, 0, request.length, 4559, '127.0.0.1');
  log('Sonic'+JSON.stringify(message));

  udp.send(request, 0, request.length, touchPort, touchHost);
  log('Sent Feedback OSC message to TouchOSC '+touchHost+':'+touchPort);
}

@robin.newman

Looks great (saw the video!) Will hook it tonight into my mannequin and post video too! :slight_smile:

Thanks

2 Likes

@robin.newman

This seem to work nicely, but is there some rules when adding new loops? Do they need to be samples to some BPM You seem to just add very random and work I add 100 or 120 BPM and they dont sync so well?

It seem beat_stretch: will work most of the time Q: If I want to wrap some samples with with_fx how do I kill the fx ? I tried passing the reference from sampel to with_fx :s7 but seem not work?

ta

More live loops (and input buttons) are added as for the existing ones. Each needs a p-on and p-off loop, and a function to call the new live loop.
Beat_stretch is the key to syncing the live loops. Basically each sample must be a whole number of 4 beats if they are to keep in step, so beat_stretch: 4,8,16 etc will work. Obviously the larger the number the lower the pitch of the sample will be. If beat_stretch is to short then the sample will be very high pitched and not easy to hear. Find the best value by experiment, always keeping to to a mutliple of 4. There should only be ONE bpm setting the same for all, Best to set it at top of the program. You can alter it, stop then re-run and everything will change hopefully in step. You can probably only use a fairly small range or the samples may begin to sound a bit weird. It’s not like changing notes, where the pitch will remain the same, just the durations will change. If you change the tempo the samples will sound different in pitch too.

As far as adding an fx to one of the samples, you can wrap this around the calling function. Thus to add it to the loop_amen you would do.

live_loop :p1on do
  b = sync "/osc/1/push1"
  if b[0]==1
    set :c1,1
    with_fx :gverb,room: 25,mix: 0.8 do
      doLoopAmen
    end
  end
end

(referring to the example published in a previous answer above).

As you will realise, as you add further inputs things will tend to get a bit unwieldy. I fact I have rewritten the code to make it more concise, although perhaps a little harder to follow. First I used wild cards so that I could use a single p-on and p-off loop and extract which switch was pushed, and then use a Ruby case statement to choose what to do. I also produced generalised functions for adding a live loop or a oneShot sample play. That is what was driving the video I posted last night.
Since then I have added some with_fx wrappers as discussed above, and also realised that the in_thread sections in the live loops were being re-setup on each pass, and so managed to relocate them outside the loops. This is less resource hungry and gives a better response.

I will try and publish the latest code on my gist site with a link here, either later tonight or tomorrow.

Hi cerw,

I think it’s been pointed out in another thread (Set, get, kill, and other deep commands)
that the kill command is not really a good way of killing with_fx’s… it might work, it might not.

Robin’s app is a wonderful thing, and improves every time he updates it… but behind it all is Sonic Pi,
and you have to factor in the limitations of SPi as it currently stands. Kill may change in the future… but
for now its still a bit ‘iffy’ using it to stop loops and fx’s.

Eli…

Eli , I don’t actually kill live_loops in the program but use stop to stop them. I use kill to kill a playing sample as documented in the help files.
In the same way I don’t use kill on an fx. I stop the live loop inside the fx, but don’t touch that directly. By calling the live_loop inside a function each time it is possible to restart it.

My final code is now available at

Hi Robin,

I was really replying to Cerw… forgive me, I should have quoted him first time.

Eli…