Yet another thread about controling Global Rackspace widgets from the local rackspace

In my global rackspace, I’ve implemented a sort of a mixer with 16 stereo channels representing 16 VST instruments. For each of the 16 channel strips, I have widgets representing the name of the instrument, the volume level, which physical controller and what range of notes it responds to, its transposition in octaves, and some toggles including whether it’s active or not, whether its VST block is in the global or the local rackspace, whether that block should receive sustain pedal messages, whether it should receive expression pedal messages and so on. For specific instruments whose VST block is in the global rackspace, there will be additional widgets such as organ drawbars, effects controls, and so forth.

The reason I’m not just doing all of this in the local rackspace (using the built-in features of the Midi In blocks, etc) is that I’m doing a lot of work under the hood to do magic with sustain and expression pedals, velocity crossfades between overlapping keyboard zones, a “midi learn” mechanism for changing the zone boundaries on the fly, and so forth. I want all of this functionality to be available in every rackspace, which is why I’m putting it in the Global Rackspace.

I want to be able to control those widgets from the local rackspace. I gather that the usual way to do this would be using global parameters to link each widget with a corresponding widget in the local rackspace, but I would need a lot more than just 128 parameters to do that, so this isn’t feasible.

Maybe the answer is to put code into the “on Activate” block of each local rackspace which programmatically changes the values of the relevant widgets in the global rackspace (which is why I’m posting this in the Scripting category). Then during performance I’ll stay in the Global Rackspace view so that I can see the widgets while playing. However, I remember @dhj mentioning once that he considers BindExternalWidget() to be a regrettable hack, and I’d rather do this in the most elegant way, whatever that may be.

Perhaps OSC is the correct answer? I’ve found documentation on sending MIDI messages from the local to the global rackspace using OSC, but I can’t figure out how to send arbitrary non-MIDI data.

Can anyone point me in the right direction?

1 Like

OSC opens a Universe of possibilities.
But why send midi messages?

I have zero desire to send MIDI messages. I just haven’t found any documentation on using OSC to send arbitrary data between the local and the global rackspaces. Hence my question.

When you want to control,widgets just send the appropriate messages to the gig performer listening port on the local IP address.
Tomorrow I can upload an example gig file.

1 Like

Here’s what I’ve tried so far, with no luck:

  • turned on “Enable OSC” under OSC Setup
  • turned on “Enable OSC” for a particular widget in the Global Rackspace. The OSC/GPScript name of this widget is “zoneFader_1”.
  • I notice that very faint, almost invisible text appears next to the name: “/GlobalRackspace/zoneFader_1/SetValue”. I take this as a hint (aimed mostly at people who have excellent vision) about the OSC address?
  • I put this in the rackspace script for a local rackspace:
On Activate
  Print("sending a double")
  OSC_SendDouble("/GlobalRackspace/zoneFader_1/SetValue", 0.0)
  Print("sent")
End
  • When I switch to that rackspace, nothing happens to the fader and no error message is produced (but I do indeed see “sending a double” and “sent” in my Script Logger)

I guess I’m getting closer? What am I missing? Probably something about IP ports and addresses?

Actually, I take it back… it started doing exactly what I want. I don’t know what changed (maybe I had a typo in the OSC address) but hurray.

Speaking of typos in the OSC address… is there no way of determining the success or failure of sending the message?

I think what I did to make it work was to change the OSC listening port from its default of 54344 to 54341 to match the remote client port. Is that correct and recommended, or will that cause feedback loops?

You might easily run into trouble with that approach. But there are also OSC_Sendxxxx that takes an address and a port. For the address you can use “127.0.0.1”. The port should be the receive port (there is a function to retrieve that, if you want to make your code more bullet-proof). This way only explicit calls will use the listening port of gp, the implicit stuff will still send to the remote client.

My 2 cents

No - that’s the nature of the underlying UDP network protocol (which is used by OSC). There’s no ACK phase like there is with TCP

That said, it might be useful to have a mechanism to send back a “No such message defined” OSC message if one sends a bogus message to a GP instance. I’ve added this concept into our tracking system.

Thanks, everybody. Y’all are super-helpful.

How do I retrieve the return value from, say, a “/oscHandle/GetValue” OSC message? None of the “OSC_Send…” functions in the docs appears to return anything.

You don’t! OSC messages are not synchronous. There’s no such thing as a synchronous return value!

The purpose of /somehandle/GetValue is for an external application to request the current value of a widget in Gig Performer associated with the handle somehandle

Hmm… and once the external application made that request, what happens next? What would be the purpose of making such a request if there is no way to retrieve the response?

The external application will have a mechanism that recognizes incoming OSC messages and route them appropriately.

Lemur and TouchOSC work this way as does MaxMSP, M4L, Gig Performer and, well, yeah, every external app out there. That’s just how OSC works.

The Lemur template that we created to work with GP (search our blog) implements this specifically for widgets.

1 Like

Thanks, I’m catching on. :slight_smile:

How looks your OSC setup in the options?

Let me ask you a question……what are you trying to do that requires you to perform a “getValue” from GPScriot?

@pianopaul - In the Options I simply have “Enable OSC” turned on, and eveything else is unchanged from the defaults. It seems to be working great now.

Well, to be honest, I have no need for that at all. I was just implementing some low-level utility functions to put in an Include file to be used in all of the local rackspaces that need access to Global Rackspace widgets and was going for completeness, but in practice I can’t think of any situation where I would need that.

Here’s my Include file as it currently stands:

// These functions are for the local rackspaces to communicate with widgets in the Global Rackspace

var OscIpPort: Integer = OSC_GetGPListeningPort()
var OscIpAddress: String = "127.0.0.1"

Function globalSetCaption(widgetName: String, caption: String)
  OSC_SendStringSpecific("/GlobalRackspace/" + widgetName + "/SetCaption", caption, OscIpAddress, OscIpPort)
End

Function globalSetValue(widgetName: String, value: Double)
  OSC_SendDoubleSpecific("/GlobalRackspace/" + widgetName + "/SetValue", value, OscIpAddress, OscIpPort)
End

I would discourage that approach.

GP Script (and GP itself) was designed with a top-down philosophy which means that one should never add/implement functions until it’s obvious they’re needed.

In other words write the high level stuff first, assume the low level stuff you need is there and then implement the lower level stuff on demand.

Saves a lot of time and unnecessary bloat😉

2 Likes

Thanks. One thing I love so much about this community (and its leaders) is the focus on sharing the philosophy and not just the practices.

(Of course, practice and philosophy are inseparable when it comes down to it.)

3 Likes