Listening OSC over different ports on ISADORA 4 possible?
-
Congrats for the release and the hard work!!!
I've not yet downloaded v4 but have this question
Is it possible to listen to several ports receiving OSC commands over the network. I'm using more than 1 identical sensors. I can't change their OSC addresses, but the port number. Is this solvable in Isadora 4. Afaik in V3 I can only define one port in preferences and that's it.
Could this be something to do with the pythoner actor? I have no python knowledge so far, but if @DusX keeps uploading examples maybe this could be a good one.
In puredata, or max I can just specify several ports and listen to them. The workaround could be listening to several ports in PD and send the variables prepending sensor number over to ISADORA through another port. But this will introduce more lag which is not very desirable.
If this is not possible I might log it as feature request.
Cheers
-
Isadora 4 can still only listen to one OSC port (set in Isadora Preferences), but I don't see a reason that you can't set all the devices to send to port 1234. Isadora will hear them all. It would be the same if you had six computers on a network and you wanted all of them to send data to one Isadora computer. You'd have them all send OSC with whatever address they have to the IP address of the Isadora computer on port 1234.
Do you have a link to the documentation for the sensors? (It's a strange design decision that there'd be no way other than the port to tell them apart if you're using more than one simultaneously.)
-
@gaspar said:
Could this be something to do with the pythoner actor?
I went through my emails and dug this up from a past project using Python and OSC (this predates Pythoner)
As a starting point, I asked ChatGPT:
Can you write me Python code that lets me listen on a variable number of ports, collects all OSC messages sent from those ports, and then, in real-time, forwards the same OSC messages to the local host on port 1234 with the original port number appended to the original OSC address? I want there to be as little lag as possible.
Example:
Sensor 1 sends data to the OSC address "/sensor/data" on port 1111
Sensor 2 sends data to the OSC address "/sensor/data" on port 2222
Data collected by Python and forwarded to local host on port 1234 with the OSC addresses "/1111/sensor/data" and "/2222/sensor/data"ChatGPT responded:
Below is the Python code to achieve this functionality. You'll need the
python-osc
library, which can be installed usingpip install python-osc
.The code listens on a variable number of ports, collects all OSC messages sent to those ports, and then forwards them to the localhost on port 1234 with the original port number appended to the OSC address.
from pythonosc import dispatcher from pythonosc import osc_server from pythonosc import udp_client import threading # Function to handle incoming OSC messages and forward them def forward_message(client, port, address, *args): new_address = f"/{port}{address}" client.send_message(new_address, args) print(f"Forwarded {address} from port {port} to {new_address} with args {args}") # Function to create a server for each port def create_server(port, client): disp = dispatcher.Dispatcher() disp.set_default_handler(lambda addr, *args: forward_message(client, port, addr, *args)) server = osc_server.ThreadingOSCUDPServer(("0.0.0.0", port), disp) print(f"Serving on {server.server_address}") server.serve_forever() # Main function to start servers on multiple ports def start_servers(ports): client = udp_client.SimpleUDPClient("127.0.0.1", 1234) for port in ports: threading.Thread(target=create_server, args=(port, client)).start() # List of ports to listen on ports_to_listen = [1111, 2222] # Add more ports as needed start_servers(ports_to_listen)
Explanation
- forward_message: This function is called whenever an OSC message is received. It modifies the address to include the original port number and sends the message to localhost on port 1234 using the
udp_client
. - create_server: This function creates an OSC server on the specified port. It uses a dispatcher to handle all incoming messages with
forward_message
. - start_servers: This function starts servers on all specified ports. It creates a new thread for each server to ensure they can run concurrently.
- ports_to_listen: This is the list of ports you want to listen on. You can add or remove ports from this list as needed.
How to Run
- Ensure you have Python installed and
python-osc
library. - Save the code in a Python file, for example,
osc_forwarder.py
. - Run the script using
python osc_forwarder.py
.
This code will set up listeners on the specified ports, forward incoming OSC messages to port 1234 on localhost, and modify the address to include the original port number. This setup ensures minimal lag by using threading for concurrent processing.
ChatGPT code isn't always functional right out of the gate, but it's generally a good place to start.
You could start out just running it in the free, community version of PyCharm and then adapt it for Isadora's Pythoner actor once you have it working.
- forward_message: This function is called whenever an OSC message is received. It modifies the address to include the original port number and sends the message to localhost on port 1234 using the
-
Thanks for replying.
The sensor is the NGIMU SENSOR. I've got two of them right now. Still playing around, although not in ISADORA but in Ableton over M4L, both new to me.
This is the link to the manual.
https://x-io.co.uk/downloads/N...
So far I like the sensors.
-
I spent sometime and put together what appears to be a pretty solid approach to this script.
We launch a server in a thread to capture incoming OSC, and grab the values from a que (only works with FLOATS right now).
I may refine this for other data types and add it to the example file. But for now, this should work for you.from pythonosc import dispatcher, osc_server import threading import queue import time # iz_input 1 "OSC Port" # iz_input 2 "OSC Address" # iz_input 3 "UDP Address" # iz_input 4 "Trigger" # iz_output 1 "Received Value" osc_values = queue.Queue() # Use a queue to store received values osc_server_thread = None # To keep track of the server thread osc_server_instance = None # To keep track of the server instance last_value = 0.0 # Store the last value to always return a consistent type lock = threading.Lock() # Lock for thread-safe operations def osc_handler(address, *args): global osc_values if args and len(args) > 0: with lock: osc_values.put(float(args[0])) # Ensure the value is always a float def start_osc_server(udp_address, port, osc_address): global osc_server_instance try: disp = dispatcher.Dispatcher() disp.map(osc_address, osc_handler) osc_server_instance = osc_server.ThreadingOSCUDPServer((udp_address, int(port)), disp) osc_server_instance.serve_forever() except Exception as e: print(f"Exception in OSC server: {e}") def stop_osc_server(): global osc_server_thread, osc_server_instance if osc_server_instance: osc_server_instance.shutdown() osc_server_instance.server_close() osc_server_instance = None if osc_server_thread: osc_server_thread.join() osc_server_thread = None # python_init is called when the actor is first activated. # It requires the same number of parameters input as python_main() def python_init(osc_port, osc_address, udp_address, trigger): global osc_server_thread stop_osc_server() # Ensure any existing server is stopped if osc_server_thread is None: # Start server only if it isn't already running osc_server_thread = threading.Thread(target=start_osc_server, args=(udp_address, osc_port, osc_address)) osc_server_thread.daemon = True osc_server_thread.start() return last_value # Ensure it returns a consistent type # python_main is called whenever an input value changes def python_main(osc_port, osc_address, udp_address, trigger): global last_value if trigger: with lock: if not osc_values.empty(): last_value = osc_values.get() return last_value # Always return the last value to ensure consistent output type # python_finalize is called just before the actor is deactivated def python_finalize(): stop_osc_server() if __name__ == "__main__": """ This section is used to call the python_main() function from an IDE in the same way it would be called once entered into the Pythoner actor. This allows external development of code more easily, because it can be setup to run in your IDE (assuming you link the same VENV)
Generally once the setup matches in both environment you can simply cut and paste the code from your IDE into the Pythoner code editor. Your IDE will provide powerful debugging tools that the Pythoner code editor does not. This script is a good starting point. If you can run this in your IDE and in Isadora you are off to a good start. """ python_init(123, "/ryan", "127.0.0.1", True) try: while True: value = python_main(123, "/ryan", "127.0.0.1", True) if value is not None: print(f"Received value in IDE: {value}") time.sleep(0.1) # Add a small delay to ensure proper synchronization finally: python_finalize() # Ensure cleanup happens on exit</pre><p><strong>NOTE</strong>: you may need to set this up carefully, since problem occur is ports are closed correctly or you try to open the same port more than once.<br />I have Isadora sending to port 123 and 321 (via OSC Transmit), and I am receiving on both of these (all on localhost) with Pythoner.<br />Use the Trigger to get the last value (I linked a pulse generator to this at 30hz).</p>
-
Thank you both. Looks good I'll give it a try as soon as I get some time to tinker.
By the way, I stated I could not change the address names of the sensor. Whereas I'm still thinking there's no straight forward way of doing that (can't find a command to do so over the terminal), the GUI and API are open source, so probably it can be done, if you know more than me. I'll ask them directly and report here. -
@gaspar said:
The sensor is the NGIMU SENSOR.
I've just gone to their website and asked them if it's possible to easily change the OSC addresses a sensor uses. I'll let you know as soon as I hear back.
Best wishes,
L Wilson-Spiro
-
The NGIMU guys answered the following:
"It is not possible to change the OSC addresses used by the NGIMU. Multiple NGIMUs are intended to be used by configuring each NGIMU to send to a different UDP port. For example, 8001 and 8002. You can change the OSC address using the NGIMU GUI as shown below."
Now we're in production and I have no time to test Izzy4. Once I test @DusX approach with the Pythoner I'll report back.Tks.
-
I got a similar answer to my email.
@gaspar said:
You can change the OSC address using the NGIMU GUI as shown below.
Did they mean to say "UDP port" instead of "OSC address" here, or can you actually use the NGIMU GUI to change the OSC address?
-
In the GUI I can change IPs , SSIDs + password, and port numbers, but not OSC Adresses. That's surely a unwanted lapsus on their site.
As they say, the system is meant to be used by assigning a different port to each sensor.
Cheers