pythonpython-3.xoscsupercollidermax-msp-jitter

python receive OSC message from Max


I am a fairly experienced python user, but I have no experience with OSC protocol, MAX and SuperCollider.

I am helping a friend in building a python code that should do the following:

  1. receive a message from MAX via OSC
  2. elaborate the message in python
  3. send the elaborated message to SuperCollider

What it is not clear to me is what concerns the 1 and 3 points.

PART 0

I import the following libraries

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

PART 1

I tried to setup a simple server for point 1, since I am only receiving one string at a time from MAX:

def listen2Max(ip,port):
    '''
    set up server
    '''
    # dispatcher to receive message
    dispatcher = dispatcher.Dispatcher()
    dispatcher.map("/filter", print)
    # server to listen
    server = osc_server.ThreadingOSCUDPServer((ip,port), dispatcher)
    print("Serving on {}".format(server.server_address))
    server.serve_forever()

but i do not know what to return in this function, also I do not get the use of "/filter" in the map. Is it something that must be specified in MAX?

PART 2

This is actually my strong suit: I take the string message returned by my listen2Max function, do some py stuff and prepare a message called mymove to be sent to SuperCollider

PART 3

For point 3 I set up a simple client to communicate the string var mymove to SuperCollider

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

Should it work like that?


Solution

  • This is the code that gets (at least part of) the job done.

    Bear in mind that, in this case, Max sends strings to python. The messages I get are something like this: O-O e8g8 8 1

    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+'whatever'
        # 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)
    

    This code gets messages from a client like this:

    from pythonosc import dispatcher
    from pythonosc import osc_server
    from pythonosc import udp_client
    from typing import List, Any
    
    def talk2SC(ip,port,path):
        '''
        set up client
        '''
        # maxmsg = 'Nxe6 f4e6'
        maxmsg = 'e4 e2e4'
        client = udp_client.SimpleUDPClient(ip,port)
        client.send_message(path, maxmsg)
    
    
    if __name__ == '__main__':
        talk2SC("127.0.0.1",5005,"/filter")
    

    and sends messages to a server like this:

    from pythonosc import dispatcher
    from pythonosc import osc_server
    from pythonosc import udp_client
    from typing import List, Any
    
    
    def main(address: str, *osc_arguments):
        '''
        prende il messaggio di MAX e lo traduce aggiornando la scacchiera
        '''
        # read message
        msg = osc_arguments[0]
        # initialize board
        print(osc_arguments)
        # outputc
    
    def listen2Max(ip,port,path):
        '''
        set up server
        '''
        # dispatcher to receive message
        disp = dispatcher.Dispatcher()
        handler = disp.map(path, main)
        # print(handler)
        # server to listen
        server = osc_server.ThreadingOSCUDPServer((ip,port), disp)
        print("Serving on {}".format(server.server_address))
        server.serve_forever()
    
    if __name__ == '__main__':
        listen2Max("127.0.0.1",5006,"/chmove")
    

    The tricky part is to make it communicate with Max. Luckily enough I found an answer here:

    1. format each Max message as /path "O-O e8g8 8 1"
    2. make python listen on the specified /path

    If one wants python to listen to each and every path, one could change the default path in the parser item and use "*" as default. Then

    1. Max's messages could have whatever path
    2. in the parser one will have parser.add_argument("-UI", "--uripathIN", type=str, default="*", help="MAX's URI path")

    Now, I just have to fix the SuperCollider part.