bitfocus / companion

Bitfocus Companion enables the reasonably priced Elgato Stream Deck and other controllers to be a professional shotbox surface for an increasing amount of different presentation switchers, video playback software and broadcast equipment.
http://bitfocus.io/companion
Other
1.61k stars 503 forks source link

Expose properties instead of actions #1187

Open Julusian opened 4 years ago

Julusian commented 4 years ago

Describe the feature I have been thinking about this for a while, as exposing numeric properties in device modules gets really repetitive, especially when wanting to allow actions to do relative changes and comparison operators in feedbacks.
The basic idea is to avoid having to expose an action, feedback and variable for the same value. Instead a single property can be defined, and companion can present it as an action/feedback/variable/something depending on what is most appropriate for the context.

The current actions architecture works nicely for devices which work in actions, but puts a lot of work on the modules when the devices expose properties/state. This leads to the functionality and flow being inconsistent even within modules. The idea of this is not to replace all actions, just some of them. Actions are appropriate for some things (eg triggering a restart of a device), but properties make sense in places where it is possible to provide feedback (eg setting preview to input1)

As an example lets look at an audio fader. For this, we want to be able to set it to an absolute value (perhaps based on the value of another property/variable), and to do small changes to bump the level up and down. We also want feedback for that value using a few different comparison operators (<, <=, ==, !=, =>, >) In this example, that isnt much to do. But it is work that is going to want to be replicated for each module which has an audio fader, and some will even have different types of faders and so will need to duplicate it themselves too.

A better approach to handle these would be for modules to instead report back the properties they have which can be set. Each property could be defined as something like:

{
  id: 'fader',
  name: 'Fader',

  type: 'number',
  min: '-90',
  max: '10',

  /**
   * Instances of this property.
   * eg, channel number of the audio fader
   * null if no instances
   */
  instanceIds: [
    { id: 'ch1', label: 'Channel 1'},
    { id: 'ch2', label: 'Channel 2'},
  ],

  getValue: () => {...},
  setValue: () => {...}, // optional, it could be readonly

  subscribe: () => {...},
  unsubscribe: () => {...},
}
{
  id: 'desk-mode',
  label: 'Desk Mode',
  type: 'dropdown',
  options: [
    { label: 'Something', id: 0 },
    { label: 'Another', id: 1 }
  ],
  getValue: () => {...},
  setValue: () => {...},

  instanceIds: null,

  subscribe: () => {...},
  unsubscribe: () => {...},

}

And to go with this, whenever the module has a change to a property it should inform core, which can then calculate where this is used

  this.notifyPropertyChange('ch1-type')

Companion core would wrap the properties up in actions/feedbacks so that they can be added to buttons like normal. In this way, users will be mostly unaware of this concept, other than ui and functionality becoming more standardised.

This is intended to be a replacement for variables. I think it will be pretty simple to map the existing variables apis onto this, for modules which arent ready to be converted. It will require a small change in variable parsing, to allow for the instances to be addressed, such as $(atem:fader:0), which should be easily doable.

Q) Should getValue() be optional too? I can see some value in write only properties.

Q) Should there be getValue(), or should the module push values out to companion like variables do today? What will be the impact for modules which have 'secret' variables?

Q) What is the cost of updating this manifest? Should we be worried that some modules will want to have a flexible amount of instanceIds? Perhaps the instanceIds should be possible to define as a regex too?

The exact definition of subscribe/unsubscribe may need refining. It might be that they want to be combined, and fed the array of ids that should be subscribed to. Otherwise the lifecycle of this could become incredibly complex.

Q) What happens if someone does $(atem:$(internal:custom_a):0) or $(atem:fader:$(internal:custom_a))? These could fail if there is no active subscription to the correct value.

Future This will serve as a good starting point for when we want to support input devices with faders or dials, this could start as allowing osc to provide values.
It could be used to create some more dynamic behaviour too, such as actions which sets the level of channel 1 to match channel 2.
A way to 'bind' a property to follow one from another module would be really useful. It could be done manually by defining a trigger to act on a change, and fire off a change, but that will be very slow and tedious to setup and maintain. A way of doing the same thing for simple things in a few clicks would be ideal. Perhaps even with a 'transform' step

issue-label-bot[bot] commented 4 years ago

Issue-Label Bot is automatically applying the label Enhancement to this issue, with a confidence of 0.96. Please mark this comment with :thumbsup: or :thumbsdown: to give our bot feedback!

Links: app homepage, dashboard and code for this bot.

MeestorX commented 4 years ago

Can't say I completely understand what you've got in mind, but anything that allows for more flexibility I'm definitely up for.

I wish there was a Zoom meeting for module developers to discuss ideas. Slack messages aren't really a great way to brainstorm!

josephdadams commented 4 years ago

@MeestorX host one!

MarauderSeeker commented 4 years ago

maybe do a Discord Voice call