Skip to content

Commit 7e57cd7

Browse files
committed
Controller Shared Data: Initial Proposal
Signed-off-by: Owen Williams <owilliams@mixxx.org>
1 parent 04dce16 commit 7e57cd7

1 file changed

Lines changed: 243 additions & 0 deletions

File tree

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# Controller Shared Data
2+
3+
* **Owners:**
4+
* `@ywwg`
5+
* `@acolombier`
6+
7+
* **Implementation Status:** `Partially implemented`
8+
9+
* **Related Issues and PRs:**
10+
* [Ability for controller to share data at
11+
runtime](https://github.com/mixxxdj/mixxx/pull/12199)
12+
13+
> TL;DR: Allow controllers mappings to set and retrieve variables of different
14+
> data types in order to exchange them between the controller code and the
15+
> engine. Think: ControlObjects of arbitrary type that controllers can declare.
16+
17+
## Why
18+
19+
There are multiple scenarios where controller mapping scripts need to share and
20+
access data outside the container of their own controller script engine. This
21+
includes situations where some controllers expose more than one USB interface
22+
that need to communicate with each other, or when a DJ connects multiple
23+
instances of the same hardware to Mixxx.
24+
25+
### Pitfalls of the current solution
26+
27+
ControlObjects are the normal way we share data, but:
28+
29+
* Controllers can't declare control objects.
30+
* Putting controller-specific data inside Mixxx itself would be bad and lead to
31+
bloat.
32+
* Controllers need to share more types of data than just double values.
33+
34+
## Goals
35+
36+
Goals and use cases for the solution as proposed in [How](#how):
37+
38+
* Namespacing: Allow different controllers to declare same-named data objects
39+
without risk of collisions
40+
* Support controllers with screens that require communication from HID to
41+
separate Bulk USB devices (Traktor S4 MK3).
42+
* Build a data model foundation for users with multiple instances of the same
43+
controller (CDJ-2000).
44+
* Ensure that the API could support the features we may wish to add in the
45+
future, such as global namespace, without breaking controller mappings that
46+
use the API defined here.
47+
48+
### Audience
49+
50+
Users of modern controllers or multiple controllers will appreciate this work.
51+
Specifically, this work is required to fully support the Traktor S4 MK3, which
52+
has separate USB interfaces for the controller and the two screens (two total USB
53+
interfaces).
54+
55+
## Non-Goals
56+
57+
* We do not intend to fully support the multi-device scenario yet, that needs
58+
further design to associate specific devices with specific controller
59+
configurations.
60+
* We do not intend to immediately support "global" objects accessible across
61+
namespaces, like "universal shift".
62+
63+
## "Universal Shift"
64+
65+
"Universal Shift" refers to the idea that a shift button pressed on one
66+
controller can be detected by any and all other controllers. This may be a
67+
useful use-case but creates a lot of difficulties, so for now Universal Shift is
68+
out of scope for this first implementation.
69+
70+
## How
71+
72+
### Data Object
73+
74+
We will create a central object inside Mixxx that contains a triple-keyed map:
75+
76+
* Namespace (string)
77+
* Entity (string)
78+
* Key (string)
79+
* Value ("SafeData")
80+
81+
#### Namespace
82+
83+
`Namespace` is a string that is unique to each controller **mapping
84+
definition**. All connected controllers of the same model will share the same
85+
namespace. e.g. Two CDJ-2000's will both have a namespace like `CDJ_2000`. All
86+
hardware mappings must have distinct namespaces.
87+
88+
#### Entity
89+
90+
`Entity` is a logical value defined by the controller mapping definition or
91+
during the initialization of the mapping script (e.g. for readout of a serial
92+
number using MIDI commands). It can be like a Mixxx-style group ("`[Channel1]`")
93+
but it can be any arbitrary string. Many controllers will want to define
94+
something like `deck1` to refer to a device that can itself be assigned to
95+
multiple mixxx channels. For the case of multiple devices of the same type, it
96+
is intended to implement functionality to gather a unique device identifier in a
97+
later step. These will than be used as Entity to distinguish the identical
98+
devices. These identifiers include, but are not limited to:
99+
100+
* USB device serial number (only works for USB and not each manufacturer use
101+
unique serial numbers)
102+
* Operating system provided device identifiers like Container-ID on Windows or
103+
Location-ID on macOS
104+
105+
The controller mapping decides how these entities behave and Mixxx does no
106+
enforcement of them. To reiterate: even if an "entity" "looks like" a Mixxx
107+
group, it is not.
108+
109+
Here is partial suggestion for entity definition, which could be extended over
110+
time.
111+
112+
```typescript
113+
declare Entities {
114+
type Mixer = 'mixer';
115+
type Main = 'main';
116+
type Library = 'library';
117+
type Decks = 'deck1' | 'deck2' | 'deck3' | 'deck4';
118+
type Channels = 'channel1' | 'channel2' | 'channel3' | 'channel4';
119+
type Controller = 'controller';
120+
}
121+
type Entity = Entities.Mixer | Entities.Main | Entities.Library | Entities.Decks | Entities.Channels | Entities.Controller;
122+
```
123+
124+
Numbered entity names could also be validated with a regular expression such as
125+
`deck[0-9]+`.
126+
127+
#### Key
128+
129+
`Key` is a logical value defined by the controller mapping definition. It could
130+
refer to a button, light, knob, or abstract name.
131+
132+
The controller mapping decides how these keys behave and Mixxx does no
133+
enforcement of them. Similar to "entity", keys bear no relation to equivalent
134+
Mixxx keys.
135+
136+
#### Value
137+
138+
In the engine, the `value` is stored as a QVariant, however we want to only
139+
support a limited set of types in Javascript / Typescript. For the first
140+
implementation, we will support bool, number, and string, and Arrays of those:
141+
142+
```typescript
143+
type SafePrimitive = string | number | boolean | null;
144+
145+
type SafeData =
146+
| SafePrimitive
147+
| SafePrimitive[];
148+
```
149+
150+
The list of allowable types can be expanded as needed, but we want to be sure
151+
that the shared data system does not become a "bag of bytes" message bus for
152+
large pieces of data like bitmaps or code, nor should it be used to circumvent
153+
intentional limitations or gaps in the overall javascript framework.
154+
155+
#### Example
156+
157+
The shift button on the left side of a Traktor S4MK3 would be stored this way:
158+
159+
pseudocode -- not final naming:
160+
161+
`m_shared_data["S4MK3"]["deck1"]["shift"] = true`
162+
163+
### API
164+
165+
The shared data API should be roughly the same across Controllers, QML, and C++.
166+
The primary difference is that C++ will have access to the namespace value at
167+
all times, whereas controllers and QML will have that value elided.
168+
169+
The controller javascript has access to the shared data object through three
170+
functions:
171+
172+
#### Get
173+
174+
excuse the pseudo-js:
175+
176+
`engine.getSharedValue(entity: Entity|string, key: string): SafeData?`
177+
178+
`namespace` is set automatically by the engine code, so controllers can't get
179+
that wrong.
180+
181+
This function returns error if the value is not found.
182+
183+
#### Set
184+
185+
`engine.setSharedData(entity: Entity, key: string, value: SafeData): void`
186+
187+
`namespace` is set automatically by the engine code, so controllers can't get
188+
that wrong.
189+
190+
Calling "set" triggers "updated" signals to all subscribers across the engine,
191+
QML (e.g. controller screen code), and controllers.
192+
193+
Controllers do not get notified about updates they initiated themselves, to
194+
prevent circular signal loops.
195+
196+
#### Updated
197+
198+
Controllers can subscribe to notifications about data updates via a method
199+
similar to how they subscribe to engine Control Object updates:
200+
201+
(not final naming)
202+
203+
`function makeSharedDataConnection(entity: Entity, name: string, callback: CoCallback): ScriptConnection | undefined;`
204+
205+
In this first implementation, controllers can only subscribe to updates for
206+
their own namespace.
207+
208+
### Possible future directions
209+
210+
The following are possible future extensions to this proposal that are currently
211+
out of scope and will not be implemented in the first version, but we want to
212+
make sure to leave room in case we add them in the future:
213+
214+
#### Cross-device communication / subscription
215+
216+
There is a possibility controller authors may want access to signals sent from
217+
other controllers, for instance a "universal shift" button. For this purpose we
218+
may choose to allow controller to "subscribe" to updates from other namespaces,
219+
or all namespaces, in a read-only fashion. In this scenario, controllers would
220+
not have the ability to write updates to namespaces outside their own. This may
221+
be brittle because controllers would need to know about all possible valid
222+
namespaces, so this feature would need more care to make it maintainable.
223+
224+
#### "Global" namespace
225+
226+
Another possibility is that we may want a "global" namespaces that all
227+
controllers can read and write to. This would be another way to support a
228+
"universal shift" button. This would have to be carefully managed to prevent
229+
collisions between controller configs. One way to do this would be to "bless"
230+
specific entities and keys for the global namespace, and controller authors
231+
would have to add their requested global entity/key to Mixxx.
232+
233+
## Alternatives
234+
235+
The original implementation did not have entities and keys and instead had a
236+
single namespaced data blob that controllers had to manage themselves. This
237+
approach requires a lot more work on the part of the controller author to merge
238+
and manage the data object.
239+
240+
## Action Plan
241+
242+
1. Build on the existing PR to implement the desired API
243+
2. Rewrite the Traktor S4 MK3 mapping to support the new API.

0 commit comments

Comments
 (0)