Control Change Constraints.. Same Range from different MIDI input?

Hi All,

I’m scripting the direct conversion of incoming Midi CCs to plugin parameters (for MSuperLooper.) I will need more than one instance of MSuperLooper, each to be separately controlled. However, once I’ve use this constrained callback:

On ControlChangeEvent(m : ControlChangeMessage) Matching [20…31] from MSL1_CHANNEL_CONTROL

…it seems I can not subsequently use THIS callback:

On ControlChangeEvent(m : ControlChangeMessage) Matching [20…31] from MSL2_CHANNEL_CONTROL


The goal here is to use identical MIDI CCs for each instance/MIDI IN.
Is there syntax for including multiple MIDI inputs within a single constraint?
Or can I remove the constraint entirely and gracefully exit the script later on if the input does not match?

Any and all comments welcome… I’m quite new to this!

EDIT For some context, I’m basically scripting around the shortcomings of MSuperLooper. Mobius is what I really need… sadly, I need to be on a post-Catalina OS for other critical feature support.


Why aren’t you using widgets?

I certainly could, but it seemed ungainly to me:
4 x MSuperLooper Instances on four Virtual MIDI inputs
16 x Channels of Per-Track Control commands
25 Commands available per Track

It just seemed like too many widgets, especially considering I don’t actually have any free hands or feet with which to manipulate widgets whilst I perform. Also, I had hoped to simplify the ‘language’ of commands by using identical CC numbers across 16 channels, and sticking to ‘discrete’ commands (ie no relational commands like ‘previous,’ ‘next,’ etc) to avoid strange and cacophonic results should a command be missed… and I wasn’t sure how to encapsulate this in widgets.

Of course, I know widgets can probably be the ANSWER to this problem rather than an encumbering nuisance. I’m just a bit frustrated that my 128 x 16 available CCs in Ableton cannot simply directly control the parameters. I shall have to dive deeper into widgets and find a way to better manage things.

Of course, I know there are ways to approach this more intelligently by crafting the widgets properly.

I repeat my question -


Specifically, how are they declared in your script? And are they associated with the same MIDI port or different MIDI ports?

How were you going to manipulate them from GPScript?


They refer to two virtual MIDI ports, each of which is meant to control one instance of MSuperLooper.


That is the declaration.
I had MSL2_CHANNEL_CONTROL declared in a separate callback routine when I got the error. Not sure whether they can share a callback, as every midi control change callback requires a (singular?) MIDI input block as a parameter…

All is automated from a separate computer running Ableton. It sends the looper control commands as MIDI over the virtual midi port(s) as it plays through a sequence.

and why can’t you control widgets with that approach?

I wasn’t suggesting that I could not control them… only that I had no need for the visual display or interactive features of widgets. But as I’ve acquiesced elsewhere, I ought to know after years of following this forum that widgets are also a means for more intelligent parameter manipulation.

In that light, I’ll first look into using my script to manipulate widget output rather than CC input?

I’m asking why you can’t map the output messages from Ableton to GP widgets?
Why do you need scripting at all?

The number of widgets required was in the hundreds.

4 x MSuperLooper Instances on four Virtual MIDI inputs
16 x Channels of Per-Track Control commands
25 Commands available per Track

@celoranta in that case you can move the constraint/check inside the midi received callback, by using a simple IF statement or by adjusting the SELECT conditions.

I wasn’t able to envision a widget scheme that made sense, given the massive number of parameters I’m attempting to address:

There are (at minimum) sixteen track-specific commands to be replicated across sixteen tracks within the plugin. There are also sixteen (at minimum) global commands which apply across all tracks. That’s 272 parameters to map to plugins per-instance. Do this across four looper instances, and I’ve got 1088 widgets to create on my rackspaces. (Probably this could be intelligently collapsed… I just need to figure out how.)

The attractive thing about doing this without widgets was the ability to reuse the same Ableton MIDI clip across multiple MIDI channels to keep the overall level of commands from getting overwhelming. Of course, I could use this to drive widgets instead of parameters… I just didn’t see the benefit of doing so… it seemed like unnecessary work. It seems it is not.

So, I would create a separate callback for each virtual midi port / looper instance and remove the MATCHING statement within the definition of each, instead dealing with the range matching within the SELECT clauses?

Actually I’m not sure yet if you have found a bug. You should be able to use the same constraints with different MidiIn Blocks

Can you post a small example that doesn’t compile?

Also, and others would know this better than me but can you use M4L to generate OSC messages in Ableton? With OSC messages you can control plugins directly.

I didn’t realize that!

M4L is pretty powerful, and I do own it. I will take a peek.

Of course my examples are compiling now. The original one was not saved, but I must have misinterpreted the error. I’ll chalk it up to my own inexperience unless I encounter it again.

This seems to be working in principle… both Virtual Midi ports are receiving different data, so the concept works. I think some of my value mapping is wrong, but no time at the moment to look into it.

// 20  Record (0)
// 21  Mark End (1)
// 22  Restart (2)
// 23  Undo (3)
// 24  Previous (4)
// 25  Next (5)
// 26  Clear Loop (6)
// 27  Clear Track (7)
// 28  Reset All (8)
// 29  previous (Preset Trigger) (195)
// 30  next (Preset Trigger) (196)
// 31  Redo (197)

// 85  Cancel Record (248)
// 86  Play (249)
// 87  Replace (318)
// 88  Clear Loop Immediately (335)
// 89  
// 90  

// Track parameters are controlled via CC numbers 27 to 38, with each MIDI channel controlling a different MSL track (1-16).

//  102      Reset Track
//  103	Play Track
//  104	Reverse Track
//  105	Select Track
//  106	Select and record Track
//  107	Loop Number
//  108	Gain
//  109	Pan
//  110	Feedback when overdub
//  111	Feedback
//  112	Feedback FX
//  113 Volume
//  114  Dry Fx Even if Not Selected
//  115  Input
//  116  Output
//  117  Default Bars

Function ParamFromCC (CC : Integer, Channel : Integer) Returns Integer
        CCtoParam : Integer 
        CC : Integer
        CC <= 28 Do  
            CCtoParam = CC - 20
        CC <= 31 Do
            CCtoParam =  CC + 166
        CC == 85 Do  
            CCtoParam = 248
        CC == 86 Do
            CCtoParam = 249
        CC == 87 Do
            CCtoParam = 318
        CC == 88 Do
            CCtoParam = 335

        CC <= 109 Do  // Main group of track parameters (starts from parameter 27). Each track is spaced out every 8 parameters.
            CCtoParam = 27 + CC - 102 + (Channel * 8)
        CC <= 112 Do  // Track feedback parameters (starts from parameter 200). Each track is spaced out every 3 parameters.
            CCtoParam = 200 + CC - 110 + (Channel * 3)
        CC <= 113 Do // Track volume parameters (starts from parameter 254).
            CCtoParam = 254 + Channel
        CC <= 116 Do // I/O and Dry FX parameters (starts from parameter 270).  Each track is spaced out every 3 parameters
            CCtoParam = 270 + CC - 114 + (Channel * 3)
        CC <= 117 Do // Track Default Bars parameters (starts from parameter 319)
            CCtoParam = 319 + Channel
    result = CCtoParam

   MSL1 : PluginBlock
   MSL2 : PluginBlock

On ControlChangeEvent(m : ControlChangeMessage)from MSL1_CHANNEL_CONTROL

        Channel : Integer = GetChannel(m) - 1
        CC : Integer = GetCCNumber(m)
        CCtoParam : Integer = ParamFromCC (CC, Channel)

        // Set the parameter directly in MSL
    SetParameter(MSL1, CCtoParam, MidiToParam(GetCCValue(m)))


On ControlChangeEvent(m : ControlChangeMessage)from MSL2_CHANNEL_CONTROL
        Channel : Integer = GetChannel(m) - 1
        CC : Integer = GetCCNumber(m)
        CCtoParam : Integer = ParamFromCC (CC, Channel)
        // Set the parameter directly in MSL
    SetParameter(MSL2, CCtoParam, MidiToParam(GetCCValue(m)))