diff --git a/build/config-lnx.def b/build/config-lnx.def index 09879a3..cfdbcd6 100644 --- a/build/config-lnx.def +++ b/build/config-lnx.def @@ -1,8 +1,13 @@ # what is the base prefix of the Python installation? +ifdef PY_CONDA_ROOT +PYTHONPREFIX=$(PY_CONDA_ROOT) +else PYTHONPREFIX=/usr +endif # which Python version do you want to compile against? -PYTHONVERSION=2.7 +PY_MAJOR_VERSION=3 +PY_MINOR_VERSION=7 # uncomment if numpy/numarray/numeric support should be compiled in # for info see http://numeric.scipy.org @@ -15,3 +20,6 @@ PY_USE_GIL=1 # use inofficial (pure data) functionality # PY_USE_INOFFICIAL=1 + +# use python with pymalloc (look for "pythonX.Ym" files) +PY_USE_PYMALLOC=1 diff --git a/build/config-mac.def b/build/config-mac.def index 0a59aa9..a86906f 100644 --- a/build/config-mac.def +++ b/build/config-mac.def @@ -1,6 +1,9 @@ -# set PY_DEFAULT=0 if you manually installed Python on your system -# (overriding the one that came with OS X by default) -PY_DEFAULT=1 +# which kind of Python installation to use +# system - macOS system default +# local - local installation +# conda - conda environment; specify the environment root dir using the +# PY_CONDA_ROOT environment variable +PY_KIND=conda ######################################################################### @@ -10,8 +13,8 @@ PY_DEFAULT=1 # Mac OSX 10.5 -> default Python version (major.minor) = 2.5 # Mac OSX 10.6 -> default Python version (major.minor) = 2.6 -PY_MAJOR_VERSION=2 -PY_MINOR_VERSION=6 +PY_MAJOR_VERSION=3 +PY_MINOR_VERSION=7 ######################################################################### @@ -19,7 +22,7 @@ PY_MINOR_VERSION=6 # for info see http://numeric.scipy.org # numarray and numeric are outdated -# PY_NUMPY=1 +PY_NUMPY=1 # PY_NUMARRAY=1 # PY_NUMERIC=1 @@ -30,3 +33,6 @@ PY_USE_GIL=1 # use inofficial (pure data) functionality # PY_USE_INOFFICIAL=1 + +# use python with pymalloc (look for "pythonX.Ym" files) +PY_USE_PYMALLOC=1 diff --git a/build/gnumake-lnx-gcc.inc b/build/gnumake-lnx-gcc.inc index 7bbde1f..e9177b4 100644 --- a/build/gnumake-lnx-gcc.inc +++ b/build/gnumake-lnx-gcc.inc @@ -1,5 +1,19 @@ +ifdef PY_USE_PYMALLOC +PYTHONVERSION=$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)m +else +PYTHONVERSION=$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION) +endif + DEFS += -DPY_EXPORTS + INCPATH += -I$(PYTHONPREFIX)/include/python$(PYTHONVERSION) + +ifdef PY_CONDA_ROOT +LIBPATH += -L$(PY_CONDA_ROOT)/lib +LDFLAGS += -Wl,-R$(PY_CONDA_ROOT)/lib +DEFS += -DPY_INTERPRETER=$(PY_CONDA_ROOT)/bin/python +endif + LIBS += -lpython$(PYTHONVERSION) ifdef PY_NUMARRAY @@ -7,6 +21,7 @@ DEFS += -DPY_NUMARRAY endif ifdef PY_NUMPY DEFS += -DPY_NUMPY +INCPATH += -I$(PYTHONPREFIX)/lib/python$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)/site-packages/numpy/core/include endif ifdef PY_NUMERIC DEFS += -DPY_NUMERIC diff --git a/build/gnumake-mac-gcc.inc b/build/gnumake-mac-gcc.inc index 8806024..3bd383e 100644 --- a/build/gnumake-mac-gcc.inc +++ b/build/gnumake-mac-gcc.inc @@ -1,3 +1,50 @@ +DEFS += -DPY_EXPORTS + +ifdef PY_NUMPY +DEFS += -DPY_NUMPY +endif + +ifdef PY_NUMARRAY +DEFS += -DPY_NUMARRAY +endif + +ifdef PY_NUMERIC +DEFS += -DPY_NUMERIC +endif + +ifdef PY_USE_GIL +DEFS += -DPY_USE_GIL +endif + +ifdef PY_USE_INOFFICIAL +DEFS += -DPY_USE_INOFFICIAL +endif + +ifeq ($(PY_KIND),conda) + +ifndef PY_CONDA_ROOT +$(error PY_CONDA_ROOT is undefined) +endif + +DEFS += -DPY_INTERPRETER=$(PY_CONDA_ROOT)/bin/python +INCPATH += -I$(PY_CONDA_ROOT)/include +ifdef PY_USE_PYMALLOC +INCPATH += -I$(PY_CONDA_ROOT)/include/python$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)m +LIBS += $(PY_CONDA_ROOT)/lib/libpython$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)m.dylib +else +INCPATH += -I$(PY_CONDA_ROOT)/include/python$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION) +LIBS += $(PY_CONDA_ROOT)/lib/libpython$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION).dylib +endif +LDFLAGS += -rpath $(PY_CONDA_ROOT)/lib + +ifdef PY_NUMPY +INCPATH += -I$(PY_CONDA_ROOT)/lib/python$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)/site-packages/numpy/core/include +endif + +else + +DEFS += -DPY_USE_FRAMEWORK + # don't use -framework Python, since this will stick to the default system version _LOCAL_FRAMEWORK := /Library/Frameworks/Python.framework/Versions/$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION) @@ -5,10 +52,9 @@ _SYSTEM_FRAMEWORK := /System/Library/Frameworks/Python.framework/Versions/$(PY_M _LOCAL_LIBRARY := /Library/Python/$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION) _SYSTEM_LIBRARY := /System/Library/Python/$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION) -DEFS += -DPY_EXPORTS INCPATH += -F/Library/Frameworks -framework Python -ifeq ($(PY_DEFAULT),1) +ifeq ($(PY_KIND),system) LIBS += $(_SYSTEM_FRAMEWORK)/Python INCPATH += -I$(_SYSTEM_FRAMEWORK)/Headers else @@ -16,27 +62,18 @@ LIBS += $(_LOCAL_FRAMEWORK)/Python INCPATH += -I$(_LOCAL_FRAMEWORK)/Headers endif -ifdef PY_NUMARRAY -DEFS += -DPY_NUMARRAY -endif ifdef PY_NUMPY -DEFS += -DPY_NUMPY INCPATH += -I$(_LOCAL_LIBRARY)/python$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)/site-packages/numpy/core/include -ifeq ($(PY_DEFAULT),1) + +ifeq ($(PY_KIND),system) INCPATH += -I$(_SYSTEM_FRAMEWORK)/lib/python$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)/site-packages/numpy/core/include INCPATH += -I$(_SYSTEM_FRAMEWORK)/Extras/lib/python/numpy/core/include else INCPATH += -I$(_LOCAL_FRAMEWORK)/lib/python$(PY_MAJOR_VERSION).$(PY_MINOR_VERSION)/site-packages/numpy/core/include endif endif -ifdef PY_NUMERIC -DEFS += -DPY_NUMERIC -endif -ifdef PY_USE_GIL -DEFS += -DPY_USE_GIL endif -ifdef PY_USE_INOFFICIAL -DEFS += -DPY_USE_INOFFICIAL -endif +#DEBUG = 1 +#DFLAGS += -DFLEXT_DEBUG diff --git a/package.txt b/package.txt index 9002aa8..2a19933 100644 --- a/package.txt +++ b/package.txt @@ -11,6 +11,6 @@ SRCS= \ py.cpp pyext.cpp modmeth.cpp clmeth.cpp \ register.cpp bound.cpp pyargs.cpp \ pysymbol.cpp pybuffer.cpp pybundle.cpp pydsp.cpp \ - pyatom.cpp pybase.cpp pymeth.cpp + pyatom.cpp pybase.cpp pymeth.cpp pycompat.cpp -HDRS= pyprefix.h main.h pyext.h pysymbol.h pybuffer.h pybundle.h pyatom.h pybase.h +HDRS= pyprefix.h main.h pyext.h pysymbol.h pybuffer.h pybundle.h pyatom.h pybase.h pycompat.h diff --git a/pd/methods-1.pd b/pd/methods-1.pd index 5b85c43..253fa6a 100644 --- a/pd/methods-1.pd +++ b/pd/methods-1.pd @@ -1,24 +1,24 @@ -#N canvas 540 469 734 369 12; -#X obj 16 13 cnv 15 650 40 empty empty py/pyext 10 22 0 24 -260818 --1 0; -#X text 235 32 http://grrrr.org/ext; -#X symbolatom 21 139 10 0 0 0 - - -; -#X symbolatom 25 298 10 0 0 0 - - -; -#X obj 22 179 py .str @py 1; -#X text 145 170 convert the symbol to a Python string; -#X text 35 216 pass it as a true Python object; -#X symbolatom 364 295 10 0 0 0 - - -; -#X text 462 269 use module function; -#X text 23 119 enter some text; -#X text 145 187 using the built-in str function; -#X obj 25 252 pym swapcase; -#X text 63 270 use swapcase method; -#X obj 363 250 py string.swapcase; -#X text 235 16 Python script objects \, (C)2003-2006 Thomas Grill; -#X text 21 73 Py can act on Python objects in an object-oriented manner -; -#X connect 2 0 4 1; -#X connect 4 0 11 1; -#X connect 4 0 13 1; -#X connect 11 0 3 0; -#X connect 13 0 7 0; +#N canvas 540 469 734 369 12; +#X obj 16 13 cnv 15 650 40 empty empty py/pyext 10 22 0 24 -260818 +-1 0; +#X text 235 32 http://grrrr.org/ext; +#X symbolatom 21 139 10 0 0 0 - - -; +#X symbolatom 25 298 10 0 0 0 - - -; +#X obj 22 179 py .str @py 1; +#X text 145 170 convert the symbol to a Python string; +#X text 35 216 pass it as a true Python object; +#X symbolatom 364 295 10 0 0 0 - - -; +#X text 462 269 use module function; +#X text 23 119 enter some text; +#X text 145 187 using the built-in str function; +#X obj 25 252 pym swapcase; +#X text 63 270 use swapcase method; +#X text 235 16 Python script objects \, (C)2003-2006 Thomas Grill; +#X text 21 73 Py can act on Python objects in an object-oriented manner +; +#X obj 363 250 py string.capwords; +#X connect 2 0 4 1; +#X connect 4 0 11 1; +#X connect 4 0 15 1; +#X connect 11 0 3 0; +#X connect 15 0 7 0; diff --git a/readme.txt b/readme.txt index fa2d42a..83d3626 100644 --- a/readme.txt +++ b/readme.txt @@ -1,24 +1,21 @@ py/pyext - python script objects for PD and Max/MSP +This fork by SOPI research group (https://sopi.aalto.fi) implements support for Python 3 as well as Conda Python installations. + +It was developed for use with GANSpaceSynth (https://github.com/SopiMlab/GANSpaceSynth) and our Deep Learning with Audio course (https://github.com/SopiMlab/DeepLearningWithAudio). + +See also the original repository: https://github.com/grrrr/py + Copyright (c)2002-2020 Thomas Grill (gr@grrrr.org) For information on usage and redistribution, and for a DISCLAIMER OF ALL WARRANTIES, see the file, "license.txt," in this distribution. -Donations for further development of the package are highly appreciated. -Visit https://www.paypal.com/xclick/business=gr%40grrrr.org&item_name=pyext&no_note=1&tax=0¤cy_code=EUR - ---------------------------------------------------------------------------- You need to have Python installed on your system for the py/pyext external to work. -For Windows pick an up-to-date package from http://www.python.org . -For linux use the package manager. -For OS X keep things as the are - it has Python installed by default. - +We recommend using Conda (https://conda.io). -The py/pyext package should run with Python version >= 2.1. -It has been tested with versions 2.2 to 2.7 - -The default build setting using PY_USE_GIL requires Python version >= 2.3. +SopiMlab/py has been tested with Python 3.7-3.8 and 2.7. Check out the sample patches and scripts @@ -27,11 +24,15 @@ Check out the sample patches and scripts INSTALLATION ============ -PD version >= 0.38 - Add "py" to the Startup items ("binaries to load") and add the folder "scripts" to the pd search path. -PD version < 0.38 - Load it as a library with e.g. "pd -lib py -path scripts" -Under Windows, py/pyext needs at least PD 0.43 +We have detailed tutorials for setting up GANSpaceSynth with Pd in a Conda environment: +https://github.com/SopiMlab/DeepLearningWithAudio/tree/master/utilities/pyext-setup + +Known issues: -Max/MSP - Copy py-objectmappings.txt into the init folder and py.mxe (Windows) or py.mxo (OSX) into the externals folder. +- We haven't been able to build on Windows +- Max/MSP is untested + +Contributions are welcome! ---------------------------------------------------------------------------- @@ -43,22 +44,15 @@ With the pyext you can use python classes to represent full-featured pd/Max mess Multithreading (detached methods) is supported for both objects. You can send messages to named objects or receive (with pyext) with Python methods. - -Known bugs: -- The TCL/TK help patch is not usable under OSX. -- With standard PD 0.37, threaded py scripts will cause "Stack overflows" under some circumstances - -> use PD 0.38 or the devel_0_37 cvs branch instead -- It has been reported that pyext crashes on AMD64 with SSE enabled (for these CPUs, disable the respective compiler flags) - ---------------------------------------------------------------------------- BUILDING from source ==================== You will need the flext C++ layer for PD and Max/MSP externals to compile this. -See http://grrrr.org/ext/flext -Download and unzip, or git-clone the package. +See https://github.com/SopiMlab/flext +TODO: Document our changes to the build configuration. For now, the build.py script from https://github.com/SopiMlab/DeepLearningWithAudio/blob/master/utilities/pyext-setup/build.py can give you some hints Pure data - any platform supporting gcc-compatible compilers ------------------------------------------------------------ @@ -77,166 +71,11 @@ CFLAGS="-mmacosx-version-min=10.9" LDFLAGS="-mmacosx-version-min=10.9" pd/Max - Windows - Microsoft Visual C, Borland C++, MinGW: ---------------------------------------------------------- -Start a command shell with your eventual build environment -(e.g. run vcvars32.bat for Microsoft Visual Studio) - -then run - ..\flext\build.bat -(you would have to substitute ..\flext with the respective path to the flext package) - +Please see further setup instructions at https://github.com/grrrr/py pd/Max - OSX/Linux - GCC: ------------------------- -From a shell run -bash ../flext/build.sh -(you would have to substitute ../flext with the respective path to the flext package) - - ----------------------------------------------------------------------------- - -Python array support for py/pyext@Max/MSP: - -In the Max/MSP SDK change the file -4.5 headers\c74support\max-includes\ext_types.h, line 45 -from - typedef unsigned long UInt32; - typedef signed long SInt32; -to - typedef unsigned int UInt32; - typedef signed int SInt32; -to avoid a compile-time type definition clash. - +Please see further setup instructions at https://github.com/grrrr/py ---------------------------------------------------------------------------- +Please see further setup instructions at https://github.com/grrrr/py -Version history: - -0.2.2: -- FIX: pyext._send(receiversym) sent an empty list to receiversym, now it sends a bang message (equivalent to pyext._send(receiversym,"bang",()) ). Thanks to Yvan Volochine for spotting that. -- FIX: A bug in Pd < 0.43 under Windows causes various errors on loading the modules. This is fixed, py/pyext for Windows now needs Pd 0.43 - -0.2.1: -- FIX: some simplifications in py and pyext -- ADD: Python objects can be sent/received through outlets/inlets -- ADD: py can have multiple inlets for multiple function arguments (right inlets are non-triggering) -- ADD: allow module.function syntax for py and pyext -- FIX: pyext: cleanup float vs. int ... first decision is made by tag, afterwards a conversion is tried -- ADD: pym: object-oriented object... Python methods for any object type -- ADD: py: allow all callables (also object constructors and builtins) -- ADD: py: enable Python built-in functions (like range, str etc.) -- ADD: sequence protocol for symbol type -- FIX: cleanup for outbound messages (e.g. symbol atoms instead of one-element general messages) -- FIX: better exception handling (no longer leaves reference to function object) and cleared misleading error message -- FIX: better definition of output values for atoms, lists and anythings -- FIX: much better detached method handling (one thread for all object instances!) -- ADD: open module file in editor on "edit" message (or shift-click (PD) or double click (Max)) -- FIX: _inlets and _outlets default to 0 if not given -- ADD: enable optimization of Python code in reease build -- CHG: _isthreaded is now a data member instead of a method -- FIX: more safety for calls where association python-pd has already been removed -- ADD: __str__ method for pyext, to enable print self calls -- ADD: enable symbol binding for all callables (not only functions and methods) -- ADD: Buffer.resize(frames,keep=1,zero=1) method -- ADD: py.Bundle class to support flext message bundles -- ADD: enable usage of compiled-only modules (.py[co]) -- ADD: enable usage of module packages (with module/__init__.py[co]) -- ADD: make use of the PyGILState_*() functions -- ADD: always run the Python interpreter in the background (to keep alive Python threads) -- ADD: added PY_USE_INOFFICIAL to enable usage of s_stuff.h PD header, to have access to search and help paths -- ADD: pyext: _init method is now called after __init__ (after inlets/outlets have been created) -- FIX: buffer protocol adapted to Python 2.5 -- FIX: subpath support for modules (tested for Pd) - -0.2.0: -- ADD: handling of Python threads -- FIX: output of single atoms instead of 1-element lists -- ADD: new detach mechanism (call queue) -- ADD: support for Max/MSP @ OSX and Windows -- DEL: eliminated meaningless inchannels and outchannels methods -- ADD: enabled "int"-tags for pyext class functions -- ADD: py: when no function is given on the command line, let it be selected by message tag -- FIX: __init__ wasn't called on reload -- FIX: bound instance methods weren't correctly decref'd -- ADD: Python symbol type -- ADD: _del method in pyext-derived class can be used to clean up things on exit -- FIX: solved py->py messaging problem with lock count instead of message queuing -- ADD: buffer handling with optional numarray support (if present) -- ADD: new objects for dsp processing: pyext~,pyx~,pyext.~,pyx.~ -- FIX: correctly report Python errors while contructing the object - -0.1.4: -- ADD: better (and independent) handling of inlet and outlet count (as class variables or dynamically initialized in __init__) -- FIX: many memory leaks associated to ***GetItem stuff (big thanks to sven!) -- FIX: set "this" memory in object after reloading script -- ADD: _getvalue,_setvalue to access PD values -- FIX: don't shout when Python script returns PyNone -- ADD: alias creation names pyext. and pyx. take the script name also for the class name - -0.1.3: -- FIX: class variables are now set atomic if parameter list has only 1 element -- ADD: introduced shortcut "pyx" for pyext. -- ADD: arguments to the pyext class are now exposed as attributes "args" -- FIX: parameters to Python functions are treated as integers when they can be. -- ADD: inlet and outlet count can be given for pyext, python _inlet and _outlet members are ignored then -- FIX: crash if script or class names are non-strings -- FIX: long multi-line doc strings are now printed correctly -- FIX: message "doc+" for class/instance __doc__ now working -- FIX: improved/debugged handling of reference counts -- FIX: _pyext._send will now send anythings if feasible -- CHANGE: no more finalization - it's really not necessary... -- FIX: calling from unregistered threads (like flext helper thread) now works - -0.1.2: -- CHANGE: updates for flext 0.4.1 - method registering within class scope -- FIX: bugs in bound.cpp (object_free calls) -- FIX: bug with threaded methods along with flext bug fix. -- ADD: map Python threads to system threads -- ADD: shut down the Python interpreter appropriately -- CHANGE: use flext timer and bind functionality -- ADD: attribute functionality -- ADD: dir and dir+ methods for Python dictionary listing -- ADD: get and set methods for Python class attributes - -0.1.1: -- CHANGE: updates for flext 0.4.0 -- FIX: crash when module couldn't be loaded -- FIX: GetBound method (modmeth.cpp, line 138) doesn't exist in flext any more -- FIX: deadlock occured when connecting two py/pyext boxes in non-detached mode -- ADD: current path and path of the canvas is added to the python path -- FIX: path is not added to python path if already included - -0.1.0: -- completely reworked all code -- added class functionality for full-featured objects and renamed the merge to pyext -- enabled threads and made everything thread-safe ... phew! -- using flext 0.3.2 -- pyext now gets full python path -- python's argv[0] is now "py" or "pyext" -- etc.etc. - -0.0.2: -- fixed bug when calling script with no function defined (thanks to Ben Saylor) -- cleaner gcc makefile - -0.0.1: -- using flext 0.2.1 - ---------------------------------------------------------------------------- - -TODO list: - -bugs: -- crashes with long Python printouts - -general: -- Documentation and better example patches -- better error reporting for runtime errors -- we should pre-scan and cache class methods - -features: -- enable multiple interpreters? ( -> not possible within one thread) -- options to fully detach a script (included initialization and finalization) -- stop individual threads -- support named (keyword) arguments (like attributes for messages) - -tests: -- compile-time check for python threading support diff --git a/scripts/buffer.py b/scripts/buffer.py index b58ea42..401cc6b 100644 --- a/scripts/buffer.py +++ b/scripts/buffer.py @@ -12,18 +12,20 @@ Numeric, numarray and numpy (for all of them see http://numeric.scipy.org) """ +from __future__ import print_function + import sys try: import pyext except: - print "ERROR: This script must be loaded by the PD/Max py/pyext external" + print("ERROR: This script must be loaded by the PD/Max py/pyext external") try: # numpy is assumed here... numeric and numarray are considered deprecated import numpy as N except: - print "Failed importing numpy module:",sys.exc_value + print("Failed importing numpy module:",sys.exc_value) def mul(*args): # create buffer objects diff --git a/scripts/script.py b/scripts/script.py index ff41730..0ef6492 100644 --- a/scripts/script.py +++ b/scripts/script.py @@ -7,14 +7,17 @@ """Several functions to show the py script functionality""" +from __future__ import print_function + +from functools import reduce import sys -print "Script initialized" +print("Script initialized") try: - print "Script arguments: ",sys.argv + print("Script arguments: ",sys.argv) except: - print + print() def numargs(*args): # variable argument list """Return the number of arguments""" diff --git a/scripts/sendrecv.py b/scripts/sendrecv.py index 9d873ba..25596a7 100644 --- a/scripts/sendrecv.py +++ b/scripts/sendrecv.py @@ -18,10 +18,12 @@ """ +from __future__ import print_function + try: - import pyext + import pyext except: - print "ERROR: This script must be loaded by the PD/Max pyext external" + print("ERROR: This script must be loaded by the PD/Max pyext external") from time import sleep @@ -29,92 +31,92 @@ ################################################################# def recv_gl(arg): - """This is a global receive function, it has no access to class members.""" - print "GLOBAL",arg + """This is a global receive function, it has no access to class members.""" + print("GLOBAL",arg) class ex1(pyext._class): - """Example of a class which receives and sends messages + """Example of a class which receives and sends messages - It has two creation arguments: a receiver and a sender name. - There are no inlets and outlets. - Python functions (one global function, one class method) are bound to PD's or Max/MSP's receive symbols. - The class method sends the received messages out again. - """ + It has two creation arguments: a receiver and a sender name. + There are no inlets and outlets. + Python functions (one global function, one class method) are bound to PD's or Max/MSP's receive symbols. + The class method sends the received messages out again. + """ - # no inlets and outlets - _inlets=1 - _outlets=0 + # no inlets and outlets + _inlets=1 + _outlets=0 - recvname="" - sendname="" + recvname="" + sendname="" - def recv(self,*arg): - """This is a class-local receive function, which has access to class members.""" + def recv(self,*arg): + """This is a class-local receive function, which has access to class members.""" - # print some stuff - print "CLASS",self.recvname,arg + # print some stuff + print("CLASS",self.recvname,arg) - # send data to specified send address - self._send(self.sendname,arg) + # send data to specified send address + pyext._send(self.sendname,arg) - def __init__(self,*args): - """Class constructor""" + def __init__(self,*args): + """Class constructor""" - # store sender/receiver names - if len(args) >= 1: self.recvname = args[0] - if len(args) >= 2: self.sendname = args[1] + # store sender/receiver names + if len(args) >= 1: self.recvname = args[0] + if len(args) >= 2: self.sendname = args[1] - self.bind_1() + self.bind_1() - def bind_1(self): - # bind functions to receiver names - # both are called upon message - self._bind(self.recvname,self.recv) - self._bind(self.recvname,recv_gl) + def bind_1(self): + # bind functions to receiver names + # both are called upon message + self._bind(self.recvname,self.recv) + self._bind(self.recvname,recv_gl) - def unbind_1(self): - self._unbind(self.recvname,self.recv) - self._unbind(self.recvname,recv_gl) + def unbind_1(self): + self._unbind(self.recvname,self.recv) + self._unbind(self.recvname,recv_gl) - def __del__(self): - """Class destructor""" + def __del__(self): + """Class destructor""" - # unbinding is automatically done at destruction - pass + # unbinding is automatically done at destruction + pass ################################################################# class ex2(pyext._class): - """Example of a class which receives a message and forwards it to an outlet + """Example of a class which receives a message and forwards it to an outlet - It has one creation argument: the receiver name. - """ + It has one creation argument: the receiver name. + """ - # define inlets and outlets - _inlets=0 - _outlets=1 + # define inlets and outlets + _inlets=0 + _outlets=1 - recvname="" + recvname="" - def recv(self,*arg): - """This is a class-local receive function""" + def recv(self,*arg): + """This is a class-local receive function""" - # send received data to outlet - self._outlet(1,arg) + # send received data to outlet + self._outlet(1,arg) - def __init__(self,rname): - """Class constructor""" + def __init__(self,rname): + """Class constructor""" - # store receiver names - self.recvname = rname + # store receiver names + self.recvname = rname - # bind function to receiver name - self._bind(self.recvname,self.recv) + # bind function to receiver name + self._bind(self.recvname,self.recv) ################################################################# @@ -124,57 +126,57 @@ def __init__(self,rname): from random import random,randint class ex3(pyext._class): - """Example of a class which does some object manipulation by scripting""" - - - # define inlets and outlets - _inlets=1 - _outlets=0 - - def __init__(self): - """Class constructor""" - - # called scripting method should run on its own thread - if self._isthreaded: - print "Threading is on" - self._detach(1) - - def bang_1(self): - """Do some scripting - PD only!""" - - num = 12 # number of objects - ori = complex(150,180) # origin - rad = 100 # radius - l = range(num) # initialize list - - # make flower - self._tocanvas("obj",ori.real,ori.imag,"bng",20,250,50,0,"empty","yeah","empty",0,-6,64,8,-24198,-1,-1) - for i in xrange(num): - l[i] = ori+rad*exp(complex(0,i*2*pi/num)) - self._tocanvas("obj",l[i].real,l[i].imag,"bng",15,250,50,0,"empty","yeah"+str(i),"empty",0,-6,64,8,0,-1,-1) - self._tocanvas("connect",6,0,7+i,0) - - # blink - for i in range(10): - self._send("yeah","bang") - sleep(1./(i+1)) - - # move objects around - for i in xrange(200): - ix = randint(0,num-1) - l[ix] = ori+rad*complex(2*random()-1,2*random()-1) - self._send("yeah"+str(ix),"pos",l[ix].real,l[ix].imag) - sleep(0.02) - - # now delete - # this is not well-done... from time to time an object remains - self._tocanvas("editmode",1) - for i in xrange(num): - self._tocanvas("mouse",l[i].real,l[i].imag,0,0) - self._tocanvas("cut") - - self._tocanvas("mouse",ori.real+1,ori.imag+1,0,0) - self._tocanvas("cut") - - self._tocanvas("editmode",0) + """Example of a class which does some object manipulation by scripting""" + + + # define inlets and outlets + _inlets=1 + _outlets=0 + + def __init__(self): + """Class constructor""" + + # called scripting method should run on its own thread + if self._isthreaded: + print("Threading is on") + self._detach(1) + + def bang_1(self): + """Do some scripting - PD only!""" + + num = 12 # number of objects + ori = complex(150,180) # origin + rad = 100 # radius + l = list(range(num)) # initialize list + + # make flower + self._tocanvas("obj",ori.real,ori.imag,"bng",20,250,50,0,"empty","yeah","empty",0,-6,64,8,-24198,-1,-1) + for i in range(num): + l[i] = ori+rad*exp(complex(0,i*2*pi/num)) + self._tocanvas("obj",l[i].real,l[i].imag,"bng",15,250,50,0,"empty","yeah"+str(i),"empty",0,-6,64,8,0,-1,-1) + self._tocanvas("connect",6,0,7+i,0) + + # blink + for i in range(10): + pyext._send("yeah","bang") + sleep(1./(i+1)) + + # move objects around + for i in range(200): + ix = randint(0,num-1) + l[ix] = ori+rad*complex(2*random()-1,2*random()-1) + pyext._send("yeah"+str(ix),"pos",l[ix].real,l[ix].imag) + sleep(0.02) + + # now delete + # this is not well-done... from time to time an object remains + self._tocanvas("editmode",1) + for i in range(num): + self._tocanvas("mouse",l[i].real,l[i].imag,0,0) + self._tocanvas("cut") + + self._tocanvas("mouse",ori.real+1,ori.imag+1,0,0) + self._tocanvas("cut") + + self._tocanvas("editmode",0) diff --git a/scripts/sig.py b/scripts/sig.py index 7009cd8..a7de91e 100644 --- a/scripts/sig.py +++ b/scripts/sig.py @@ -11,15 +11,17 @@ It will probably once be replaced by Numeric(3) """ +from __future__ import print_function + try: import pyext except: - print "ERROR: This script must be loaded by the PD/Max py/pyext external" + print("ERROR: This script must be loaded by the PD/Max py/pyext external") try: import psyco psyco.full() - print "Using JIT compilation" + print("Using JIT compilation") except: # don't care pass @@ -29,7 +31,7 @@ try: import numpy as N except: - print "Failed importing numpy module:",sys.exc_value + print("Failed importing numpy module:",sys.exc_value) class gain(pyext._class): @@ -40,10 +42,10 @@ def __init__(self): def _signal(self): # Multiply input vector by gain and copy to output - try: - self._outvec(0)[:] = self._invec(0)*self.gain - except: - pass + try: + self._outvec(0)[:] = self._invec(0)*self.gain + except: + pass class gain2(pyext._class): @@ -53,8 +55,8 @@ def __init__(self): self.gain = 0 def _dsp(self): - if not self._arraysupport(): - print "No DSP support" + if not pyext._arraysupport(): + print("No DSP support") return False # cache vectors in this scope diff --git a/scripts/simple.py b/scripts/simple.py index 1aa211c..9e57f10 100644 --- a/scripts/simple.py +++ b/scripts/simple.py @@ -13,202 +13,211 @@ - Inherit your class from pyext._class - Specfiy the number of inlets and outlets: - Use the class members (variables) _inlets and _outlets - If not given they default to 1 - You can also use class methods with the same names to return the respective number + Use the class members (variables) _inlets and _outlets + If not given they default to 1 + You can also use class methods with the same names to return the respective number - Constructors/Destructors - You can specify an __init__ constructor and/or an __del__ destructor. - The constructor will be called with the object's arguments + You can specify an __init__ constructor and/or an __del__ destructor. + The constructor will be called with the object's arguments - e.g. if your PD or MaxMSP object looks like - [pyext script class arg1 arg2 arg3] + e.g. if your PD or MaxMSP object looks like + [pyext script class arg1 arg2 arg3] - then the __init__(self,*args) function will be called with a tuple argument - args = (arg1,arg2,arg3) - With this syntax, you will have to give at least one argument. - By defining the constructor as __init__(self,*args) you can also initialize - the class without arguments. + then the __init__(self,*args) function will be called with a tuple argument + args = (arg1,arg2,arg3) + With this syntax, you will have to give at least one argument. + By defining the constructor as __init__(self,*args) you can also initialize + the class without arguments. - Methods called by pyext - The general format is 'tag_inlet(self,arg)' resp. 'tag_inlet(self,*args)': - tag is the PD or MaxMSP message header.. either bang, float, list etc. - inlet is the inlet (starting from 1) from which messages are received. - args is a tuple which corresponds to the content of the message. args can be omitted. + The general format is 'tag_inlet(self,arg)' resp. 'tag_inlet(self,*args)': + tag is the PD or MaxMSP message header.. either bang, float, list etc. + inlet is the inlet (starting from 1) from which messages are received. + args is a tuple which corresponds to the content of the message. args can be omitted. - The inlet index can be omitted. The method name then has the format 'tag_(self,inlet,args)'. - Here, the inlet index is a additional parameter to the method + The inlet index can be omitted. The method name then has the format 'tag_(self,inlet,args)'. + Here, the inlet index is a additional parameter to the method - You can also set up methods which react on any message. These have the special forms - _anything_inlet(self,*args) - or - _anything_(self,inlet,*args) + You can also set up methods which react on any message. These have the special forms + _anything_inlet(self,*args) + or + _anything_(self,inlet,*args) - Please see below for examples. + Please see below for examples. - Any return values are ignored - use _outlet (see below). + Any return values are ignored - use _outlet (see below). - Generally, you should avoid method_, method_xx forms for your non-pyext class methods. - Identifiers (variables and functions) with leading underscores are reserved for pyext. + Generally, you should avoid method_, method_xx forms for your non-pyext class methods. + Identifiers (variables and functions) with leading underscores are reserved for pyext. - Send messages to outlets: - Use the inherited _outlet method. - You can either use the form - self._outlet(outlet,arg1,arg2,arg3,arg4) ... where all args are atoms (no sequence types!) - or - self._outlet(outlet,arg) ... where arg is a sequence containing only atoms - + Use the inherited _outlet method. + You can either use the form + self._outlet(outlet,arg1,arg2,arg3,arg4) ... where all args are atoms (no sequence types!) + or + self._outlet(outlet,arg) ... where arg is a sequence containing only atoms + Do not use _outlet inside __init__, since the outlets have not been created at that time. - Use pyext functions and methods: - See the __doc__ strings of the pyext module and the pyext._class base class. + See the __doc__ strings of the pyext module and the pyext._class base class. """ +from __future__ import print_function + try: - import pyext + import pyext except: - print "ERROR: This script must be loaded by the PD/Max pyext external" + print("ERROR: This script must be loaded by the PD/Max pyext external") +try: + # Python 2 + _long = long + del _long +except NameError: + long = int + ################################################################# class ex1(pyext._class): - """Example of a simple class which receives messages and prints to the console""" - - # number of inlets and outlets - _inlets=3 - _outlets=0 + """Example of a simple class which receives messages and prints to the console""" + + # number of inlets and outlets + _inlets=3 + _outlets=0 + # methods for first inlet - # methods for first inlet + def bang_1(self): + print("Bang into first inlet") - def bang_1(self): - print "Bang into first inlet" + def int_1(self,f): + print("Integer",f,"into first inlet") - def int_1(self,f): - print "Integer",f,"into first inlet" + def float_1(self,f): + print("Float",f,"into first inlet") - def float_1(self,f): - print "Float",f,"into first inlet" + def list_1(self,*s): + print("List",s,"into first inlet") - def list_1(self,*s): - print "List",s,"into first inlet" + # methods for second inlet - # methods for second inlet + def hey_2(self): + print("Tag 'hey' into second inlet") - def hey_2(self): - print "Tag 'hey' into second inlet" + def ho_2(self): + print("Tag 'ho' into second inlet") - def ho_2(self): - print "Tag 'ho' into second inlet" + def lets_2(self): + print("Tag 'lets' into second inlet") - def lets_2(self): - print "Tag 'lets' into second inlet" + def go_2(self): + print("Tag 'go' into second inlet") - def go_2(self): - print "Tag 'go' into second inlet" + def _anything_2(self,*args): + print("Some other message into second inlet:",args) - def _anything_2(self,*args): - print "Some other message into second inlet:",args + # methods for third inlet - # methods for third inlet + def onearg_3(self,a): + print("Tag 'onearg' into third inlet:",a) - def onearg_3(self,a): - print "Tag 'onearg' into third inlet:",a + def twoargs_3(self,*a): + if len(a) == 2: + print("Tag 'twoargs' into third inlet:",a[0],a[1]) + else: + print("Tag 'twoargs': wrong number of arguments") - def twoargs_3(self,*a): - if len(a) == 2: - print "Tag 'twoargs' into third inlet:",a[0],a[1] - else: - print "Tag 'twoargs': wrong number of arguments" + def threeargs_3(self,*a): + if len(a) == 3: + print("Tag 'threeargs' into third inlet",a[0],a[1],a[2]) + else: + print("Tag 'threeargs': wrong number of arguments") - def threeargs_3(self,*a): - if len(a) == 3: - print "Tag 'threeargs' into third inlet",a[0],a[1],a[2] - else: - print "Tag 'threeargs': wrong number of arguments" + def varargs_3(self,*args): + # with *args there can be arguments or not - def varargs_3(self,*args): - # with *args there can be arguments or not - - print "Tag 'varargs' into third inlet",args + print("Tag 'varargs' into third inlet",args) ################################################################# class ex2(pyext._class): - """Example of a simple class which receives messages and writes to outlets""" + """Example of a simple class which receives messages and writes to outlets""" - # number of inlets and outlets - _inlets=3 - _outlets=2 + # number of inlets and outlets + _inlets=3 + _outlets=2 - # methods for all inlets + # methods for all inlets - def hello_(self,n): - print "Tag 'hello' into inlet",n + def hello_(self,n): + print("Tag 'hello' into inlet",n) - def _anything_(self,n,*args): - print "Message into inlet",n,":",args + def _anything_(self,n,*args): + print("Message into inlet",n,":",args) - # methods for first inlet + # methods for first inlet - def float_1(self,f): - self._outlet(2,f) + def float_1(self,f): + self._outlet(2,f) - # methods for second inlet + # methods for second inlet - def float_2(self,f): - self._outlet(1,f) + def float_2(self,f): + self._outlet(1,f) ################################################################# # helper function - determine whether argument is a numeric type def isNumber(value): - import types - if type(value) in (types.FloatType, types.IntType, types.LongType): - return 1 - else: - return 0 + import types + if type(value) in (float, int, long): + return 1 + else: + return 0 class ex3(pyext._class): - """Example of a simple class doing a typical number addition - - It uses a constructor and a class member as temporary storage. - """ + """Example of a simple class doing a typical number addition + + It uses a constructor and a class member as temporary storage. + """ + + # number of inlets and outlets + _inlets=2 + _outlets=1 - # number of inlets and outlets - _inlets=2 - _outlets=1 + # temporary storage + tmp=0 - # temporary storage - tmp=0 + # constructor + def __init__(self,*args): + if len(args) == 1: + if isNumber(args[0]): + self.tmp = args[0] + else: + print("ex3: __init__ has superfluous arguments") - # constructor - def __init__(self,*args): - if len(args) == 1: - if isNumber(args[0]): - self.tmp = args[0] - else: - print "ex3: __init__ has superfluous arguments" + # methods - # methods + def float_1(self,f): + self._outlet(1,self.tmp+f) - def float_1(self,f): - self._outlet(1,self.tmp+f) + def float_2(self,f): + self.tmp = f - def float_2(self,f): - self.tmp = f + # handlers for MaxMSP int type + def int_1(self,f): + self.float_1(f) - # handlers for MaxMSP int type - def int_1(self,f): - self.float_1(f) + def int_2(self,f): + self.float_2(f) - def int_2(self,f): - self.float_2(f) diff --git a/scripts/tcltk.py b/scripts/tcltk.py index 0813199..a02b7ef 100644 --- a/scripts/tcltk.py +++ b/scripts/tcltk.py @@ -7,10 +7,12 @@ """This is an example script for showing a nonsense tcl/tk application.""" +from __future__ import print_function + try: import pyext except: - print "ERROR: This script must be loaded by the PD/Max pyext external" + print("ERROR: This script must be loaded by the PD/Max pyext external") from Tkinter import * import random @@ -74,7 +76,7 @@ def __init__(self): self._detach(1) def bang_1(self): - self._priority(-3) + pyext._priority(-3) # display the tcl/tk dialog app = Application(self) app.mainloop() diff --git a/scripts/threads.py b/scripts/threads.py index b029910..769127f 100755 --- a/scripts/threads.py +++ b/scripts/threads.py @@ -16,13 +16,22 @@ """ +from __future__ import print_function + try: import pyext except: - print "ERROR: This script must be loaded by the PD/Max pyext external" + print("ERROR: This script must be loaded by the PD/Max pyext external") from time import sleep +try: + # Python 2 + range = xrange +except NameError: + # Python 3 + pass + ################################################################# class ex1(pyext._class): @@ -37,10 +46,10 @@ class ex1(pyext._class): # method for bang to any inlet def bang_(self,n): - for i in xrange(self.loops): + for i in range(self.loops): # if _shouldexit is true, the thread ought to stop if self._shouldexit: - print "BREAK" + print("BREAK") break self._outlet(n,i) diff --git a/source/bound.cpp b/source/bound.cpp index 419760e..4ba7700 100644 --- a/source/bound.cpp +++ b/source/bound.cpp @@ -81,12 +81,12 @@ bool pyext::boundmeth(flext_base *th,t_symbol *sym,int argc,t_atom *argv,void *d return true; } -PyObject *pyext::pyext_bind(PyObject *,PyObject *args) +PyObject *pyext::pyext_bind(PyObject *self, PyObject *args) { - PyObject *self,*meth,*name; - if(!PyArg_ParseTuple(args, "OOO:pyext_bind", &self,&name,&meth)) // borrowed references + PyObject *meth,*name; + if(!PyArg_ParseTuple(args, "OO:pyext_bind",&name,&meth)) // borrowed references post("py/pyext - Wrong arguments!"); - else if(!PyInstance_Check(self) || !PyCallable_Check(meth)) { + else if(!PyCallable_Check(meth)) { post("py/pyext - Wrong argument types!"); } else { @@ -126,12 +126,12 @@ PyObject *pyext::pyext_bind(PyObject *,PyObject *args) return Py_None; } -PyObject *pyext::pyext_unbind(PyObject *,PyObject *args) +PyObject *pyext::pyext_unbind(PyObject *self, PyObject *args) { - PyObject *self,*meth,*name; - if(!PyArg_ParseTuple(args, "OOO:pyext_bind", &self,&name,&meth)) // borrowed references + PyObject *meth,*name; + if(!PyArg_ParseTuple(args, "OO:pyext_bind",&name,&meth)) // borrowed references post("py/pyext - Wrong arguments!"); - else if(!PyInstance_Check(self) || !PyCallable_Check(meth)) { + else if(!PyCallable_Check(meth)) { post("py/pyext - Wrong argument types!"); } else { diff --git a/source/clmeth.cpp b/source/clmeth.cpp index 4427b54..77f1a73 100644 --- a/source/clmeth.cpp +++ b/source/clmeth.cpp @@ -1,7 +1,7 @@ /* py/pyext - python external object for PD and Max/MSP -Copyright (c)2002-2008 Thomas Grill (gr@grrrr.org) +Copyright (c)2002-2019 Thomas Grill (gr@grrrr.org) For information on usage and redistribution, and for a DISCLAIMER OF ALL WARRANTIES, see the file, "license.txt," in this distribution. */ @@ -29,17 +29,11 @@ PyMethodDef pyext::meth_tbl[] = #endif { "_invec", pyext::pyext_invec, METH_VARARGS,"Get input vector" }, { "_outvec", pyext::pyext_outvec, METH_VARARGS,"Get output vector" }, - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - -PyMethodDef pyext::attr_tbl[] = -{ { "__setattr__", pyext::pyext_setattr, METH_VARARGS,"Set class attribute" }, { "__getattr__", pyext::pyext_getattr, METH_VARARGS,"Get class attribute" }, - { NULL, NULL,0,NULL }, + {NULL, NULL, 0, NULL} /* Sentinel */ }; - const char *pyext::pyext_doc = "py/pyext - python external object for PD and Max/MSP, (C)2002-2008 Thomas Grill\n" "\n" @@ -58,45 +52,28 @@ const char *pyext::pyext_doc = #endif ; -/* -PyObject* pyext::pyext__init__(PyObject *,PyObject *args) -{ -// post("pyext.__init__ called"); - - Py_INCREF(Py_None); - return Py_None; -} - -PyObject* pyext::pyext__del__(PyObject *,PyObject *args) -{ -// post("pyext.__del__ called"); - - Py_INCREF(Py_None); - return Py_None; -} -*/ - -PyObject* pyext::pyext__str__(PyObject *,PyObject *args) -{ - PyObject *self; - if(!PyArg_ParseTuple(args, "O:pyext__str__",&self)) { - // handle error - ERRINTERNAL(); - return NULL; - } - - return PyString_FromFormat("",self); +PyObject* pyext::pyext__str__(PyObject *self, PyObject *args) +{ + return +#if PY_MAJOR_VERSION < 3 + PyString_FromFormat +#else + PyUnicode_FromFormat +#endif + ("", self); } -PyObject* pyext::pyext_setattr(PyObject *,PyObject *args) +PyObject* pyext::pyext_setattr(PyObject *self, PyObject *args) { - PyObject *self,*name,*val; - if(!PyArg_ParseTuple(args, "OOO:pyext_setattr", &self,&name,&val)) { + PyObject *name,*val; + if(!PyArg_ParseTuple(args, "OO:pyext_setattr",&name,&val)) + { // handle error ERRINTERNAL(); return NULL; } - + + bool handled = false; /* @@ -108,26 +85,37 @@ PyObject* pyext::pyext_setattr(PyObject *,PyObject *args) } */ if(!handled) { - if(PyInstance_Check(self)) - PyDict_SetItem(((PyInstanceObject *)self)->in_dict, name,val); - else + if(PyObject_GenericSetAttr(self, name, val) < 0) { ERRINTERNAL(); + return NULL; + } } Py_INCREF(Py_None); return Py_None; } -PyObject* pyext::pyext_getattr(PyObject *,PyObject *args) +PyObject* pyext::pyext_getattr(PyObject *self, PyObject *args) { - PyObject *self,*name,*ret = NULL; - if(!PyArg_ParseTuple(args, "OO:pyext_getattr", &self,&name)) { + PyObject *name, *ret = NULL; + if(!PyArg_ParseTuple(args, "O:pyext_getattr", &name)) + { // handle error ERRINTERNAL(); + return NULL; } - if(PyString_Check(name)) { - char* sname = PyString_AS_STRING(name); +#if PY_MAJOR_VERSION < 3 + if(PyString_Check(name)) +#else + if(PyUnicode_Check(name)) +#endif + { +#if PY_MAJOR_VERSION < 3 + const char *sname = PyString_AS_STRING(name); +#else + const char *sname = PyUnicode_AsUTF8(name); +#endif if(sname) { #ifdef FLEXT_THREADS if(!strcmp(sname,"_shouldexit")) { @@ -151,6 +139,16 @@ PyObject* pyext::pyext_getattr(PyObject *,PyObject *args) ret = Py_False; #endif } + else if(!strcmp(sname, "_canvas_dir")) { + pyext *ext = GetThis(self); + char dir[1024]; + ext->GetCanvasDir(dir, sizeof(dir)); +#if PY_MAJOR_VERSION < 3 + ret = PyString_InternFromString(dir); +#else + ret = PyUnicode_InternFromString(dir); +#endif + } } } @@ -167,7 +165,7 @@ PyObject* pyext::pyext_getattr(PyObject *,PyObject *args) } //! Send message to outlet -PyObject *pyext::pyext_outlet(PyObject *,PyObject *args) +PyObject *pyext::pyext_outlet(PyObject *self, PyObject *args) { bool ok = false; @@ -177,12 +175,16 @@ PyObject *pyext::pyext_outlet(PyObject *,PyObject *args) int sz = PyTuple_GET_SIZE(args); // borrowed references! - PyObject *self,*outl; + PyObject *outl; if( - sz >= 2 && - (self = PyTuple_GET_ITEM(args,0)) != NULL && PyInstance_Check(self) && - (outl = PyTuple_GET_ITEM(args,1)) != NULL && PyInt_Check(outl) + sz >= 1 && + (outl = PyTuple_GET_ITEM(args,0)) != NULL && +#if PY_MAJOR_VERSION < 3 + PyInt_Check(outl) +#else + PyLong_Check(outl) +#endif ) { pyext *ext = GetThis(self); if(!ext) { @@ -192,8 +194,8 @@ PyObject *pyext::pyext_outlet(PyObject *,PyObject *args) PyObject *val; #if 0 - if(sz == 3) { - val = PyTuple_GET_ITEM(args,2); // borrow reference + if(sz == 2) { + val = PyTuple_GET_ITEM(args,1); // borrow reference Py_INCREF(val); tp = PySequence_Check(val); } @@ -201,17 +203,22 @@ PyObject *pyext::pyext_outlet(PyObject *,PyObject *args) tp = false; if(!tp) - val = PySequence_GetSlice(args,2,sz); // new ref + val = PySequence_GetSlice(args,1,sz); // new ref #else - if(sz == 3) { - val = PyTuple_GET_ITEM(args,2); // borrow reference + if(sz == 2) { + val = PyTuple_GET_ITEM(args,1); // borrow reference Py_INCREF(val); } else - val = PyTuple_GetSlice(args,2,sz); // new ref + val = PyTuple_GetSlice(args,1,sz); // new ref #endif - int o = PyInt_AS_LONG(outl); + int o; +#if PY_MAJOR_VERSION < 3 + o = PyInt_AS_LONG(outl); +#else + o = PyLong_AS_LONG(outl); +#endif if(o >= 1 && o <= ext->Outlets()) { // offset outlet by signal outlets o += ext->sigoutlets; @@ -238,11 +245,12 @@ PyObject *pyext::pyext_outlet(PyObject *,PyObject *args) #ifdef FLEXT_THREADS //! Detach threads -PyObject *pyext::pyext_detach(PyObject *,PyObject *args) +PyObject *pyext::pyext_detach(PyObject *self, PyObject *args) { - PyObject *self; int val; - if(!PyArg_ParseTuple(args, "Oi:pyext_detach",&self,&val)) { + if( + !PyArg_ParseTuple(args, "i:pyext_detach",&val) + ) { // handle error PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _detach(self,[0/1/2])"); return NULL; @@ -266,11 +274,12 @@ PyObject *pyext::pyext_detach(PyObject *,PyObject *args) } //! Stop running threads -PyObject *pyext::pyext_stop(PyObject *,PyObject *args) +PyObject *pyext::pyext_stop(PyObject *self, PyObject *args) { - PyObject *self; int val = -1; - if(!PyArg_ParseTuple(args, "O|i:pyext_stop",&self,&val)) { + if( + !PyArg_ParseTuple(args, "|i:pyext_stop",&val) + ) { // handle error PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _stop(self,{wait time})"); return NULL; @@ -302,72 +311,67 @@ PyObject *pyext::pyext_stop(PyObject *,PyObject *args) #if FLEXT_SYS == FLEXT_SYS_PD //! Send message to canvas -PyObject *pyext::pyext_tocanvas(PyObject *,PyObject *args) +PyObject *pyext::pyext_tocanvas(PyObject *self, PyObject *args) { FLEXT_ASSERT(PyTuple_Check(args)); int sz = PyTuple_GET_SIZE(args); bool ok = false; - PyObject *self; // borrowed ref - if( - sz >= 1 && - (self = PyTuple_GET_ITEM(args,0)) != NULL && PyInstance_Check(self) - ) { - pyext *ext = GetThis(self); - if(!ext) { - PyErr_SetString(PyExc_RuntimeError,"pyext - _tocanvas: instance not associated with pd object"); - return NULL; - } - - PyObject *val; - - bool tp = - sz == 2 && - PySequence_Check( - val = PyTuple_GET_ITEM(args,1) // borrowed ref - ); - - if(!tp) - val = PyTuple_GetSlice(args,1,sz); // new ref + pyext *ext = GetThis(self); + if(!ext) { + PyErr_SetString(PyExc_RuntimeError,"pyext - _tocanvas: instance not associated with pd object"); + return NULL; + } - flext::AtomListStatic<16> lst; - const t_symbol *sym = GetPyArgs(lst,val); - if(sym) { - t_glist *gl = ext->thisCanvas(); - if(gl) { - // \TODO find a flext-based non-locking method - sys_lock(); - pd_forwardmess((t_class **)gl,lst.Count(),lst.Atoms()); - sys_unlock(); - } + PyObject *val; + + bool tp = + sz == 2 && + PySequence_Check( + val = PyTuple_GET_ITEM(args,0) // borrowed ref + ); + + if(!tp) + val = PyTuple_GetSlice(args,0,sz); // new ref + + flext::AtomListStatic<16> lst; + const t_symbol *sym = GetPyArgs(lst,val); + if(sym) { + t_glist *gl = ext->thisCanvas(); + if(gl) { + // \TODO find a flext-based non-locking method + sys_lock(); + pd_forwardmess((t_class **)gl,lst.Count(),lst.Atoms()); + sys_unlock(); + } #ifdef FLEXT_DEBUG - else - post("pyext - no parent canvas?!"); + else + post("pyext - no parent canvas?!"); #endif - ok = true; - } - else - post("py/pyext - No data to send"); - - if(!tp) Py_DECREF(val); + ok = true; } + else + post("py/pyext - No data to send"); - if(!ok) { - PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _tocanvas(self,args...)"); - return NULL; - } + if(!tp) Py_DECREF(val); - Py_INCREF(Py_None); - return Py_None; +if(!ok) { + PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _tocanvas(self,args...)"); + return NULL; + } + +Py_INCREF(Py_None); +return Py_None; } #endif -PyObject *pyext::pyext_invec(PyObject *,PyObject *args) +PyObject *pyext::pyext_invec(PyObject *self, PyObject *args) { - PyObject *self; int val = -1; - if(!PyArg_ParseTuple(args, "O|i:pyext_invec",&self,&val)) { + if( + !PyArg_ParseTuple(args, "|i:pyext_invec",&val) + ) { // handle error PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _invec(self,inlet)"); return NULL; @@ -392,11 +396,12 @@ PyObject *pyext::pyext_invec(PyObject *,PyObject *args) return Py_None; } -PyObject *pyext::pyext_outvec(PyObject *,PyObject *args) +PyObject *pyext::pyext_outvec(PyObject *self, PyObject *args) { - PyObject *self; int val = -1; - if(!PyArg_ParseTuple(args, "O|i:pyext_outvec",&self,&val)) { + if( + !PyArg_ParseTuple(args, "|i:pyext_outvec",&val) + ) { // handle error PyErr_SetString(PyExc_SyntaxError,"pyext - Syntax: _outvec(self,inlet)"); return NULL; diff --git a/source/modmeth.cpp b/source/modmeth.cpp index 2a35b07..2325f10 100644 --- a/source/modmeth.cpp +++ b/source/modmeth.cpp @@ -52,6 +52,20 @@ const char *pybase::py_doc = "_tuple(args...): Make a tuple from args\n" ; +#if PY_MAJOR_VERSION >= 3 +PyModuleDef pybase::pyext_module_def = { + PyModuleDef_HEAD_INIT, // PyModuleDef_Base m_base + PYEXT_MODULE, // const char *m_name + py_doc, // const char *m_doc + -1, // Py_ssize_t m_size + func_tbl, // PyMethodDef *m_methods + NULL, // PyModuleDef_Slot *m_slots + NULL, // traverseproc m_traverse + NULL, // inquiry m_clear + NULL // freefunc m_free +}; +#endif + #ifdef FLEXT_THREADS void pybase::tick(void *) { diff --git a/source/pyargs.cpp b/source/pyargs.cpp index 837c899..0dd3c15 100644 --- a/source/pyargs.cpp +++ b/source/pyargs.cpp @@ -1,7 +1,7 @@ /* py/pyext - python external object for PD and MaxMSP -Copyright (c)2002-2015 Thomas Grill (gr@grrrr.org) +Copyright (c)2002-2019 Thomas Grill (gr@grrrr.org) For information on usage and redistribution, and for a DISCLAIMER OF ALL WARRANTIES, see the file, "license.txt," in this distribution. */ @@ -20,7 +20,13 @@ static PyObject *MakePyAtom(const t_atom &at) // if a number can be an integer... let it be an integer! int ival = flext::GetAInt(at); double fval = flext::GetAFloat(at); - return (double)ival == fval?PyInt_FromLong(ival):PyFloat_FromDouble(fval); + return (double)ival == fval? +#if PY_MAJOR_VERSION < 3 + PyInt_FromLong(ival) +#else + PyLong_FromLong(ival) +#endif + :PyFloat_FromDouble(fval); } #else else if(flext::IsFloat(at)) @@ -69,7 +75,13 @@ PyObject *pybase::MakePyArgs(const t_symbol *s,int argc,const t_atom *argv,int i int pix = 0; if(inlet >= 0) - PyTuple_SET_ITEM(ret,pix++,PyInt_FromLong(inlet)); + PyTuple_SET_ITEM(ret, pix++, +#if PY_MAJOR_VERSION < 3 + PyInt_FromLong(inlet) +#else + PyLong_FromLong(inlet) +#endif + ); if(any) PyTuple_SET_ITEM(ret,pix++,pySymbol_FromSymbol(s)); @@ -126,7 +138,12 @@ PyObject *pybase::MakePyArg(const t_symbol *s,int argc,const t_atom *argv) inline bool issym(PyObject *p) { - return PyString_Check(p) || pySymbol_Check(p); +return + PyUnicode_Check(p) +#if PY_MAJOR_VERSION < 3 + || PyString_Check(p) +#endif + || pySymbol_Check(p); } inline bool isseq(PyObject *p) @@ -136,16 +153,28 @@ inline bool isseq(PyObject *p) const t_symbol *pybase::getone(t_atom &at,PyObject *arg) { +#if PY_MAJOR_VERSION < 3 if(PyInt_Check(arg)) { flext::SetInt(at,PyInt_AsLong(arg)); return sym_fint; } - else if(PyLong_Check(arg)) { flext::SetInt(at,PyLong_AsLong(arg)); return sym_fint; } + else +#endif + if(PyLong_Check(arg)) { flext::SetInt(at,PyLong_AsLong(arg)); return sym_fint; } else if(PyFloat_Check(arg)) { flext::SetFloat(at,(float)PyFloat_AsDouble(arg)); return flext::sym_float; } else if(pySymbol_Check(arg)) { flext::SetSymbol(at,pySymbol_AS_SYMBOL(arg)); return flext::sym_symbol; } +#if PY_MAJOR_VERSION < 3 else if(PyString_Check(arg)) { flext::SetString(at,PyString_AS_STRING(arg)); return flext::sym_symbol; } +#else + else if(PyUnicode_Check(arg)) { flext::SetString(at,PyUnicode_AsUTF8(arg)); return flext::sym_symbol; } +#endif else { PyObject *tp = PyObject_Type(arg); PyObject *stp = tp?PyObject_Str(tp):NULL; const char *tmp = ""; - if(stp) tmp = PyString_AS_STRING(stp); + if(stp) +#if PY_MAJOR_VERSION < 3 + tmp = PyString_AS_STRING(stp); +#else + tmp = PyUnicode_AsUTF8(stp); +#endif flext::post("py/pyext: Could not convert argument %s",tmp); Py_XDECREF(stp); Py_XDECREF(tp); diff --git a/source/pybase.cpp b/source/pybase.cpp index 7311a5a..340c920 100644 --- a/source/pybase.cpp +++ b/source/pybase.cpp @@ -1,7 +1,7 @@ /* py/pyext - python external object for PD and MaxMSP -Copyright (c)2002-2015 Thomas Grill (gr@grrrr.org) +Copyright (c)2002-2019 Thomas Grill (gr@grrrr.org) For information on usage and redistribution, and for a DISCLAIMER OF ALL WARRANTIES, see the file, "license.txt," in this distribution. */ @@ -15,9 +15,15 @@ WARRANTIES, see the file, "license.txt," in this distribution. #include #endif +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define WSTRINGIFY(x) L ## #x +#define TOWSTRING(x) WSTRINGIFY(x) + static PyMethodDef StdOut_Methods[] = { { "write", pybase::StdOut_Write, 1 }, + { "flush", pybase::StdOut_Flush, 1 }, { NULL, NULL, } }; @@ -98,12 +104,65 @@ void initsymbol(); void initsamplebuffer(); void initbundle(); +MOD_INIT(pyext) +{ + PyObject *init_ret = pybase::pyext_init(); + return MOD_SUCCESS_VAL(init_ret); +} + +PyObject *pybase::pyext_init() +{ + if(module_obj == NULL) { + PyObject *m; + + MOD_DEF(m, PYEXT_MODULE, py_doc, func_tbl); + + PyModule_AddStringConstant(m,"__doc__",(char *)py_doc); + + // add symbol type + initsymbol(); + PyModule_AddObject(m,"Symbol",(PyObject *)&pySymbol_Type); + + // pre-defined symbols + PyModule_AddObject(m,"_s_",(PyObject *)pySymbol__); + PyModule_AddObject(m,"_s_bang",(PyObject *)pySymbol_bang); + PyModule_AddObject(m,"_s_list",(PyObject *)pySymbol_list); + PyModule_AddObject(m,"_s_symbol",(PyObject *)pySymbol_symbol); + PyModule_AddObject(m,"_s_float",(PyObject *)pySymbol_float); + PyModule_AddObject(m,"_s_int",(PyObject *)pySymbol_int); + + // add samplebuffer type + initsamplebuffer(); + PyModule_AddObject(m,"Buffer",(PyObject *)&pySamplebuffer_Type); + + // add message bundle type + initbundle(); + PyModule_AddObject(m,"Bundle",(PyObject *)&pyBundle_Type); + + module_obj = m; + module_dict = PyModule_GetDict(m); // borrowed reference + } + + return module_obj; +} + void pybase::lib_setup() -{ +{ +#ifdef PY_INTERPRETER + { +#if PY_MAJOR_VERSION < 3 + static char py_program_name[] = TOSTRING(PY_INTERPRETER); +#else + static wchar_t py_program_name[] = TOWSTRING(PY_INTERPRETER); +#endif + Py_SetProgramName(py_program_name); + } +#endif + post(""); post("------------------------------------------------"); post("py/pyext %s - python script objects",PY__VERSION); - post("(C)2002-2015 Thomas Grill - http://grrrr.org/ext"); + post("(C)2002-2019 Thomas Grill - http://grrrr.org/ext"); post(""); post("using Python %s",Py_GetVersion()); @@ -124,6 +183,8 @@ void pybase::lib_setup() // ------------------------------------------------------------- + PyImport_AppendInittab(PYEXT_MODULE, MOD_INIT_NAME(pyext)); + Py_Initialize(); #ifdef FLEXT_DEBUG @@ -150,22 +211,30 @@ void pybase::lib_setup() #endif // sys.argv must be set to empty tuple +#if PY_MAJOR_VERSION < 3 const char *nothing = ""; PySys_SetArgv(0,const_cast(¬hing)); +#else + const wchar_t *nothing = L""; + PySys_SetArgv(0,const_cast(¬hing)); +#endif - // register/initialize pyext module only once! - module_obj = Py_InitModule(const_cast(PYEXT_MODULE), func_tbl); - module_dict = PyModule_GetDict(module_obj); // borrowed reference - - PyModule_AddStringConstant(module_obj,"__doc__",(char *)py_doc); - + // import the pyext module to ensure init + PyImport_ImportModule(PYEXT_MODULE); + // redirect stdout PyObject* py_out; - py_out = Py_InitModule(const_cast("stdout"), StdOut_Methods); + + { + MOD_DEF(py_out, "stdout", "pyext standard output", StdOut_Methods); + } PySys_SetObject(const_cast("stdout"), py_out); - py_out = Py_InitModule(const_cast("stderr"), StdOut_Methods); + + { + MOD_DEF(py_out, "stderr", "pyext standard error", StdOut_Methods); + } PySys_SetObject(const_cast("stderr"), py_out); - + // get garbage collector function PyObject *gcobj = PyImport_ImportModule("gc"); if(gcobj) { @@ -173,29 +242,13 @@ void pybase::lib_setup() Py_DECREF(gcobj); } +#if PY_MAJOR_VERSION < 3 builtins_obj = PyImport_ImportModule("__builtin__"); +#else + builtins_obj = PyImport_ImportModule("builtins"); +#endif builtins_dict = PyModule_GetDict(builtins_obj); // borrowed reference - // add symbol type - initsymbol(); - PyModule_AddObject(module_obj,"Symbol",(PyObject *)&pySymbol_Type); - - // pre-defined symbols - PyModule_AddObject(module_obj,"_s_",(PyObject *)pySymbol__); - PyModule_AddObject(module_obj,"_s_bang",(PyObject *)pySymbol_bang); - PyModule_AddObject(module_obj,"_s_list",(PyObject *)pySymbol_list); - PyModule_AddObject(module_obj,"_s_symbol",(PyObject *)pySymbol_symbol); - PyModule_AddObject(module_obj,"_s_float",(PyObject *)pySymbol_float); - PyModule_AddObject(module_obj,"_s_int",(PyObject *)pySymbol_int); - - // add samplebuffer type - initsamplebuffer(); - PyModule_AddObject(module_obj,"Buffer",(PyObject *)&pySamplebuffer_Type); - - // add message bundle type - initbundle(); - PyModule_AddObject(module_obj,"Bundle",(PyObject *)&pyBundle_Type); - // ------------------------------------------------------------- #if FLEXT_SYS == FLEXT_SYS_PD && defined(PD_DEVEL_VERSION) && defined(PY_USE_INOFFICIAL) // add PD paths @@ -238,6 +291,7 @@ FLEXT_LIB_SETUP(py,pybase::lib_setup) pybase::pybase() : module(NULL) , dict(NULL) + , respond(false) #ifdef FLEXT_THREADS , thrcount(0) , shouldexit(false),stoptick(0) @@ -310,10 +364,21 @@ void pybase::m__doc(PyObject *obj) ThrLock lock; PyObject *docf = PyDict_GetItemString(obj,"__doc__"); // borrowed!!! - if(docf && PyString_Check(docf)) { + if(docf && +#if PY_MAJOR_VERSION < 3 + PyString_Check(docf) +#else + PyUnicode_Check(docf) +#endif + ) { post(""); - const char *s = PyString_AS_STRING(docf); + const char *s; +#if PY_MAJOR_VERSION < 3 + s = PyString_AS_STRING(docf); +#else + s = PyUnicode_AsUTF8(docf); +#endif // FIX: Python doc strings can easily be larger than 1k characters // -> split into separate lines @@ -421,13 +486,32 @@ void pybase::SetArgs() // script arguments int argc = args.Count(); const t_atom *argv = args.Atoms(); +#if PY_MAJOR_VERSION < 3 char **sargv = new char *[argc+1]; +#else + wchar_t **sargv = new wchar_t *[argc+1]; +#endif for(int i = 0; i <= argc; ++i) { +#if PY_MAJOR_VERSION < 3 sargv[i] = new char[256]; - if(!i) +#else + sargv[i] = new wchar_t[256]; +#endif + if(!i) { +#if PY_MAJOR_VERSION < 3 strcpy(sargv[i],"py/pyext"); - else +#else + wcscpy(sargv[i],L"py/pyext"); +#endif + } else { +#if PY_MAJOR_VERSION < 3 GetAString(argv[i-1],sargv[i],255); +#else + char *arg = new char[256]; + GetAString(argv[i-1],arg,255); + mbstowcs(sargv[i],arg,255); +#endif + } } // the arguments to the module are only recognized once! (at first use in a patcher) @@ -650,7 +734,12 @@ void pybase::AddToPath(const char *dir) if(dir && *dir) { PyObject *pobj = PySys_GetObject(const_cast("path")); if(pobj && PyList_Check(pobj)) { - PyObject *ps = PyString_FromString(dir); + PyObject *ps; +#if PY_MAJOR_VERSION < 3 + ps = PyString_FromString(dir); +#else + ps = PyUnicode_FromString(dir); +#endif if(!PySequence_Contains(pobj,ps)) PyList_Append(pobj,ps); // makes new reference Py_DECREF(ps); @@ -745,30 +834,67 @@ PyObject* pybase::StdOut_Write(PyObject* self, PyObject* args) for(int i = 0; i < sz; ++i) { PyObject *val = PyTuple_GET_ITEM(args,i); // borrowed reference PyObject *str = PyObject_Str(val); // new reference - char *cstr = PyString_AS_STRING(str); - char *lf = strchr(cstr,'\n'); + const char *cstr; +#if PY_MAJOR_VERSION < 3 + cstr = PyString_AS_STRING(str); +#else + cstr = PyUnicode_AsUTF8(str); +#endif + const char *lf = strchr(cstr, '\n'); // line feed in string if(!lf) { // no -> just append - if(output) - PyString_ConcatAndDel(&output,str); // str is decrefd + if(output) { +#if PY_MAJOR_VERSION < 3 + PyString_ConcatAndDel(&output, str); // str is decrefd +#else + PyObject *newobj = PyUnicode_Concat(output, str); + Py_DECREF(output); + Py_DECREF(str); + output = newobj; +#endif + } else output = str; // take str reference } else { // yes -> append up to line feed, reset output buffer to string remainder - PyObject *part = PyString_FromStringAndSize(cstr,lf-cstr); // new reference - if(output) - PyString_ConcatAndDel(&output,part); // str is decrefd + PyObject *part = +#if PY_MAJOR_VERSION < 3 + PyString_FromStringAndSize +#else + PyUnicode_FromStringAndSize +#endif + (cstr, lf-cstr); // new reference + if(output) { +#if PY_MAJOR_VERSION < 3 + PyString_ConcatAndDel(&output, part); // part is decrefd +#else + PyObject *newobj = PyUnicode_Concat(output, part); + Py_DECREF(output); + Py_DECREF(part); + output = newobj; +#endif + } else output = part; // take str reference // output concatenated string +#if PY_MAJOR_VERSION < 3 post(PyString_AS_STRING(output)); +#else + post(PyUnicode_AsUTF8(output)); +#endif Py_DECREF(output); - output = PyString_FromString(lf+1); // new reference + output = +#if PY_MAJOR_VERSION < 3 + PyString_FromString +#else + PyUnicode_FromString +#endif + (lf+1); // new reference } } @@ -776,6 +902,12 @@ PyObject* pybase::StdOut_Write(PyObject* self, PyObject* args) return Py_None; } +// dummy flush method, since some logging libraries call this +PyObject* pybase::StdOut_Flush(PyObject* self, PyObject* args) +{ + Py_INCREF(Py_None); + return Py_None; +} class work_data { @@ -928,7 +1060,11 @@ bool pybase::collect() PyObject *ret = PyObject_CallObject(gcollect,NULL); if(ret) { #ifdef FLEXT_DEBUG +#if PY_MAJOR_VERSION < 3 int refs = PyInt_AsLong(ret); +#else + int refs = PyLong_AsLong(ret); +#endif if(refs) post("py/pyext - Garbage collector reports %i unreachable objects",refs); #endif Py_DECREF(ret); diff --git a/source/pybase.h b/source/pybase.h index 8a3105e..bb12b07 100644 --- a/source/pybase.h +++ b/source/pybase.h @@ -24,6 +24,26 @@ WARRANTIES, see the file, "license.txt," in this distribution. typedef int ThrState; // dummy #endif +#if PY_MAJOR_VERSION < 3 +#define MOD_ERROR_VAL +#define MOD_SUCCESS_VAL(val) +#define MOD_INIT_NAME(name) init##name +#define MOD_INIT(name) void MOD_INIT_NAME(name)(void) +#define MOD_DEF(ob, name, doc, methods) \ + ob = Py_InitModule3(name, methods, doc); +#else +#define MOD_ERROR_VAL NULL +#define MOD_SUCCESS_VAL(val) val +#define MOD_INIT_NAME(name) PyInit_##name +#define MOD_INIT(name) PyMODINIT_FUNC MOD_INIT_NAME(name)(void) +#define MOD_DEF(ob, name, doc, methods) \ + static struct PyModuleDef moduledef = { \ + PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \ + ob = PyModule_Create(&moduledef); +#endif + +MOD_INIT(pyext); + class pybase : public flext { @@ -38,6 +58,7 @@ class pybase static const t_symbol *GetPyArgs(AtomList &lst,PyObject *pValue,int offs = 0); static const t_symbol *GetPyAtom(AtomList &lst,PyObject *pValue); + static PyObject *pyext_init(); static void lib_setup(); protected: @@ -110,7 +131,10 @@ class pybase static PyObject *module_obj,*module_dict; static PyObject *builtins_obj,*builtins_dict; - static PyMethodDef func_tbl[],attr_tbl[]; + static PyMethodDef func_tbl[]; +#if PY_MAJOR_VERSION >= 3 + static PyModuleDef pyext_module_def; +#endif static PyObject *py__doc__(PyObject *,PyObject *args); static PyObject *py_send(PyObject *,PyObject *args); @@ -269,6 +293,7 @@ class pybase }; static PyObject* StdOut_Write(PyObject* Self, PyObject* Args); + static PyObject* StdOut_Flush(PyObject* Self, PyObject* Args); }; #endif diff --git a/source/pybuffer.cpp b/source/pybuffer.cpp index 4690381..9238d02 100644 --- a/source/pybuffer.cpp +++ b/source/pybuffer.cpp @@ -1,7 +1,7 @@ /* py/pyext - python script object for PD and Max/MSP -Copyright (c)2002-2015 Thomas Grill (gr@grrrr.org) +Copyright (c)2002-2019 Thomas Grill (gr@grrrr.org) For information on usage and redistribution, and for a DISCLAIMER OF ALL WARRANTIES, see the file, "license.txt," in this distribution. */ @@ -18,7 +18,7 @@ WARRANTIES, see the file, "license.txt," in this distribution. #ifdef PY_ARRAYS #ifdef PY_NUMARRAY -# if FLEXT_OS == FLEXT_OS_MAC +# ifdef PY_USE_FRAMEWORK # include # else # include @@ -30,8 +30,13 @@ inline bool arrsupport() { return numtype != tAny; } #else # if defined(PY_NUMPY) # include +# if _FLEXT_NEED_SAMPLE_CONV +# define PY_NUMPY_BUFFER_FORMAT "f" +# else +# define PY_NUMPY_BUFFER_FORMAT "d" +# endif # else -# if FLEXT_OS == FLEXT_OS_MAC +# ifdef PY_USE_FRAMEWORK # include # else # include @@ -43,6 +48,7 @@ inline bool arrsupport() { return numtype != tAny; } #endif #endif +#include "pycompat.h" PyObject *pybase::py_arraysupport(PyObject *self,PyObject *args) { @@ -57,14 +63,12 @@ PyObject *pybase::py_arraysupport(PyObject *self,PyObject *args) } -// PD defines a T_OBJECT symbol +// undefine PD's T_OBJECT to avoid conflict with Python's #undef T_OBJECT -#if FLEXT_OS == FLEXT_OS_MAC -#include "Python/bufferobject.h" +#ifdef PY_USE_FRAMEWORK #include "Python/structmember.h" #else -#include "bufferobject.h" #include "structmember.h" #endif @@ -105,8 +109,13 @@ static int buffer_init(PyObject *obj, PyObject *args, PyObject *kwds) if(pySymbol_Check(arg)) self->sym = pySymbol_AS_SYMBOL(arg); +#if PY_MAJOR_VERSION < 3 else if(PyString_Check(arg)) self->sym = flext::MakeSymbol(PyString_AS_STRING(arg)); +#else + else if(PyUnicode_Check(arg)) + self->sym = flext::MakeSymbol(PyUnicode_AsUTF8(arg)); +#endif else ret = -1; Py_DECREF(arg); @@ -125,7 +134,13 @@ static int buffer_init(PyObject *obj, PyObject *args, PyObject *kwds) static PyObject *buffer_repr(PyObject *self) { FLEXT_ASSERT(pySamplebuffer_Check(self)); - return (PyObject *)PyString_FromFormat("",pySamplebuffer_AS_STRING(self)); + return (PyObject *) +#if PY_MAJOR_VERSION < 3 + PyString_FromFormat +#else + PyUnicode_FromFormat +#endif + ("", pySamplebuffer_AS_STRING(self)); } static long buffer_hash(PyObject *self) @@ -158,7 +173,8 @@ static PyObject *buffer_dirty(PyObject *obj) static PyObject *buffer_resize(PyObject *obj,PyObject *args,PyObject *kwds) { - flext::buffer *b = ((pySamplebuffer *)obj)->buf; + pySamplebuffer *self = reinterpret_cast(obj); + flext::buffer *b = self->buf; if(b) { int frames,keep = 1,zero = 1; static char const *kwlist[] = {"frames", "keep", "zero", NULL}; @@ -182,8 +198,6 @@ static PyMethodDef buffer_methods[] = { {NULL} /* Sentinel */ }; - - // support the buffer protocol static Py_ssize_t buffer_readbuffer(PyObject *obj, Py_ssize_t segment, void **ptrptr) @@ -218,11 +232,50 @@ static Py_ssize_t buffer_charbuffer(PyObject *obj, Py_ssize_t segment, return b->Channels()*b->Frames()*sizeof(t_sample); } +static int buffer_getbuffer(PyObject *obj, Py_buffer *view, int flags) { + pySamplebuffer *self = reinterpret_cast(obj); + flext::buffer *b = self->buf; + const Py_ssize_t len = b->Channels()*b->Frames()*sizeof(t_sample); + + if(!(flags & PyBUF_STRIDES)) { + view->obj = NULL; + PyErr_SetString(PyExc_BufferError, "PyBUF_STRIDES is required"); + return -1; + } + + if(!(flags & PyBUF_FORMAT)) { + view->obj = NULL; + PyErr_SetString(PyExc_BufferError, "PyBUF_FORMAT is required"); + return -1; + } + + std::pair *shape_strides = new std::pair(); + shape_strides->first = b->Channels() * b->Frames(); + shape_strides->second = sizeof(FLEXT_ARRAYTYPE); + + view->buf = (void *) b->Data(); + view->obj = obj; + view->len = len; + view->readonly = false; + view->itemsize = sizeof(t_sample); + view->format = (flags & PyBUF_FORMAT) ? (char *) PY_NUMPY_BUFFER_FORMAT : NULL; + view->ndim = 1; + view->shape = &shape_strides->first; + view->strides = &shape_strides->second; + view->suboffsets = NULL; + view->internal = (void *) shape_strides; + + Py_INCREF(self); + return 0; +} + +static void buffer_releasebuffer(PyObject *obj, Py_buffer *view) { + delete (std::pair *) view->internal; +} + static PyBufferProcs buffer_as_buffer = { - buffer_readbuffer, - buffer_writebuffer, - buffer_segcount, - buffer_charbuffer + .bf_getbuffer = buffer_getbuffer, + .bf_releasebuffer = buffer_releasebuffer }; static Py_ssize_t buffer_length(PyObject *s) @@ -236,6 +289,9 @@ static PyObject *buffer_item(PyObject *s,Py_ssize_t i) pySamplebuffer *self = reinterpret_cast(s); PyObject *ret; if(self->buf) { + if(i < 0) { + i += self->buf->Frames(); + } if (i < 0 || i >= self->buf->Frames()) { PyErr_SetString(PyExc_IndexError,"Index out of range"); ret = NULL; @@ -269,16 +325,32 @@ PyObject *arrayfrombuffer(PyObject *buf,int c,int n) #ifdef PY_NUMARRAY arr = (PyObject *)NA_NewAllFromBuffer(c == 1?1:2,shape,numtype,buf,0,0,NA_ByteOrder(),1,1); #else - void *data; - Py_ssize_t len; - int err = PyObject_AsWriteBuffer(buf,&data,&len); + Py_buffer view; + int err = PyObject_GetBuffer(buf, &view, PyBUF_WRITABLE | PyBUF_FORMAT | PyBUF_STRIDES); if(!err) { - FLEXT_ASSERT(len <= n*c*sizeof(t_sample)); + FLEXT_ASSERT(view.len <= n*c*sizeof(t_sample)); // Py_INCREF(buf); // ATTENTION... this won't be released any more!! # ifdef PY_NUMPY - arr = PyArray_NewFromDescr(&PyArray_Type,PyArray_DescrNewFromType(numtype),c == 1?1:2,shape,0,(char *)data,NPY_WRITEABLE|NPY_C_CONTIGUOUS,NULL); + // the results of PyObject_GetBuffer() differ a bit depending on + // whether we're dealing with a pySamplebuffer (Pd array) or a numpy + // array (Pd signal). + // + // for pySamplebuffer, we get stride information that must be used + // to correctly deal with float32 Pd. + // + // for numpy arrays, we get strides=1 (due to format="B"), which + // breaks PyArray_NewFromDescr(), so instead we pass strides=NULL. + bool use_strides = pySamplebuffer_Check(buf); + npy_intp strides_arr[2] = {0, 0}; + npy_intp *strides = NULL; + if(use_strides) { + strides_arr[0] = *view.strides; + strides = strides_arr; + } + arr = PyArray_NewFromDescr(&PyArray_Type, PyArray_DescrNewFromType(numtype), + c == 1 ? 1 : 2, shape, strides, (char *) view.buf, NPY_ARRAY_WRITEABLE | NPY_ARRAY_C_CONTIGUOUS, NULL); # else - arr = PyArray_FromDimsAndData(c == 1?1:2,shape,numtype,(char *)data); + arr = PyArray_FromDimsAndData(c == 1?1:2,shape,numtype,(char *)view.buf); # endif } else { @@ -450,15 +522,140 @@ static PyObject *buffer_repeat(PyObject *s,Py_ssize_t rep) return NULL; } +static PyObject *buffer_subscript(PyObject *s, PyObject *item) +{ + pySamplebuffer *self = reinterpret_cast(s); + PyObject *ret; + +#ifdef PY_ARRAYS + if(arrsupport()) { + if(self->buf) { + const int n = self->buf->Frames(); + const int c = self->buf->Channels(); + + PyObject *nobj = arrayfrombuffer((PyObject *) self, c, n); + ret = PyObject_GetItem(nobj, item); + Py_DECREF(nobj); + } else { + Py_INCREF(Py_None); + ret = Py_None; + } + } + else +#endif + if(PyIndex_Check(item)) { + Py_ssize_t i; + i = PyNumber_AsSsize_t(item, PyExc_IndexError); + if(i == -1 && PyErr_Occurred()) { + ret = NULL; + } else { + if(i < 0) + i += PyList_GET_SIZE(self); + + ret = buffer_item(s, i); + } + } else if(PySlice_Check(item)) { + PyErr_SetString(PyExc_RuntimeError, "No array support"); + ret = NULL; + } else { + PyErr_Format( + PyExc_TypeError, + "buffer indices must be integers or slices, not %.200s", + item->ob_type->tp_name + ); + ret = NULL; + } + + return ret; +} + +static int buffer_ass_subscript(PyObject *s, PyObject *item, PyObject *value) +{ + pySamplebuffer *self = reinterpret_cast(s); + int ret; + + if(PyIndex_Check(item)) { + Py_ssize_t i; + i = PyNumber_AsSsize_t(item, PyExc_IndexError); + if(i == -1 && PyErr_Occurred()) { + ret = -1; + } else { + ret = buffer_ass_item(s, i, value); + } + } else if(PySlice_Check(item)) { +#ifdef PY_ARRAYS + if(arrsupport()) { + if(self->buf) { + const int n = self->buf->Frames(); + const int c = self->buf->Channels(); + + PyArrayObject *out = (PyArrayObject *) PyArray_ContiguousFromObject(value, numtype, 1, 2); + + if(!out) { + // exception already set + ret = -1; + } else if(out->nd != 1) { + PyErr_SetString(PyExc_NotImplementedError, "Multiple dimensions not supported yet"); + ret = -1; + } else { + const t_sample *src = (t_sample *) out->data; + + Py_ssize_t ilow, ihigh, istep; + + if(PySlice_Unpack(item, &ilow, &ihigh, &istep) < 0) { + ret = -1; + } else { + Py_ssize_t dlen = PySlice_AdjustIndices(n, &ilow, &ihigh, istep); + int slen = out->dimensions[0]; + int cnt = slen < dlen ? slen : dlen; + flext::buffer::Element *dst = self->buf->Data() + ilow; + for(int i = 0; i < cnt; i += istep) { + dst[i] = src[i]; + } + + self->dirty = true; + ret = 0; + } + } + + Py_XDECREF(out); + } else { + PyErr_SetString(PyExc_ValueError,"Buffer is not assigned"); + ret = -1; + } + } + else +#endif + { + PyErr_SetString(PyExc_RuntimeError, "No array support"); + ret = -1; + } + } else { + PyErr_Format( + PyExc_TypeError, + "buffer indices must be integers or slices, not %.200s", + item->ob_type->tp_name + ); + ret = -1; + } + + return ret; +} static PySequenceMethods buffer_as_seq = { - buffer_length, /* inquiry sq_length; __len__ */ - buffer_concat, /* __add__ */ - buffer_repeat, /* __mul__ */ - buffer_item, /* intargfunc sq_item; __getitem__ */ - buffer_slice, /* intintargfunc sq_slice; __getslice__ */ - buffer_ass_item, /* intobjargproc sq_ass_item; __setitem__ */ - buffer_ass_slice, /* intintobjargproc sq_ass_slice; __setslice__ */ + buffer_length, /* lenfunc sq_length __len__ */ + buffer_concat, /* binaryfunc sq_concat __add__ */ + buffer_repeat, /* ssizeargfunc sq_repeat __mul__ */ + NULL, /* ssizeargfunc sq_item; __getitem__ */ + NULL, /* intintargfunc sq_slice; __getslice__ */ + NULL, /* ssizeobjargproc sq_ass_item __setitem__ */ + NULL, /* intintobjargproc sq_ass_slice; __setslice__ */ +}; + +static PyMappingMethods buffer_as_mapping = { + buffer_length, // lenfunc mp_length + buffer_subscript, // binaryfunc mp_subscript + buffer_ass_subscript // objobjargproc mp_ass_subscript }; static PyObject *buffer_iter(PyObject *s) @@ -517,6 +714,7 @@ static PyObject *buffer_multiply(PyObject *s,PyObject *op) return NULL; } +#if PY_MAJOR_VERSION < 3 static PyObject *buffer_divide(PyObject *s,PyObject *op) { pySamplebuffer *self = reinterpret_cast(s); @@ -530,6 +728,35 @@ static PyObject *buffer_divide(PyObject *s,PyObject *op) else return NULL; } +#endif + +static PyObject *buffer_true_divide(PyObject *s,PyObject *op) +{ + pySamplebuffer *self = reinterpret_cast(s); + PyObject *nobj = buffer_slice(s); + if(nobj) { + PyObject *ret = PyNumber_TrueDivide(nobj, op); + if(ret == nobj) self->dirty = true; + Py_DECREF(nobj); + return ret; + } + else + return NULL; +} + +static PyObject *buffer_floor_divide(PyObject *s,PyObject *op) +{ + pySamplebuffer *self = reinterpret_cast(s); + PyObject *nobj = buffer_slice(s); + if(nobj) { + PyObject *ret = PyNumber_FloorDivide(nobj, op); + if(ret == nobj) self->dirty = true; + Py_DECREF(nobj); + return ret; + } + else + return NULL; +} static PyObject *buffer_remainder(PyObject *s,PyObject *op) { @@ -667,6 +894,7 @@ static PyObject *buffer_inplace_multiply(PyObject *s,PyObject *op) return NULL; } +#if PY_MAJOR_VERSION < 3 static PyObject *buffer_inplace_divide(PyObject *s,PyObject *op) { pySamplebuffer *self = reinterpret_cast(s); @@ -680,6 +908,35 @@ static PyObject *buffer_inplace_divide(PyObject *s,PyObject *op) else return NULL; } +#endif + +static PyObject *buffer_inplace_true_divide(PyObject *s,PyObject *op) +{ + pySamplebuffer *self = reinterpret_cast(s); + PyObject *nobj = buffer_slice(s); + if(nobj) { + PyObject *ret = PyNumber_InPlaceTrueDivide(nobj,op); + if(ret == nobj) self->dirty = true; + Py_DECREF(nobj); + return ret; + } + else + return NULL; +} + +static PyObject *buffer_inplace_floor_divide(PyObject *s,PyObject *op) +{ + pySamplebuffer *self = reinterpret_cast(s); + PyObject *nobj = buffer_slice(s); + if(nobj) { + PyObject *ret = PyNumber_InPlaceFloorDivide(nobj,op); + if(ret == nobj) self->dirty = true; + Py_DECREF(nobj); + return ret; + } + else + return NULL; +} static PyObject *buffer_inplace_remainder(PyObject *s,PyObject *op) { @@ -712,49 +969,59 @@ static PyObject *buffer_inplace_power(PyObject *s,PyObject *op1,PyObject *op2) static PyNumberMethods buffer_as_number = { - (binaryfunc)buffer_add, /*nb_add*/ - (binaryfunc)buffer_subtract, /*nb_subtract*/ - (binaryfunc)buffer_multiply, /*nb_multiply*/ - (binaryfunc)buffer_divide, /*nb_divide*/ - (binaryfunc)buffer_remainder, /*nb_remainder*/ - (binaryfunc)buffer_divmod, /*nb_divmod*/ - (ternaryfunc)buffer_power, /*nb_power*/ - (unaryfunc)buffer_negative, - (unaryfunc)buffer_pos, /*nb_pos*/ - (unaryfunc)buffer_absolute, /* (unaryfunc)buffer_abs, */ - 0, //(inquiry)buffer_nonzero, /*nb_nonzero*/ - 0, /*nb_invert*/ - 0, /*nb_lshift*/ - 0, /*nb_rshift*/ - 0, /*nb_and*/ - 0, /*nb_xor*/ - 0, /*nb_or*/ - (coercion)buffer_coerce, /*nb_coerce*/ - 0, /*nb_int*/ - 0, /*nb_long*/ - 0, /*nb_float*/ - 0, /*nb_oct*/ - 0, /*nb_hex*/ - (binaryfunc)buffer_inplace_add, /* nb_inplace_add */ - (binaryfunc)buffer_inplace_subtract, /* nb_inplace_subtract */ - (binaryfunc)buffer_inplace_multiply, /* nb_inplace_multiply */ - (binaryfunc)buffer_inplace_divide, /* nb_inplace_divide */ - (binaryfunc)buffer_inplace_remainder, /* nb_inplace_remainder */ - (ternaryfunc)buffer_inplace_power, /* nb_inplace_power */ - 0, /* nb_inplace_lshift */ - 0, /* nb_inplace_rshift */ - 0, /* nb_inplace_and */ - 0, /* nb_inplace_xor */ - 0, /* nb_inplace_or */ -// buffer_floor_div, /* nb_floor_divide */ -// buffer_div, /* nb_true_divide */ -// buffer_inplace_floor_div, /* nb_inplace_floor_divide */ -// buffer_inplace_div, /* nb_inplace_true_divide */ + (binaryfunc)buffer_add, // binaryfunc nb_add + (binaryfunc)buffer_subtract, // binaryfunc nb_subtract + (binaryfunc)buffer_multiply, // binaryfunc nb_multiply +#if PY_MAJOR_VERSION < 3 + (binaryfunc)buffer_divide, // binaryfunc nb_divide +#endif + (binaryfunc)buffer_remainder, // nb_binaryfunc remainder + (binaryfunc)buffer_divmod, // binaryfunc nb_divmod + (ternaryfunc)buffer_power, // ternaryfunc nb_power + (unaryfunc)buffer_negative, // unaryfunc nb_negative + (unaryfunc)buffer_pos, // unaryfunc nb_pos + (unaryfunc)buffer_absolute, // unaryfunc np_absolute + 0, //(inquiry)buffer_nonzero, // inquiry nb_nonzero + 0, // unaryfunc nb_invert + 0, // binaryfunc nb_lshift + 0, // binaryfunc nb_rshift + 0, // binaryfunc nb_and + 0, // binaryfunc nb_xor + 0, // binaryfunc nb_or +#if PY_MAJOR_VERSION < 3 + (coercion)buffer_coerce, // coercion nb_coerce +#endif + 0, // unaryfunc nb_int +#if PY_MAJOR_VERSION < 3 + 0, // unaryfunc nb_long + 0, // unaryfunc nb_float + 0, // unaryfunc nb_oct + 0, // unaryfunc nb_hex +#else + 0, // void *nb_reserved + 0, // unaryfunc nb_float +#endif + (binaryfunc)buffer_inplace_add, // binaryfunc nb_inplace_add + (binaryfunc)buffer_inplace_subtract, // binaryfunc nb_inplace_subtract + (binaryfunc)buffer_inplace_multiply, // binaryfunc nb_inplace_multiply +#if PY_MAJOR_VERSION < 3 + (binaryfunc)buffer_inplace_divide, // binaryfunc nb_inplace_divide +#endif + (binaryfunc)buffer_inplace_remainder, // binaryfunc nb_inplace_remainder + (ternaryfunc)buffer_inplace_power, // ternaryfunc nb_inplace_power + 0, // binaryfunc nb_inplace_lshift + 0, // binaryfunc nb_inplace_rshift + 0, // binaryfunc nb_inplace_and + 0, // binaryfunc nb_inplace_xor + 0, // binaryfunc nb_inplace_or + (binaryfunc)buffer_floor_divide, // binaryfunc nb_floor_divide + (binaryfunc)buffer_true_divide, // binaryfunc nb_true_divide + (binaryfunc)buffer_inplace_floor_divide, // binaryfunc nb_inplace_floor_divide + (binaryfunc)buffer_inplace_true_divide, // binaryfunc nb_inplace_true_divide }; PyTypeObject pySamplebuffer_Type = { - PyObject_HEAD_INIT(NULL) - 0, /*ob_size*/ + PyVarObject_HEAD_INIT(NULL, 0) "Buffer", /*tp_name*/ sizeof(pySamplebuffer), /*tp_basicsize*/ 0, /*tp_itemsize*/ @@ -766,14 +1033,18 @@ PyTypeObject pySamplebuffer_Type = { buffer_repr, /*tp_repr*/ &buffer_as_number, /*tp_as_number*/ &buffer_as_seq, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ + &buffer_as_mapping, /*tp_as_mapping*/ buffer_hash, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ &buffer_as_buffer, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT /*| Py_TPFLAGS_BASETYPE*/, /*tp_flags*/ + Py_TPFLAGS_DEFAULT /*| Py_TPFLAGS_BASETYPE*/ /*tp_flags*/ +#if PY_MAJOR_VERSION < 3 + | Py_TPFLAGS_HAVE_NEWBUFFER +#endif + , "Samplebuffer objects", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ @@ -796,14 +1067,26 @@ PyTypeObject pySamplebuffer_Type = { // Must have this as a function because the import_array macro in numpy version 1.01 strangely has a return statement included. // Furthermore the import error printout from this macro is ugly, but we accept that for now, waiting for later numpy updates to fix all of this. +// The situation is further complicated by Python 3, where numpy's import_array returns NULL... #ifdef PY_ARRAYS -static void __import_array__() +#ifdef PY_NUMARRAY +#define IMPORT_ARRAY_RET_TYPE void +#define IMPORT_ARRAY_RET_VALUE +#elif PY_MAJOR_VERSION < 3 +#define IMPORT_ARRAY_RET_TYPE void +#define IMPORT_ARRAY_RET_VALUE +#else +#define IMPORT_ARRAY_RET_TYPE void * +#define IMPORT_ARRAY_RET_VALUE NULL +#endif +static IMPORT_ARRAY_RET_TYPE __import_array__() { #ifdef PY_NUMARRAY import_libnumarray(); #else import_array(); #endif + return IMPORT_ARRAY_RET_VALUE; } #endif diff --git a/source/pybuffer.h b/source/pybuffer.h index 5869d13..8c98255 100644 --- a/source/pybuffer.h +++ b/source/pybuffer.h @@ -1,7 +1,7 @@ /* py/pyext - python script object for PD and Max/MSP -Copyright (c)2002-2015 Thomas Grill (gr@grrrr.org) +Copyright (c)2002-2019 Thomas Grill (gr@grrrr.org) For information on usage and redistribution, and for a DISCLAIMER OF ALL WARRANTIES, see the file, "license.txt," in this distribution. */ @@ -15,7 +15,7 @@ WARRANTIES, see the file, "license.txt," in this distribution. #error You need at least flext version 0.5.0 #endif -#if FLEXT_OS == FLEXT_OS_MAC +#ifdef PY_USE_FRAMEWORK #include #else #include @@ -56,7 +56,17 @@ inline PyObject *pySamplebuffer_FromString(const char *str) inline PyObject *pySamplebuffer_FromString(PyObject *str) { - return pySamplebuffer_FromString(PyString_AsString(str)); + const char *cstr; +#if PY_MAJOR_VERSION < 3 + if(PyString_Check(str)) + cstr = PyString_AsString(str); +#else + if(PyUnicode_Check(str)) + cstr = PyUnicode_AsUTF8(str); +#endif + else + PyErr_SetString(PyExc_TypeError, "Type must be string or unicode"); + return pySamplebuffer_FromString(cstr); } inline const t_symbol *pySamplebuffer_AS_SYMBOL(PyObject *op) diff --git a/source/pybundle.cpp b/source/pybundle.cpp index 9bfacea..b2512d6 100644 --- a/source/pybundle.cpp +++ b/source/pybundle.cpp @@ -1,7 +1,7 @@ /* py/pyext - python script object for PD and Max/MSP -Copyright (c)2002-2015 Thomas Grill (gr@grrrr.org) +Copyright (c)2002-2019 Thomas Grill (gr@grrrr.org) For information on usage and redistribution, and for a DISCLAIMER OF ALL WARRANTIES, see the file, "license.txt," in this distribution. */ @@ -56,7 +56,13 @@ static PyObject *bundle_send(PyObject *obj) static PyObject *bundle_repr(PyObject *self) { FLEXT_ASSERT(pyBundle_Check(self)); - return (PyObject *)PyString_FromFormat("",pyBundle_AS_BUNDLE(self)); + return (PyObject *) +#if PY_MAJOR_VERSION < 3 + PyString_FromFormat +#else + PyUnicode_FromFormat +#endif + ("", pyBundle_AS_BUNDLE(self)); } static PyObject *bundle_str(PyObject *self) @@ -102,13 +108,23 @@ static PyObject *bundle_append(PyObject *self,PyObject *args) int o; if(sz > 2 && - (tg = PyTuple_GET_ITEM(args,0)) != NULL && PyInstance_Check(tg) && - (outl = PyTuple_GET_ITEM(args,1)) != NULL && PyInt_Check(outl) + (tg = PyTuple_GET_ITEM(args,0)) != NULL && + (outl = PyTuple_GET_ITEM(args,1)) != NULL && +#if PY_MAJOR_VERSION < 3 + PyInt_Check(outl) +#else + PyLong_Check(outl) +#endif ) { // Sending to outlet ext = pyext::GetThis(tg); + +#if PY_MAJOR_VERSION < 3 o = PyInt_AS_LONG(outl); - +#else + o = PyLong_AS_LONG(outl); +#endif + if(o < 1 || o > ext->Outlets()) { PyErr_SetString(PyExc_ValueError,"Outlet index out of range"); return NULL; @@ -179,8 +195,7 @@ static PyMethodDef bundle_methods[] = { PyTypeObject pyBundle_Type = { - PyObject_HEAD_INIT(NULL) - 0, /*ob_size*/ + PyVarObject_HEAD_INIT(NULL, 0) "Bundle", /*tp_name*/ sizeof(pyBundle), /*tp_basicsize*/ 0, /*tp_itemsize*/ diff --git a/source/pybundle.h b/source/pybundle.h index b397ffb..f862235 100644 --- a/source/pybundle.h +++ b/source/pybundle.h @@ -15,7 +15,7 @@ WARRANTIES, see the file, "license.txt," in this distribution. #error You need at least flext version 0.5.0 #endif -#if FLEXT_OS == FLEXT_OS_MAC +#ifdef PY_USE_FRAMEWORK #include #else #include diff --git a/source/pycompat.cpp b/source/pycompat.cpp new file mode 100644 index 0000000..809c830 --- /dev/null +++ b/source/pycompat.cpp @@ -0,0 +1,93 @@ +#include "pycompat.h" + +// copied from Python 3.8.2 for compatibility with pre-3.6.1 versions because +// doing the right thing with the older slice functions seems difficult... + +#if PY_VERSION_HEX < 0x03060100 +int +PySlice_Unpack(PyObject *_r, + Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step) +{ + PySliceObject *r = (PySliceObject*)_r; + /* this is harder to get right than you might think */ + + Py_BUILD_ASSERT(PY_SSIZE_T_MIN + 1 <= -PY_SSIZE_T_MAX); + + if (r->step == Py_None) { + *step = 1; + } + else { + if (!_PyEval_SliceIndex(r->step, step)) return -1; + if (*step == 0) { + PyErr_SetString(PyExc_ValueError, + "slice step cannot be zero"); + return -1; + } + /* Here *step might be -PY_SSIZE_T_MAX-1; in this case we replace it + * with -PY_SSIZE_T_MAX. This doesn't affect the semantics, and it + * guards against later undefined behaviour resulting from code that + * does "step = -step" as part of a slice reversal. + */ + if (*step < -PY_SSIZE_T_MAX) + *step = -PY_SSIZE_T_MAX; + } + + if (r->start == Py_None) { + *start = *step < 0 ? PY_SSIZE_T_MAX : 0; + } + else { + if (!_PyEval_SliceIndex(r->start, start)) return -1; + } + + if (r->stop == Py_None) { + *stop = *step < 0 ? PY_SSIZE_T_MIN : PY_SSIZE_T_MAX; + } + else { + if (!_PyEval_SliceIndex(r->stop, stop)) return -1; + } + + return 0; +} + +Py_ssize_t +PySlice_AdjustIndices(Py_ssize_t length, + Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t step) +{ + /* this is harder to get right than you might think */ + + assert(step != 0); + assert(step >= -PY_SSIZE_T_MAX); + + if (*start < 0) { + *start += length; + if (*start < 0) { + *start = (step < 0) ? -1 : 0; + } + } + else if (*start >= length) { + *start = (step < 0) ? length - 1 : length; + } + + if (*stop < 0) { + *stop += length; + if (*stop < 0) { + *stop = (step < 0) ? -1 : 0; + } + } + else if (*stop >= length) { + *stop = (step < 0) ? length - 1 : length; + } + + if (step < 0) { + if (*stop < *start) { + return (*start - *stop - 1) / (-step) + 1; + } + } + else { + if (*start < *stop) { + return (*stop - *start - 1) / step + 1; + } + } + return 0; +} +#endif diff --git a/source/pycompat.h b/source/pycompat.h new file mode 100644 index 0000000..3f5cb26 --- /dev/null +++ b/source/pycompat.h @@ -0,0 +1,40 @@ +#ifndef __PYCOMPAT_H +#define __PYCOMPAT_H + +#ifdef PY_USE_FRAMEWORK +#include +#else +#include +#endif + +// copied from Python 3.8.2 for compatibility with pre-3.6.1 versions because +// doing the right thing with the older slice functions seems difficult... + +#if PY_VERSION_HEX < 0x03060100 +#if PY_MAJOR_VERSION < 3 +/* Assert a build-time dependency, as an expression. + Your compile will fail if the condition isn't true, or can't be evaluated + by the compiler. This can be used in an expression: its value is 0. + Example: + #define foo_to_char(foo) \ + ((char *)(foo) \ + + Py_BUILD_ASSERT_EXPR(offsetof(struct foo, string) == 0)) + Written by Rusty Russell, public domain, http://ccodearchive.net/ */ +#define Py_BUILD_ASSERT_EXPR(cond) \ + (sizeof(char [1 - 2*!(cond)]) - 1) + +#define Py_BUILD_ASSERT(cond) do { \ + (void)Py_BUILD_ASSERT_EXPR(cond); \ + } while(0) +#endif + +int +PySlice_Unpack(PyObject *_r, + Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step); + +Py_ssize_t +PySlice_AdjustIndices(Py_ssize_t length, + Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t step); +#endif + +#endif diff --git a/source/pydsp.cpp b/source/pydsp.cpp index 49e6aee..db8a8ad 100644 --- a/source/pydsp.cpp +++ b/source/pydsp.cpp @@ -38,6 +38,7 @@ FLEXT_LIB_DSP_V("pyext~ pyext.~ pyx~ pyx.~",pydsp) pydsp::pydsp(int argc,const t_atom *argv) : pyext(argc,argv,true) , dspfun(NULL),sigfun(NULL) + , buffers(NULL) {} bool pydsp::DoInit() @@ -83,7 +84,15 @@ void pydsp::NewBuffers() for(i = 0; i < ins; ++i) { Py_XDECREF(buffers[i]); - PyObject *b = PyBuffer_FromReadWriteMemory(insigs[i],n*sizeof(t_sample)); + PyObject *b = +#if PY_MAJOR_VERSION < 3 + PyBuffer_FromReadWriteMemory(insigs[i],n*sizeof(t_sample)); +#elif PY_MINOR_VERSION >= 3 + PyMemoryView_FromMemory(reinterpret_cast(insigs[i]), n*sizeof(t_sample), PyBUF_WRITE); +#else +#error "TODO" +#endif + buffers[i] = arrayfrombuffer(b,1,n); Py_DECREF(b); } @@ -95,7 +104,15 @@ void pydsp::NewBuffers() Py_XINCREF(buffers[i]); } else { - PyObject *b = PyBuffer_FromReadWriteMemory(outsigs[i],n*sizeof(t_sample)); + PyObject *b = +#if PY_MAJOR_VERSION < 3 + PyBuffer_FromReadWriteMemory(outsigs[i],n*sizeof(t_sample)); +#elif PY_MINOR_VERSION >= 3 + PyMemoryView_FromMemory(reinterpret_cast(outsigs[i]), n*sizeof(t_sample), PyBUF_WRITE); +#else +#error "TODO" +#endif + buffers[ins+i] = arrayfrombuffer(b,1,n); Py_DECREF(b); } diff --git a/source/pyext.cpp b/source/pyext.cpp index 152e126..9b19bd2 100644 --- a/source/pyext.cpp +++ b/source/pyext.cpp @@ -1,7 +1,7 @@ /* py/pyext - python script object for PD and Max/MSP -Copyright (c)2002-2015 Thomas Grill (gr@grrrr.org) +Copyright (c)2002-2019 Thomas Grill (gr@grrrr.org) For information on usage and redistribution, and for a DISCLAIMER OF ALL WARRANTIES, see the file, "license.txt," in this distribution. */ @@ -9,6 +9,14 @@ WARRANTIES, see the file, "license.txt," in this distribution. #include "pyext.h" #include +// undefine PD's T_OBJECT to avoid conflict with Python's +#undef T_OBJECT + +#ifdef PY_USE_FRAMEWORK +#include "Python/structmember.h" +#else +#include "structmember.h" +#endif FLEXT_LIB_V("pyext pyext. pyx pyx.",pyext) @@ -47,43 +55,32 @@ void pyext::Setup(t_classid c) // ---------------------------------------------------- // register/initialize pyext base class along with module - class_dict = PyDict_New(); - PyObject *className = PyString_FromString(PYEXT_CLASS); - PyMethodDef *def; - - // add setattr/getattr to class - for(def = attr_tbl; def->ml_name; def++) { - PyObject *func = PyCFunction_New(def, NULL); - PyDict_SetItemString(class_dict, def->ml_name, func); - Py_DECREF(func); - } - class_obj = PyClass_New(NULL, class_dict, className); - Py_DECREF(className); - - // add methods to class - for (def = meth_tbl; def->ml_name != NULL; def++) { - PyObject *func = PyCFunction_New(def, NULL); - PyObject *method = PyMethod_New(func, NULL, class_obj); // increases class_obj ref count by 1 - PyDict_SetItemString(class_dict, def->ml_name, method); - Py_DECREF(func); - Py_DECREF(method); + if(PyType_Ready(&pyPyext_Type) < 0) { + PyErr_Print(); + return; } - -#if PY_VERSION_HEX >= 0x02020000 + +#if 0 && PY_VERSION_HEX >= 0x02020000 // not absolutely necessary, existent in python 2.2 upwards // make pyext functions available in class scope PyDict_Merge(class_dict,module_dict,0); #endif - // after merge so that it's not in class_dict as well... - PyDict_SetItemString(module_dict, PYEXT_CLASS,class_obj); // increases class_obj ref count by 1 + + PyDict_SetItemString(module_dict, PYEXT_CLASS, (PyObject *) &pyPyext_Type); // increases class_obj ref count by 1 - PyDict_SetItemString(class_dict,"__doc__",PyString_FromString(pyext_doc)); + PyObject *str; +#if PY_MAJOR_VERSION < 3 + str = PyString_FromString(pyext_doc); +#else + str = PyUnicode_FromString(pyext_doc); +#endif } pyext *pyext::GetThis(PyObject *self) { PyObject *th = PyObject_GetAttrString(self,"_this"); + if(th) { pyext *ret = static_cast(PyLong_AsVoidPtr(th)); Py_DECREF(th); @@ -108,9 +105,6 @@ void pyext::ClearThis() FLEXT_ASSERT(ret != -1); } -PyObject *pyext::class_obj = NULL; -PyObject *pyext::class_dict = NULL; - pyext::pyext(int argc,const t_atom *argv,bool sig): methname(NULL), pyobj(NULL), @@ -168,7 +162,13 @@ pyext::pyext(int argc,const t_atom *argv,bool sig): PyErr_SetString(PyExc_ValueError,"Invalid module name"); // check for alias creation names - if(dotted) clname = scr; + // ...this seems wrong? + // e.g. "pyext. a.b c" becomes the equivalent of: + // from a.b import a.b + // instead of + // from a.b import c + // so let's disable it for now. + //if(dotted) clname = scr; } Register(GetRegistry(REGNAME)); @@ -196,7 +196,9 @@ bool pyext::Init() if(methname) { MakeInstance(); - if(pyobj) InitInOut(inlets,outlets); + if(pyobj) { + InitInOut(inlets,outlets); + } } else inlets = outlets = 0; @@ -265,25 +267,11 @@ bool pyext::DoInit() bool ok = true; SetThis(); - - PyObject *init = PyObject_GetAttrString(pyobj,"__init__"); // get ref - if(init) { - if(PyMethod_Check(init)) { - PyObject *res = PyObject_CallObject(init,pargs); - if(!res) { - // exception is set - ok = false; - // we want to know why __init__ failed... - PyErr_Print(); - } - else - Py_DECREF(res); - } - Py_DECREF(init); + + if(pyobj->ob_type->tp_init(pyobj, pargs, NULL) < 0) { + ok = false; + PyErr_Print(); } - else - // __init__ has not been found - don't care - PyErr_Clear(); Py_DECREF(pargs); return ok; @@ -327,12 +315,24 @@ bool pyext::InitInOut(int &inl,int &outl) { if(inl >= 0) { // set number of inlets - int ret = PyObject_SetAttrString(pyobj,"_inlets",PyInt_FromLong(inl)); + PyObject *obj; +#if PY_MAJOR_VERSION < 3 + obj = PyInt_FromLong(inl); +#else + obj = PyLong_FromLong(inl); +#endif + int ret = PyObject_SetAttrString(pyobj, "_inlets", obj); FLEXT_ASSERT(!ret); } if(outl >= 0) { // set number of outlets - int ret = PyObject_SetAttrString(pyobj,"_outlets",PyInt_FromLong(outl)); + PyObject *obj; +#if PY_MAJOR_VERSION < 3 + obj = PyInt_FromLong(outl); +#else + obj = PyLong_FromLong(outl); +#endif + int ret = PyObject_SetAttrString(pyobj, "_outlets", obj); FLEXT_ASSERT(!ret); } @@ -350,8 +350,15 @@ bool pyext::InitInOut(int &inl,int &outl) Py_DECREF(res); res = fres; } +#if PY_MAJOR_VERSION < 3 if(PyInt_Check(res)) inl = PyInt_AS_LONG(res); +#else + if(PyLong_Check(res)) + inl = PyLong_AS_LONG(res); +#endif + else + PyErr_SetString(PyExc_TypeError, "Type must be integer"); Py_DECREF(res); } else @@ -367,8 +374,16 @@ bool pyext::InitInOut(int &inl,int &outl) Py_DECREF(res); res = fres; } +#if PY_MAJOR_VERSION < 3 if(PyInt_Check(res)) outl = PyInt_AS_LONG(res); +#else + if(PyLong_Check(res)) + outl = PyLong_AS_LONG(res); +#endif + else + PyErr_SetString(PyExc_TypeError, "Type must be integer"); + Py_DECREF(res); } else @@ -383,14 +398,21 @@ bool pyext::MakeInstance() // pyobj should already have been decref'd / cleared before getting here!! if(module && methname) { - PyObject *pref = PyObject_GetAttrString(module,const_cast(GetString(methname))); + PyObject *pref = PyObject_GetAttrString(module, const_cast(GetString(methname))); if(!pref) PyErr_Print(); else { - if(PyClass_Check(pref)) { - // make instance, but don't call __init__ - pyobj = PyInstance_NewRaw(pref,NULL); + if(PyType_Check(pref) && PyType_IsSubtype((PyTypeObject *) pref, &pyPyext_Type)) { + PyTypeObject *pytypeobj = (PyTypeObject *) pref; + PyObject *pargs = MakePyArgs(NULL, initargs.Count(), initargs.Atoms()); + if(!pargs) { + PyErr_Print(); + } else { + pyobj = pytypeobj->tp_new(pytypeobj, pargs, NULL); + + Py_DECREF(pargs); + } if(!pyobj) PyErr_Print(); } else @@ -499,7 +521,7 @@ bool pyext::CbMethodResort(int n,const t_symbol *s,int argc,const t_atom *argv) void pyext::m_help() { post(""); - post("%s %s - python class object, (C)2002-2012 Thomas Grill",thisName(),PY__VERSION); + post("%s %s - python class object, (C)2002-2019 Thomas Grill",thisName(),PY__VERSION); #ifdef FLEXT_DEBUG post("DEBUG VERSION, compiled on " __DATE__ " " __TIME__); #endif @@ -644,3 +666,50 @@ void pyext::DumpOut(const t_symbol *sym,int argc,const t_atom *argv) { ToOutAnything(GetOutAttr(),sym?sym:thisTag(),argc,argv); } + +static PyObject *pyext_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + pyPyext *self = (pyPyext *) type->tp_alloc(type, 0); + + self->this_ptr = 0; + + PyObject *ret = (PyObject *) self; + + if(self) { + PyObject *this_long = kwds != NULL + ? PyDict_GetItemString(kwds, "_pyext_this") + : NULL; + + if(this_long) { + if(!PyLong_Check(this_long)) { + self->this_ptr = PyLong_AsLong(this_long); + } else { + Py_DECREF(self); + ret = NULL; + } + + Py_DECREF(this_long); + } + } else { + ret = NULL; + } + + return (PyObject *) ret; +} + +static PyMemberDef pyPyext_members[] = { + {"_this", T_OBJECT_EX, offsetof(pyPyext, this_ptr), 0, "pointer to pyext object"}, + {NULL} +}; + +PyTypeObject pyPyext_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = PYEXT_MODULE "." PYEXT_CLASS, + .tp_basicsize = sizeof(pyPyext), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = pyext::pyext_doc, + .tp_methods = pyext::meth_tbl, + .tp_members = pyPyext_members, + .tp_new = pyext_new +}; diff --git a/source/pyext.h b/source/pyext.h index 61b892a..92e5649 100644 --- a/source/pyext.h +++ b/source/pyext.h @@ -20,30 +20,32 @@ class pyext public: pyext(int argc,const t_atom *argv,bool sig = false); - static PyObject *pyext__str__(PyObject *,PyObject *args); + static PyObject *pyext__str__(PyObject *self, PyObject *args); - static PyObject *pyext_outlet(PyObject *,PyObject *args); + static PyObject *pyext_outlet(PyObject *self, PyObject *args); #if FLEXT_SYS == FLEXT_SYS_PD - static PyObject *pyext_tocanvas(PyObject *,PyObject *args); + static PyObject *pyext_tocanvas(PyObject *self, PyObject *args); #endif - static PyObject *pyext_setattr(PyObject *,PyObject *args); - static PyObject *pyext_getattr(PyObject *,PyObject *args); + static PyObject *pyext_setattr(PyObject *self, PyObject *args); + static PyObject *pyext_getattr(PyObject *self, PyObject *args); - static PyObject *pyext_detach(PyObject *,PyObject *args); - static PyObject *pyext_stop(PyObject *,PyObject *args); - static PyObject *pyext_isthreaded(PyObject *,PyObject *); + static PyObject *pyext_detach(PyObject *self, PyObject *args); + static PyObject *pyext_stop(PyObject *self, PyObject *args); - static PyObject *pyext_inbuf(PyObject *,PyObject *args); - static PyObject *pyext_invec(PyObject *,PyObject *args); - static PyObject *pyext_outbuf(PyObject *,PyObject *args); - static PyObject *pyext_outvec(PyObject *,PyObject *args); + static PyObject *pyext_inbuf(PyObject *self, PyObject *args); + static PyObject *pyext_invec(PyObject *self, PyObject *args); + static PyObject *pyext_outbuf(PyObject *self, PyObject *args); + static PyObject *pyext_outvec(PyObject *self, PyObject *args); int Inlets() const { return inlets; } int Outlets() const { return outlets; } static pyext *GetThis(PyObject *self); + static PyMethodDef meth_tbl[]; + static const char *pyext_doc; + protected: virtual bool Init(); @@ -65,7 +67,7 @@ class pyext void ms_initargs(const AtomList &a) { m_reload_(a.Count(),a.Atoms()); } void m_dir_() { m__dir(pyobj); } void mg_dir_(AtomList &lst) { GetDir(pyobj,lst); } - void m_doc_() { m__doc(((PyInstanceObject *)pyobj)->in_class->cl_dict); } + void m_doc_() { m__doc(pyobj); } void m_get(const t_symbol *s); void m_set(int argc,const t_atom *argv); @@ -98,13 +100,9 @@ class pyext bool MakeInstance(); bool InitInOut(int &inlets,int &outlets); - static PyObject *class_obj,*class_dict; - static PyMethodDef attr_tbl[],meth_tbl[]; - static const char *pyext_doc; - // -------- bind stuff ------------------ - static PyObject *pyext_bind(PyObject *,PyObject *args); - static PyObject *pyext_unbind(PyObject *,PyObject *args); + static PyObject *pyext_bind(PyObject *self, PyObject *args); + static PyObject *pyext_unbind(PyObject *self, PyObject *args); // --------------------------- @@ -151,4 +149,13 @@ class pyext #endif }; +typedef struct { + PyObject_HEAD + long this_ptr; +} pyPyext; + +PY_EXPORT extern PyTypeObject pyPyext_Type; + +#define pyPyext_Check(op) PyObject_TypeCheck((op), &pyPyext_Type) + #endif diff --git a/source/pymeth.cpp b/source/pymeth.cpp index 0ff87ed..42cbfca 100644 --- a/source/pymeth.cpp +++ b/source/pymeth.cpp @@ -382,7 +382,11 @@ bool pymeth::CbMethodResort(int n,const t_symbol *s,int argc,const t_atom *argv) else if(self != objects[0]) { // type hasn't changed, but object has PyObject *f = function; +#if PY_MAJOR_VERSION < 3 function = PyMethod_New(PyMethod_GET_FUNCTION(f),objects[0],PyMethod_GET_CLASS(f)); +#else + function = PyMethod_New(PyMethod_GET_FUNCTION(f),objects[0]); +#endif Py_DECREF(f); } } diff --git a/source/pyprefix.h b/source/pyprefix.h index 8b242a4..7da820d 100644 --- a/source/pyprefix.h +++ b/source/pyprefix.h @@ -16,7 +16,7 @@ WARRANTIES, see the file, "license.txt," in this distribution. // otherwise some functions don't get defined #include -#if FLEXT_OS == FLEXT_OS_MAC +#ifdef PY_USE_FRAMEWORK #include #else #include @@ -47,4 +47,27 @@ extern "C" { typedef int Py_ssize_t; #endif +// these are copied from the Python 3.8.2 source because doing the right thing +// with the pre-3.6.1 slice functions seems difficult... + +#if PY_MAJOR_VERSION < 3 +/* Assert a build-time dependency, as an expression. + Your compile will fail if the condition isn't true, or can't be evaluated + by the compiler. This can be used in an expression: its value is 0. + Example: + #define foo_to_char(foo) \ + ((char *)(foo) \ + + Py_BUILD_ASSERT_EXPR(offsetof(struct foo, string) == 0)) + Written by Rusty Russell, public domain, http://ccodearchive.net/ */ +#define Py_BUILD_ASSERT_EXPR(cond) \ + (sizeof(char [1 - 2*!(cond)]) - 1) + +#define Py_BUILD_ASSERT(cond) do { \ + (void)Py_BUILD_ASSERT_EXPR(cond); \ + } while(0) +#endif + +#if PY_VERSION_HEX < 0x03060100 +#endif + #endif diff --git a/source/pysymbol.cpp b/source/pysymbol.cpp index 50ee9da..d03cb2f 100644 --- a/source/pysymbol.cpp +++ b/source/pysymbol.cpp @@ -1,7 +1,7 @@ /* py/pyext - python script object for PD and Max/MSP -Copyright (c)2002-2015 Thomas Grill (gr@grrrr.org) +Copyright (c)2002-2019 Thomas Grill (gr@grrrr.org) For information on usage and redistribution, and for a DISCLAIMER OF ALL WARRANTIES, see the file, "license.txt," in this distribution. */ @@ -32,10 +32,15 @@ static int symbol_init(PyObject *self, PyObject *args, PyObject *kwds) if(pySymbol_Check(arg)) ((pySymbol *)self)->sym = pySymbol_AS_SYMBOL(arg); +#if PY_MAJOR_VERSION < 3 else if(PyString_Check(arg)) ((pySymbol *)self)->sym = flext::MakeSymbol(PyString_AS_STRING(arg)); +#else + else if(PyUnicode_Check(arg)) + ((pySymbol *)self)->sym = flext::MakeSymbol(PyUnicode_AsUTF8(arg)); +#endif else { - PyErr_SetString(PyExc_TypeError,"string or symbol argument expected"); + PyErr_SetString(PyExc_TypeError, "string, unicode or symbol argument expected"); ret = -1; } Py_DECREF(arg); @@ -46,13 +51,25 @@ static int symbol_init(PyObject *self, PyObject *args, PyObject *kwds) static PyObject *symbol_str(PyObject *self) { FLEXT_ASSERT(pySymbol_Check(self)); - return (PyObject *)PyString_FromString(pySymbol_AS_STRING(self)); + return (PyObject *) +#if PY_MAJOR_VERSION < 3 + PyString_FromString +#else + PyUnicode_FromString +#endif + (pySymbol_AS_STRING(self)); } static PyObject *symbol_repr(PyObject *self) { FLEXT_ASSERT(pySymbol_Check(self)); - return (PyObject *)PyString_FromFormat("",pySymbol_AS_STRING(self)); + return (PyObject *) +#if PY_MAJOR_VERSION < 3 + PyString_FromFormat +#else + PyUnicode_FromFormat +#endif + ("", pySymbol_AS_STRING(self)); } static PyObject *symbol_richcompare(PyObject *a,PyObject *b,int cmp) @@ -101,7 +118,13 @@ static PyObject *symbol_item(PyObject *s,Py_ssize_t i) if(i < 0) i += len; if(i >= 0 && i < len) - return PyString_FromStringAndSize(str+i,1); + return +#if PY_MAJOR_VERSION < 3 + PyString_FromStringAndSize +#else + PyUnicode_FromStringAndSize +#endif + (str+i,1); else { Py_INCREF(Py_None); return Py_None; @@ -120,7 +143,13 @@ static PyObject *symbol_slice(PyObject *s,Py_ssize_t ilow = 0,Py_ssize_t ihigh = if(ihigh < 0) ihigh += len; if(ihigh >= len) ihigh = len-1; - return PyString_FromStringAndSize(str+ilow,ilow <= ihigh?ihigh-ilow+1:0); + return +#if PY_MAJOR_VERSION < 3 + PyString_FromStringAndSize +#else + PyUnicode_FromStringAndSize +#endif + (str+ilow, ilow <= ihigh?ihigh-ilow+1:0); } static PyObject *symbol_concat(PyObject *s,PyObject *op) @@ -150,13 +179,14 @@ static PyObject *symbol_repeat(PyObject *s,Py_ssize_t rep) } static PySequenceMethods symbol_as_seq = { - symbol_length, /* inquiry sq_length; __len__ */ - symbol_concat, /* __add__ */ - symbol_repeat, /* __mul__ */ - symbol_item, /* intargfunc sq_item; __getitem__ */ - symbol_slice, /* intintargfunc sq_slice; __getslice__ */ - NULL, /* intobjargproc sq_ass_item; __setitem__ */ - NULL, /* intintobjargproc sq_ass_slice; __setslice__ */ + symbol_length, /* lenfunc sq_length __len__ */ + symbol_concat, /* binaryfunc sq_concat __add__ */ + symbol_repeat, /* ssizeargfunc sq_repeat __mul__ */ + symbol_item, /* ssizeargfunc sq_item; __getitem__ */ + NULL, /* ssizeobjargproc sq_ass_item __setitem__ */ + NULL, /* objobjproc sq_contains __contains__ */ + NULL, /* binaryfunc sq_inplace_concat __iadd__ */ + NULL /* ssizeargfunc sq_inplace_repeat __imul */ }; static PyObject *symbol_iter(PyObject *s) @@ -175,45 +205,44 @@ static PyObject *symbol_iter(PyObject *s) PyTypeObject pySymbol_Type = { - PyObject_HEAD_INIT(NULL) - 0, /*ob_size*/ - "Symbol", /*tp_name*/ - sizeof(pySymbol), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - 0, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - symbol_repr, /*tp_repr*/ - 0, /*tp_as_number*/ - &symbol_as_seq, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - symbol_hash, /*tp_hash */ - 0, /*tp_call*/ - symbol_str, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT /*| Py_TPFLAGS_BASETYPE*/, /*tp_flags*/ - "Symbol objects", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ + PyVarObject_HEAD_INIT(NULL, 0) + "Symbol", /* tp_name */ + sizeof(pySymbol), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + symbol_repr, /* tp_repr */ + 0, /* tp_as_number */ + &symbol_as_seq, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + symbol_hash, /* tp_hash */ + 0, /* tp_call */ + symbol_str, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT /* | Py_TPFLAGS_BASETYPE*/, /* tp_flags */ + "Symbol objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ symbol_richcompare, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - symbol_iter, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - 0, /* tp_members */ + 0, /* tp_weaklistoffset */ + symbol_iter, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ - symbol_init, /* tp_init */ + symbol_init, /* tp_init */ 0, /* tp_alloc */ - symbol_new, /* tp_new */ + symbol_new, /* tp_new */ }; pySymbol *pySymbol__; diff --git a/source/pysymbol.h b/source/pysymbol.h index 0febfde..5e7353c 100644 --- a/source/pysymbol.h +++ b/source/pysymbol.h @@ -1,7 +1,7 @@ /* py/pyext - python script object for PD and Max/MSP -Copyright (c)2002-2015 Thomas Grill (gr@grrrr.org) +Copyright (c)2002-2019 Thomas Grill (gr@grrrr.org) For information on usage and redistribution, and for a DISCLAIMER OF ALL WARRANTIES, see the file, "license.txt," in this distribution. */ @@ -15,7 +15,7 @@ WARRANTIES, see the file, "license.txt," in this distribution. #error You need at least flext version 0.5.0 #endif -#if FLEXT_OS == FLEXT_OS_MAC +#ifdef PY_USE_FRAMEWORK #include #else #include @@ -61,7 +61,18 @@ inline PyObject *pySymbol_FromString(const char *str) inline PyObject *pySymbol_FromString(PyObject *str) { - return pySymbol_FromString(PyString_AsString(str)); + const char *cstr; +#if PY_MAJOR_VERSION < 3 + if(PyString_Check(str)) + cstr = PyString_AsString(str); +#else + if(PyUnicode_Check(str)) + cstr = PyUnicode_AsUTF8(str); +#endif + else + PyErr_SetString(PyExc_TypeError, "Type must be string or unicode"); + + return pySymbol_FromString(cstr); } inline const t_symbol *pySymbol_AS_SYMBOL(PyObject *op) @@ -81,8 +92,13 @@ inline const char *pySymbol_AS_STRING(PyObject *op) inline const t_symbol *pyObject_AsSymbol(PyObject *op) { +#if PY_MAJOR_VERSION < 3 if(PyString_Check(op)) return flext::MakeSymbol(PyString_AS_STRING(op)); +#else + if(PyUnicode_Check(op)) + return flext::MakeSymbol(PyUnicode_AsUTF8(op)); +#endif else return pySymbol_AsSymbol(op); }