Library / SDK
attwad/python-osc avatar
attwad/python-osc

python-osc: a pure Python Open Sound Control client and server

Open Sound Control server and client in pure python

586 stars121 forksPythonUnlicense

At a glance

What is it?
python-osc implements the Open Sound Control protocol in pure Python, with UDP and TCP clients and servers in blocking, threaded, forked and asyncio flavours, a dispatcher that maps addresses to callbacks, and no external dependencies. It suits anyone wiring a controller, a synth or a sensor grid to Python without wanting a compiler in the loop.
Who is it for?
python-osc is a good fit for a Python developer who needs OSC today, wants no C extension and no dependency tree, and needs the whole range from a blocking UDP server to an asyncio client in one library. It is the wrong choice for a project that needs message schemas, service discovery, a session protocol or any transport other than UDP and TCP, since OSC itself does not provide those.
Can I use it commercially?
Yes. Unlicense is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 6 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

OSC without a compiler in the loop

python-osc is a server and client implementation of Open Sound Control written in pure Python. The README is emphatic about the pure part, twice, and the packaging backs it up: the project metadata declares an empty dependency list, and the build backend is uv-build with no compilation step.

That matters more than it sounds. OSC libraries in most languages are either a binding to a C library or a package with a binary extension, which means a compiler on the machine, a wheel that may not exist for your platform and Python version, and a build failure at install time. Here there is nothing to compile and nothing to resolve.

The protocol itself is the reason the library exists. OSC is a lightweight messaging protocol built around address patterns and typed arguments, and it is the lingua franca of music software, of modular synthesis environments, of academic installations and of control surfaces. A project that talks to a hardware controller, a lighting desk or an audio workstation over OSC needs a client, and the choice is between writing the packet format yourself and using a library.

The README states the library was developed following the Open Sound Control Specification 1.0 and is currently in a stable state. The specification is the one published by Stanford, and the link is in the README, which is the right place to look if you need to know what is guaranteed and what is a local extension.

The audience, then, is a Python developer in any of those fields, plus anyone who wants OSC in a script without a build toolchain. The project is classified as Development Status 5, Production/Stable, requires Python 3.10 or newer, and its last push was on 2026-10-17.

The dispatcher is the part you will actually write code against

Most of the API surface is a handful of modules, and the one that shapes an application is the dispatcher.

The feature list calls it a simple OSC address to callback matching system, and the server example shows how it works in three lines of registration. The dispatcher is created, then addresses are mapped to handlers.

python
    dispatcher = Dispatcher()
    dispatcher.map("/filter", print)
    dispatcher.map("/volume", print_volume_handler, "Volume")
    dispatcher.map("/logvolume", print_compute_handler, "Log volume", math.log)

The third argument is a value passed to the handler, and the fourth, in the logvolume case, is a callable applied to the incoming argument before the handler sees it. So `/logvolume` does not merely receive the volume, it receives the logarithm of it, which is the kind of convenience that turns a raw protocol into something you can build a control surface on.

The example handlers also show the callback signature. A handler receives the address, the list of arguments, and the extra value, and the print handler is registered directly because `print` fits that shape.

python
    server = osc_server.ThreadingOSCUDPServer(
        (args.ip, args.port), dispatcher, timeout=10)
    print("Serving on {}".format(server.server_address))
    server.serve_forever()

The server is constructed with a bind address, the dispatcher, and a timeout, and then told to serve forever. The address handling comes from `argparse` in the example, with the IP defaulting to 127.0.0.1 and the port to 5005, and both the client and the server use the same defaults, which is why the two halves of the example work against each other out of the box.

The client side is the mirror image of this, and it is worth noting that the two are symmetric.

python
    client = udp_client.SimpleUDPClient(args.ip, args.port, timeout=10)

    for x in range(10):
      client.send_message("/filter", random.random())
      time.sleep(1)

A client is created with an address and a timeout, and messages are sent with an address and one or more arguments. The example sends ten random values to `/filter`, one per second.

Blocking, threaded, forked and asyncio servers over UDP and TCP

The server implementations are where this library is more complete than its low dependency count suggests, and the feature list names the variants directly: UDP and TCP in blocking, threading, forking and asyncio forms, and UDP and TCP clients including asyncio support.

That spread matters because OSC traffic has two very different shapes. A controller sending a continuous stream of parameter values is a fire and forget workload, where a threaded UDP server that handles each packet and moves on is the right answer. A request and reply exchange, where a client asks a server for state and waits for the response, is a different problem, and the feature list calls out support for sending responses from callback handlers in both the client and the server, which is exactly the mechanism you need for that.

The TCP support covers both protocol formats. The README says TCP support for 1.0 and 1.1 protocol formats, and the 1.1 format is the one that adds the length prefix that makes framing work reliably over a stream transport, so the two exist for a reason rather than as an accident of the specification's history.

The type coverage is listed as well: int, int64, float, string, double, MIDI, timestamps, blob and nil OSC arguments. MIDI in particular is a 32 bit packed message type, and supporting it as a first class argument rather than as bytes is a sign that the library is aimed at real music software rather than only at generic message passing.

The examples directory in the repository is a fair map of that surface. It holds simple and echo clients and servers over UDP, a two way example, a dispatcher example, TCP clients and servers, and three asyncio examples, an async server, an async simple TCP client and an async TCP server. The presence of async_simple_tcp_client.py as a separate file from simple_tcp_client.py says the asyncio path is not an afterthought bolted onto the blocking one.

Bundles: several messages that must be applied together

OSC has a concept that goes beyond a single message, and the README spends a section on it, because it is the part of the protocol that changes how you design an application.

A bundle is a container that groups messages which should be treated as happening at the same time. The builder API in the example shows how to construct one.

python
    bundle = osc_bundle_builder.OscBundleBuilder(
        osc_bundle_builder.IMMEDIATELY)
    msg = osc_message_builder.OscMessageBuilder(address="/SYNC")
    msg.add_arg(4.0)
    # Add 4 messages in the bundle, each with more arguments.
    bundle.add_content(msg.build())
    msg.add_arg(2)
    bundle.add_content(msg.build())
    msg.add_arg("value")
    bundle.add_content(msg.build())

The pattern is clear. A message builder is created for an address, arguments are added to it, and the built message is added to the bundle as content. The same message builder is reused for each message, so the bundle ends up with several messages on the same address, each carrying a different argument type, which demonstrates the type support in a single place.

The `IMMEDIATELY` argument is the timetag, and it says the bundle should be processed as soon as it arrives rather than at a scheduled time, which is the common case for a group of state updates that must not be applied one by one.

The example then goes one step further and shows a bundle containing a bundle, and notes that the bundle has five elements in total at that point. Bundles can nest, and the builder tracks that.

The last line is the one that closes the loop with the rest of the library.

python
    # You can now send it via a client with the `.send()` method:
    client.send(bundle)

The same client object that sends single messages sends a bundle, so bundling is a property of the protocol layer rather than a separate transport.

Packaging, typing and a public domain dedication

The distribution story is unusually clean. The library is published on PyPI as python-osc, and the README gives the install as a single command.

bash
    $ pip install python-osc

The project metadata shows the version as 1.10.2, the readme as README.rst, and requires-python as 3.10 or newer, with the dependency list empty. The classifiers list the licence as Freely Distributable, which matches the licence section of the README: unlicensed, do what you want with it, pointing at unlicense.org. The Unlicense is a public domain dedication rather than a permissive licence with conditions, and for an OSC implementation that is a defensible choice, since the specification itself is open and the libraries around it tend to be permissively licensed. It does mean there is no implied warranty and no explicit patent grant, which is the trade for the simplicity, and anyone with a compliance process that expects an Apache or MIT header will need an exception rather than a scan of a standard file.

The repository also has a CHANGELOG.md, a CONTRIBUTING.md, a `docs/` directory with the readthedocs configuration, and a `uv.lock` alongside a `pyproject.toml`, so the development environment is locked with uv while the build itself uses the uv-build backend with the module root set to the repository root.

The typing setup is a good indicator of how the project is maintained. The mypy configuration is strict in some places and deliberately relaxed in others, with the comment that disallowing any generics would be great but there are too many cases it would break right now. It requires complete annotations where they are present, forbids subclassing Any, and warns on redundant casts, unused ignores and unused configs. The examples are in `examples/`.

Where OSC itself, and therefore this library, stops

The limitations of python-osc are mostly the limitations of OSC, and it is worth being explicit about them before anyone builds a service on top of it.

OSC has no schema. An address like `/filter` is a string that both sides agree on by convention, and the argument list is typed but not described. There is no negotiation, no version field, no capability discovery, so a client and a server that disagree about what `/logvolume` means will not notice, they will just produce nonsense. The dispatcher makes this easy to handle in code, since the mapping is a single visible table, but it does not make the protocol self describing.

OSC has no security of its own. The protocol is a plain datagram or stream with no authentication and no encryption, and the fact that the library is pure Python with no dependencies means there is no TLS layer built in either. Any deployment that crosses a network boundary has to provide that itself, or run on a trusted local segment.

OSC has no reliable delivery. A UDP message can be lost or arrive out of order, and the library will not tell you. For continuous control data that is acceptable, and for anything else you need an application level acknowledgement, and the response support from callback handlers is the hook for building one.

On the library side, the honest gaps are visible in the repository. There are no GitHub releases, so a version pin has to come from PyPI or the changelog, and the project has no stated support policy beyond the stable status in the README. The client and server are blocking-first, with asyncio provided as a separate set of modules, so an application that wants one consistent concurrency model has to choose rather than mix. And the absence of a discovery or service layer means an application with several OSC endpoints needs to keep its own registry.

None of that is unusual for a protocol library that has been around long enough to be boring, and that is the point of the project. The README describes the state as stable, the type coverage is complete for the protocol, and the test coverage is described as extensive. For a message bus that has to behave predictably for years, that is a better set of properties than an exciting one.

Editorial conclusion

python-osc is a good fit for a Python developer who needs OSC today, wants no C extension and no dependency tree, and needs the whole range from a blocking UDP server to an asyncio client in one library. It is the wrong choice for a project that needs message schemas, service discovery, a session protocol or any transport other than UDP and TCP, since OSC itself does not provide those. Verify first that the OSC version your hardware speaks matches the protocol support here, since the TCP path covers the 1.0 and 1.1 formats, then read the changelog before pinning a version, because the project publishes no GitHub releases.

Frequently asked questions

How do I install python-osc?

It is a pure Python library with no external dependencies, so `pip install python-osc` is the whole installation. The package requires Python 3.10 or newer and is published on PyPI as python-osc.

What server and client variants does python-osc provide?

The feature list names UDP and TCP servers in blocking, threading, forking and asyncio implementations, and UDP and TCP clients including asyncio support. The examples directory includes simple and echo clients and servers, a dispatcher example, TCP examples and three asyncio examples.

How do I map OSC addresses to handlers in python-osc?

You create a Dispatcher and call its map method with an address pattern, a handler and optional fixed values, for example `dispatcher.map("/logvolume", print_compute_handler, "Log volume", math.log)`, then pass the dispatcher to a server such as ThreadingOSCUDPServer.

Which argument types does python-osc support?

The README lists int, int64, float, string, double, MIDI, timestamps, blob and nil OSC arguments, and the bundle example shows a float, an int, a string and raw bytes being added to messages in one bundle.

Is python-osc licensed under a standard open source licence?

No. The README states it is unlicensed, do what you want with it, referring to the Unlicense, and the package classifiers list it as Freely Distributable. The LICENSE.txt file in the repository holds the dedication.

Official sources

  1. attwad/python-osc on GitHub
  2. Issues
  3. License: Unlicense
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/attwad-python-osc.svg)](https://hysenlabs.com/projects/attwad-python-osc)

Was this useful?