View Source X32Remote.Session (x32_remote v0.1.0)
A module for running commands on an X32 mixer.
To use this module, you will need to create an ExOSC.Client process (via
ExOSC.Client.start_link/1) that points to the target mixer IP and port.
Then you can start this module using start_link/1, specifying the client
PID via the (mandatory) client argument. (The GenStage subscription will
be set up automatically.)
To run commands on the mixer, you can use either call_command/3 if you
expect a reply, or cast_command/3 if you do not. (See the "How replies
work" section below.)
Most programs will not need to directly use this module at all:
X32Remote.Mixerprovides a ready-to-use version of the X32 command set, suitable for use if your program only needs to talk to a single mixer.If you need to talk to multiple mixers simultaneously, you can launch supervised client-session pairs with
X32Remote.Supervisor, and use theX32Remote.Commands.*modules directly to select which session to send each command to.
Using the call_* / cast_* functions directly can be useful for commands that this
library does not (yet) support, however.
how-replies-work
How replies work
Technically, the OSC protocol is stateless and symmetrical — there is no built-in concept of "replies".
To simulate replies, when receiving an OSC message that requests the value of
a mixer parameter at a given path, the X32 device responds by sending a
return message using the same path, with the response data in the
arguments.
For our purposes, this creates a number of issues:
commands-without-replies
Commands without replies
Many commands do not produce a reply. As a general rule, commands that get mixer parameters will have a reply, while those that set mixer parameters will not. (There are exceptions to both of these rules.)
Typically, these "getters" and "setters" both use the same path, with their
behaviour depending on whether the request has arguments or not. For
example, getting the fader level of ch/01 involves two messages (request
and a reply):
X32Remote.Session.call_command(session, "/ch/01/mix/fader", [])
>>> %OSC.Message{path: "/ch/01/mix/fader", args: []}
<<< %OSC.Message{path: "/ch/01/mix/fader", args: [1.0]}
[1.0]While setting the fader level uses the same path, but only an outbound message, with no reply:
X32Remote.Session.cast_command(session, "/ch/01/mix/fader", [0.5])
>>> %OSC.Message{path: "/ch/01/mix/fader", args: [0.5]}Thus, to set the fader level, you must use the cast_* function variants,
not the call_* variants.
More generally, you need to know what kind of command you're running, and
whether you can expect it to produce a reply or not. Attempting to use
call_* functions on a command with no reply will result in a timeout.
invalid-commands
Invalid commands
The X32 does not produce any sort of message if you issue an invalid command. Any error or typo in the request path will just result in no reply.
For example, trying to get the fader of ch/1 (instead of ch/01) results in no reply:
X32Remote.Session.call_command(session, "/ch/1/mix/fader", [])
>>> %OSC.Message{path: "/ch/1/mix/fader", args: []}
[... five seconds pass ...]
** (exit) exited in: GenServer.call(...)
** (EXIT) time outThere's no real solution here except to always ensure you're supplying a valid command path, which (in many cases) also means ensuring you supply a valid, existing channel name.
race-conditions
Race conditions
Aside from the path, there is no way to identify exactly which "get" command a reply is in response to.
Given this limitation, there are at least two cases where we might mistakenly return the "wrong" reply:
If multiple processes are using the same
X32Remote.Session(or the underlyingExOSC.Client) and they issue the same "get" command at the same time.If you issue the
/xremotecommand (which reports whenever mixer settings change) and the value gets changed as you're requesting it.
In both these cases, the first incoming message with a matching path
will be returned, even if that particular message was not a result of the
corresponding request. However, this is not usually a problem, since you're
receiving current information about the resource you requested either way.
(Be aware that when you issue "set" commands, those changes may not immediately show up in "get" commands, even if you are the only client talking to the mixer.)
Link to this section Summary
Types
Option values used by start_link/1
Options used by start_link/1
A reference to a running X32Remote.Session
Functions
Constructs and sends a request message to the mixer, then waits for a reply message and returns its arguments.
Sends a request message to the mixer, then waits for a reply message and returns it.
Constructs and sends a request message to the mixer. Does not wait for a reply.
Sends a request message to the mixer. Does not wait for a reply.
Starts a session that will send and receive messages to/from the given client.
Link to this section Types
@type option() :: {:client, GenStage.stage()} | GenServer.option()
Option values used by start_link/1
@type options() :: [option()]
Options used by start_link/1
@type session() :: GenStage.stage()
A reference to a running X32Remote.Session
Link to this section Functions
@spec call_command(session(), OSC.Message.path(), OSC.Message.args()) :: OSC.Message.args()
Constructs and sends a request message to the mixer, then waits for a reply message and returns its arguments.
session can any of the values detailed in the "Name registration" section
of the GenServer documentation.
path and args will become the path and arguments of the OSC.Message request.
Returns the args list from the received reply.
@spec call_message(session(), OSC.Message.t()) :: OSC.Message.args()
Sends a request message to the mixer, then waits for a reply message and returns it.
session can any of the values detailed in the "Name registration" section
of the GenServer documentation.
This is a lower-level version of call_command/3. In most cases, you can
use that instead.
Returns the received reply, as an OSC.Message structure.
@spec cast_command(session(), OSC.Message.path(), OSC.Message.args()) :: :ok
Constructs and sends a request message to the mixer. Does not wait for a reply.
session can any of the values detailed in the "Name registration" section
of the GenServer documentation.
path and args will become the path and arguments of the OSC.Message request.
Always returns :ok immediately, regardless of whether the session exists,
and/or whether it handled the message successfully.
@spec cast_message(session(), OSC.Message.t()) :: :ok
Sends a request message to the mixer. Does not wait for a reply.
session can any of the values detailed in the "Name registration" section
of the GenServer documentation.
msg should be a valid OSC.Message structure.
This is a lower-level version of cast_command/3. In most cases, you can
use that instead.
Always returns :ok immediately, regardless of whether the session exists,
and/or whether it handled the message successfully.
@spec start_link(options()) :: GenServer.on_start()
Starts a session that will send and receive messages to/from the given client.
options
Options
:client(required) — PID or name of anExOSC.Clientprocess. (See the "Name registration" section ofGenServerfor acceptable values.)
This function also accepts all the options accepted by GenServer.start_link/3.
return-values
Return values
Same as GenServer.start_link/3.