Nested Functions in a MIDI Def

Hi -

I’m experimenting with several nested functions - simplified in the example below.

Essentially, I would like to have the option of turning on a variety of “processing” options on incoming MIDI notes in different channels - the example below uses only one.

It seems like re-establishing the entire ~multipleChannels function in order to switch a processing toggle would be overkill - but I’m not sure if there is a better way to do this.

I’d love to get some suggestions about how to do this.

Thanks all.

m=MIDIIn.findPort("IAC Driver", "Bus 1");

~multipleChannels = {|channel, ptoggle|
	var process, midiOn;
	
process = {|toggle = 0, note|
		if (toggle == 0, {note = note}, {note == 120.rand})};
	
		midiOn = MIDIdef.noteOn((\noteOn++i).asSymbol,
		{arg vel, note, chan,
				Synth.new(\default, [
				\freq, (process.(ptoggle, note)).midicps,
				\amp, vel]);
		}, nil,  channel, m.uid );
		
	};


10.do{|i|
	~multipleChannels[i]
};

You’re on the right track, but there are a few things in your code that aren’t quite right. I reformatted it slightly for readability (but didn’t change anything), and added some comments. Hopefully this is helpful!
In general, your approach is a good one for what I think you’re trying to do, I wouldn’t second guess yourself re. “overkill” - get this code working and you’ll have a decent starting point.

(
m = MIDIIn.findPort("IAC Driver", "Bus 1");

~multipleChannels = {
    |channel, ptoggle|
    var process, midiOn;
    
    // `note` is local to the process function - setting it is only meaningful inside the function.
    // You're NOT modifying the note that you pass in as an argument, for example.
    // Functions return the result of the last statement that they execute - in your case, one of th4e two 
    // if blocks. So, you don't need the = assignment, you can just do either `note` or `120.rand`. When you 
    // call the function like `result = process.(ptoggle, note), you'll get one of these two values returned.
    process = {
        |toggle = 0, note|
        if (toggle == 0) {
            note = note
        } {
            // note that == is a comparison - you're not setting `note` to a value here, just comparing
            note == 120.rand
        }
    };
    
    // MIDIdefs are registered based on their name, so you don't need to track it in a variable here
    // unless you want to use midiOn somewhere else later.
    midiOn = MIDIdef.noteOn(
        (\noteOn++i).asSymbol,
        {
            // You have a comma after `chan`, so supercollider is expecting another aargument.
            // The args section should be ended with a ;  before you create your Synth.\
            arg vel, note, chan,
                Synth.new(
                    \default, 
                    [
                        // Since you're not passing a value into your function for ptoggle, this value will be nil
                        // and so your `toggle == 0` comparison above will always be false.
                        \freq, (process.(ptoggle, note)).midicps,
                        // \amp on the default synth expects a value from 0..1 (this is very common but not a fixed rule).
                        // vel is a midi value from 0..127, so you'll need to scale this to 0..1. The linlin method is good for this.
                        \amp, vel
                    ]
                );
        }, 
        nil,
        channel, 
        m.uid 
    );
};

10.do{
    |i|
    // The square braces syntax is for accessing arrays. ~multipleChannels is a function - to call it,
    // you instead need to do either ~multipleChannels.value(i) or ~multipleChannels.(i) - these are equivalent.
    // Also, your function takes two arguments, but you're only passing channel.
    ~multipleChannels[i]
};

)
2 Likes

Btw, what I meant with the comments about the if block - your process function could look like:

process = {
  |toggle = 0, note|
  if (toggle == 0) {
     note;
  } {
    120.rand;
  }
}
1 Like

Thanks, @scztt - I’ve implemented all of the changes you suggested and of course, thanks to you, it works now!
There was one thing that I wanted to confirm, though - even though I know you said this would be OK…
If I want to change a toggle, so that I go from something like:

~multipleChannels.(i, 0) ;
//into this:
~multipleChannels.(i, 1);

This is the best way to do this? The plan is to have a lot of different processes, so I’m expecting my messaging is going to get a little more ornate. I’d also probably plan on establishing MIDI messages for cc controls and note-offs within the same “~multipleChannels” function, so I could be invoking a function with several hundred lines of code before it was done. I guess I just don’t have a sense of how powerful SC is, so I’m being a little cautious about building something elaborate.

Yes, this is more or less exactly what you want to do. I wouldn’t worry about efficiency. There are places where sclang can bog down, but these are only in VERY sophisticated and complex event processing scenarios, or if you’re processing very large amounts of data - and almost always it’s easy to fix these problems. If you’re ever concerned, the bench method will tell you how long something takes. So:

{
    60.do {
        ~multipleChannels.(i, 0) ;
    }
}.bench

e.g. assuming you were switching your modes 60 times per second, this measurement would represent how much of that second would be spend doing this processing. I think you’ll find that it’s pretty microscopically small :slight_smile:

1 Like

A small update: this seems to immediately cause some issues with stuck midi notes. Is there a standard “clear all” message that should be run before the MIDIdef intercepts a message?

I should mention that I’ve added a MIDIdef noteOff message and an array to store each note…

not sure if this answers your question but there is a MIDIOut:allNotesOff(channel) method available…

1 Like

There are several possible reasons for stuck notes. It would be good to figure out which reason and tailor the solution to that.

One reason could be that the “set gate to 0” messages are not going out. Server dump OSC (server menu) would help you determine if this is the case.

Another reason could be that a Synth.new may be too quickly followed by a “set gate to 0.” This can cause silent stuck nodes.

There might be something else, but, your note-release code isn’t publicly visible here so guessability is going to be limited to a rather poor level.

If all else fails, you can put your synths into a group. Then “standard ‘clear all’” would be to set the group’s gate to 0. But this is just hiding, rather than solving, the problem.

hjh

1 Like