def __init__( self, port=30321, nogui=True, open="python-interface-help.pd", cmd=None, path=["patches"], extra=None, stderr=True, pdexe=None ): """ port - what port to connect to [netreceive] on. nogui - boolean: whether to start Pd with or without a gui. Defaults to nogui=True open - string: full path to a .pd file to open on startup. cmd - message to send to Pd on startup. path - an array of paths to add to Pd startup path. extra - a string containing extra command line arguments to pass to Pd. """ self.connectCallback = None args = [self._getPdExe(pdexe)] if stderr: args.append("-stderr") if nogui: args.append("-nogui") if open: if not os.path.isabs(open): open = os.path.join(os.path.dirname(__file__), "patches", open) args.append("-open") args.append(open) if cmd: args.append("-send") args.append(cmd) for p in path: args.append("-path") args.append(p) if extra: args += extra.split(" ") #print "COMMAND:", " ".join(args) try: self.pd = Popen(args, stdin=None, stderr=PIPE, stdout=PIPE, close_fds=(sys.platform != "win32")) except OSError: raise PdException( "Problem running `{}` from '{}'".format(pdexe, os.getcwd())) if port: self.port = port self._map = {} self._pdSend = PdSend(map=self._map) self._pdReceive = PdReceive(self, map=self._map)
class Pd: """ Start Pure Data in a subprocess. >>> from time import time, sleep >>> from os import path, getcwd >>> >>> start = time() >>> # launching pd >>> pd = Pd(nogui=False) >>> pd.Send(["test message", 1, 2, 3]) >>> >>> def Pd_hello(self, message): ... print "Pd called Pd_hello(%s)" % message ... >>> pd.Pd_hello = Pd_hello >>> >>> sentexit = False >>> # running a bunch of stuff for up to 20 seconds >>> while time() - start < 20 and pd.Alive(): ... if time() - start > 0.5 and not sentexit: ... pd.Send(["exit"]) ... sentexit = True ... pd.Update() ... ... Pd called Pd_hello(['this', 'is', 'my', 'message', 'to', 'python']) untrapped message: ['this', 'is', 'another', 'message'] untrapped stderr output: "connecting to port 30322" untrapped stderr output: "python-connected: 0" untrapped stderr output: "python-connected: 1" untrapped stderr output: "from-python: test message 1 2 3" untrapped stderr output: "closing audio..." untrapped stderr output: "pd_gui: pd process exited" untrapped stderr output: "closing MIDI..."... Pd died! >>> pd.Exit() """ errorCallbacks = {} def _getPdExe(self, pdexe): if pdexe is None: if "PD_BIN" in os.environ: return os.environ["PD_BIN"] else: if sys.platform == "win32": return os.path.join("pd", "bin", "pd.exe") elif sys.platform == "linux2": return "pd" elif sys.platform == "darwin": return os.path.join("", "Applications", "Pd.app", "Contents", "Resources", "bin", "pd") else: raise PdException("Unknown Pd executable location" "on your platform ('%s')." % sys.platform) else: return pdexe def __init__( self, port=30321, nogui=True, open="python-interface-help.pd", cmd=None, path=["patches"], extra=None, stderr=True, pdexe=None ): """ port - what port to connect to [netreceive] on. nogui - boolean: whether to start Pd with or without a gui. Defaults to nogui=True open - string: full path to a .pd file to open on startup. cmd - message to send to Pd on startup. path - an array of paths to add to Pd startup path. extra - a string containing extra command line arguments to pass to Pd. """ self.connectCallback = None args = [self._getPdExe(pdexe)] if stderr: args.append("-stderr") if nogui: args.append("-nogui") if open: if not os.path.isabs(open): open = os.path.join(os.path.dirname(__file__), "patches", open) args.append("-open") args.append(open) if cmd: args.append("-send") args.append(cmd) for p in path: args.append("-path") args.append(p) if extra: args += extra.split(" ") #print "COMMAND:", " ".join(args) try: self.pd = Popen(args, stdin=None, stderr=PIPE, stdout=PIPE, close_fds=(sys.platform != "win32")) except OSError: raise PdException( "Problem running `{}` from '{}'".format(pdexe, os.getcwd())) if port: self.port = port self._map = {} self._pdSend = PdSend(map=self._map) self._pdReceive = PdReceive(self, map=self._map) def Update(self): poll(map=self._map) stdin = self.pd.recv() stderr = self.pd.recv_err() if stdin: [self.CheckStart(t) for t in cr.split(stdin) if t] if stderr: [self.Error(t) for t in cr.split(stderr) if t] def Send(self, msg): """ Send an array of data to Pd. It will arrive at the [python-interface] object as a space delimited list. p.Send(["my", "test", "yay"]) """ self._pdSend.Send(msg) def PdMessage(self, data): """ Override this method to receive messages from Pd. """ print("untrapped message:", data) def Connect(self, addr): self._pdSend.Connect((addr[0], self.port)) def CheckStart(self, msg): if "_Start() called" in msg: self.PdStarted() def Error(self, error): """ Override this to catch anything sent by Pd to stderr (e.g. [print] objects). """ errors = error.split(" ") method = getattr(self, 'Error_' + errors[0], None) if method: method(errors) elif error in self.errorCallbacks: self.errorCallbacks[error]() else: print('untrapped stderr output: "' + error + '"') def Dead(self): self.pd = None self.PdDied() def PdStarted(self): """ Override this to catch the definitive start of Pd. """ pass def PdDied(self): """ Override this to catch the Pd subprocess exiting. """ print("Pd died!") def Alive(self): """ Check whether the Pd subprocess is still alive. """ return bool(self.pd and self.pd.poll() != 0) def Exit(self): """ Kill the Pd process right now. """ if self.Alive(): #self.close() if sys.platform == "win32": # Kill the process using pywin32 import win32api win32api.TerminateProcess(int(self.pd._handle), -1) else: # kill the process using os.kill from os import kill kill(self.pd.pid, signal.SIGINT) self._pdReceive.close() if self.pd: self.pd.wait()