Block/ Filter MIDI CC in a routing function

I made a script for a machine (Digitakt) which is both the source and destination for MIDI cc (control change) messages. The script works for the rounting task, but I want to filter a specific CC message within the routing, and it didn’t work well.

(
~midiOut = MIDIOut.newByName("Elektron Digitakt", "Elektron Digitakt").latency_(Server.default.latency);
MIDIdef.cc(\cc, {
	arg ccNum, chan, srcID, argTemplate, dispatcher;
	["MIDI CHAN:" + (srcID + 1), "CC MSB:" + chan, "CC VAL:" + ccNum].postln;
~midiOut.control(chan: srcID, ctlNum: chan, val: ccNum);
})
)

Above script is working for routing task, (by the way I found an inconsistent naming convention on MIDIdef.cc and MIDIOut.control)

I want to do something like this, but didn't success:
MIDIdef.cc(\cc, {
	arg ccNum, chan, srcID, argTemplate, dispatcher;
	["MIDI CHAN:" + (srcID + 1), "CC MSB:" + chan, "CC VAL:" + ccNum].postln;
	if (chan != 120,
	~midiOut.control(chan: srcID, ctlNum: chan, val: ccNum),
	chan = nil
	);
})

I also tried to apply a function on argTemplate but did not work either:

I want to do something like this, but didn't success:
MIDIdef.cc(\cc, {
	arg ccNum, chan, srcID, argTemplate, dispatcher;
	["MIDI CHAN:" + (srcID + 1), "CC MSB:" + chan, "CC VAL:" + ccNum].postln;
~midiOut.control(chan: srcID, ctlNum: chan, val: ccNum);
}, argTemplate: {arg chan; chan != 120 })

I want to know:

  1. How to apply a if statement function inside MIDIdef
  2. Any practical application on argTemplate ?

Thank you!

Documentation says “When evaluated for noteOn, noteOff, control, and polytouch messages it will be passed the arguments val, num, chan, and src, corresponding to the message value (e.g. velocity, control value, etc.), message number (e.g. note number), MIDI channel, and MIDI source uid.”

You’ve omitted the val argument, so naturally the others will appear to be strange.

Then, your if block is written incorrectly. The form is if(condition, { true block }, { false block }) or if(condition) { true block } { false block }. If you omit the curly braces, then it will not behave as you expect.

AFAIK functions are not allowed for argTemplate. You could try argTemplate: [nil, ccNum] but this will match, not exclude, the given cc number. (To exclude, I think the if is a better idea.)

hjh

1 Like

Thanks @jamshark70!

Post the script here if anyone come after.

(
~midiOut = MIDIOut.newByName("Elektron Digitakt", "Elektron Digitakt").latency_(Server.default.latency);
MIDIdef.cc(\controlChange, {
	| ccNum, chan, srcID |
	["MIDI CH:" + (srcID + 1), "CC MSB:" + chan, "CC VAL:" + ccNum].postln;
	if (
		chan != 120,

		{~midiOut.control(chan: srcID, ctlNum: chan, val: ccNum);
			["TRUE"].postln},
		{chan = nil;
			["FALSE"].postln}
	)
})
)

I’ll suggest one more improvement: the argument names are wrong and that will confuse anyone reading the code. (If you find yourself writing "CC VAL:" + ccNum, where the variable name clearly disagrees with the label, this is not something to accept. You will come back to this code years later and not understand it.)

It should really be:

(
~midiOut = MIDIOut.newByName("Elektron Digitakt", "Elektron Digitakt").latency_(Server.default.latency);
MIDIdef.cc(\controlChange, {
	| val, ccNum, chan |
	["MIDI CH:" + (chan + 1), "CC MSB:" + ccNum, "CC VAL:" + val].postln;
	if (
		ccNum != 120,

		{~midiOut.control(chan: chan, ctlNum: ccNum, val: val);
			["TRUE"].postln},
		{  // ccNum = nil;  // delete this; meaningless to clear a local variable
			["FALSE"].postln}
	)
})
)

hjh

Yes this confused me the most, I found that the arguments name on MIDIdef.cc is not the same as MIDIOut.control. On the Help Docs:

MIDIdef.cc(key, func, ccNum, chan, srcID, argTemplate, dispatcher)

MIDIOut.control(chan, ctlNum: 7, val: 64)

I am unsure even now, but seem the srcID and chan on MIDIdef.cc is opposite if compare with MIDIOut.control.

You’re confusing the MIDIdef constructor method arguments with the arguments to the response function. They are not the same. Check the part of the documentation that I quoted earlier.

Documentation says “When evaluated for noteOn, noteOff, control, and polytouch messages it will be passed the arguments val, num, chan, and src, corresponding to the message value (e.g. velocity, control value, etc.), message number (e.g. note number), MIDI channel, and MIDI source uid.”

hjh

Thank you for helping me out! I think I understand more now.
I revised the code a bit, and added another task.
To recap my goal:

  1. To receive the CC msg from Digitakt and send back to itself
  2. To block the CC msg number 120
  3. To receive a range of CC msg from another MIDI source (Korg nanoCtrl2) and send back to Digitakt. Here’s the code:
MIDIClient.init;
MIDIIn.connectAll;

(
//~midiOut = MIDIOut.newByName("nanoKONTROL2", "CTRL").latency_(Server.default.latency);
~midiOut = MIDIOut.newByName("Elektron Digitakt", "Elektron Digitakt").latency_(Server.default.latency);

MIDIdef.cc(\controlChange, {
	| val, num, chan, src |
	if (
		num != 120, //If the CC msg is not 120
		if (
			(num <= 89) && (num >= 82), //Check if the CC msg is within range from 82 to 89, do routing if yes
			{~midiOut.control(
				chan: chan = num % 82,
				ctlNum: 95,
				val: val 

			)},
			if (
				num != 120, // If not, check if the CC msg is 120 again (seem a bit awkward, but I cannot figure out a better way by now...)
				{~midiOut.control(
					chan: chan,
					ctlNum: num,
					val: val
				//Route all CC msg except range from 82 to 89
				)},
				{~midiOut.control(
					chan: chan,
					ctlNum: num = 0,
					val: val
				//Block the CC msg num 120 by assign it to 0
				)}
			)
		)
	);
	["MIDI CH:" + (chan + 1), "CC MSB:" + num, "CC VAL:" + val].postln; //Print the result to console window
})
)

I would like to know a more efficient (or more elegant) way to achieve what I am doing. Also can you point me out a way to find the srcID of the devices so I can specify the function listen exclusively for respective devices? I tried .srcID and .uid but no luck. I use MacOS.

Thanks again @jamshark70 ! You helped me a lot.

There’s nothing particularly wrong with your logic here. The biggest problem I see is that unclear code formatting is causing you to make mistakes in the syntactic construction, and this is changing the meaning of the code.

I almost always write if constructs like this, unless the blocks are very short:

if(condition) {
    true code;
} {
    false code;
};

Then, not only the syntax but also the visual layout makes absolutely, unmistakably clear what it means.

Choose a format and then always do it that way.

So you’ve got an if that goes like this:

if(
    (num <= 89) && (num >= 82),
    { ~midiOut..... },
    ....
)

But just above that, you wrote an if with a very different syntax:

if(
    num != 120,
    if(  // what is missing on this line?
....

Again, you’re not using curly braces to enclose the true/false branches.

My point about formatting is, if you settle on a format that clarifies the syntax, then when you write something that isn’t correct, it will “look wrong” and then you’ll go fix it. It also helps you to avoid falling into unfortunate coding habits like omitting braces for function blocks.

When you put in the braces properly, then you won’t need to repeat the num != 120 condition.

hjh

1 Like

Thanks again! You are right, I am struggle with how to correctly formatting code.
Here is my revised script:

MIDIClient.init;
MIDIIn.connectAll;

(
~midiOut = MIDIOut.newByName("Elektron Digitakt", "Elektron Digitakt").latency_(Server.default.latency);

MIDIdef.cc(\controlChange, {
	| val, num, chan, src |
	if (num != 120) { //If the CC msg is not 120
		if ((num <= 89) && (num >= 82)) {//Check if the CC msg is within range from 82 to 89, do routing if yes
			~midiOut.control(
				chan: chan = num % 82,
				ctlNum: 95,
				val: val
				// If not, check if the CC msg is 120 again
			)
		} {
			if (num != 120) {
				~midiOut.control(
					chan: chan,
					ctlNum: num,
					val: val
					//Route all CC msg except range from 82 to 89
				)
			} {
				~midiOut.control(
					chan: chan,
					ctlNum: num = 0,
					val: val
					//Block the CC msg num 120 by assign it to 0
				)
			}
		}
	};
["MIDI CH:" + (chan + 1), "CC MSB:" + num, "CC VAL:" + val].postln; //Print the result to console window
})
)

However I still don’t know how to do it without check to num != 120 twice.

You don’t have to.

if(num != 120) {
	// true branch: num must be != 120
	// that's the only way to get here
} {
	// false branch: must be 'not(num != 120)'
	// meaning, to get here, num must be 120
	
	// so then, if you do:
	if(num != 120) {
		// this branch can never ever execute
		// because we already know num == 120 at this point
		// therefore the inner `num != 120` will ALWAYS be false
		// so it's redundant: you never need it
	} {
		// it'll be this branch
	}
}

So you can simply write:

MIDIdef.cc(\controlChange, { |val, num, chan, src|
	if (num != 120) { //If the CC msg is not 120
		if ((num <= 89) && (num >= 82)) {//Check if the CC msg is within range from 82 to 89, do routing if yes
			~midiOut.control(
				chan: chan = num % 82,
				ctlNum: 95,
				val: val
			)
		} {
			~midiOut.control(
				chan: chan,
				ctlNum: num = 0,
				val: val
				//Block the CC msg num 120 by assign it to 0
			)
		}
	};
["MIDI CH:" + (chan + 1), "CC MSB:" + num, "CC VAL:" + val].postln; //Print the result to console window
})

hjh

1 Like