Max to python-osc

Matteo  Facchini's icon

Matteo Facchini

2月 14 2021 | 10:28 午後

I am trying to help a friend with python. I am a fairly experienced python user, he is a fairly experienced Max user.

The aim of my code is:

  1. receive a message from max over a local IP 127.0.0.1 on a specified port (5005)

  2. elaborate the message in python

  3. send the message to super collider over the same IP but on another port

So far, I have successfully set up a server/client (basically a "proxy") with python-osc and I can comunicate with its server (tested it with a python-osc client) and get messages from its client.

I am not able to make max communicate with it. My friend set up a patch for me to test the code, but I think I am missing something: is there a default handler (URI path) in Max? If i click on the messages in Max, which are connected to the udpsend part shouldn't I get see those messages on the 5005 port?

If I fire up the server it listens to my python-client but it gets nothing from max. I do not get even a message concerning the default handler.

I am working on a 2020 macbook pro

Matteo  Facchini's icon

Matteo Facchini

2月 15 2021 | 9:05 午前

Just to help you all understand a bit better, here's my python code

import argparse

from pythonosc import dispatcher
from pythonosc import osc_server
from pythonosc import udp_client

def main(path: str, *osc_arguments):
    msg = osc_arguments[-1]
    print("input message: {}".format(msg))
    msgOUT = msg+'out'
    # output
    print("output message: {}".format(msgOUT))
    ipOUT = osc_arguments[0][0]
    portOUT = osc_arguments[0][1]
    pathOUT= osc_arguments[0][2]
    talk2SC(ipOUT,portOUT,pathOUT,msgOUT)

def listen2Max(addrIN,addrOUT):
    '''
    set up server
    '''
    # input address
    ipIN   = addrIN[0]
    portIN = addrIN[1]
    pathIN = addrIN[2]
    # output address
    portOUT = addrOUT[0]
    pathOUT = addrOUT[1]
    # dispatcher to receive message
    disp = dispatcher.Dispatcher()
    disp.map(pathIN, main, ipIN, portOUT, pathOUT)
    # server to listen
    server = osc_server.ThreadingOSCUDPServer((ipIN,portIN), disp)
    print("Serving on {}".format(server.server_address))
    server.serve_forever()

def talk2SC(ip,port,path,mymove):
    '''
    set up client
    '''
    client = udp_client.SimpleUDPClient(ip,port)
    client.send_message(path, mymove)

if __name__ == "__main__":
    # generate parser
    parser = argparse.ArgumentParser(prog='scacchiOSC', formatter_class=argparse.RawDescriptionHelpFormatter, description='Interprete di messaggi OSC da Max\n')
    parser.add_argument("-II","--ipIN", type=str, default="127.0.0.1", help="The ip to listen on")
    parser.add_argument("-PI", "--portIN", type=int, default=5005, help="The port to listen on")
    parser.add_argument("-UI", "--uripathIN", type=str, default="/filter", help="MAX's URI path")
    parser.add_argument("-PO", "--portOUT", type=int, default=5006, help="The port to send messages to")
    parser.add_argument("-UO", "--uripathOUT", type=str, default="/filter", help="output URI path")
    args = parser.parse_args()
    # wrap up inputs
    outputAddress = [args.portOUT, args.uripathOUT]
    inputAddress = [args.ipIN, args.portIN, args.uripathIN]
    # listen to max
    listen2Max(inputAddress, outputAddress)
Source Audio's icon

Source Audio

2月 15 2021 | 3:02 午後

you set parser to listen to /filter

msgOUT = msg+'out' ????

once removed + 'out' it started working, but with
server set to Thread type printout is miserable and disrupted
set to
server = osc_server.OSCUDPServer((ipIN,portIN), disp)
kind of works, but still very slow.
after all printout were removed
looping from max to pyton-osc - and back works ok

py.mov
video/quicktime 1.36 MB

Matteo  Facchini's icon

Matteo Facchini

2月 15 2021 | 3:34 午後

Cool, thank you.
The part that gives you an error is actually just an example, in your case you could try msgOUT = msg+1
In my case, I am getting string messages, thus your example does not apply.
Could you please elaborate a bit the /filter $1 message on Max? What if I am sending strings with spaces (like in my case)? The $1 sends only the first part of the string, which is split at each space.

Source Audio's icon

Source Audio

2月 15 2021 | 3:58 午後

sure i could add any kind of math operation to msg , but it is not my point of interest.
Max is not limiting what it sends.
I used filter $1 in max , because in that code you posted only single item gets parsed.
you can send
/filter bla bla 33 77 12.55555 message from max
but .... then you have to parse that message on the receiver side.
sending same message to your python code (blue), or direct loopback to max


Matteo  Facchini's icon

Matteo Facchini

2月 15 2021 | 4:27 午後

Great, basically if I parse

/filter "bla bla 33 77 12.55555"

everything (all the strings within quote marks) goes directly to my python code.

What if I wanted to make my python code listen to every message coming from 127.0.0.1 port 5005 regardless of the path (i.e. without /filter)?

Source Audio's icon

Source Audio

2月 15 2021 | 4:46 午後

parser.add_argument("-UI", "--uripathIN", type=str, default="*")

i think would allow any / prepended message
like /ha/ho/hi 33
I am not really into python-osc.
probably there is a way to route anthing from input ,
you can try reading dispatcher docs

Matteo  Facchini's icon

Matteo Facchini

2月 15 2021 | 4:55 午後

That is awesome!!!

I just wonder why with python-osc I have to specify a path. I would really love to send a message with just ip and port...

(see the following pictures)

fig.1 - not sending anything to my script

fig.2 - sending message to my script

Source Audio's icon

Source Audio

2月 16 2021 | 8:34 午前

You are forgetting that poblem is not Max and udpsend or udpreceive,
which can send and receive anything,
but OSC specification which defines
that OSC message has to start with "/" slash.
Means max is sending that messages but your script is filtering them out,
because of missing slash.

You could avoid using OSC at all
and capture use python udp instead if that is possible in your environment.
Or modify python-osc libary

have a look into dispatcher.py

Matteo  Facchini's icon

Matteo Facchini

2月 16 2021 | 11:58 午前

That's actually great.

I set-up my code to receive and send with UDP, but the problem now is dealing with bytes.

Source Audio's icon

Source Audio

2月 16 2021 | 12:44 午後

I was expecting that, seeing that you want to send list as symbol
form max in order to keep it single item, but as you are
fairly experienced python user, I am sure you will find your way through all this.

Matteo  Facchini's icon

Matteo Facchini

2月 16 2021 | 3:10 午後

I did: osc objects sent from max (as i stated before I AM NOT an experienced Max user) are not pure bytes object that can be easily decoded as bytes in python.

Thus, I used the _parse_datagram method of the OSCMessage class inside osc_message.py (in the python-osc library) to translate OSC messages and I am using "pure" udp protocol with socket to send and receive messages.

Source Audio's icon

Source Audio

2月 16 2021 | 3:39 午後

There are also alternative objects in sadam's library for udp communication.
sadam.udpSender for example
It is available through max package manager
P.S: sends "pure" bytes or lists of bytes