Cabbage Logo
Back to Cabbage Site

Update to JSON structure

After quite a bit of head-scratching over how best to handle channels, the problem is that some widgets support multiple channels, xypad for example, which in turns involves multiple ranges. This seems clunky when a channel can never really exist without a range anyway.

So I’ve drafted up a new strucuture that feels kind of future proof to me. Ranges are now part of a channel object, and each channel object resides within a ‘channels’ array. The channel id is the channel that Csound communicates on, and it’s also the parameter name as shown in a DAW. The updated structure looks like this. Note that each channel has an event associated with it under this new scheme.

[
  {
    "type": "button",
    "channels": [
      {
        "id": "startStop",
        "event": "valueChanged",
        "range": { "min": 0, "max": 1, "value": 0 }
      }
    ]
  },
  {
    "type": "rotarySlider",
    "channels": [
      {
        "id": "harmonic1",
        "event": "valueChanged",
        "range": { "min": 0.0, "max": 1.0, "value": 0.0, "skew": 1.0, "increment": 0.001 }
      }
    ]
  },
  {
    "type": "image",
    "channels": [
      {
        "id": "cursorX",
        "event": "mouseDragX",
        "range": { "min": 0, "max": 1, "value": 0 }
      },
      {
        "id": "cursorY",
        "event": "mouseDragY",
        "range": { "min": 0, "max": 1, "value": 0 }
      },
      {
        "id": "leftButton",
        "event": "mousePressLeft",
        "range": { "min": 0, "max": 1, "value": 0 }
      },
      {
        "id": "rightButton",
        "event": "mousePressRight",
        "range": { "min": 0, "max": 1, "value": 0 }
      }
    ]
  },
  {
    "type": "xyPad",
    "channels": [
      {
        "id": "cutoff",
        "event": "mouseDragX",
        "range": { "min": 100, "max": 10000, "value": 1000, "skew": 0.5, "increment": 1 }
      },
      {
        "id": "resonance",
        "event": "mouseDragY",
        "range": { "min": 0.1, "max": 20, "value": 5, "skew": 1, "increment": 0.1 }
      }
    ]
  }
]

Under this scheme users can also define custom mapping for buttons, mouse positions, etc. 1D controllers liks sliders, button, checkboxes, etc., use a single valueChanged event. Multi dimensional widgets can define channels for any of these events:

Event Description
mouseDragX User is dragging along the X axis; the channel value updates accordingly.
mouseDragY User is dragging along the Y axis; the channel value updates accordingly.
mouseDragZ (Optional) 3rd axis, if supporting 3D gestures or depth.
mouseMove Movement of the pointer over the widget (normalized X/Y can be separate channels or combined).
mousePressLeft Left mouse button pressed; channel goes 0→1.
mousePressRight Right mouse button pressed; channel goes 0→1.
mouseReleaseLeft Left mouse button released; channel goes 1→0.
mouseReleaseRight Right mouse button released; channel goes 1→0.
mouseClickLeft Optional shorthand for press+release detection.
mouseClickRight Optional shorthand for press+release detection.

Indeed, any widget can define channels for these events. If you need to know when a user clicks on a slider for example, you can add a ‘mousePressLeft’ channel event. These events are all defined in the frontend, the backend is agnostic to events, it simple parses the channel/value pairs. User develping their own widgets can define whatever events they like so the system is scalable to other user interactions.

Let me know what you think. :+1:

p.s. if range is left out,a default range between 0 and 1 will be used.

1 Like

Just a follow up, this is not bullet proof either. Take this for instance:

"type": "xyPad",
    "channels": [
      {
        "id": "cutoff",
        "event": "mouseDragX",
        "range": { "min": 100, "max": 10000, "value": 1000, "skew": 0.5, "increment": 1 }
      },
      {
        "id": "resonance",
        "event": "mouseDragY",
        "range": { "min": 0.1, "max": 20, "value": 5, "skew": 1, "increment": 0.1 }
      }
    ]

What if we want to update its colour? Which channel do we send a colour update to? :thinking: It seems to me that each widget needs a unique name. So the previous would become:

"type": "xyPad",
    "id":"xypad1",
    "channels": [
      {
        "id": "cutoff",
        "event": "mouseDragX",
        "range": { "min": 100, "max": 10000, "value": 1000, "skew": 0.5, "increment": 1 }
      },
      {
        "id": "resonance",
        "event": "mouseDragY",
        "range": { "min": 0.1, "max": 20, "value": 5, "skew": 1, "increment": 0.1 }
      }
    ] 

If we want to update its colour we would do:

cabbageSet("xyPad1", "colour.fill", "#ff0000");

If we want to update its value we would do:

cabbageSetValue("resonance", .5)

In this way channels become much more closely aligned to parameters. What’s annoying is its another thing to define. But it could also be optional. In the case where there is only a single channel, that same channel name could be used for values and attributes. But in the case of multichannel widgets, one should set an id property?

Any thoughts?

Could we just use the first channel of the xyPad as the one with which to communicate changes of appearance? I think soundFiler behaves like this, perhaps accidentally.

In the previous example you posted, this would be "cutoff’ and I appreciate that it might seem odd to be changing the colour of ‘cutoff’.

Mostly we are creating sliders and dials and it would feel like an imposition to insist that the user defines another channel name for every widget. Cabbage ‘polling’ used to do this with ident channels.

Attaching ranges to channel objects seems sensible. This scares me though: ‘… kind of future proof…’

so mouseDragX is updated during click-and-drag? Should mouseMove just be mouseMoveX or how can multiple axes be combined?

I not totally clear about how mouseClickLeft would behave.

Will there be a mouseHover switch for when the mouse hovers within the boundaries of a widget?

This is my issue with it. It doesn’t lend itself to clear and intuitive code.

I share your concerns here for sure. Using JSON is already an extra step up the ladder of verbosity. I think we can set it to use the first channel id in the array if it’s not explicilty set. So in the previous case, ‘cutoff’. Those of us we are looking to reach a total zen state of coding nirvana can use widgets ids :rofl:

Sorry, I mean, eh, completely and 100% future proof!

Thanks Iain for looking over this. Those events came out of the top of my head without serious thought. We can of course define whatever events we think are appropriate. mouseMove, mouseDrag, mouseClick, etc.

Just starting a list of potential events that could be exposed, let me know if anything is left out.

Event Description
mouseMoveX User is moving mouse along the X axis; the channel value updates accordingly.
mouseMoveY User is moving mouse along the Y axis; the channel value updates accordingly.
mousePressLeft Left mouse button pressed; channel goes 0→1 - (can add release events, but they are not really needed considered this will return a 0 on released?).
mousePressRight Right mouse button pressed; channel goes 0→1.
mousePressMiddle Right mouse button pressed; channel goes 0→1.
mousePressed Returns 1 when any mouse button is pressed;.
mouseInside Returns 1 when any mouse button is within the bounds of the widget, 0 if outside.

I think with these we can figure out a most things. I can also add further utility event like dragOutside which would basically be a mousePressed == true && mouseInside == false.

With regards to global mouse events, we can just query the form widget for its mouseMoveX/Y events.

Small feedback, the value is not necessarily part of a range for other widgets such as buttons and dropdowns. Would it make sense to move it out of the range and into it’s own property? Might also make it easier in terms of preset storing in a JSON-object so you don’t need to store the whole channel’s range?

Sorry, you’re right. In the current spec, value isn’t part of the range, but defaultValue is, which I think is relevant for initialising a parameter to a set value. That was a typo. value is a top level property, for the very reasons you’ve outlined. :+1:

1 Like

So here are the new events I’ve added for the image class. I’ll add them to the form widget too, and the groupbox. It’s simple to roll these out across all widgets over time. All of these events can have ranges attached to them in order to help normalization procedures, and custom mapping.

Event Description
valueChanged Tracks simple toggle-like behavior, emulates what we currently have in Cabbage 2
mousePressLeft Tracks presses of left mouse button
mousePressRight Tracks presses of right mouse button
mousePressMiddle Tracks presses of middle mouse button
mouseMoveX Tracks movement along X axis.
mouseMoveY Tracks movement along Y axis.
mouseDragX Tracks movement along X axis with mouse pressed
mouseDragY Tracks movement along Y axis with mouse pressed
mouseInside Returns max when any mouse button is within the bounds of the widget, min if outside.