1. Usage¶
1.1. Introduction¶
1.1.1. What is osc4py3?¶
It is a fresh implementation of the Open Sound Control (OSC) protocol [1] for Python3. It tries to have a “pythonic” programming interface at top layers, and to support different scheduling schemes in lower layers to have efficient processing.
[1] | See http://opensoundcontrol.org/ for complete OSC documentation. |
1.1.2. What Python?¶
The package targets Python3.2 or greater, see other OSC implementations for older Python versions (OSC.py, simpleosc, TxOSC).
1.1.3. Why?¶
It was initially developed for use in a larger software as a background system communicating via OSC in separate threads, spending less possible time in main thread: sending OSC is just building messages/bundles objects and identifying targets, receiving OSC is just calling identified functions with OSC messages. All other operations, encoding and decoding, writing and reading, monitoring for communication channels availability, pattern matching messages addresses, etc, can be realized in other threads. On a multi-core CPU with multi-threading, these optimizations allow C code to run in parallel with communication operations, we gain milliseconds which are welcome in the project context.
1.1.4. What’s new?¶
The whole package is really bigger and the underlying system is complex to understand.
But, two base modules, oscbuildparse
and oscmethod
(see the modules
documentation), are autonomous and can be used as is to provide: encoding and decoding of
OSC messages and bundles, pattern matching and functions calling.
As we start from scratch, we use latest information about OSC with support for advanced data types (adding new types is easy), and insert support for OSC extensions like address pattern compression or messages control.
1.1.5. Complicated to use?¶
No. See the “Simple use” chapter below. It may be even easier to use than other Python OSC implementations. Don’t mistake, the underlying processing is complex, but the high level API hide it.
1.2. Quick OSC¶
Open Sound Control is a simple way to do remote procedure calls between applications, triggered on string pattern matching, transported by any way inside binary packets. It mainly defines packets encoding format to transmit data and pattern matching to select functions to call. Using network broadcast or multicast, it allows to send data in one shot to several systems at same time.
1.2.1. Messages¶
Messages structure only contains: an address string (to be matched by patterns for routing to processing functions), description of data content (using type codes as defined in Supported atomic data types), and data itself (as packed binary).
For basic OSC address pattern, see Address patterns.
Data in OSC messages are generally simple, like can be function parameters, some
int or float numbers, string, boolean, an “impulse” or “bang” (OSC come from
MIDI [2] musical world where
a kind of “start” event is necessary). It could be used to transmit complex structures,
but it’s not its main purpose.
See Supported atomic data types to see types allowed by osc4py3
.
[2] | See https://en.wikipedia.org/wiki/MIDI |
1.3. Simple use¶
1.3.1. Examples¶
We will first start by small commented examples, then give some explanations and precisions.
Note
Even if examples have been splitted in two parts, sending and receiving OSC messages
can be realized in same program ; osc_startup()
, osc_process()
and
osc_terminate()
are client & server ready.
1.3.1.1. Receiving OSC messages¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | # Import needed modules from osc4py3
from osc4py3.as_eventloop import *
from osc4py3 import oscmethod as osm
def handlerfunction(s, x, y):
# Will receive message data unpacked in s, x, y
pass
def handlerfunction2(address, s, x, y):
# Will receive message address, and message data flattened in s, x, y
pass
# Start the system.
osc_startup()
# Make server channels to receive packets.
osc_udp_server("192.168.0.0", 3721, "aservername")
osc_udp_server("0.0.0.0", 3724, "anotherserver")
# Associate Python functions with message address patterns, using default
# argument scheme OSCARG_DATAUNPACK.
osc_method("/test/*", handlerfunction)
# Too, but request the message address pattern before in argscheme
osc_method("/test/*", handlerfunction2, argscheme=osm.OSCARG_ADDRESS + osm.OSCARG_DATAUNPACK)
# Periodically call osc4py3 processing method in your event loop.
finished = False
while not finished:
# …
osc_process()
# …
# Properly close the system.
osc_terminate()
|
1.3.1.2. Sending OSC messages¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | # Import needed modules from osc4py3
from osc4py3.as_eventloop import *
from osc4py3 import oscbuildparse
# Start the system.
osc_startup()
# Make client channels to send packets.
osc_udp_client("192.168.0.4", 2781, "aclientname")
# Build a simple message and send it.
msg = oscbuildparse.OSCMessage("/test/me", ",sif", ["text", 672, 8.871])
osc_send(msg, "aclientname")
# Build a message with autodetection of data types, and send it.
msg = oscbuildparse.OSCMessage("/test/me", None, ["text", 672, 8.871])
osc_send(msg, "aclientname")
# Buils a complete bundle, and postpone its executions by 10 sec.
exectime = time.time() + 10 # execute in 10 seconds
msg1 = oscbuildparse.OSCMessage("/sound/levels", None, [1, 5, 3])
msg2 = oscbuildparse.OSCMessage("/sound/bits", None, [32])
msg3 = oscbuildparse.OSCMessage("/sound/freq", None, [42000])
bun = oscbuildparse.OSCBundle(oscbuildparse.unixtime2timetag(exectime),
[msg1, msg2, msg3])
osc_send(bun, "aclientname")
# Periodically call osc4py3 processing method in your event loop.
finished = False
while not finished:
# You can send OSC messages from your event loop too…
# …
osc_process()
# …
# Properly close the system.
osc_terminate()
|
You can take a look at the rundemo.py
script in demos/
directory.
It is a common demo for the different scheduling options, where main osc4py3
functions
are highlighted by surrounding comments.
You can also look at speedudpsrv.py
and speedudpcli.py
in the demos/
directory.
1.3.1.3. General layout¶
So, the general layout for using osc4py3
is
Import
osc4py3.as_xxx
module upon desired threading model.Import
osc4py3.oscbuildparse
if you plan to send messages.Call
osc_startup
function to start the system.Create a set of client/server identified via channels names.
Register some handler functions with address matching.
Enter the processing loop.
- Send created messages via client channels.
- Call
osc_process
function to run the system. - Received messages from server channels, have their address matching handlers called for you.
Call
osc_terminate
to properly release resources (communications, threads…).
1.3.2. Sending messages¶
As seen in Examples, you need to import oscbuildparse
module
(or directly some of its content) to build a new OSCMessage
object.
Then, simply and send it via osc_send()
specifying target client channel
names (see Client channels).
msg = oscbuildparse.OSCMessage("/test/me", ",sif", ["text", 672, 8.871])
osc_send(msg, "aclientname")
You can see supported message data types in Supported atomic data types.
It is also possible to send multiple OSC messages, or to request an immediate or
on the contrary differed execution time using OSCBundle
(see OSC Bundles).
bun = oscbuildparse.OSCBundle(unixtime2timetag(time.time()+30),
[oscbuildparse.OSCMessage("/test/me", ",sif", ["none", -45, 11.34]),
oscbuildparse.OSCMessage("/test/him", ",sif", ["two", 15, 11.856])])
osc_send(bun, "aclientname")
bun = oscbuildparse.OSCBundle(.oscbuildparse.OSC_IMMEDIATELY,
[oscbuildparse.OSCMessage("/test/me", ",sif", ["three", 92, 454]),
oscbuildparse.OSCMessage("/test/him", ",sif", ["four", 107, -3e2])])
osc_send(bun, "aclientname")
Warning
Note that when running the system with osc4py3.as_eventloop
, several calls to
osc_process()
may be necessary to achieve complete processing of message sending
(the message itself is generally sent / received in one osc_process()
call if possible,
but some monitoring status on sockets may need other calls to be stopped, and several
sent messages may take time to be processed on sockets).
1.3.3. Message handlers¶
A message handler is a Python callable (function or bound method, or
Python partial),
which will be called when an OSCMessage
’s addresspattern
is matched by
a specified addrpattern filter.
By default it is called with the (unpacked) OSC message data as parameters.
You can build message handlers with an interface specifying each independent argument, like:
def handler_set_location(x, y, z):
# Wait for x,y,z of new location.
Or use of *args
variable arguments count.
def handler_set_location(*args):
# Wait for x,y,z of new location.
Hint
If you want your method to be called even if sender gives an invalid parameters count,
you may prefer the variable arguments count solution or the OSCARG_DATA
argscheme
see below), else with an invalid argument count message, your handler call will
fail immediately with a ValueError
exception.
1.3.3.1. Installing message handlers¶
The normal way is to use osc_method()
function, where you associate your handler function
with an OSC address pattern filter string expression (see Address patterns).
def osc_method(addrpattern, function [,argscheme[,extra]])
There is by default a simple mapping of the OSC message data to your function parameters,
but you can change this using the argscheme
parameter (see Arguments schemes).
You can provide an extra parameter(s) for your handler function, given as extra
(you must require this parameter in your argscheme
too).
1.3.3.2. Address patterns¶
When calling osc_method
function you specify the message address pattern filter
as first parameter.
def osc_method(addrpattern, function [,argscheme[,extra=None]])
By default the addrpattern
follows OSC pattern model [3] , which looks like
unix/dos globbing for filenames:
- use
/
separators between “levels” *
match anything, operator stop at / boundary//
at beginning allow any level deep from start?
match any single char[abcdef]
match any any char in abcdef[a-z]
match any char from a to z[!a-z]
match any char non from a to z{truc,machin,chose}
match truc or machin or chose
Note
There is no unbind function at this level of osc4py3
use.
[3] | It is internally rewritten as Python re pattern
to directly use Python regular expressions engine for matching. |
1.3.3.3. Arguments schemes¶
By default message data are unpacked as handler parameters.
You can change what and how parameters are passed to handler’s arguments
with the argscheme
parameter of osc_method
function.
This parameter uses some tuple constant from oscmethod
module,
which can be added to build your handler function required arguments list.
OSCARG_DATA
— OSC message data tuple as one parameter.OSCARG_DATAUNPACK
— OSC message data tuple as N parameters (default).OSCARG_MESSAGE
— OSC message as one parameter.OSCARG_MESSAGEUNPACK
— OSC message as three parameters (addrpattern, typetags, arguments).OSCARG_ADDRESS
— OSC message address as one parameter.OSCARG_TYPETAGS
— OSC message typetags as one parameter.OSCARG_EXTRA
— Extra parameter you provide as one parameter - may be None.OSCARG_EXTRAUNPACK
— Extra parameter you provide (unpackable) as N parameters.OSCARG_METHODFILTER
— Method filter object as one parameter. This argument is aMethodFilter
internal structure containing informations about the message filter and your handler (argument scheme, filter expression…).OSCARG_PACKOPT
— Packet options object as one parameter - may be None. This argument is aPacketOption
internal structure containing information about packet processing (source, time, etc).OSCARG_READERNAME
— Name of transport channel which receive the OSC packet - may be None.OSCARG_SRCIDENT
— Indentification information about packet source (ex. (ip,port)) - may be None.OSCARG_READTIME
— Time when the packet was read - may be None.
By combining these constants with addition operator, you can change what arguments your handler function will receive, and in what order. You can then adapt your handler function and/or the arguments scheme to fit. Examples:
# Import one of osc4py3 top level functions, and argument schemes definitions
from osc4py3.as_allthreads import * # does osc_method
from osc4py3.oscmethod import * # does OSCARG_XXX
# Request the unpacked data (default).
def fct(arg0, arg1, arg2, argn): pass
def fct(*args): pass
osc_method("/test/*", fct)
# Request the message address pattern and the unpacked data.
def fct(address, arg0, arg1, arg2, argn): pass
def fct(address, *args): pass
osc_method("/test/*", fct, argscheme=OSCARG_ADDRESS + OSCARG_DATAUNPACK)
# Request the message address pattern and the packed data.
def fct(address, args): pass
osc_method("/test/*", fct, argscheme=OSCARG_ADDRESS + OSCARG_DATA)
# Request the message address pattern, its type tag and the unppacked data.
def fct(address, typetag, arg0, arg1, arg2, argn): pass
def fct(address, typetag, *args): pass
osc_method("/test/*", fct, argscheme=OSCARG_ADDRESS + OSCARG_TYPETAGS + OSCARG_DATAUNPACK)
# Request the message address pattern, its type tag and the packed data… the OSCMessage parts.
def fct(address, typetag, args): pass
osc_method("/test/*", fct, argscheme=OSCARG_MESSAGEUNPACK)
# Request the OSCMessage as a whole.
def fct(msg): pass
osc_method("/test/*", fct, argscheme=OSCARG_MESSAGE)
# Request the message source identification, the whole message, and some extra data.
def fct(srcident, msg, extra): pass
osc_method("/test/*", fct, argscheme=OSCARG_SRCIDENT + OSCARG_MESSAGE + OSCARG_EXTRA,
extra=dict(myoption1=4, myoption2="good"))
Hint
Informations made available via argument scheme constants should be enough for
large common usage.
But, if some handler function need even more information from received messages, other
members of internal objects coming with OSCARG_METHODFILTER
and OSCARG_PACKOPT
argscheme
can be of interest.
1.3.4. Threading model¶
From osc4py3
package, you must import functions from one of the as_eventloop
,
as_comthreads
or as_allthreads
modules (line 2 in examples).
Each of these modules publish the same set of osc_…
functions for different threading models
(star import is safe with as_xxx
modules, importing only the osc_fctxxx
functions).
1.3.4.1. All threads¶
Using as_allthreads
module, threads are used in several places, including message handlers calls
- by default a pool of ten working threads are allocated for message handlers calls.
To control the final execution of methods, you can add an execthreadscount
named parameter to
osc_startup()
.
Setting it to 0 disable executions of methods in working threads, and all methods are called in the context
of the raw packets processing thread, or if necessary in the context of the delayed bundle processing thread.
Setting it to 1 build only one working thread to process methods calls, which are then all called in
sequence in the context of that thread.
If you want to protect your code against race conditions between
your main thread and message handlers calls, you should better use as_comthreads
.
1.3.4.2. Communication threads¶
Using as_comthreads
module, threads are used for communication operations (sending and receiving,
intermediate processing), and messages are stored in queues between threads.
Message handler methods are called only when you call yourself osc_process()
.
This is optimal if you want to achieve maximum background processing of messages transmissions,
but final messages processing must occur within an event loop.
1.3.4.3. No thread¶
Using as_eventloop
module, there is no multi-threading, no background processing.
All operations (communications and message handling) take place only when you call osc_process()
.
Warning
Multi-threading add some overhead in the whole processing (due to synchronization between threads).
By example, on my computer, transmitting 1000 messages each executing a simple method call,
speedudpsrv.py
goes from 0.2 sec with eventloop scheduling to 0.245 sec with comthreads scheduling and
0.334 sec with allthreads scheduling.
But multi-threading can remain interesting if your main thread does C computing while osc4py3 process messages in background (which is the first use case of this development).
Important
If you choose to run the system with as_eventloop
or with as_comthreads
, you must
periodically call the function osc_process()
sometime in your event loop.
This call all code needed in the context of the event loop (all transmissions and dispatching
code for as_eventloop
, and only dispatching for as_comthreads
).
If you choose to run the system with as_allthreads
, you don’t need to call osc_process()
(this function is defined in this module too, but does nothing).
But you should have checked that your code will not have problems with concurrent access to
resources (if needed there is a way to simply install a message handler to be called with
an automatic Lock).
1.3.5. Servers channels¶
Server channel are identified by names, but you should not have to use these names once the server channel has been created (unless you do advanced internal processing on incoming OSC packets).
In the example server channels are simple UDP ports which will be listened to read some incoming datagram, first server listen for data from a specific IPV4 network, second server listen on all IPV4 networks.
You can also create servers via osc_broadcast_server()
or osc_multicast_server()
.
1.3.6. Client channels¶
Client channels are identified by names, allowing light coupling in your code when sending OSC messages.
In the example client channels is a simple UDP transport. It will transmit data to server channels listening on the other side.
You can also create clients via osc_broadcast_client()
or osc_multicast_client()
(providing ad-hoc broadcast/multicast addresses).
1.3.7. Light coupling¶
Client and server channels being identified via names, where name resolution is only done when communication activity is required, creation order of the different parts is not important.
In this way, in your code you send a message to a named channel, without knowing how and where this channel connection has been established. You can change transport protocol with no impact on your communication code. If you target multiple channels via a list or tuple or set, eventually nested — this allow to easily define groups of channels as target for sending.
If you use the special reserved "_local"
name target, you send a message to be processed
directly by your own program without network communication (no need to create a channel,
it’s a bypass name).
1.3.8. Logging OSC operations¶
Once imported, use of the package begin by initializing the system with a call
to osc_startup()
function.
This function allows an optional logger
named parameter, accepting a logging.logger
object [4] . Example:
import logging
logging.basicConfig(format='%(asctime)s - %(threadName)s ø %(name)s - '
'%(levelname)s - %(message)s')
logger = logging.getLogger("osc")
logger.setLevel(logging.DEBUG)
osc_startup(logger=logger)
[4] | This is highly recommended in case of problems with multi-threads usage. |
1.3.9. Advanced pattern/handler¶
You may setup your pattern / handler binding using low level functions, giving access to some more advanced options.
from osc4py3 import oscmethod, oscdispatching
mf = oscmethod.MethodFilter(...)
oscdispatching.register_method(mf)
By creating the osc4py3.oscmethod.MethodFilter
object directly, you can provide some
extra construction parameters which are not available using high level osc_method()
function (we don’t list parameters which can be specified using argscheme
):
patternkind
default to"osc"
, indicates to use OSC syntax for pattern. You can use here"re"
to specify that you provide inaddrpattern
a Python regular expression.workqueue
allows you to specify a processingWorkQueue
associated to threads to call the handler with matched incoming messages — by default it’sNone
and indicate to use the common work queue.lock
can be used to specify athread.Lock
(orthread.RLock
) object to acquire before processing matched messages with this handler function — by default it’sNone
.logger
is a specific logger to use to specifically trace that handler calling on matched messages reception. By default top levelosc_method()
functions use the logger given asosc_startup()
logger
parameter.
1.4. User main functions¶
See OSCMessage
or OSCBundle
for their respective documentation.
We document using the common as_xxx
module
Note
Startup and termination functions (osc_startup()
and osc_terminate()
) are
intended to be called once only, at begining of program and at end of program.
They install/uninstall a set of communication and filtering tools which
normally stay active during program execution.
Terminating then restarting osc4py3 may work… but has not been extensively tested.
Each as_eventloop
, as_allthreads
and as_comthreads
module define the same set of functions documented here.
-
osc4py3.as__common.
osc_startup
(**kwargs)¶ Once call startup function for all osc processing in the event loop.
Create the global dispatcher and register it for all packets and messages. Create threads for background processing when used with as_allthreads or as_comthreads scheduling.
Parameters: - logger (logging.Logger) – Python logger to trace activity. Default to None
- execthreadscount (int) – number of execution threads for methods to create. Only used with as_allthreads scheduling. Default to 10.
- writethreadscount (int) – number of write threads for packet sending to create. Only used with as_allthreads scheduling. Default to 10.
-
osc4py3.as__common.
osc_terminate
()¶ Once call termination function to clean internal structures before exiting process.
-
osc4py3.as__common.
osc_process
()¶ Function to call from your event loop to receive/process OSC messages.
-
osc4py3.as__common.
osc_method
(addrpattern, function, argscheme=('data_unpack', ), extra=None)¶ Add a method filter handler to automatically call a function.
Note
There is no unregister function at this level of osc4py use, but module
oscdispatching
providesMethodFilter
object and functions to register and unregister them.Parameters: - addrpattern (str) – OSC pattern to match
- function (callable) – code to call with the message arguments
- argscheme (tuple) – scheme for handler function arguments. By default message data are transmitted, flattened as N parameters.
- extra (anything) – extra parameters for the function (must be specified in argscheme too).
-
osc4py3.as__common.
osc_send
(packet, names)¶ Send the packet using channels via names.
Parameters: - packet (OSCMessage or OSCBundle) – the message or bundle to send.
- names (str or list or set) – name of target channels (can be a string, list or set).
-
osc4py3.as__common.
osc_udp_server
(name, address, port)¶ Create an UDP server channel to receive OSC packets.
Parameters: - name (str) – internal identification of the channel server.
- address (str) – network address for binding UDP socket
- port (int) – port number for binding UDP port
-
osc4py3.as__common.
osc_udp_client
(name, address, port)¶ Create an UDP client channel to send OSC packets.
Parameters: - name (str) – internal identification of the channel client.
- address (str) – network address for binding UDP socket
- port (int) – port number for binding UDP port
-
osc4py3.as__common.
osc_multicast_server
(name, address, port)¶ Create a multicast server to receive OSC packets.
Parameters: - name (str) – internal identification of the channel server.
- address (str) – network address for binding socket
- port (int) – port number for binding port
-
osc4py3.as__common.
osc_multicast_client
(name, address, port, ttl)¶ Create a multicast client channel to send OSC packets.
Parameters: - name (str) – internal identification of the channel client.
- address (str) – multicast network address for binding socket
- port (int) – port number for binding port
- ttl (int) – time to leave for multicast packets. Default to 1 (one hop max).
-
osc4py3.as__common.
osc_broadcast_server
(name, address, port)¶ Create a broadcast server channel to receive OSC packets.
Parameters: - name (str) – internal identification of the UDP server.
- address (str) – network address for binding UDP socket
- port (int) – port number for binding UDP port
-
osc4py3.as__common.
osc_broadcast_client
(name, address, port, ttl)¶ Create a broadcast client channel to send OSC packets.
Parameters: - name (str) – internal identification of the channel client.
- address (str) – broadcast network address for binding socket
- port (int) – port number for binding port
- ttl (int) – time to leave for broadcast packets. Default to 1 (one hop max).