OSC + Loop machine + Makeymakey

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…

Hi @robin.newman

Love the recent refactor, less duplication of code :slight_smile:

I had to modify the parsing function to

live_loop :pon do
  b = sync "/osc/sound/*"
  if b[0]==1
    r=parse_sync_address "/osc/sound/*"
    ns= r[2]#[4..-1]
    set ("c"+ns).to_sym,1
    doCommandSelect(ns .to_i)
  end
end
live_loop :poff do
  b = sync "/osc/sound/*"
  if b[0]==0
    r=parse_sync_address "/osc/sound/*"
    ns= r[2]#[4..-1]
    set ("c"+ns).to_sym,0
  end
end

And then it works for my OCS messages!

Looking good! FX is crazy

Putting right samples on it tonight!

thanks

I’ve done one more update to enable switching on and off of the background pusling screen and the background pulse tones. I’ve made a video on you tube showing the final project in action, and updated the code on teh gist site. see link on my last post above.

2 Likes

Hello

So I have patched the MakeyMakey to fit all 11 keys and that works great on MAC! However, on PI the sound is really really bad… I have removed the nodejs script from PI and running it on Mac so its not the node so it seems Sonic PI 3.0.1 running on PI3 just does not have the power? How can I troubleshoot what’s taking most resources?

Realtime is pretty much gone. and it can not keep up with timing… :frowning: Seem like I have to use bigger computer…

P

I’m currently looking at performance on a Pi3, which as you say is not good. I may be able to tweak things a bit, but it will not give the same performance as on a more powerful machine.

Also going to try it on a Pi3 B+ just released.

I have spent this evening looking at the program on a Pi3 and on a new Pi3 B+. I have produecd a version which is slightly less agressive on resources at the expsnes of a slightly longer response time and the loss of the gverb effects on the Mac version. I have left this on the background pulse which seems to work OK.

The main diference with the Pi3 B+ is that SP boots a little bit quicker, and it has a much faster WiFi connection, but I haven’t detected much change in performance in SP itself yet. I have added the Pi3 version to the gist. which is linked here

Hi, i’m using makey makey with sonic pi and processing, you can find the codes here for now, but i’m gonna write a more complete post soon.

1 Like