OSC + Loop machine + Makeymakey

#11

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
#12

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

#13

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

2 Likes
#14

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
#16

@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?

#17

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

#18

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.

#19

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…

#20

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

Help a newbie with OSC - IN
"Launch" fx at midi_cc
#21

Hi Robin,

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

Eli…

#22

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

#23

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
#24

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

#25

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.

#26

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

#27

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
#28

Thats awesome. I actually do a lot with Makey Makey and p5.js, the javascript version of processing. Unfortunately there aren’t as many libraries yet to do things with OSC, midi etc to have it interact with Sonic Pi but I am playing around with what’s available. I love the idea of making reactive visual sketches in Processing or p5 that run with SPi.

Can’t wait to see more of what you are doing.

#29

guess what… surprise! i worked on getting this running in 2016 (throwback wednesday!) while wathcing all those dan shiffman coding rainbow videos. it’s heartbeat.js to get midi notes to p5.js. the tricky part was just where to load in the heartbeat stuff, so that it would work with p5’s setup/draw functions. i think they changed something, something to do with promises maybe, cause when i initially opened it up again, it had stopped working… but anyway, now it’s fixed. so you can have your midi events be recognized in a browser sketch, and go to town with all your javascript mayhem. enjoy!

<html>
<head>
  <meta charset="UTF-8">
  <script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.0/p5.min.js"></script>
	<script language="javascript" type="text/javascript" src="https://cdn.rawgit.com/abudaan/heartbeat/gh-pages/build/heartbeat.js"></script>
	

	<script>
var portname = "", 
    inputName = "", 
    midiEventData1 = 0, 
    midiEventData2 = 0;
		
window.onload = function(){
    var sequencer = window.sequencer;
    
	sequencer.ready(function init(){		
		var song = sequencer.createSong();
		
		sequencer.getMidiInputs(function(port){
			portname = port.name;
		});
		
		song.addMidiEventListener('note on', 'note off', function(midiEvent, input){
			inputName = input.name;
			midiEventData1 = midiEvent.data1;
			midiEventData2 = midiEvent.data2;
			
			console.log(midiEventData1);
		});
    });
};
	
	</script>
	
 <script>
 function preload(){
 }

function setup() {
  createCanvas(windowWidth,windowHeight);
  background(0);
  fill(255);
	
	text(portname,100,100);
} 

function draw() {
  clear()
  text(portname,100,100);
  text(midiEventData1,100,150);
}  

</script>
  <style>
  	* { margin:0; padding:0; } /* to remove the top and left whitespace */
	html, body { width:100%; height:100%; } /* just to be sure these are full screen*/
  	canvas {
	 outline: 0px;
	 position: absolute;
	 left: 0px;
	 top: 0px;
	 width: 100%;
	 height: 100%;

	 display:block;
	 background: #000011;
		}
	footer { 
			width: 175px; 
			height: 1.5em; 
			position: fixed; 
			bottom: 0;
			right: 0;
			padding: 3px 5px 5px 5px;
			text-align: left;
			z-index: 200;
		}
 </style>
</head>

<body>
	
</body>
</html> 
1 Like
#30

Woah! This looks really cool. Thanks so much for sharing. I went to the examples on github and got the basic midi in and midi out talking to SPi. I’ll see what I can do with p5 although I’m not very experienced with Vanilla Javascript, mostly just the p5 API.

Should I just copy the code above into the html file of my p5 sketch or would I just put the part on top to load the library and put the rest in the sketch file?

#31

hmm… actually, i haven’t kept up with what they’ve added into p5.js lately, so maybe they’ve added in WebMIDI, and you don’t even need this… if you can find that then maybe this isn’t necessary, but if not, then just put this chunk in the head of your html file (or i guess anywhere before the p5 setup/draw functions, so that it gets loaded in, and the portname variable gets set before the p5 setup function is called)

<script language="javascript" type="text/javascript" src="https://cdn.rawgit.com/abudaan/heartbeat/gh-pages/build/heartbeat.js"></script>
<script>
var portname = "", 
    inputName = "", 
    midiEventData1 = 0, 
    midiEventData2 = 0;
		
window.onload = function(){
    var sequencer = window.sequencer;
    
	sequencer.ready(function init(){		
		var song = sequencer.createSong();
		
		sequencer.getMidiInputs(function(port){
			portname = port.name;
		});
		
		song.addMidiEventListener('note on', 'note off', function(midiEvent, input){
			inputName = input.name;
			midiEventData1 = midiEvent.data1;
			midiEventData2 = midiEvent.data2;
			
			console.log(midiEventData1);
		});
    });
};
</script>

actually, this was one of my first things off of the p5 api, trying to figure out some of the very confusing other aspects of how javascript works… but i gotta say, for however frustrating it is, learning javascript is time well-spent!