Triggering different functions every time I send a midicc control message

“Hallo world”.nl,

I am back to Supercollider after a long brake and I need help big time.

I am trying to use the SC to trigger groups of midi messages to control different devices (looper, guitar synth and digital mixer) in a predetermined sequence.

The messages need to be triggered throughout the song using one midi message to trigger the (sets of) midi messages step by step (like you would do it in QLab)

So far I managed to get the control message in via the MIDIDef and managed to send those messages to a Bus.

I’ve also succesfully sent the midi messages out, triggered by those messages.

But this is the problem:

I wanted to count the incoming midi messages (with counter or stepper) and then use the current value of the stepper to trigger the presets of midi messages via the if function. Problem is I can’t get that to work.

I actually don’t know should I use MIDIDef of SynthDef for this conversion. I also saw somewhere that if function is not operating like you would expect it inside the SynthDef.

I tried everything I could and I could be very close to the solution but I am not very skilled in SC and can’t wrap my head around this problem.

I don’t expect anyone to solve the problem for me but if someone can send me in the right direction, that would be most appreciated.

Kind regards,

Aleksandar

The key is to define the counter outside of the MIDIdef and update the counter inside the MIDIdef. You probably want to wrap the counter between 0 and the number of different actions, eg. if you have three different actions, use ~counter = (~counter + 1).wrap(0, 2), this will produce values of 0, 1, 2, 0, 1, 2, 0… etc. then use something like:
case
{ ~counter == 0 } { …action 1…}
{~counter == 1 } {…action 2… }
{~counter == 2} {…action 3…}

1 Like

Thor, thank you so much for your swift reply.

Today I was working the whole day, just found a moment to send my s.o.s. :slight_smile:

Can’t wait to get to my studio tomorrow and try to implement your idea.

I will keep you posted!

Ok, I threw everything at it I had, I think I am very close but I am still missing something.
So what I did so far:

s.boot;
//booted the server

MIDIClient.init;
// Initialized midi

MIDIIn.connect(0, 5);
// Defined my midi input

~m1 = MIDIOut.newByName("Midihub MH-3KXE5SS", "Midihub MH-3KXE5SS");
// defined my midi output

b = Bus.control(s,1);
c = Bus.control(s,1);

// Defined two kr busses

(
MIDIdef.cc(\most,
	{arg val;
		c.value_(val-0.5);
	}, 127);
)

// got a controller 127 which toggles between 0 and 1 to toggle between -0.5 and 0.5 and send it to a bus c (this is working, I can see it on the Scope)

SynthDef("pulse", { 
	Out.kr(b, PulseCount.kr(In.kr(c), reset: 3));
}).add;

//created a Synthdef that listens to the input from a bus “c” and with the use of PulseCount raises the value every time I send the midi message to bus “c” and sends the value to the bus “b”

x = Synth.new("pulse");
// started that synth and saw on the scope to my great joy that the value in the bus “b” is indeed raising every time by 1.

So far so good, I though.

Then I tried to apply the if logic in the Synthdef using your suggested syntax:

SynthDef("midi", {
	if (b == 1) {~m1.control(0, ctlNum: 99, val: 11)};
	if (b == 2) {"2".postln};
	if (b == 3) {"3".postln};
	if (b == 4) {"4".postln};

	
}).add;

y = Synth.new("midi");

but nothing is comming out.

Using the info from Eli’s video ( SuperCollider Mini Tutorial: 7. Using ‘if’ in a SynthDef )
I tried this as well:

SynthDef("midi", {var st;
	st = In.kr(c);
	Select.kr(st,
	[~m1.control(0, ctlNum: 99, val: 11),
	   "2".postln,
	   "3".postln,
	    "4".postln,
		]
	);
}).add;

… hoping to use the Select UGen to differenciate between the different inputs on the In.kr(c) but I am not getting any response, neither with the midi message I am trying to send, nor the postln actions.

I know I am very close now but I have no idea what to try anymore.

Help would be most appreciated.

Kind regards,

Aleksandar

You cannot use conditional logic on the server the same way as in sclang, so that is why SynthDef '‘midi’ does not work as you expect. User FAQ | SuperCollider 3.12.2 Help
Without knowing precisely what you are after I think in your case all the logic should be language side. You also cannot use postln on the server, the somewhat equivalent method is .poll. MIDI is all language side, no MIDI on the server. You can however set the value of the bus language side with c with eg. c.set(2). You can also get the value of c with either c.get (asynchronous) or c.getSynchronous (synchronous). Bus | SuperCollider 3.12.2 Help
So again without understanding exactly what you are after, I would suggest setting the bus value language side or simply using a synth input instead of a bus. No need to use PulseCount which is a Ugen (server side). I have a feeling that the behavior you are after is very simple to implement language side, can you explain exactly what you want to obtain?

Once again, thank you for swift response.
As you can see from my previous message I am struggling with basic concepts. The reason why my approach seams illogical is because it is.

My efforts so far have been the result of reverse engineering and hit and miss approach, hoping to get the hang of the basics as I go along.

Based on what I read from your last message, I understand that I have been further away from the solution than I believed, making it unnecessarily complex.

I will try to explain again what I am trying to accomplish:

  1. send a midi control message 127 to SC, toggling between 0 and 1

  2. Every time the value of midi cc message goes from 0 to 1 a certain value should go +1, let’s call it “n”

  3. Have an object that looks at “n” and depending on the “n” sends midi messages out. So every time I send the midi cc message, n goes up by 1, triggering the following pre-determined set of midi messages.

If there is an easy way to do it, I am all ears.
I don’t need to use SynthDef for this, I just couldn’t figure out a different way to make the object that would count the steps.

I apologize if I am not making sense.

Something like this? (Just replace the contents of the functions with whatever you want the cues to do)

I’m on my phone so not tested yet…

(
var prevVal = 0;
~n = 0;
~cues = [{ 0.postln }, { 1.postln }, { 2.postln }];

MIDIdef.cc(\most,
	{arg val;
		if ((val == 1) and: (prevVal == 0)) {
			~cues[~n].value;
			~n = ~n+1;
		};
		prevVal = val;
	}, 127);
)
1 Like

Hi Eric,

Thank you so much for your input.
This will be a torture, also now on my phone, I will need to wait until Monday to try this.

One other question…

What would be the right approach to wrap different recipes for different songs so I can recall them on the fly?

I would need to store them somehow and then recall them by song

… this is because each song will have a different set of midi messages

You could store your cues in a dictionary

~cues = (
  songA: [{ 0.postln }, { 1.postln }, { 2.postln }],
  songB: [{ 0.postln }, { 1.postln }, { 2.postln }]
)

And keep track of e g

~curSong = \songA;

Then you would access

~cues[~curSong][~n]

Or if the order is predefined you can put the cue lists in order in an array:

~cues = [
  [{ 0.postln }, { 1.postln }, { 2.postln }],
  [{ 0.postln }, { 1.postln }, { 2.postln }]
]

And then ~curSong would be a number that you could similarly increment with maybe a different midi cc

(And either way when you change to a new song, be sure to set ~n back to 0)

1 Like

Oh and just remembering a gotcha when you want to use a Midi out with real time input: set the latency to 0, e.g.

~m1 = MIDIOut.newByName("Midihub MH-3KXE5SS", "Midihub MH-3KXE5SS");
~m1.latency = 0;

otherwise you will have a 0.2 second delay between input and output.

https://doc.sccode.org/Classes/MIDIOut.html#-latency

1 Like

Just to build further on what @Eric_Sluyter and I wrote previously.

  1. There is no need to involve the server in you case, MIDI is all language based
  2. It is usually a good idea to break down code into several smaller bits. In your case the two parts are:
    a. Receiving midi and updating a variable in SC to reflect the current state, ‘n’, ~counter or whatever you call it.
    b. Sending midi out of SC to change the state of software/hardware outside of SC

Write code in way that allows you to test a. and b. separately and only after confirming that both bits work put them together. This makes it much easier to troubleshoot.

Here are some midiout messages which you can adapt to your needs

// Initialize midi connections
MIDIClient.init;
MIDIIn.connectAll;

MIDIClient.destinations // query the midi destinations (MIDIEndPoints)
m = MIDIOut(0) // select the appropiate out, in my case on OSX with no midikeyboard connected, 0 = IAC bus
// note that channel 0 might be named channel 1 in your receiving software, most DAWs start counting from 1 rather than 0
m.noteOn(0, 60, 64) // send a midinote on message to channel 0 with for midinote number 60 with velocity 64  
m.noteOff(0, 60) // send a midinote off message to channel 0 for midinote number 60
m.control(0, 7, 32) // send a midi control message to channel 0, control number 7 (volume) with the value 32
m.program(0, 5) // send a midi program change to channel 0 witht the value 5
1 Like

Wow,
Now I really can’t wait until Monday :joy:

Eric and Thor, thank you so much !!!
I will let you know as soon as I have this running properly.

What bugs me now is why the default 200ms delay on the midi out?

But that is more out of curriousity, not really a thing that worries me :rofl:

The very long (200ms) delay is probably a leftover from many years ago. You can easily lower it, however I would advice setting it to nil rather than 0, because a setting of 0 will get you in trouble if you are using the pattern library, I can’t go lower than around 30 ms without getting late messages on my setup with patterns. The reason for the delay is to avoid jitter when scheduling events (midi or osc). The scheduling of events is restricted by the block size. With default settings (blocksize = 64, sr = 44100), the per event jitter (worst case) is 64/44100 = 1.45 ms, so worst case for consecutive events should be around 3ms. I personally normally work with latency = nil and accept the jitter to gain near immediate firing of events. But if you want very precise timing of events you will have to accept some latency. I try to think of the jitter as humanizing factor instead of an obstacle - it’s is just a mental trick:). You can also play around with blocksizes of 32, 16, 8 etc. to minimize the jitter at the expense of a higher CPU hit.

I think Thor is talking about server latency, which yes is there so you can have precise relative timing on sounding events. (Scheduling and Server timing | SuperCollider 3.12.2 Help) Synth patterns automatically apply this latency.

As I understand the default latency on a midi out is just intended to keep up with these server events, e.g. if you have two patterns playing at the same time, and one is playing Synths on the server and the other is sending midi out, you probably want them to be synchronized. Since the default server latency is 1/5 second, so is the default midi latency.

But for real time use the way you are doing you want the midi out to be instantaneous and there should be no problem setting it to 0.

Yes my bad and your are absolutely right @Eric_Sluyter, I was confusing midi latency and server latency. The part I wrote about jitter should be the same for midi latency and server latency, I think, please correct with if I am wrong about that:)

I wasn’t sure, to check, I sent MIDI out into reaper to record.

MIDIClient.init;
MIDIIn.connectAll;
o = MIDIOut.newByName("IAC Driver", "Bus 1");
o.latency = 0; // 0.2;

Pdef(\midiboop, Pbind(
  \type, \midi,
  \midicmd, \noteOn,
  \midiout, o,
  \chan, 0,
  \freq, 200,
  \dur, 1/16
)).play

Both latency 0 and 0.2 have about the same amount of timing jitter, I think.

(Intuitively, I don’t think MIDI can ever be perfect because the messages don’t have a timestamp. The way server messages can have perfect timing relative to each other is that they include a timestamp for exactly when to do the thing, which can be scheduled sample-accurately if you use OffsetOut.)

1 Like

Yes, that makes sense. Did you test with different blocksizes? I would suspect (or hope) that you get better timing by lowering the blocksize.

I did a speed test of the communication on the IAC bus with a while ago and came to the conclusion that it is a very fast. I can’t remember exactly what I did, but it was probably a round trip test.

@Eric_Sluyter and @Thor_Madsen

I am so glad that you two guys are getting jiggy with the subject. I am learning so much just by reading your correspondence.

Not only the practical solution but also all the information surrounding the solution that reveals many hidden aspects.

Speaking of the practical solution:

This morning I hurried up to get to the studio and YES, I am already getting results. The script is triggering the cues and my devices are singing in beautiful synced choruses. This opens up everything!

I am overwhelmed, happy and a trully grateful camper !!!

I will keep you posted on the further development. Soon I will have somehing to show to the broader audience and the two of you are my two angels of synchronicity.

:pray::pray::pray::pray: