Search code examples
pythonoscsupercollider

How to debug OSC protocol between Supercollider and Python app


Being new to SuperCollider I am using a tutorial to try to get a Pycharm application to talk to Supercollider (Python version: 3.7/SuperCollider 3.9.3). On the client side i have tried with pythonosc and OSC:

pythosc code:

import argparse
import random
from pythonosc import osc_message_builder
from pythonosc import udp_client
import socket

if __name__ == "__main__":

    parser = argparse.ArgumentParser()
    parser.add_argument("--ip", default='127.0.0.1',
                        help="The ip of the OSC server")
    parser.add_argument("--port", type=int, default=57110,
                        help="The port the OSC server is listening on")
    args = parser.parse_args()

    client = udp_client.SimpleUDPClient(args.ip, args.port)

    client.send_message("/print", 500)

OSC code:

import OSC
import time, random

if __name__ == "__main__":
    client = OSC.OSCClient()
    client.connect(("127.0.0.1", 57110))
    msg = OSC.OSCMessage()
    msg.setAddress("/print")
    msg.append(500)
    client.send(msg)

Code in SuperCollider:

s.boot;

(
SynthDef( \sin, { | amp = 0.01, freq = 333, trig = 1 |
    var env, sig;
    env = EnvGen.kr( Env.asr( 0.001, 0.9, 0.001 ), trig, doneAction: 0 );
    sig = LFTri.ar( [ freq, freq * 0.999 ], 0.0, amp ) * env;
    Out.ar( [ 0 ], sig * 0.6 );
}).add;

h = Synth( \sin, [ \amp, 0.4 ] );

x = OSCFunc( { | msg, time, addr, port |
    var pyFreq;

    pyFreq = msg[1].asFloat;
    ( "freq is " + pyFreq ).postln;
    h.set( \freq, pyFreq );
}, '/print' );
)

When using both the pythonosc and OSC code I get: 'FAILURE IN SERVER: /print Command not found'. Clearly the python app is establishing communication with SC but cannot interpret '/print'. I note that the SC server boots on port 57110 but NetAddr.langPort returns as 57120. I'm not sure why they are different. I spent an entire evening reviewing possible solutions online with no luck. A couple of other StackOverflow users have published similar problems with one suggesting that it may be a port issue. In the present case I don't see how it can be as, under that condition, SC would not anything at all. (Changing the port to 57120 in python code results in no response from the SC server). Any suggestions emanating from peoples' previous experience of this bogey would be most welcome as, basically, it's driving me. very slowly, nuts!!


Solution

  • Short answer: use the port returned by NetAddr.langPort.

    The SuperCollider server (scsynth) and language process (sclang) are different things. You might be getting confused if you're using the SuperCollider IDE because output from both processes is posted in the post window there, and the distinction is also difficult to grasp as a beginner.

    You're very close to the answer -- you noticed that the server's port is different from the language's port. In order for the two processes to talk to each other via UDP-based OSC, they need to use separate ports. The server uses OSC addresses to implement a command protocol, which you can find the documentation for here. As you'll see, there are commands for things like creating new synth nodes (/s_new), allocating buffers (/b_alloc), and set values on control busses (/s_set), but no command for /print. That's why you're seeing that specific error message: 'FAILURE IN SERVER: /print Command not found'. (That message is coming from the server, not the language, by the way.)

    OSCFunc objects, on the other hand, respond to OSC messages coming into the language process. So, all you need to do is send your OSC message from Python to the right port -- 57120.

    This documentation article might help in understanding the difference between the SuperCollider server (scsynth) and the client (Python, sclang, or whatever other program you have that sends it OSC messages).