From a764e59e1d3a8e330f0d484fdb26b35ca3f0b2e4 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Sat, 22 Mar 2008 02:15:12 +0000 Subject: bringing pdj-0.8.3 into the main branch svn path=/trunk/externals/loaders/pdj/; revision=9621 --- .cdtproject | 94 +++ .project | 125 +++ .settings/org.eclipse.cdt.core.prefs | 3 + LICENSE | 22 + Linux-build.properties | 15 + Mac OS X-build.properties | 16 + Makefile | 11 + README | 174 ++++ Windows XP-build.properties | 14 + build.properties | 21 + build.xml | 250 ++++++ lib/ant/cpptasks.jar | Bin 0 -> 345204 bytes res/help-pdj.pd | 63 ++ res/pdj-test.pd | 42 + res/pdj.properties | 81 ++ res/pdj~-panner.pd | 10 + res/pdj~-test.pd | 42 + src/MSPBuffer.c | 159 ++++ src/MaxClock.c | 74 ++ src/MaxObject.c | 131 +++ src/MaxSystem.c | 178 ++++ src/init.c | 349 ++++++++ src/java/com/cycling74/io/ErrorStream.java | 10 + src/java/com/cycling74/io/PostStream.java | 10 + src/java/com/cycling74/max/Atom.java | 715 ++++++++++++++++ src/java/com/cycling74/max/AtomFloat.java | 96 +++ src/java/com/cycling74/max/AtomString.java | 63 ++ src/java/com/cycling74/max/Attribute.java | 463 ++++++++++ src/java/com/cycling74/max/Callback.java | 213 +++++ src/java/com/cycling74/max/DataTypes.java | 14 + src/java/com/cycling74/max/Executable.java | 14 + src/java/com/cycling74/max/MaxClock.java | 113 +++ src/java/com/cycling74/max/MaxContext.java | 21 + src/java/com/cycling74/max/MaxObject.java | 933 +++++++++++++++++++++ src/java/com/cycling74/max/MaxPatcher.java | 17 + src/java/com/cycling74/max/MaxQelem.java | 114 +++ .../com/cycling74/max/MaxRuntimeException.java | 15 + src/java/com/cycling74/max/MaxSystem.java | 186 ++++ src/java/com/cycling74/max/MessageReceiver.java | 11 + src/java/com/cycling74/max/package.html | 5 + src/java/com/cycling74/msp/AudioFileBuffer.java | 116 +++ src/java/com/cycling74/msp/MSPBuffer.java | 127 +++ src/java/com/cycling74/msp/MSPObject.java | 163 ++++ src/java/com/cycling74/msp/MSPPerformable.java | 17 + src/java/com/cycling74/msp/MSPPerformer.java | 36 + src/java/com/cycling74/msp/MSPSignal.java | 70 ++ src/java/com/cycling74/msp/package.html | 5 + src/java/com/e1/pdj/ConsoleStream.java | 35 + src/java/com/e1/pdj/ConsoleStreamWin32.java | 34 + src/java/com/e1/pdj/GenericCompiler.java | 71 ++ src/java/com/e1/pdj/JavacCompiler.java | 34 + src/java/com/e1/pdj/JikesCompiler.java | 27 + src/java/com/e1/pdj/PDJClassLoader.java | 206 +++++ src/java/com/e1/pdj/PDJClassLoaderException.java | 17 + src/java/com/e1/pdj/PDJError.java | 15 + src/java/com/e1/pdj/PDJSystem.java | 142 ++++ src/java/com/e1/pdj/PriorityQueue.java | 68 ++ src/java/com/e1/pdj/test/AtomTest.java | 92 ++ src/java/com/e1/pdj/test/CallbackTest.java | 10 + src/java/com/e1/pdj/test/MaxQelemTest.java | 7 + src/java/help_class.java | 26 + src/java/panner.java | 37 + src/java/pdj_test_class.java | 72 ++ src/java/pdj_test_tilde.java | 45 + src/java/pdj_tilde.java | 45 + src/pdj-linux.c | 91 ++ src/pdj-osx.c | 37 + src/pdj-win32.c | 107 +++ src/pdj.c | 396 +++++++++ src/pdj.h | 140 ++++ src/pdj~.c | 206 +++++ src/type_handler.c | 151 ++++ src/type_handler.h | 18 + www/index.html | 49 ++ www/pdj-help.png | Bin 0 -> 23617 bytes www/pdj-logo.png | Bin 0 -> 1420 bytes www/stylesheet.css | 35 + 77 files changed, 7634 insertions(+) create mode 100644 .cdtproject create mode 100644 .project create mode 100644 .settings/org.eclipse.cdt.core.prefs create mode 100644 LICENSE create mode 100644 Linux-build.properties create mode 100644 Mac OS X-build.properties create mode 100644 Makefile create mode 100644 README create mode 100644 Windows XP-build.properties create mode 100644 build.properties create mode 100644 build.xml create mode 100644 lib/ant/cpptasks.jar create mode 100644 res/help-pdj.pd create mode 100644 res/pdj-test.pd create mode 100644 res/pdj.properties create mode 100644 res/pdj~-panner.pd create mode 100644 res/pdj~-test.pd create mode 100644 src/MSPBuffer.c create mode 100644 src/MaxClock.c create mode 100644 src/MaxObject.c create mode 100644 src/MaxSystem.c create mode 100644 src/init.c create mode 100644 src/java/com/cycling74/io/ErrorStream.java create mode 100644 src/java/com/cycling74/io/PostStream.java create mode 100644 src/java/com/cycling74/max/Atom.java create mode 100644 src/java/com/cycling74/max/AtomFloat.java create mode 100644 src/java/com/cycling74/max/AtomString.java create mode 100644 src/java/com/cycling74/max/Attribute.java create mode 100644 src/java/com/cycling74/max/Callback.java create mode 100644 src/java/com/cycling74/max/DataTypes.java create mode 100644 src/java/com/cycling74/max/Executable.java create mode 100644 src/java/com/cycling74/max/MaxClock.java create mode 100644 src/java/com/cycling74/max/MaxContext.java create mode 100644 src/java/com/cycling74/max/MaxObject.java create mode 100644 src/java/com/cycling74/max/MaxPatcher.java create mode 100644 src/java/com/cycling74/max/MaxQelem.java create mode 100644 src/java/com/cycling74/max/MaxRuntimeException.java create mode 100644 src/java/com/cycling74/max/MaxSystem.java create mode 100644 src/java/com/cycling74/max/MessageReceiver.java create mode 100644 src/java/com/cycling74/max/package.html create mode 100644 src/java/com/cycling74/msp/AudioFileBuffer.java create mode 100644 src/java/com/cycling74/msp/MSPBuffer.java create mode 100644 src/java/com/cycling74/msp/MSPObject.java create mode 100644 src/java/com/cycling74/msp/MSPPerformable.java create mode 100644 src/java/com/cycling74/msp/MSPPerformer.java create mode 100644 src/java/com/cycling74/msp/MSPSignal.java create mode 100644 src/java/com/cycling74/msp/package.html create mode 100644 src/java/com/e1/pdj/ConsoleStream.java create mode 100644 src/java/com/e1/pdj/ConsoleStreamWin32.java create mode 100644 src/java/com/e1/pdj/GenericCompiler.java create mode 100644 src/java/com/e1/pdj/JavacCompiler.java create mode 100644 src/java/com/e1/pdj/JikesCompiler.java create mode 100644 src/java/com/e1/pdj/PDJClassLoader.java create mode 100644 src/java/com/e1/pdj/PDJClassLoaderException.java create mode 100644 src/java/com/e1/pdj/PDJError.java create mode 100644 src/java/com/e1/pdj/PDJSystem.java create mode 100644 src/java/com/e1/pdj/PriorityQueue.java create mode 100644 src/java/com/e1/pdj/test/AtomTest.java create mode 100644 src/java/com/e1/pdj/test/CallbackTest.java create mode 100644 src/java/com/e1/pdj/test/MaxQelemTest.java create mode 100644 src/java/help_class.java create mode 100644 src/java/panner.java create mode 100644 src/java/pdj_test_class.java create mode 100644 src/java/pdj_test_tilde.java create mode 100644 src/java/pdj_tilde.java create mode 100644 src/pdj-linux.c create mode 100644 src/pdj-osx.c create mode 100644 src/pdj-win32.c create mode 100644 src/pdj.c create mode 100644 src/pdj.h create mode 100644 src/pdj~.c create mode 100644 src/type_handler.c create mode 100644 src/type_handler.h create mode 100644 www/index.html create mode 100644 www/pdj-help.png create mode 100644 www/pdj-logo.png create mode 100644 www/stylesheet.css diff --git a/.cdtproject b/.cdtproject new file mode 100644 index 0000000..d299513 --- /dev/null +++ b/.cdtproject @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + +make +all +false +true + + +make +all +false +true + + +make +test +false +true + + +make +clean +false +true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..a6a2067 --- /dev/null +++ b/.project @@ -0,0 +1,125 @@ + + + pdj + + + + + + org.eclipse.cdt.make.core.makeBuilder + clean,full,incremental, + + + org.eclipse.cdt.core.errorOutputParser + org.eclipse.cdt.core.MakeErrorParser;org.eclipse.cdt.core.GCCErrorParser;org.eclipse.cdt.core.GASErrorParser;org.eclipse.cdt.core.GLDErrorParser;org.eclipse.cdt.core.VCErrorParser; + + + org.eclipse.cdt.make.core.fullBuildTarget + clean all + + + org.eclipse.cdt.make.core.incrementalBuildTarget + all + + + org.eclipse.cdt.make.core.enableAutoBuild + false + + + org.eclipse.cdt.make.core.buildLocation + + + + org.eclipse.cdt.make.core.environment + + + + org.eclipse.cdt.make.core.enableFullBuild + true + + + org.eclipse.cdt.make.core.build.target.inc + all + + + org.eclipse.cdt.make.core.enabledIncrementalBuild + true + + + org.eclipse.cdt.make.core.build.target.clean + clean + + + org.eclipse.cdt.make.core.enableCleanBuild + true + + + org.eclipse.cdt.make.core.cleanBuildTarget + clean + + + org.eclipse.cdt.make.core.append_environment + true + + + org.eclipse.cdt.make.core.useDefaultBuildCmd + true + + + org.eclipse.cdt.make.core.buildCommand + make + + + org.eclipse.cdt.make.core.autoBuildTarget + all + + + org.eclipse.cdt.make.core.stopOnError + false + + + org.eclipse.cdt.make.core.build.target.auto + all + + + + + org.eclipse.cdt.make.core.ScannerConfigBuilder + + + org.eclipse.cdt.make.core.ScannerConfigDiscoveryEnabled + true + + + org.eclipse.cdt.make.core.makeBuilderParserId + org.eclipse.cdt.make.core.GCCScannerInfoConsoleParser + + + org.eclipse.cdt.make.core.esiProviderCommandEnabled + true + + + org.eclipse.cdt.make.core.siProblemGenerationEnabled + true + + + org.eclipse.cdt.make.core.useDefaultESIProviderCmd + true + + + org.eclipse.cdt.make.core.makeBuilderParserEnabled + true + + + org.eclipse.cdt.make.core.esiProviderParserId + org.eclipse.cdt.make.core.GCCSpecsConsoleParser + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.make.core.makeNature + org.eclipse.cdt.make.core.ScannerConfigNature + + diff --git a/.settings/org.eclipse.cdt.core.prefs b/.settings/org.eclipse.cdt.core.prefs new file mode 100644 index 0000000..d57d8bd --- /dev/null +++ b/.settings/org.eclipse.cdt.core.prefs @@ -0,0 +1,3 @@ +#Mon Jul 03 15:02:11 EDT 2006 +eclipse.preferences.version=1 +indexerId=org.eclipse.cdt.core.fastIndexer diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fd043d8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2004-2007, Pascal Gauthier +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/Linux-build.properties b/Linux-build.properties new file mode 100644 index 0000000..12b5a90 --- /dev/null +++ b/Linux-build.properties @@ -0,0 +1,15 @@ +# property file for Linux + +# put the path of where the jdk (java sdk) is installed +jdk.home=/usr/local/java + +# put the path of where pd is installed +pd.home=/home/asb2m10/pd-0.39-0 + +# common linux properties +isLinux=true +compiler=gcc +linker.type=shared +platform=linux +jdk.libs=${jdk.home}/jre/lib/i386 +pdj.outfile=${dist.dir}/pdj \ No newline at end of file diff --git a/Mac OS X-build.properties b/Mac OS X-build.properties new file mode 100644 index 0000000..95e4761 --- /dev/null +++ b/Mac OS X-build.properties @@ -0,0 +1,16 @@ +# properties file for OSX. + +# put the root of your pd path installation +pd.home=/Applications/pd.app/Contents/Resources + +# set this to 'pd_darwin' if you are building for PowerPC +pdj.archBuild=d_fat + +# common OS X properties +isOSX=true +compiler=gcc +linker.type=plugin +platform=osx +jdk.libs=/System/Library/Frameworks/JavaVM.framework/Libraries +jdk.home=/Library/Java/Home +pdj.outfile=${dist.dir}/pdj.${pdj.archBuild} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d34f915 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +# check out the build.properties / {your platform}.properties / build.xml +# for path settings + +all: + ant package + +clean: + ant clean + +test: + ant test diff --git a/README b/README new file mode 100644 index 0000000..5737e2a --- /dev/null +++ b/README @@ -0,0 +1,174 @@ +########################################################################### +pdj - java plugin interface to pure-data +########################################################################### + +RELEASE 0.8.3 / September 2007 + + +PDJ enables you to write java code to interact with pure-data objects. The +API is totally based on Cycling74 Max/MSP 'mxj' object implementation. This +will enable java mxj objects to run on pure-data with pdj. You can also +create signal/tilde (~) objects. + +This is a work in progress and some of features are missing and some might +not even be ported. + +IMPLEMENTED: + + * MaxClock 100% + * MSPBuffer 100% + * Callback 100% + * MaxObject 100% + * Atom 98% + * MaxSystem 90% + * MaxQelem 90% + * MSPObject 85% (missing signal in/outlet detection) + * MSPPerformable 100% + * MaxPatcher 5% + +TODO: + + * MSPObject connection status + * MaxBox/MaxPatcher object; inspiration from dyn~ (kudo Mr. Grill!) + +LIMITATION: + + * Signal inlets cannot be hot inlets for receiving atom message. + This means that if you create a signal inlet and send an atom + to this inlet, pdj won't be able to process it; float or symbol. + This looks like a work as designed in pd. + +BUGS: + + * OS X: cannot use AWT classes with OS X because of a weird bug + that locks the thread when the GUI libraries are loaded. + -> http://lists.apple.com/archives/java-dev/2004/Mar/msg00910.html + this solution has been tested but the loadLibrary still hang. + +WORKAROUNDS: + + * on most cases you don't have to put the JVM libs dir to the + LD_LIBRARY_PATH. If you have a UnsatisfiedLinkError, add it + before you start PD. + * on some machines, the System.out redirection can crash PD. If + it is the case, you can disable it in the pdj.properties file; + check the property: pdj.redirect-pdio. + +REQUIEREMENTS: + + * pure-data 0.38.x or better + * java JDK version 1.4.x or better; use 1.5 if you can ! + * works on linux, windows + +REQUIEREMENTS FOR BUILDING: + + * apache ant 1.6.x or better + * c compiler + +INSTALLATION: + + * if you are using the source distribution, build it before + -> download java sdk from java.sun.com (unless you are using OS X) + -> download apache ant for ant.apache.org + -> edit file -build.properties + -> in the root directory of pdj, run 'ant package' + * add the pdj external (pdj.pd_linux/pd.dll/pd.pd_dawrin) directory + to your pd path (very important on OS X) + * the other files of the original binary directory must be in + the same directory + * double check dist/pdj.properties to be sure that the JAVA + environment parameters are right + * === OS X WARNING ==== + I am working on a universal binary of pdj. This is planned on version + 0.8.4. If you really need a OS X build, email me. + +USAGE: + + * put your .java file in the /classes directory + * create a pdj object with the name of the java class; if + you have not compiled it before, pdj will do it for you + +CHANGELOG: + --- VERSION 0.8.3 --- + * Atom.getInt() on a float now works. + * corrected some classpath definition issues with windows + * bypass the java compilation with pdj.compiler=null + * remove dependencies task for cpptasks.jar (now part of distribution) + * FIXED: comment on property pdj.vm_args failed to initialize VM + * BUG: cannot link with OS X and cpptasks. Planned on 0.8.4 + + --- VERSION 0.8.2 --- + * if javac is set, using the javac compiler from the JAVA_HOME first + * optimizations (main thread JVM and symbol method resolution) + * FIXED: using pdj.classpath with directories + * FIXED: search path on file in the current directory + * FIXED: leak with open_path and casting warnings + * build for Intel Mac + * getting better at attribute support + + --- VERSION 0.8.1 --- + * added MaxPatcher object to get patch path + * FIXED: to match mxj: method with args[] will always be called first + + --- VERSION 0.8 --- + * pdj~ object support (MSPSignal/MSPObject/MSPPerformable) + * The JVM can now be server or in client mode (windows/linux only) + * JVM selection for OS X + * FIXED: working jikes compiler on OS X + * FIXED: declareIO inlets count + * FIXED: windows: System.out CR/LF errors on the PD console + + --- VERSION 0.7.1 --- + * OS X support with distribution + * FIXED: MaxSystem.locateFile now works + + --- VERSION 0.7 --- + * Better JVM resolution on win32 (eg: with the registry) + * Full implementation of MSPBuffer + * Added Atom methods with unit tests + * Windows binaries distribution + * FIXED: now filtering { and } with post() not to lock PD + * FIXED: anything gets called on a undefined bang/float/list method + * FIXED: defined inlet(int) without inlet(float) now works... :-\ + + --- VERSION 0.6.1 --- + * FIXED: outlet(Atom) and outlet(Atom[]) resolution error + + --- VERSION 0.6 --- + * MaxObject constructor with arguments + * partial attribute support; automatic field setter/getter + * added method bail on MaxObject + * strict declaration of inlets/outlets (with info outlet) + * System.out and System.err linked to the pd console + * outlet(atom[]) checks type before calling the right func. + + --- VERSION 0.5 --- + * method inlet resolution (bang->float->list->anything) now works + * windows support (premilinary support) + * auto-compiling class of every .java in /class (max -1; pdj +1) + + --- VERSION 0.4 --- + * more javadoc and documentation... but could be better + * multiple inlet operations + * loadbang now works + + --- VERSION 0.3 --- + * added a small and cheap javadoc. the original MXJ should be the + reference. + * partial MSPBuffer support + * Atom and MaxObject now supports Atom arrays + + --- VERSION 0.2 --- + * added classpath of $PDJ_HOME/classes and $PDJ_HOME/lib/* by default + * 95 % Dynamic JVM resolution with the JAVA_HOME; via dlsym (see BUGS) + * MaxSystem.deferLow(...) works ! :-\ + * added an help patch + +THANKS: + + * Thomas Grill + * patrick a 11h11 + * pd-mtl crew ! + +(c) Pascal Gauthier 2004-2007, under BSD like license +asb2m10@users.sourceforge.net diff --git a/Windows XP-build.properties b/Windows XP-build.properties new file mode 100644 index 0000000..0d777bf --- /dev/null +++ b/Windows XP-build.properties @@ -0,0 +1,14 @@ +# properties file for windows + +# put the path of where the jdk (java sdk) is installed +jdk.home=C:/Java/jdk1.6.0_02 + +# put the path of where pd is installed +pd.home=C:/Projets/pd +# common windows properties +isWin32=true +compiler=msvc +linker.type=shared +platform=win32 +jdk.libs=${jdk.home}/jre/lib/i386 +pdj.outfile=${dist.dir}/pdj \ No newline at end of file diff --git a/build.properties b/build.properties new file mode 100644 index 0000000..e28db31 --- /dev/null +++ b/build.properties @@ -0,0 +1,21 @@ +# + +# PDJ packaging version +# ========================================================================= +pdj.version=0.8.3 + +# Various path definition +# ========================================================================= +src.dir=src +java.src.dir=src/java +work.dir=work +www.dir=www +javadoc.dir=${www.dir}/api +classes.dir=${work.dir}/classes +dist.dir=dist +javah.file=${work.dir}/native_classes.h +pdj.jar=${dist.dir}/pdj.jar +pdj.test-patch=pdj-test.pd +pdj-tilde.test-patch="pdj~-test.pd" + + diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..a1414c7 --- /dev/null +++ b/build.xml @@ -0,0 +1,250 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The MXJ Java API for pure-data +

This API is based on MXJ for Max/MSP.

]]>
+ + Max/MSP by Cycling74. Please see original MXJ + implementation.]]> +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/lib/ant/cpptasks.jar b/lib/ant/cpptasks.jar new file mode 100644 index 0000000..1b6e6ad Binary files /dev/null and b/lib/ant/cpptasks.jar differ diff --git a/res/help-pdj.pd b/res/help-pdj.pd new file mode 100644 index 0000000..c325441 --- /dev/null +++ b/res/help-pdj.pd @@ -0,0 +1,63 @@ +#N canvas 338 130 812 533 12; +#X text 17 7 pdj - JAVA PLUGIN EXTERNAL; +#X obj 18 327 pdj help_class; +#X text 16 49 The pdj object enables you to write Java external with +Max/MSP API mxj.; +#X obj 18 410 nbx 5 14 -1e+37 1e+37 0 0 empty empty empty 0 -6 0 10 +-262144 -1 -1 0 256; +#X floatatom 18 97 5 0 0 0 - - -; +#X text 81 95 <-- the method inlet(float) will be called; +#X text 230 204 <-- the method callme(Atom []) will be called; +#X msg 50 241 dynamic_method; +#X text 203 238 <-- the method dynamic_method() will be called; +#X msg 61 276 idontexists 10; +#X text 222 275 <-- since idontexists is not in help_class \, an error +will be thrown; +#X msg 37 204 callme hola ouain; +#X text 73 162 symbols sended to the java class will be resolved by +the method name defined in the user class.; +#X obj 32 126 bng 30 250 50 0 empty empty empty 0 -6 0 8 -262144 -1 +-1; +#X text 80 132 <-- the method bang() will be called; +#X text 16 25 ====================================================== +; +#X obj 139 373 print help_class outlet1; +#N canvas 297 319 715 331 attributes 0; +#X obj 19 229 pdj help_class @attr1 10; +#X msg 39 127 get attr1; +#X msg 19 92 attr1 \$1; +#X floatatom 19 66 5 0 0 0 - - -; +#X obj 254 260 print info-outlet; +#X text 144 126 <-- using get with attribute name will output the value +using the info outlet (the last outlet); +#X text 18 8 Attributes are used to access fields in a class. To do +this \, you can use the method declareAttribute in the constructor. +; +#X text 109 69 <-- calling the attribute with his name will set the +attribute(field) value; +#X text 48 172 you can also declare a attribute within a class by using +@ in the constructor arguments. You must also provide a initial value +to this field.; +#X text 17 288 attribute value will always be sended to the last outlet +of the object that is called the info-outlet.; +#X connect 0 2 4 0; +#X connect 1 0 0 0; +#X connect 2 0 0 0; +#X connect 3 0 2 0; +#X restore 656 40 pd attributes; +#X text 652 7 *** see also :; +#X text 169 321 first argument is the classname to find in classpath. +additionnal arguments are given to the class constructor.; +#X text 682 504 VERSION 0.8; +#X text 99 406 see help_class.java in classes folder to see how this +example is handled.; +#X text 13 454 CLASSPATH INFORMATION : by default \, (whereis pdj)/lib/*.jar +and /classes is added to the classpath. edit pdj.properties to add +more .jar or directories to the pdj or system classpath; +#X connect 1 0 3 0; +#X connect 1 1 16 0; +#X connect 4 0 1 0; +#X connect 7 0 1 0; +#X connect 9 0 1 0; +#X connect 11 0 1 0; +#X connect 13 0 1 0; diff --git a/res/pdj-test.pd b/res/pdj-test.pd new file mode 100644 index 0000000..7622c67 --- /dev/null +++ b/res/pdj-test.pd @@ -0,0 +1,42 @@ +#N canvas 765 257 450 295 10; +#X obj 23 187 pdj pdj_test_class; +#X obj 39 157 bng 15 250 50 0 empty empty empty 0 -6 0 8 -262144 -1 +-1; +#X obj 254 47 r allo; +#X floatatom 263 98 5 0 0 0 - - -; +#X obj 326 93 print xc; +#X msg 82 160 wer wer 1; +#X obj 135 34 metro 300; +#X obj 170 3; +#X obj 127 9 bng 15 250 50 0 empty empty empty 0 -6 0 8 -262144 -1 +-1; +#X floatatom 218 11 5 0 0 0 - - -; +#X obj 30 246 print outlet_1; +#N canvas 0 0 450 300 graph1 0; +#X array array_tester 100 float 1; +#A 0 0 0.585717 0.528574 0.471431 0.442859 0.485716 0.585717 0.528574 +0.257144 0.385716 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; +#X coords 0 1 99 -1 200 140 1; +#X restore 220 143 graph; +#X msg 25 117 testle; +#X msg 21 11 sizeArray 10; +#X msg 29 50 sizeArray 100; +#X msg 169 68 get patate; +#X msg 161 116 set patate 1; +#X obj 105 222 print setter; +#X connect 0 0 10 0; +#X connect 0 1 17 0; +#X connect 1 0 0 0; +#X connect 2 0 3 0; +#X connect 2 0 4 0; +#X connect 5 0 0 0; +#X connect 6 0 5 0; +#X connect 8 0 6 0; +#X connect 9 0 6 1; +#X connect 12 0 0 0; +#X connect 13 0 0 0; +#X connect 14 0 0 0; +#X connect 15 0 0 0; +#X connect 16 0 0 0; diff --git a/res/pdj.properties b/res/pdj.properties new file mode 100644 index 0000000..1f00908 --- /dev/null +++ b/res/pdj.properties @@ -0,0 +1,81 @@ +# pdj.properties +# +# this property file is not 100% java properties aware (like: \\) so +# pay special attention +# +# all parameters from this properties file will be copied to the java +# system properties +# ========================================================================= + +# by default the pdj.jar (where the pdj.pd_linux is located) will be added +# to the system classpath +# +# add your jars or directories of jars seperated with a ':' for unix and +# ';' for windows +# +pdj.system-classpath=/home/asb2m10/workspace/mxdublin/work/mxdublin.jar + +# by default from where the pdj.pd_linux is located, every .jar in +# ${pdj.home}/lib will be added to the pdj classpath +# +# add you jars or directories of jars seperated with a ':' for unix and +# ';' for windows +# +# this is the pdj classpath (dynamic) to set : +pdj.classpath= + +# this will print all .jars that are loaded before compiling/loading the +# user class +pdj.verbose-classloader=true + +# the type of compiler to use with the pdj classloader. use only 'javac' +# or 'jikes'. do not put full path to your compiler +# +# to disable the automatic compilation simply use +# pdj.compiler=null +# +pdj.compiler=javac + +# pdj compiler/classloader directory. by default, ${pdj.home}/classes +# directory is used if this property is not defined. Before compiling, pdj +# will check if your .java needs to be compiled. +# +# pdj.classes-dir=[your working directory] + +# this will redirect the java out/err streams to the pd console. If you need +# to log pdj errors and exceptions to stderr and stdout, set this to false. +# +pdj.redirect-pdio=true + +# the jvm to use. If it is not found and the system is on Windows, +# the jvm installed with the registry will be used. Not used on OS X +# +pdj.JAVA_HOME=/usr/local/java + +# the JVM version to use with OS X; linked to the Java Framework on OS X +# +pdj.osx.JVM_JAVA_VERSION=1.5.0 + +# NOTE: pdj.home is set automagicly from where the pdj.pd_linux or pdj.dll +# is installed (you need to put it in your pd path in OS X) + +# ========================================================================= +# VM ARGUMENTS +# +# to monitor memory and thread usage with 1.5 (jconsole) use +# -Dcom.sun.management.jmxremote +# to do remote debugging at port 3999 +# -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=3999 +# to check jni calls +# -Xcheck:jni -verbose:jni +# +# All arguments on the same line just like $ java -X... -X... myclass +# +pdj.vm_args=-Xincgc -Xms32m -Xmx256m + +# tells pdj what type of jvm it must use: client or server. Not used on +# OS X. client has lower starting time and can take less memory. server +# starts slower and takes more memory, but once the code is executed, +# it will be faster than in client mode. +# +pdj.vm_type=server diff --git a/res/pdj~-panner.pd b/res/pdj~-panner.pd new file mode 100644 index 0000000..9c16e2c --- /dev/null +++ b/res/pdj~-panner.pd @@ -0,0 +1,10 @@ +#N canvas 0 0 450 300 10; +#X obj 54 90 pdj~ panner; +#X obj 126 48 nbx 5 14 0 127 0 0 empty empty empty 0 -6 0 10 -262144 +-1 -1 0 256; +#X obj 54 48 osc~ 440; +#X obj 55 136 dac~; +#X connect 0 0 3 0; +#X connect 0 1 3 1; +#X connect 1 0 0 1; +#X connect 2 0 0 0; diff --git a/res/pdj~-test.pd b/res/pdj~-test.pd new file mode 100644 index 0000000..f7e1c18 --- /dev/null +++ b/res/pdj~-test.pd @@ -0,0 +1,42 @@ +#N canvas 699 369 461 376 10; +#N canvas 0 0 450 300 graph1 0; +#X array signal-debug 441 float 0; +#X coords 0 1.02 440 -1.02 400 150 1; +#X restore 17 -91 graph; +#X obj 18 -264 osc~ 440; +#X obj 17 -183 *~; +#X obj 49 -182 *~; +#X obj 33 -208 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X obj 65 -208 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X obj 17 -143 tabwrite~ signal-debug; +#X obj 202 -185 metro 250; +#X obj 202 -208 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X obj 81 -182 *~; +#X obj 97 -208 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0 +1; +#X obj 84 -264 phasor~ 440; +#X obj 172 -264 osc~ 880; +#X obj 246 -255 nbx 5 14 0.01 1 1 0 empty empty empty 0 -6 0 10 -262144 +-1 -1 0.01 256; +#X obj 184 -229 bng 15 250 50 0 empty empty empty 0 -6 0 8 -262144 +-1 -1; +#X obj 18 -230 pdj~ pdj_test_tilde; +#X connect 1 0 15 0; +#X connect 2 0 6 0; +#X connect 3 0 6 0; +#X connect 4 0 2 1; +#X connect 5 0 3 1; +#X connect 7 0 6 0; +#X connect 8 0 7 0; +#X connect 9 0 6 0; +#X connect 10 0 9 1; +#X connect 11 0 15 1; +#X connect 12 0 15 2; +#X connect 13 0 15 3; +#X connect 14 0 15 3; +#X connect 15 0 2 0; +#X connect 15 1 3 0; +#X connect 15 2 9 0; diff --git a/src/MSPBuffer.c b/src/MSPBuffer.c new file mode 100644 index 0000000..084fdf3 --- /dev/null +++ b/src/MSPBuffer.c @@ -0,0 +1,159 @@ +#include "native_classes.h" +#include "type_handler.h" +#include "pdj.h" + + +static t_garray *getArray(JNIEnv *env, jstring name) { + t_symbol *s = jstring2symbol(env, name); + t_garray *ret = (t_garray *)pd_findbyclass(s, garray_class); + + if ( ret == NULL ) { + post("pdj: array %s not found", s->s_name); + return NULL; + } + + return ret; +} + + +JNIEXPORT jlong JNICALL Java_com_cycling74_msp_MSPBuffer_getSize + (JNIEnv *env, jclass cls, jstring name) { + t_garray *array = getArray(env, name); + + if ( array == NULL ) + return -1; + + return garray_npoints(array); +} + + +JNIEXPORT jfloatArray JNICALL Java_com_cycling74_msp_MSPBuffer_peek + (JNIEnv *env , jclass cls, jstring name) { + t_garray *array = getArray(env, name); + t_sample *vec; + jfloatArray ret; + int size; + + if ( array == NULL ) + return NULL; + + if ( ! garray_getfloatarray(array, &size, &vec) ) + return NULL; + + ret = (*env)->NewFloatArray(env, size); + + if ( ret == NULL ) { + SHOWEXC; + return NULL; + } + + (*env)->SetFloatArrayRegion(env, ret, 0, size, (jfloat *) vec); + + return ret; +} + + +JNIEXPORT void JNICALL Java_com_cycling74_msp_MSPBuffer_poke + (JNIEnv *env, jclass cls, jstring name, jfloatArray values) { + t_garray *array = getArray(env, name); + t_sample *vec; + jsize jarray_length; + int size; + + if ( array == NULL ) + return; + + if ( ! garray_getfloatarray(array, &size, &vec) ) + return; + + jarray_length = (*env)->GetArrayLength(env, values); + + if ( jarray_length < size ) { + error("pdj: warning: array too short"); + } else { + size = jarray_length; + } + + (*env)->GetFloatArrayRegion(env, values, 0, size, vec); + garray_redraw(array); +} + + +JNIEXPORT void JNICALL Java_com_cycling74_msp_MSPBuffer_setSize + (JNIEnv *env, jclass cls, jstring name, jint channel, jlong size) { + t_garray *array = getArray(env, name); + + if ( array == NULL ) + return; + + garray_resize(array, size); +} + + +JNIEXPORT jfloatArray JNICALL Java_com_cycling74_msp_MSPBuffer_getArray + (JNIEnv *env, jclass cls, jstring name, jlong from, jlong arraySize) { + t_garray *array = getArray(env, name); + t_sample *vec; + jfloatArray ret; + int size, ifrom = (int) from, iarraySize = (int) arraySize; + + if ( array == NULL ) + return NULL; + + if ( ! garray_getfloatarray(array, &size, &vec) ) + return NULL; + + if ( iarraySize != -1 ) { + if ( size < ifrom ) { + error("pdj: array is shorter than the starting point"); + return NULL; + } + + if ( size < ifrom + iarraySize ) { + error("pdj: array is not big enough to fill the array"); + return NULL; + } + size = iarraySize; + } + + ret = (*env)->NewFloatArray(env, size); + if ( ret == NULL ) { + SHOWEXC; + return NULL; + } + + vec += ifrom; + (*env)->SetFloatArrayRegion(env, ret, 0, size, (jfloat *) vec); + + return ret; +} + + +JNIEXPORT void JNICALL Java_com_cycling74_msp_MSPBuffer_setArray + (JNIEnv *env, jclass cls, jstring name, jlong from, jfloatArray values) { + t_garray *array = getArray(env, name); + t_sample *vec; + jsize jarray_length; + int size, ifrom = from; + + if ( array == NULL ) + return; + + if ( ! garray_getfloatarray(array, &size, &vec) ) + return; + + if ( size < from ) { + error("pdj: array too short from starting point"); + return; + } + jarray_length = (*env)->GetArrayLength(env, values); + + if ( jarray_length + ifrom > size ) { + error("pdj: warning: array too short from java array size"); + return; + } + + vec += from; + (*env)->GetFloatArrayRegion(env, values, 0, jarray_length, vec); + garray_redraw(array); +} diff --git a/src/MaxClock.c b/src/MaxClock.c new file mode 100644 index 0000000..e7e5886 --- /dev/null +++ b/src/MaxClock.c @@ -0,0 +1,74 @@ +#include +#include "native_classes.h" +#include "type_handler.h" +#include "pdj.h" + +typedef struct _clockCtnr { + jobject instance; + jmethodID tick; + t_clock *pd_clock; +} t_clockCtnr; + + +static t_clockCtnr *getClock(JNIEnv *env, jobject obj) { + return (t_clockCtnr *) (*env)->GetLongField(env, obj, pdjCaching.FIDMaxClock_clock_ptr); +} + + +void clock_callback(t_clockCtnr *clk) { + JNIEnv *env = pdjAttachVM(); + JASSERT(clk->instance); + + (*env)->CallVoidMethod(env, clk->instance , clk->tick, NULL); + pdjDetachVM(env); +} + + +JNIEXPORT jdouble JNICALL Java_com_cycling74_max_MaxClock_getTime + (JNIEnv *env, jclass cls) { + return sys_getrealtime() * 1000; +} + + +JNIEXPORT void JNICALL Java_com_cycling74_max_MaxClock_create_1clock + (JNIEnv *env, jobject obj) { + jclass cls = (*env)->GetObjectClass(env, obj); + t_clockCtnr *clk; + + clk = malloc(sizeof(t_clockCtnr)); + ASSERT(clk); + + clk->pd_clock = clock_new(clk, (t_method) clock_callback); + (*env)->SetLongField(env, obj, pdjCaching.FIDMaxClock_clock_ptr, (long) clk); + + clk->instance = (*env)->NewGlobalRef(env, obj); + JASSERT(clk->instance); + clk->tick = (*env)->GetMethodID(env, cls, "tick", "()V"); + JASSERT(clk->tick); +} + + +JNIEXPORT void JNICALL Java_com_cycling74_max_MaxClock_delay + (JNIEnv *env, jobject obj, jdouble value) { + t_clockCtnr *clk = getClock(env, obj); + clock_delay(clk->pd_clock, value); +} + + +JNIEXPORT void JNICALL Java_com_cycling74_max_MaxClock_unset + (JNIEnv *env, jobject obj) { + t_clockCtnr *clk = getClock(env, obj); + clock_unset(clk->pd_clock); +} + + +JNIEXPORT void JNICALL Java_com_cycling74_max_MaxClock_release + (JNIEnv *env, jobject obj) { + t_clockCtnr *clk = getClock(env, obj); + clock_unset(clk->pd_clock); + clock_free(clk->pd_clock); + free(clk); + + (*env)->SetObjectField(env, obj, pdjCaching.FIDMaxClock_clock_ptr, 0); + (*env)->DeleteGlobalRef(env, obj); +} diff --git a/src/MaxObject.c b/src/MaxObject.c new file mode 100644 index 0000000..3c77f49 --- /dev/null +++ b/src/MaxObject.c @@ -0,0 +1,131 @@ +#include "native_classes.h" +#include "type_handler.h" +#include "pdj.h" + + +static t_pdj *getMaxObject(JNIEnv *env, jobject obj) { + t_pdj *ret = (t_pdj *) (*env)->GetLongField(env, obj, + pdjCaching.FIDMaxObject_pdobj_ptr); + + if ( ret == NULL ) + error("pdj: using a native method without pd context"); + + return ret; +} + + +JNIEXPORT jlong JNICALL Java_com_cycling74_max_MaxObject_newInlet + (JNIEnv *env, jobject obj, jint type) { + t_pdj *pdj = getMaxObject(env, obj); + t_inlet_proxy *proxy; + + if ( pdj == NULL ) + return 0; + if ( type == com_cycling74_msp_MSPObject_SIGNAL ) { + inlet_new(&pdj->x_obj, &pdj->x_obj.ob_pd, &s_signal, 0); + return 0; + } + + proxy = (t_inlet_proxy *) pd_new(inlet_proxy); + pdj->nb_inlet++; + proxy->idx = pdj->nb_inlet; + proxy->peer = pdj; + + return (jlong) inlet_new(&pdj->x_obj, &proxy->x_obj.ob_pd, 0, 0); +} + + +JNIEXPORT jlong JNICALL Java_com_cycling74_max_MaxObject_newOutlet + (JNIEnv *env, jobject obj, jint type) { + t_pdj *pdj = getMaxObject(env, obj); + + if ( pdj == NULL ) + return 0; + + if ( type == com_cycling74_msp_MSPObject_SIGNAL ) { + outlet_new(&pdj->x_obj, &s_signal); + return 0; + } + + return (jlong) outlet_new(&pdj->x_obj, NULL); +} + + +JNIEXPORT void JNICALL Java_com_cycling74_max_MaxObject_doOutletBang + (JNIEnv *env, jobject obj, jlong outlet) { + t_outlet *x = (t_outlet *) ((unsigned int) outlet); + outlet_bang(x); +} + + +JNIEXPORT void JNICALL Java_com_cycling74_max_MaxObject_doOutletFloat + (JNIEnv *env, jobject obj, jlong outlet , jfloat value) { + t_outlet *x = (t_outlet *) ((unsigned int) outlet); + outlet_float(x, value); +} + + +JNIEXPORT void JNICALL Java_com_cycling74_max_MaxObject_doOutletSymbol + (JNIEnv *env, jobject obj, jlong outlet, jstring value) { + t_outlet *x = (t_outlet *) ((unsigned int) outlet); + outlet_symbol(x, jstring2symbol(env, value)); +} + + +JNIEXPORT void JNICALL Java_com_cycling74_max_MaxObject_doOutletAnything + (JNIEnv *env, jobject obj, jlong outlet, jstring str, jobjectArray value) { + t_outlet *x = (t_outlet *) ((unsigned int) outlet); + t_atom args[MAX_ATOMS_STACK]; + int argc; + + jatoms2atoms(env, value, &argc, args); + if ( str == NULL ) { + if ( args[0].a_type == A_FLOAT ) { + outlet_anything(x, &s_list, argc, args); + } else { + t_symbol *sym = atom_getsymbol(&(args[0])); + outlet_anything(x, sym, argc-1, args+1); + } + } else { + outlet_anything(x, jstring2symbol(env, str), argc, args); + } +} + +JNIEXPORT jstring JNICALL Java_com_cycling74_max_MaxObject_getPatchPath + (JNIEnv *env, jobject obj) { + t_pdj *pdj = getMaxObject(env, obj); + + if ( pdj == NULL ) + return NULL; + + return (*env)->NewStringUTF(env, pdj->patch_path); + +} + +// UGLY UGLY UGLY, but this is used not force the user from using +// a constructor. MaxObject CAN be used outside the pdj object +// but all the natives calls will break. Theses methods are always called +// within a synchronized() block. This is ugly, but less ugly than +// putting the information in a ThreadLocals? ... or using a MaxContext ? +// =========================================================================== +static t_pdj *pdjConstructor = NULL; +JNIEXPORT void JNICALL Java_com_cycling74_max_MaxObject_pushPdjPointer + (JNIEnv *env, jclass cls, jlong ptr) { + pdjConstructor = (t_pdj *) ptr; +} + + +JNIEXPORT jlong JNICALL Java_com_cycling74_max_MaxObject_popPdjPointer + (JNIEnv *env, jclass cls) { + t_pdj *tmp; + + // the object has been used instanciated without pdj. Return a null + // pointer. + if ( pdjConstructor == NULL ) { + return 0; + } + tmp = pdjConstructor; + pdjConstructor = NULL; + + return tmp; +} diff --git a/src/MaxSystem.c b/src/MaxSystem.c new file mode 100644 index 0000000..c506fa2 --- /dev/null +++ b/src/MaxSystem.c @@ -0,0 +1,178 @@ +#include "native_classes.h" +#include "type_handler.h" +#include "pdj.h" +#include +#include +#include + +#ifdef MSW + #include +#else + #include +#endif + +/* from m_imp.h... todo: ask Miller ??? */ +EXTERN void outlet_setstacklim(void); + + +/** + * Using a { or } in a post or error will lock PD, we substitute + * them with a ( and ). + */ +static char* removePdAcc(char *str, jboolean isCopy) { + char *work; + + if ( isCopy == JNI_TRUE ) { + work = str; + while(*work != 0) { + switch(*work) { + case '{' : + *work = '('; + break; + case '}' : + *work = ')'; + break; + } + work++; + } + return str; + } + + work = malloc(strlen(str)+1); + while(*str != 0) { + switch(*str) { + case '{' : + *work = '('; + break; + case '}' : + *work = ')'; + break; + default: + *work = *str; + } + work++; + str++; + } + + *work = 0; + return work; +} + + +JNIEXPORT void JNICALL Java_com_cycling74_max_MaxSystem_error + (JNIEnv *env, jclass cls, jstring message) { + jboolean isCopy; + + char *msg = (*env)->GetStringUTFChars(env, message, &isCopy); + msg = removePdAcc(msg, isCopy); + + if ( REDIRECT_PD_IO ) { + error(msg); + } else { + fprintf(stderr, msg); + fprintf(stderr, "\n"); + } + + if ( isCopy == JNI_FALSE ) + free(msg); + + (*env)->ReleaseStringUTFChars(env, message, msg); +} + + +JNIEXPORT void JNICALL Java_com_cycling74_max_MaxSystem_post + (JNIEnv *env, jclass cls, jstring message) { + jboolean isCopy; + + char *msg = (*env)->GetStringUTFChars(env, message, &isCopy); + msg = removePdAcc(msg, isCopy); + + if ( REDIRECT_PD_IO ) { + post(msg); + } else { + fprintf(stdout, msg); + fprintf(stdout, "\n"); + } + + if ( isCopy == JNI_FALSE ) + free(msg); + + (*env)->ReleaseStringUTFChars(env, message, msg); +} + + +JNIEXPORT void JNICALL Java_com_cycling74_max_MaxSystem_ouch + (JNIEnv *env, jclass cls, jstring message) { + jboolean isCopy; + + char *msg = (*env)->GetStringUTFChars(env, message, &isCopy); + fprintf(stderr, msg); + fprintf(stderr, "\n"); + msg = removePdAcc(msg, isCopy); + bug(msg); + + if ( isCopy == JNI_FALSE ) + free(msg); + + (*env)->ReleaseStringUTFChars(env, message, msg); +} + + +JNIEXPORT jboolean JNICALL Java_com_cycling74_max_MaxSystem_sendMessageToBoundObject + (JNIEnv *env, jclass cls, jstring jname, jstring jmsg, jobjectArray jatoms) { + t_symbol *name = jstring2symbol(env, jname); + t_symbol *msg = jstring2symbol(env, jmsg); + t_pd *dest = findPDObject(name); + t_atom argv[MAX_ATOMS_STACK]; + int argc; + + if ( dest == NULL ) { + post("pdj: unable to get object %s for sendMessageToBoundObject", name->s_name); + return 0; + } + + /* reset the stack pointer for pd events */ + outlet_setstacklim(); + + if ( msg == &s_bang ) { + pd_bang(dest); + return 1; + } + + if ( jatoms == NULL ) { + pd_symbol(dest, msg); + return 1; + } + + jatoms2atoms(env, jatoms, &argc, argv); + + if ( msg == &s_float ) { + pd_float(dest, atom_getfloatarg(0, argc, argv)); + return 1; + } + + pd_list(dest, msg, argc, argv); + return 1; +} + + +JNIEXPORT jstring JNICALL Java_com_cycling74_max_MaxSystem_locateFile + (JNIEnv *env, jclass cls, jstring filename) { + const jbyte *file = (*env)->GetStringUTFChars(env, filename, NULL); + char fullpath[MAXPDSTRING], *pfullpath; + FILE *fd; + + fd = open_via_path("", file, "", fullpath, &pfullpath, MAXPDSTRING, 0); + (*env)->ReleaseStringUTFChars(env, filename, file); + if ( fd != NULL ) { + close(fd); + if ( fullpath[0] != 0 ) { + if ( pfullpath == &fullpath ) { + getcwd(fullpath, MAXPDSTRING); + } + return (*env)->NewStringUTF(env, fullpath); + } + } + + return NULL; +} diff --git a/src/init.c b/src/init.c new file mode 100644 index 0000000..4666367 --- /dev/null +++ b/src/init.c @@ -0,0 +1,349 @@ +/** + * This code is very ugly and needs rewrite. PERIOD. + */ + +#include +#include +#include +#include "pdj.h" + +#define MAX_PROPERTIES 40 +char *properties[MAX_PROPERTIES+1][2]; + +char *pdj_getProperty(char *name) { + int i; + + for(i=0; properties[i][0] != NULL; i++) { + if ( !strcmp(properties[i][0], name) ) { + return properties[i][1]; + } + } + return NULL; +} + + +static void load_properties() { + char propPath[BUFFER_SIZE]; + int propIdx = 0; + char *alloc; + FILE *f; + + getuglylibpath(propPath); + + properties[0][0] = "pdj.home"; + alloc = malloc(strlen(propPath)+1); + strcpy(alloc, propPath); + properties[0][1] = alloc; + + strcat(propPath, DIR_SEP "pdj.properties"); + f = fopen(propPath, "r"); + + if ( f == NULL ) { + post("pdj: warning: property file not found at %s", propPath); + return; + } + + while(!feof(f)) { + char buffer[BUFFER_SIZE]; + char *work, *key, *value; + + fgets(buffer, BUFFER_SIZE-1, f); + + work = strchr(buffer, '\n'); + if ( work != 0 ) { + *work = 0; + + if ( work == buffer ) + continue; + } + + /* cuts comments */ + work = strchr(buffer, '#'); + if ( work != NULL ) { + *work = 0; + + if ( work == buffer ) + continue; + } + + key = strtok(buffer, "="); + if ( key == NULL ) { + continue; + } + + value = strtok(NULL, ""); + if ( value == NULL ) { + value = ""; + } + + if ( propIdx == MAX_PROPERTIES ) { + error("pdj: maximum defined properties"); + break; + } + propIdx++; + + alloc = malloc(strlen(key)+1); + strcpy(alloc, key); + properties[propIdx][0] = alloc; + + alloc = malloc(strlen(value)+1); + strcpy(alloc, value); + properties[propIdx][1] = alloc; + } + + properties[propIdx+1][0] = NULL; + properties[propIdx+1][1] = NULL; + fclose(f); +} + + +static void copyToJavaSystemProperties(JNIEnv *env) { + jclass system = (*env)->FindClass(env, "java/lang/System"); + jmethodID id = (*env)->GetStaticMethodID(env, system, "setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); + int i; + + for(i=0; properties[i][0] != NULL; i++) { + jobject key, value; + + key = (*env)->NewStringUTF(env, properties[i][0]); + JASSERT(key); + value = (*env)->NewStringUTF(env, properties[i][1]); + JASSERT(value); + + (*env)->CallStaticObjectMethod(env, system, id, key, value); + } +} + + +PdjCaching pdjCaching; +static int initIDCaching(JNIEnv *env) { + pdjCaching.cls_Atom = (*env)->FindClass(env, "com/cycling74/max/Atom"); + if ( pdjCaching.cls_Atom == NULL ) { + // if the Atom class is not found... it means that pdj.jar is not on + // classpath and the installation is broken. + error("pdj: com.cycling74.max.Atom is not found on classpath ! pdj.jar must in the same directory of the external!"); + return 1; + } + pdjCaching.cls_Atom = (*env)->NewGlobalRef(env, pdjCaching.cls_Atom); + + pdjCaching.cls_MaxClock = (*env)->FindClass(env, "com/cycling74/max/MaxClock"); + JASSERT(pdjCaching.cls_MaxClock); + pdjCaching.cls_MaxClock = (*env)->NewGlobalRef(env, pdjCaching.cls_MaxClock); + + pdjCaching.cls_MaxObject = (*env)->FindClass(env, "com/cycling74/max/MaxObject"); + JASSERT(pdjCaching.cls_MaxObject); + pdjCaching.cls_MaxObject = (*env)->NewGlobalRef(env, pdjCaching.cls_MaxObject); + + pdjCaching.cls_MSPObject = (*env)->FindClass(env, "com/cycling74/msp/MSPObject"); + JASSERT(pdjCaching.cls_MSPObject); + pdjCaching.cls_MSPObject = (*env)->NewGlobalRef(env, pdjCaching.cls_MSPObject); + + pdjCaching.cls_MSPSignal = (*env)->FindClass(env, "com/cycling74/msp/MSPSignal"); + JASSERT(pdjCaching.cls_MSPSignal); + pdjCaching.cls_MSPSignal = (*env)->NewGlobalRef(env, pdjCaching.cls_MSPSignal); + + pdjCaching.MIDAtom_newAtom_Float = + (*env)->GetStaticMethodID(env, pdjCaching.cls_Atom, "newAtom", "(F)Lcom/cycling74/max/Atom;"); + JASSERT(pdjCaching.MIDAtom_newAtom_Float); + + pdjCaching.MIDAtom_newAtom_String = + (*env)->GetStaticMethodID(env, pdjCaching.cls_Atom, "newAtom", "(Ljava/lang/String;)Lcom/cycling74/max/Atom;"); + JASSERT(pdjCaching.MIDAtom_newAtom_String); + + pdjCaching.FIDAtom_type = + (*env)->GetFieldID(env, pdjCaching.cls_Atom, "type", "I"); + JASSERT(pdjCaching.FIDAtom_type); + + pdjCaching.cls_AtomFloat = (*env)->FindClass(env, "com/cycling74/max/AtomFloat"); + JASSERT(pdjCaching.cls_AtomFloat); + + pdjCaching.FIDAtomFloat_value = + (*env)->GetFieldID(env, pdjCaching.cls_AtomFloat, "value", "F"); + JASSERT(pdjCaching.FIDAtomFloat_value); + + pdjCaching.cls_AtomString = (*env)->FindClass(env, "com/cycling74/max/AtomString"); + JASSERT(pdjCaching.cls_AtomString); + + pdjCaching.FIDAtomString_value = + (*env)->GetFieldID(env, pdjCaching.cls_AtomString, "value", "Ljava/lang/String;"); + JASSERT(pdjCaching.FIDAtomString_value); + + pdjCaching.FIDMaxClock_clock_ptr = + (*env)->GetFieldID(env, pdjCaching.cls_MaxClock, "_clock_ptr", "J"); + JASSERT(pdjCaching.FIDMaxClock_clock_ptr); + + pdjCaching.FIDMaxObject_pdobj_ptr = + (*env)->GetFieldID(env, pdjCaching.cls_MaxObject, "_pdobj_ptr", "J"); + JASSERT(pdjCaching.FIDMaxObject_pdobj_ptr); + + pdjCaching.FIDMaxObject_activity_inlet = + (*env)->GetFieldID(env, pdjCaching.cls_MaxObject, "_activity_inlet", "I"); + JASSERT(pdjCaching.FIDMaxObject_activity_inlet); + + pdjCaching.MIDMaxObject_trySetter = + (*env)->GetMethodID(env, pdjCaching.cls_MaxObject, "_trySetter", "(Ljava/lang/String;[Lcom/cycling74/max/Atom;)Z"); + JASSERT(pdjCaching.MIDMaxObject_trySetter); + + pdjCaching.FIDMSPObject_used_inputs = + (*env)->GetFieldID(env, pdjCaching.cls_MSPObject, "_used_inputs", "[Lcom/cycling74/msp/MSPSignal;"); + JASSERT(pdjCaching.FIDMSPObject_used_inputs); + + pdjCaching.FIDMSPObject_used_outputs = + (*env)->GetFieldID(env, pdjCaching.cls_MSPObject, "_used_outputs", "[Lcom/cycling74/msp/MSPSignal;"); + JASSERT(pdjCaching.FIDMSPObject_used_outputs); + + pdjCaching.MIDMSPObject_dspinit = + (*env)->GetMethodID(env, pdjCaching.cls_MSPObject, "_dspinit", "(FI)Ljava/lang/reflect/Method;"); + JASSERT(pdjCaching.MIDMSPObject_dspinit); + + pdjCaching.MIDMSPObject_emptyPerformer = + (*env)->GetMethodID(env, pdjCaching.cls_MSPObject, "_emptyPerformer", "([Lcom/cycling74/msp/MSPSignal;[Lcom/cycling74/msp/MSPSignal;)V"); + JASSERT(pdjCaching.MIDMSPObject_emptyPerformer); + + pdjCaching.FIDMSPSignal_vec = + (*env)->GetFieldID(env, pdjCaching.cls_MSPSignal, "vec", "[F"); + JASSERT(pdjCaching.FIDMSPSignal_vec); + + return 0; +} + + +static int linkClasses(JNIEnv *env) { + jclass pdjSystem = (*env)->FindClass(env, "com/e1/pdj/PDJSystem"); + jmethodID id; + if ( pdjSystem == NULL ) { + SHOWEXC; + return 1; + } + + id = (*env)->GetStaticMethodID(env, pdjSystem, "_init_system", "()V"); + if ( id == NULL ) { + SHOWEXC; + return 1; + } + + (*env)->CallStaticVoidMethod(env, pdjSystem, id); + if ( (*env)->ExceptionOccurred(env) ) { + (*env)->ExceptionDescribe(env); + return 1; + } + + return 0; +} + + +void buildVMOptions(jint *nb, JavaVMOption *options) { + static char cp[BUFFER_SIZE], pdj_cp[BUFFER_SIZE]; + char installPath[BUFFER_SIZE]; + char *prop; + char *token, *work; + int i; + *nb = 0; + + getuglylibpath(installPath); + + // first; we set the system classpath + strcpy(cp, "-Djava.class.path="); + strcat(cp, installPath); + strcat(cp, DIR_SEP "pdj.jar" PATH_SEP); + prop = pdj_getProperty("pdj.system-classpath"); + if ( prop != NULL ) + strcat(cp, prop); + options[0].optionString = cp; + + prop = pdj_getProperty("pdj.vm_args"); + if ( prop == NULL ) { + *nb = 1; + return; + } + + work = malloc(strlen(prop)+1); + strcpy(work, prop); + token = strtok(work, " "); + + for(i=*nb; i<32; i++) { + *nb += 1; + + if ( token == NULL ) { + free(work); + return; + } + + options[*nb].optionString = malloc(strlen(token)+1); + strcpy(options[*nb].optionString, token); + token = strtok(NULL, " "); + } + + bug("pdj: maximum vm_args properties defined. Go see the source Luke."); +} + + +int REDIRECT_PD_IO; +static void redirectIoInit(void) { + char *ret; + + ret = pdj_getProperty("pdj.redirect-pdio"); + if ( ret == NULL ) { + REDIRECT_PD_IO = 1; + return; + } + + if ( ret[0] == '0' ) { + REDIRECT_PD_IO = 0; + return; + } + + if ( strcmp(ret, "false") == 0 ) { + REDIRECT_PD_IO = 0; + return; + } + + REDIRECT_PD_IO = 1; +} + + +JNIEnv *init_jvm(void) { + JNI_CreateJavaVM_func *func; + JavaVMOption opt[32]; + JavaVMInitArgs vm_args; + JNIEnv *jni_env; + char *vm_type; + int rc; + + load_properties(); + + buildVMOptions(&(vm_args.nOptions), opt); + vm_args.options = opt; + vm_args.version = JNI_VERSION_1_4; + vm_args.ignoreUnrecognized = JNI_FALSE; + + vm_type = pdj_getProperty("pdj.vm_type"); + if ( vm_type == NULL ) { + error("pdj: unknown vm_type, using client"); + vm_type = "client"; + } + + func = linkjvm(vm_type); + if ( func == NULL ) + return NULL; + + rc = func(&jni_jvm, &jni_env, &vm_args); + if ( rc != 0 ) { + error("pdj: unable to create JVM: JNI_CreateJavaVM = %d", rc); + return NULL; + } + + copyToJavaSystemProperties(jni_env); + if ( initIDCaching(jni_env) != 0) { + return NULL; + } + + if ( linkClasses(jni_env) != 0 ) { + return NULL; + } + + redirectIoInit(); + + return jni_env; +} + diff --git a/src/java/com/cycling74/io/ErrorStream.java b/src/java/com/cycling74/io/ErrorStream.java new file mode 100644 index 0000000..612bf3f --- /dev/null +++ b/src/java/com/cycling74/io/ErrorStream.java @@ -0,0 +1,10 @@ +package com.cycling74.io; + +import com.e1.pdj.*; +import java.io.*; + +public class ErrorStream extends PrintStream { + public ErrorStream() { + super(PDJSystem.err, true); + } +} diff --git a/src/java/com/cycling74/io/PostStream.java b/src/java/com/cycling74/io/PostStream.java new file mode 100644 index 0000000..5d7a58e --- /dev/null +++ b/src/java/com/cycling74/io/PostStream.java @@ -0,0 +1,10 @@ +package com.cycling74.io; + +import com.e1.pdj.*; +import java.io.*; + +public class PostStream extends PrintStream { + public PostStream() { + super(PDJSystem.out, true); + } +} diff --git a/src/java/com/cycling74/max/Atom.java b/src/java/com/cycling74/max/Atom.java new file mode 100644 index 0000000..c1949df --- /dev/null +++ b/src/java/com/cycling74/max/Atom.java @@ -0,0 +1,715 @@ +package com.cycling74.max; + +import java.io.*; +import java.util.*; + +/** + * PD element that is used in message or arguments. It can contains a + * float, a int (always map to a float in pd) or a string. + */ +public abstract class Atom implements Comparable, Serializable { + + /** + * Empty array to use with the API when theres is no arguments. + */ + public static final Atom[] emptyArray = new Atom[] {}; + + int type; + + Atom(int type) { + this.type = type; + } + + // Atom factories + /////////////////////////////////////////////////////////////////// + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom newAtom(int value) { + return new AtomFloat(value); + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom[] newAtom(int value[]) { + Atom[] ret = new Atom[value.length]; + for(int i=0;iobject. Similar to "ok".equals("ok"); + */ + public abstract boolean equals(Object object); + + /** + * Returns the hashCode representation for this Atom. If it is an float, + * the float into bit value is return and if it is an String, the hashcode + * value of the string is returned. + */ + public abstract int hashCode(); + + // Array utility classes + /////////////////////////////////////////////////////////////////// + + /** + * Returns the index of the first atom that is found in the list. + * @param org the atom to find + * @param list the list of atom to search + * @return the index of the atom found. -1 if not found. + */ + public static int isIn(Atom org, Atom[] list) { + return isIn(org, list, 0, list.length-1); + } + + /** + * Returns the index of the first atom that is found in the list. + * @param org the atom to find + * @param list the list of atom to search + * @param from the start index to check + * @param to the last index to check + * @return the index of the atom found. -1 if not found. + */ + public static int isIn(Atom org, Atom[] list, int from, int to) { + for(int i=from;ifrom to index to. + * @param list the list to strip + * @param from the start index + * @param to the last index + * @return the stripped array + */ + public static Atom[] removeSome(Atom[] list, int from, int to) { + if ( from == 0 && from == list.length - 1 ) + return new Atom[] {}; + + Atom[] ret = new Atom[list.length - (to-from+1)]; + System.arraycopy(list, 0, ret, 0, from); + System.arraycopy(list, to + 1, ret, from, list.length - to - 1); + + return ret; + } + + /** + * Removes one atom in the list. + * @param list the list to strip + * @param i the index of the atom to remove + * @return the stripped array + */ + public static Atom[] removeOne(Atom[] list, int i) { + return removeSome(list, i, i); + } + + /** + * Removes the first atom in the list. + * @param list the list to strip + * @return the stripped array + */ + public static Atom[] removeFirst(Atom[] list) { + return removeFirst(list, 1); + } + + /** + * Removes the first howmany atoms in the list. + * @param list the list to strip + * @param howmany how many element to remove + * @return the stripped array + */ + public static Atom[] removeFirst(Atom[] list, int howmany) { + return removeSome(list, 0, howmany-1); + } + + /** + * Remove the last atom in the list. + * @param list the list to strip + * @return the stripped array + */ + public static Atom[] removeLast(Atom[] list) { + return removeLast(list, 1); + } + + /** + * Removes the last howmany atoms in the list. + * @param list the list to strip + * @param howmany how many element to remove + * @return the stripped array + */ + public static Atom[] removeLast(Atom[] list, int howmany) { + return removeSome(list, list.length-howmany, list.length-1); + } + + /** + * Reverses the element content; the first element is the last and so on. + * @param list the list to reverse + * @return the reversed list + */ + public static Atom[] reverse(Atom[] list) { + Atom[] ret = new Atom[list.length]; + int last = list.length - 1; + for(int i=0;i 1 ) + throw new PDJError("Method: " + setter_name + " has too much parameters to be a setter"); + typeCheck = mapType(c[0]); + setter = methods[i]; + break; + } + } + if ( typeCheck == 0 ) { + throw new NoSuchMethodException(setter_name); + } + } + } catch ( Exception e ) { + throw new PDJError(e); + } + + if ( typeCheck != type ) { + throw new PDJError("Object type for setter and getter is not the same"); + } + + } + + private char mapType(Class clz) { + if ( clz == Integer.TYPE ) + return 'i'; + if ( clz == int[].class ) + return 'I'; + if ( clz == Float.TYPE ) + return 'f'; + if ( clz == float[].class ) + return 'F'; + if ( clz == Double.TYPE ) + return 'd'; + if ( clz == double[].class ) + return 'D'; + if ( clz == Boolean.TYPE ) + return 'z'; + if ( clz == Boolean[].class ) + return 'Z'; + if ( clz == Byte.TYPE ) + return 'b'; + if ( clz == byte[].class ) + return 'B'; + if ( clz == Character.TYPE ) + return 'c'; + if ( clz == char[].class ) + return 'C'; + if ( clz == Short.TYPE ) + return 's'; + if ( clz == short[].class ) + return 'S'; + if ( clz == String.class ) + return 'g'; + if ( clz == String[].class ) + return 'G'; + if ( clz == Atom.class ) + return 'a'; + if ( clz == Atom[].class ) + return 'A'; + return '-'; + } + + private void fieldSetter(Atom[] value) throws Exception { + Object work; + int i; + + switch ( type ) { + case 'z' : + field.setBoolean(obj, value[0].toBoolean()); + break; + case 'Z' : + work = field.get(obj); + for (i=0;i
+ * 
+ * class Myclass  {
+ *    public void doit() {
+ *		do_something_fun()
+ *    }
+ * }
+ * 
+ * ...
+ * 
+ *      Myclass myclass = new Myclass();
+ *      Executable e = new Callback(myclass, "doit");
+ *      MaxClock clock = new MaxClock(e);
+ * 
+ * 

+ */ +public class Callback implements Executable { + private Method method; + private String methodName; + private Object obj; + private Object args[]; + + /** + * Will call method methodName with no argument by using execute() + * @param obj the object with the method + * @param methodName the name of the method + */ + public Callback(Object obj, String methodName) { + this(obj, methodName, null, null); + } + + /** + * Will call method methodName with a int by using execute() + * @param obj the object with the method + * @param methodName the name of the method + * @param i the int value + */ + public Callback(Object obj, String methodName, int i) { + this(obj, methodName, new Object[] { new Integer(i) }, new Class[] { Integer.TYPE }); + } + + /** + * Will call method methodName with a float by using execute() + * @param obj the object with the method + * @param methodName the name of the method + * @param f the float value + */ + public Callback(Object obj, String methodName, float f) { + this(obj, methodName, new Object[] { new Float(f) }, new Class[] { Float.TYPE }); + } + + /** + * Will call method methodName with a Stringt by using execute() + * @param obj the object with the method + * @param methodName the name of the method + * @param str the string value + */ + public Callback(Object obj, String methodName, String str) { + this(obj, methodName, new Object[] { str }); + } + + /** + * Will call method methodName with a boolean by using execute() + * @param obj the object with the method + * @param methodName the name of the method + * @param flag the boolean value + */ + public Callback(Object obj, String methodName, boolean flag) { + this(obj, methodName, new Object[] { flag ? Boolean.TRUE : Boolean.FALSE }); + } + + /** + * Will call method methodName with multiple arguments by using execute() + * @param obj the object with the method + * @param methodName the name of the method + * @param params argument to pass to the method + */ + public Callback(Object obj, String methodName, Object params[]) { + this(obj, methodName, params, buildClasses(params)); + } + + /** + * Will call method methodName with multiple arguments (typed) by using execute() + * @param obj the object with the method + * @param methodName the name of the method + * @param params argument to pass to the method + * @param params_types the type of arguments + */ + public Callback(Object obj, String methodName, Object params[], Class params_types[]) { + try { + if ( params == null ) { + method = obj.getClass().getDeclaredMethod(methodName, null); + } else { + method = obj.getClass().getDeclaredMethod(methodName, params_types); + } + this.obj = obj; + this.methodName = methodName; + } catch ( NoSuchMethodException e ) { + MaxSystem.post("pdj: unable to find method: " + methodName + ", "+ e); + } + } + + private static Class[] buildClasses(Object params[]) { + Class clz[] = new Class[params.length]; + + for(int i=0;i
+ * 
+ * import com.cycling74.max.*;
+ * 
+ * class clocktest extends MaxObject implements Executable {
+ *     MaxClock clock;
+ *     float value; 
+ * 
+ *     public clocktest() {
+ *         clock = new MaxClock(this); 
+ *     }
+ *     
+ *     public void inlet(float f) {
+ *         value = f;
+ *         // ask to call execute after 250ms
+ *         clock.delay(250);
+ *     }
+ * 
+ *     // this is called after 250ms
+ *     public void execute() {
+ *         outlet(0, value)
+ *     }
+ * }
+ * 

+ */ +public class MaxClock { + private Executable exec; + private long _clock_ptr; + + /** + * Creates a pdclock without an executable. + */ + public MaxClock() { + create_clock(); + } + + /** + * Creates a pdclock with an executable e. + * @param e the executable to execute when the clock will be triggerd. + */ + public MaxClock(Executable e) { + create_clock(); + exec = e; + } + + /** + * Creates a pdclock with a specific method on a object. + * @param o the object that holds the method + * @param methodName the name of the method to execute when the clock + * will be triggerd. + */ + public MaxClock(Object o, String methodName) { + create_clock(); + exec = new Callback(o, methodName); + } + + /** + * Returns the Executable for this clock. + * @return the Executable for this clock + */ + public Executable getExecutable() { + return exec; + } + + /** + * Set the Executable for this clock. + * @param e the Executable to call for this clock + */ + public void setExecutable(Executable e) { + exec = e; + } + + /** + * The method to override if no Executable is provided. + */ + public void tick() { + exec.execute(); + } + + protected void finalize() throws Throwable { + release(); + super.finalize(); + } + + /** + * Returns pure-data time in milliseconds. + * @return pure-data time in milliseconds + */ + public static native double getTime(); + + /** + * Time to wait until next tick. + * @param time in miliseconds + */ + public native void delay(double time); + + /** + * Release the clock from pure-data. The clock becomes unless afterwards. + */ + public native void release(); + + /** + * Cancels the last delay call. + */ + public native void unset(); + + private native void create_clock(); +} diff --git a/src/java/com/cycling74/max/MaxContext.java b/src/java/com/cycling74/max/MaxContext.java new file mode 100644 index 0000000..e5fae12 --- /dev/null +++ b/src/java/com/cycling74/max/MaxContext.java @@ -0,0 +1,21 @@ + +package com.cycling74.max; + +import java.util.Set; + +/** + * MaxContext holder. Does not do anything usefull on pdj, yet. + */ +public class MaxContext { + public Set getAllObject() { + return null; + } + + MaxObject getMaxObject(String name) { + return null; + } + + String getMxjVersion() { + return MaxSystem.MXJ_VERSION; + } +} diff --git a/src/java/com/cycling74/max/MaxObject.java b/src/java/com/cycling74/max/MaxObject.java new file mode 100644 index 0000000..3d07d49 --- /dev/null +++ b/src/java/com/cycling74/max/MaxObject.java @@ -0,0 +1,933 @@ +package com.cycling74.max; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; + +import com.e1.pdj.*; +import java.util.*; +import com.cycling74.msp.MSPObject; + +/** + * Main object to extend to use with pd. The name of this class will + * reflect the name of the pdj object when it is instanciated. + *

Here is a basic guideline for using the MaxObject:

+ *

+ * 
+ * import com.cycling74.max.*;
+ *
+ * public class example extends MaxObject {
+ * 
+ *    // called when arguments are used in object creation
+ *    public example(Atom args[]) {
+ *        for(int i=0;i<args.length;i++) {
+ *           post("args:" + args[i].toString());
+ *        }
+ *    }
+ * 
+ *    // this method will be called when a float is sended to the object
+ *    public inlet(float f) {
+ *        post("hello float:" + f);
+ *        outlet(0, f);
+ *    }
+ * 
+ *    // this method will be called when symbol callme is sended to the object
+ *    public callme(Atom args[]) {
+ *        post("hello object:" + args[0]);
+ *        outlet(0, args[0]);
+ *    }
+ * 
+ *    // this method will be called when bang is sended to the object
+ *    public bang() {
+ *        post("hello bang");
+ *        outletBang(0);
+ *    }
+ * }
+ * 

+ *

Compile this class by adding pdj.jar on the classpath and put the + * example.class in the classes directory found in your pdj home. + * You can also edit directly this class in the classes directory + * and pdj will try to compile it for you.

+ */ +public class MaxObject { + /** + * Native C pdj object pointer. + */ + private long _pdobj_ptr; + + /** + * Native C inlet pointers. + */ + private long _outlets_ptr[]; + + /** + * Native C outlets pointers. + */ + private long _inlets_ptr[]; + + /** + * The last inlet that received a message. + */ + private int _activity_inlet; + private String name; + private HashMap attributes = new HashMap(); + private boolean toCreateInfoOutlet = true; + private MaxPatcher patch = new MaxPatcher(); + + // useless statics.... + /** + * Use this to declare that your object has no inlets. + */ + public static final int[] NO_INLETS = {}; + + /** + * Use this to declare that your object has no outlets. + */ + public static final int[] NO_OUTLETS = {}; + + /** + * Defined in the original MXJ API; don't know what it is used for... + */ + public static final String[] EMPTY_STRING_ARRAY = {}; + + /** + * Default constructor for MaxObject. You can add an constructor that + * supports Atom[] to read object creation arguments. It is not defined + * in this API since it does not to force a 'super()' call in all the + * extended class. + */ + protected MaxObject() { + synchronized (MaxObject.class) { + _pdobj_ptr = popPdjPointer(); + } + name = this.getClass().getName(); + patch.patchPath = getPatchPath(); + } + + /** + * Will show an error message in the pure-data console. + * @param message the string to show + */ + public static void error(String message) { + MaxSystem.error(message); + } + + /** + * Will show an info message in the pure-data console. + * @param message the string to show + */ + public static void post(String message) { + MaxSystem.post(message); + } + + /** + * Will show an error message in the pure-data console and crash + * pure-data. Do not use if you have no friend left. + * @param message the message to show when you make pd crash + */ + public static void ouch(String message) { + MaxSystem.ouch(message); + } + + /** + * Show the exeception in the pure-data console. + * @param t the exception it self + */ + public static void showException(Throwable t) { + t.printStackTrace(PDJSystem.err); + } + + /** + * Show the exception in the pure-data console with a message. + * @param message the message that comes with the exception + * @param t the exception it self + */ + public static void showException(String message, Throwable t) { + PDJSystem.err.println(message); + t.printStackTrace(PDJSystem.err); + } + + /** + * Returns the object name. + * @return the pdj object name + */ + public String getName() { + return name; + } + + /** + * Sets the object name. + * @param name pdj object name + */ + public void setName(String name) { + this.name = name; + } + + /** + * This method is called when the object is deleted by the user. + */ + public void notifyDeleted() { + } + + /** + * Bail will throw an exception upon object instanciation. This is usefull + * if you have a missing requirement and you want to cancel object creation. + * @param errormsg the error message to show + */ + protected static void bail(String errormsg) { + throw new PDJError(errormsg); + } + + /** + * Declare the inlets used by this object. + * @see com.cycling74.max.DataTypes + * @param types the type of message that this inlet will use. + */ + protected void declareInlets(int[] types) { + if ( _inlets_ptr != null ) { + throw new IllegalStateException(name + ": inlets already defined"); + } + _inlets_ptr = new long[types.length]; + + int pos = 0; + for(int i=0; iPD doesn't type all data yet, so every + * inlet/outlet are typed as anything; except for signals. + * @param ins list of inlet to create + * @param outs list of outlet to create + */ + protected void declareTypedIO(String ins, String outs) { + if ( _inlets_ptr != null || _outlets_ptr != null ) { + throw new IllegalStateException(name + ": inlets/outles already defined."); + } + + int[] in_type = new int[ins.length()]; + int[] out_type = new int[outs.length()]; + int i; + + for (i=0;iNot fully implemented, + * returns always DataTypes.ANYTHING with pdj + * @param idx the outlet position + * @return the DataType of the inlet + */ + public int getInletType(int idx) { + return DataTypes.ANYTHING; + } + + /** + * Returns the type of the outlet at index 'idx'. Not fully implemented, + * returns always DataTypes.ANYTHING with pdj + * @param idx the outlet position + * @return the DataType of the outlet + */ + public int getOutletType(int idx) { + return DataTypes.ANYTHING; + } + + /** + * Returns the number of inlets declared. + * @return the number of inlets used by this object + */ + public int getNumInlets() { + return _inlets_ptr.length; + } + + /** + * Returns the number of outlets declared. + * @return the number of outlets used by this object + */ + public int getNumOutlets() { + return _outlets_ptr.length; + } + + /** + * Called by PD when the current patch issue a loadbang message. + */ + protected void loadbang() { + } + + /** + * Returns the index of the inlet that has just received a message. + * @return the index of the inlet + */ + protected int getInlet() { + return _activity_inlet; + } + + /** + * Returns the index of the info outlet + */ + public int getInfoIdx() { + if ( !toCreateInfoOutlet ) { + return -1; + } + return _outlets_ptr.length-1; + } + + /** + * Sends a bang to outlet x. + * @param outlet the outlet number to use + * @return always true, since PD API returns void + */ + public final boolean outletBang(int outlet) { + doOutletBang(_outlets_ptr[outlet]); + return true; + } + + /** + * Sends a float to outlet x. + * @param outlet the outlet number to use + * @param value the float value + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, float value) { + doOutletFloat(_outlets_ptr[outlet], value); + return true; + } + + /** + * Sends floats to outlet x. + * @param outlet the outlet number to use + * @param value the array of float to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, float value[]) { + doOutletAnything(_outlets_ptr[outlet], "list", Atom.newAtom(value)); + return true; + } + + /** + * Sends a symbol to outlet x. + * @param outlet the outlet number to use + * @param value the symbol + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, String value) { + doOutletSymbol(_outlets_ptr[outlet], value); + return true; + } + + /** + * Sends symbols to outlet x. + * @param outlet the outlet number to use + * @param value the array of symbol to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, String value[]) { + doOutletAnything(_outlets_ptr[outlet], "list", Atom.newAtom(value)); + return true; + } + + /** + * Sends a byte to outlet x. + * @param outlet the outlet number to use + * @param value the byte value + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, byte value) { + return outlet(outlet, (float) value); + } + + /** + * Sends byte to outlet x. + * @param outlet the outlet number to use + * @param value the array of byte to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, byte value[]) { + doOutletAnything(_outlets_ptr[outlet], "list", Atom.newAtom(value)); + return true; + } + + /** + * Sends a char to outlet x. + * @param outlet the outlet number to use + * @param value the char value + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, char value) { + return outlet(outlet, (float) value); + } + + /** + * Sends char to outlet x. + * @param outlet the outlet number to use + * @param value the array of char to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, char value[]) { + doOutletAnything(_outlets_ptr[outlet], "list", Atom.newAtom(value)); + return true; + } + + /** + * Sends a short to outlet x. + * @param outlet the outlet number to use + * @param value the short value + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, short value) { + return outlet(outlet, (float) value); + } + + /** + * Sends shorts to outlet x. + * @param outlet the outlet number to use + * @param value the array of short to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, short value[]) { + doOutletAnything(_outlets_ptr[outlet], "list" , Atom.newAtom(value)); + return true; + } + + /** + * Sends a int to outlet x. + * @param outlet the outlet number to use + * @param value the int value + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, int value) { + return outlet(outlet, (float) value); + } + + /** + * Sends ints to outlet x. + * @param outlet the outlet number to use + * @param value the array of int to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, int value[]) { + doOutletAnything(_outlets_ptr[outlet], "list", Atom.newAtom(value)); + return true; + } + + /** + * Sends a long to outlet x. + * @param outlet the outlet number to use + * @param value the long value + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, long value) { + return outlet(outlet, (float) value); + } + + /** + * Sends longs to outlet x. + * @param outlet the outlet number to use + * @param value the array of longs to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, long value[]) { + doOutletAnything(_outlets_ptr[outlet], "list", Atom.newAtom(value)); + return true; + } + + /** + * Sends a double to outlet x. + * @param outlet the outlet number to use + * @param value the double value + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, double value) { + return outlet(outlet, (float) value); + } + + /** + * Sends doubles to outlet x. + * @param outlet the outlet number to use + * @param value the array of double to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, double value[]) { + doOutletAnything(_outlets_ptr[outlet], "list" , Atom.newAtom(value)); + return true; + } + + /** + * Sends message with argument to outlet x. + * @param outlet the outlet number to use + * @param message the message symbol name + * @param value the arguments + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, String message, Atom[] value) { + doOutletAnything(_outlets_ptr[outlet], message, value); + return true; + } + + /** + * Sends atom value to outlet x. If it is a int/float, it will call + * outlet(int, float) otherwise outlet(int, String) + * @param outlet the outlet number to use + * @param value the atom value to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, Atom value) { + if ( value.isFloat() || value.isInt() ) { + doOutletFloat(_outlets_ptr[outlet], value.toFloat()); + } else { + doOutletSymbol(_outlets_ptr[outlet], value.toString()); + } + return true; + } + + /** + * Sends atoms to outlet x. If the array contains only one item, + * outlet(outlet, Atom value) will be called. If the + * first element of the array is a float/int, list will be + * appended to the message. Otherwise, the first atom will be the + * message and the rest of it the arguments. + * @param outlet the outlet number to use + * @param value the arguments + * @return true or false if atom[] is empty + */ + public final boolean outlet(int outlet, Atom[] value) { + if ( value.length == 0 ) + return false; + + if ( value.length == 1 ) { + outlet(outlet, value[0]); + } else { + doOutletAnything(_outlets_ptr[outlet], null, value); + } + return true; + } + + // user methods + ///////////////////////////////////////////////////////////// + /** + * Called by PD when pdj receives a bang from an inlet. Use + * getInlet() to know which inlet has received the message. + */ + protected void bang() { + } + + /** + * This will be called if pd sends a float and the float method is not + * overridden. Use getInlet() to know which inlet has + * received the message. + * @param i int value + */ + protected void inlet(int i) { + } + + /** + * Called by PD when pdj receives a float from an inlet. Use + * getInlet() to know which inlet has received the message. + * @param f float value received from the inlet + */ + protected void inlet(float f) { + } + + /** + * Called by PD when pdj receives a list of atoms. Use + * getInlet() to know which inlet has received the message. + * @param args the list + */ + protected void list(Atom args[]) { + } + + /** + * Called by PD when pdj receives an un-overriden method. If you need + * to catch all messages, override this method. Use getInlet() + * to know which inlet has received the message. + * @param symbol first atom symbol representation + * @param args the arguments of the message + */ + protected void anything(String symbol, Atom[] args) { + post("pdj: object '" + name + "' doesn't understand " + symbol); + } + + // compatibility methods + ///////////////////////////////////////////////////////////// + public final boolean outletBangHigh(int outlet) { + return outletBang(outlet); + } + + public final boolean outletHigh(int outlet, int value) { + return outlet(outlet, value); + } + + public final boolean outletHigh(int outlet, float value) { + return outlet(outlet, value); + } + + public final boolean outletHigh(int outlet, double value) { + return outlet(outlet, value); + } + + public final boolean outletHigh(int outlet, String value) { + return outlet(outlet, value); + } + + public final boolean outletHigh(int outlet, String msg, Atom[] args) { + return outlet(outlet, msg, args); + } + + public final boolean outletHigh(int outlet, Atom[] value) { + return outlet(outlet, value); + } + + /** + * Returns the output stream of PDJ. + * @return the PrintStream output stream of PDJ + */ + public static com.cycling74.io.PostStream getPostStream() { + return new com.cycling74.io.PostStream(); + } + + /** + * Returns the error stream of PDJ. + * @return the PrintStream error stream of PDJ + */ + public static com.cycling74.io.ErrorStream getErrorStream() { + return new com.cycling74.io.ErrorStream(); + } + + // unimplementable methods + ///////////////////////////////////////////////////////////// + /** + * NOT USED IN PD. + */ + public void viewsource() { + } + + /** + * NOT USED IN PD. Throws UnsupportdOperationException + */ + public static Object getContext() { + throw new UnsupportedOperationException(); + } + + /** + * Returns the object representing the pd patch. + */ + public MaxPatcher getParentPatcher() { + return patch; + } + + /** + * NOT USED IN PD. + */ + protected void save() { + } + + /** + * NOT USED IN PD. + */ + protected void setInletAssist(String[] messages) { + } + + /** + * NOT USED IN PD. + */ + protected void setInletAssist(int index, String message) { + } + + /** + * NOT USED IN PD. + */ + protected void setOutletAssist(String[] messages) { + } + + /** + * NOT USED IN PD. + */ + protected void setOutletAssist(int index, String message) { + } + + /** + * NOT USED IN PD. + */ + protected void embedMessage(String msg, Atom[] args) { + } + + /** + * Useless, but in the original API. + */ + public void gc() { + System.gc(); + } + + /** + * Refresh/reinitialize the PDJ classloader. + */ + public void zap() { + PDJClassLoader.resetClassloader(); + } + + // not from original class + ///////////////////////////////////////////////////////////// + + /** + * Tries to get the attribute name[0] and send its value to + * the last outlet. + * @param name the name of the attribute + */ + void get(Atom[] name) throws IllegalAccessException, InvocationTargetException { + String attrName = name[0].toString(); + if ( attributes.containsKey(attrName) ) { + Attribute attr = (Attribute) attributes.get(attrName); + outlet(_outlets_ptr.length-1, attr.get()); + } else { + error(this.name + ": attribute not defined '" + attrName + "'"); + } + } + + /** + * Called by pdj to check if it is possible to set value x. + * @param the name of the setter + * @param arg [0] the value to set + * @return true if the setter has been set + */ + private boolean _trySetter(String name, Atom[] arg) { + if ( !attributes.containsKey(name) ) + return false; + Attribute attr = (Attribute) attributes.get(name); + attr.set(arg); + return true; + } + + /** + * Tries to instanciate a MaxObject. + * @param name fq java name + * @param _pdobj_ptr C pointer to pd object + * @param args objects arguments + */ + static synchronized MaxObject registerObject(String name, long _pdobj_ptr, Atom[] args_complete) { + try { + Class clz = PDJClassLoader.dynamicResolv(name); + MaxObject obj = null; + + // map arguments and attributes + List largs = new ArrayList(); + List lattr = new ArrayList(); + + for (int i=1;i=args_complete.length ) { + post("pdj: " + name + ": attribute '" + args_complete[i].toString() + + "' must have a initial value"); + return null; + } + lattr.add(args_complete[++i]); + } else { + largs.add(args_complete[i]); + } + } + + Atom args[] = new Atom[largs.size()]; + for (int i=0;i 0 ) { + try { + Object argValue[] = new Object[1]; + argValue[0] = args; + Constructor c = clz.getConstructor(argType); + obj = (MaxObject) c.newInstance(argValue); + } catch ( NoSuchMethodException e) { + popPdjPointer(); + post("pdj: object " + name + " has no constructor with Atom[] parameters"); + return null; + } + } else { + try { + Constructor c = clz.getConstructor(null); + obj = (MaxObject) c.newInstance(null); + } catch (NoSuchMethodException e) { + try { + Constructor c = clz.getConstructor(argType); + obj = (MaxObject) c.newInstance(new Object[0]); + } catch ( Exception e1 ) { + popPdjPointer(); + throw e1; + } + } + } + + // next we process attributes from the constructor arguments + Iterator i = lattr.iterator(); + while( i.hasNext() ) { + String attrName = (String) i.next(); + obj.declareAttribute(attrName); + Attribute attr = (Attribute) obj.attributes.get(attrName); + attr.set(new Atom[] { (Atom) i.next() }); + } + + obj.name = name; + obj.postInit(); + return obj; + } catch (PDJClassLoaderException e ) { + MaxSystem.post("pdj: " + e.toString()); + } catch (Throwable e) { + e.printStackTrace(); + } + return null; + } + + /** + * Called after the constructor has been called. Check if the user + * has override the standard inlets/outlets. + */ + private void postInit() { + if ( _inlets_ptr == null ) { + declareInlets(new int[] { DataTypes.ANYTHING }); + } + + if ( _outlets_ptr == null ) { + declareOutlets(new int[] { DataTypes.ANYTHING }); + } + + // add the last/info outlet + if ( toCreateInfoOutlet ) { + long tmp[] = new long[_outlets_ptr.length+1]; + + System.arraycopy(_outlets_ptr, 0, tmp, 0, _outlets_ptr.length); + tmp[_outlets_ptr.length] = newOutlet(DataTypes.ANYTHING); + + _outlets_ptr = tmp; + } + } + + // native party + ///////////////////////////////////////////////////////////// + native private long newInlet(int type); + native private long newOutlet(int type); + + native private void doOutletBang(long ptr); + native private void doOutletFloat(long ptr, float value); + native private void doOutletSymbol(long ptr, String symbol); + native private void doOutletAnything(long ptr, String msg, Atom []args); + + native private String getPatchPath(); + + // ugly ugly... but MaxContext vs this; I prefer this.... + native private static void pushPdjPointer(long ptr); + native private static long popPdjPointer(); +} diff --git a/src/java/com/cycling74/max/MaxPatcher.java b/src/java/com/cycling74/max/MaxPatcher.java new file mode 100644 index 0000000..b2fb778 --- /dev/null +++ b/src/java/com/cycling74/max/MaxPatcher.java @@ -0,0 +1,17 @@ +package com.cycling74.max; + +/** + * Java object that encapsulate the pd patch. Minimal support on pdj; only + * getPath() is supported. + */ +public class MaxPatcher { + String patchPath; + + /** + * Returns the absolute path of the associated patch. + * @return the absolute patch path + */ + public String getPath() { + return patchPath; + } +} diff --git a/src/java/com/cycling74/max/MaxQelem.java b/src/java/com/cycling74/max/MaxQelem.java new file mode 100644 index 0000000..100f671 --- /dev/null +++ b/src/java/com/cycling74/max/MaxQelem.java @@ -0,0 +1,114 @@ +package com.cycling74.max; + +/** + * Background job utility class. This is used to execute code in the + * background that might take time to execute. Calling set + * will trigger/execute the "job". If set is called while + * the "job" is running, the "job" won't be called again. + *

On PDJ, a Qelem is simply a Java thread that wait to be triggered. + *

+ */ +public class MaxQelem { + Thread job; + Executable exec; + boolean incall; + static int nbQelem = 0; + boolean stopThread = true; + + /** + * Constructs a Qelem that is bound the overriden class with method + * name qfn. + */ + public MaxQelem() { + exec = new Callback(this, "qfn"); + do_init(); + } + + /** + * Constructs a Qelem that is bound to an executable. + * @param exec the executable to run + */ + public MaxQelem(Executable exec) { + this.exec = exec; + do_init(); + } + + /** + * Constructs a Qelem that is bound to a class with method name. + * @param src the object that contains the method + * @param method the method to execute + */ + public MaxQelem(Object src, String method) { + exec = new Callback(src, method); + do_init(); + } + + private void do_init() { + job = new Thread(new Dispatcher(), "MaxQelem#" + (++nbQelem)); + job.setPriority(Thread.MIN_PRIORITY); + } + + /** + * Puts thread in front execution. Does nothing on PDJ + */ + public void front() { + } + + /** + * The callback method, otherwise it calls the 'executable' method + * provided in constructor. + */ + public void qfn() { + } + + /** + * Ask qfn to execute. If it is already set, the qfn won't be called + * twice. + */ + public synchronized void set() { + if ( !incall ) { + job.notify(); + } + } + + /** + * Cancels execution of qelem. Use with caution since it will throw + * an InterruptedException on PDJ. + */ + public synchronized void unset() { + job.interrupt(); + } + + + /** + * Releases current Qelem and cancel the running thread. + */ + public void release() { + unset(); + stopThread = true; + } + + /** + * Returns the executable object. + * @return the executble object that is bound to this Qelem + */ + public Executable getExecutable() { + return exec; + } + + class Dispatcher implements Runnable { + public void run() { + while( !stopThread ) { + try { + incall = false; + job.wait(); + incall = true; + exec.execute(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + } +} diff --git a/src/java/com/cycling74/max/MaxRuntimeException.java b/src/java/com/cycling74/max/MaxRuntimeException.java new file mode 100644 index 0000000..9909a5c --- /dev/null +++ b/src/java/com/cycling74/max/MaxRuntimeException.java @@ -0,0 +1,15 @@ +package com.cycling74.max; + +/** + * API based runtime exception with Max/PDJ. + */ +public class MaxRuntimeException extends RuntimeException { + + public MaxRuntimeException(String msg) { + super(msg); + } + + public MaxRuntimeException() { + } + +} diff --git a/src/java/com/cycling74/max/MaxSystem.java b/src/java/com/cycling74/max/MaxSystem.java new file mode 100644 index 0000000..3a29540 --- /dev/null +++ b/src/java/com/cycling74/max/MaxSystem.java @@ -0,0 +1,186 @@ +package com.cycling74.max; + +import com.e1.pdj.*; + +/** + * MXJ System utilities. + */ +public class MaxSystem { + static private PriorityQueue low = new PriorityQueue(Thread.MIN_PRIORITY); + + /** + * Shows a message to the pd console + * @param message the string to show + */ + static native public void post(String message); + + /** + * Shows a error message to the pd console + * @param message the string to show + */ + static native public void error(String message); + + /** + * Shows a message in the pd console and kill PD afterwards. + * @param message the string to show + */ + static native public void ouch(String message); + + /** + * Sends a message to a bound object (IEM object or a receiver) + * @param name the destination of the message + * @param msg the symbol message ("bang", "float", "list") + * @param args the array of Atoms + * @return true if successfull + */ + static native public boolean sendMessageToBoundObject(String name, String msg, Atom[] args); + + /** + * Tries to locate file in pure-data search path. + * @param filename of the file to search in path + * @return the full path of this file + */ + static native public String locateFile(String filename); + + /** + * Will schedule the executable to a low priority thread + * @param fn the executable + */ + static synchronized public void deferLow(Executable fn) { + low.defer(fn); + } + + /** + * Will schedule the executable to a medium priority thread + * @param fn the executable + */ + static synchronized public void deferMedium(Executable fn) { + low.defer(fn); + } + + /** + * Returns the user classpath. + * @return Array of strings of each entries in the user classpath + */ + static public String[] getClassPath() { + return PDJClassLoader.getCurrentClassPath(); + } + + // implemented but not fully supported... + static public void defer(Executable fn) { + fn.execute(); + } + static public void deferFront(Executable fn) { + fn.execute(); + } + + static public String[] getSystemClassPath() { + return null; + } + + static boolean inMainThread() { + return false; + } + + static boolean inMaxThread() { + return false; + } + + static boolean inTimerThread() { + return false; + } + + static boolean isOsMacOsX() { + String osname = System.getProperty("os.name"); + if ( osname.indexOf("OS X") != -1 ) { + return true; + } + return false; + } + + static boolean isOsWindows() { + String osname = System.getProperty("os.name"); + if ( osname.indexOf("Windows") != -1 ) { + return true; + } + return false; + } + + /** + * Not supported in PD + */ + static void registerCommandAccelerator(char c) { + } + + /** + * Not supported in PD + */ + static void registerCommandAccelerators(char c[]) { + } + + /** + * Not supported in PD + */ + static void unRegisterCommandAccelerator(char c) { + } + /** + * Not supported in PD + */ + static void unRegisterCommandAccelerators(char c[]) { + } + + // not compatible Max methods : + //////////////////////////////////////////////////////// + /** + * Not supported in PD + */ + static public boolean isStandAlone() { + return false; + } + + static public short getMaxVersion() { + return 0; + } + + static public int[] getMaxVersionInts() { + int ret[] = new int[3]; + + ret[0] = 0; + ret[1] = 99; + ret[2] = 0; + + return ret; + } + + /** + * Not supported in PD + */ + static public void hideCursor() { + } + + /** + * Not supported in PD + */ + static public void showCursor() { + } + + /** + * Not supported in PD + */ + static public void nextWindowIsModal() { + } + + // constants + public static String MXJ_VERSION = "pdj 0.8.3"; + + public static final int PATH_STYLE_COLON = 2; + public static final int PATH_STYLE_MAX = 0; + public static final int PATH_STYLE_NATIVE = 1; + public static final int PATH_STYLE_NATIVE_WIN = 4; + public static final int PATH_STYLE_SLASH = 3; + public static final int PATH_TYPE_ABSOLUTE = 1; + public static final int PATH_TYPE_BOOT = 3; + public static final int PATH_TYPE_C74 = 4; + public static final int PATH_TYPE_IGNORE = 0; + public static final int PATH_TYPE_RELATIVE = 2; +} diff --git a/src/java/com/cycling74/max/MessageReceiver.java b/src/java/com/cycling74/max/MessageReceiver.java new file mode 100644 index 0000000..3310de0 --- /dev/null +++ b/src/java/com/cycling74/max/MessageReceiver.java @@ -0,0 +1,11 @@ +package com.cycling74.max; + +/** + * This is a utility callback class that can be used to notify background job. + */ +public interface MessageReceiver { + /** + * Called when a event has occured. + */ + public void messageReceived(Object src, int messageId, Object data); +} diff --git a/src/java/com/cycling74/max/package.html b/src/java/com/cycling74/max/package.html new file mode 100644 index 0000000..dcd23c3 --- /dev/null +++ b/src/java/com/cycling74/max/package.html @@ -0,0 +1,5 @@ + + +

Basic package for PDJ

+ + \ No newline at end of file diff --git a/src/java/com/cycling74/msp/AudioFileBuffer.java b/src/java/com/cycling74/msp/AudioFileBuffer.java new file mode 100644 index 0000000..b2e76f3 --- /dev/null +++ b/src/java/com/cycling74/msp/AudioFileBuffer.java @@ -0,0 +1,116 @@ +package com.cycling74.msp; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; + +import com.cycling74.max.MessageReceiver; + +/** + * Work in progress, target: 0.8.4 + */ +class AudioFileBuffer { + + /** file buffer value */ + public float buf[][]; + + /** Value of the message that will be sended to MessageReceiver when the file will be loaded */ + public static final int FINISHED_READING = 1; + + private MessageReceiver callback = null; + private String file; + private AudioFormat audioFormat; + private AudioFormat targetAudioFormat; + + // getters values + private float sampleRate; + private int sampleBitFormat; + private boolean sampleBigEndian; + private int sampleChannels; + private long sampleFrames; + + public AudioFileBuffer(String filename) throws FileNotFoundException, IOException, UnsupportedAudioFileException { + open(filename); + } + + public AudioFileBuffer(String filename, MessageReceiver callback) throws FileNotFoundException, IOException, UnsupportedAudioFileException { + this.callback = callback; + open(filename); + } + + public void open(String filename) throws FileNotFoundException, IOException, UnsupportedAudioFileException { + // file format + sampleRate = 0; + sampleBitFormat = 0; + sampleChannels = 0; + sampleFrames = 0; + + AudioInputStream sourceAudioInputStream = AudioSystem.getAudioInputStream( new FileInputStream(file) ); + AudioFormat sourceAudioFormat = sourceAudioInputStream.getFormat(); + targetAudioFormat = new AudioFormat( + AudioFormat.Encoding.PCM_UNSIGNED, // Encoding + getSystemSampleRate(), // Sample rate + 16, // bit + sourceAudioFormat.getChannels(), // channel + sourceAudioFormat.getFrameSize(), // frame size + sourceAudioFormat.getFrameRate(), // frame rate + false ); // big Endian + AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(audioFormat, sourceAudioInputStream ); + + FileLoader thread = new FileLoader(audioInputStream); + new Thread(thread).start(); + } + + class FileLoader implements Runnable { + AudioInputStream ais; + + FileLoader(AudioInputStream input) { + ais = input; + } + + public void run() { + try { + buf = new float[audioFormat.getChannels()][]; + + for (int i=0;ais.available() != 0;i++) { + //doc.content[i] = audioInputStream.read() / 65535; + } + + if ( callback != null ) + callback.messageReceived(this, FINISHED_READING, null); + } catch ( IOException e ) { + } + } + } + + public float getSampleRate() { + return sampleRate; + } + + public int getSampleSizeInBits() { + return sampleBitFormat; + } + + public boolean isBigEndian() { + return sampleBigEndian; + } + + public long getFrameLength() { + return sampleFrames; + } + + public int getChannels() { + return sampleChannels; + } + + public float getLengthMs() { + return buf[0].length / (sampleRate / 1000); + } + + private native int getSystemSampleRate(); +} \ No newline at end of file diff --git a/src/java/com/cycling74/msp/MSPBuffer.java b/src/java/com/cycling74/msp/MSPBuffer.java new file mode 100644 index 0000000..70b730a --- /dev/null +++ b/src/java/com/cycling74/msp/MSPBuffer.java @@ -0,0 +1,127 @@ +package com.cycling74.msp; + +/** + * Used to get or set pd array content. Please note that the channel parameter + * is added in the API to match Max/MSP MSPBuffer signature. + */ +public class MSPBuffer { + /** + * Returns the array content. + * @param name the array name + * @return the array contents + */ + public static float[] peek(String name) { + return getArray(name, 0, -1); + } + + /** + * Returns the array content. + * @param name the array name + * @param channel not used in pd + * @return the array contents + */ + public static float[] peek(String name, int channel) { + return getArray(name, 0, -1); + } + + /** + * Returns the array content. + * @param name the array name + * @param channel not used in pd + * @param start the start index of the array + * @param length the size of the array to return + * @return the array contents + */ + public static float[] peek(String name, int channel, long start, long length) { + return getArray(name, start, length); + } + + /** + * Returns the array content value at a specific position. + * @param name the array name + * @param channel not used in pd + * @param index the start index of the array + * @return the value stored at index start + */ + public static float peek(String name, int channel, long index) { + float ret[] = getArray(name, index, 1); + if ( ret == null ) + return 0; + return ret[0]; + } + + /** + * Sets array content. + * @param name the array name + * @param values the array to set + */ + public static void poke(String name, float values[]) { + setArray(name, 0, values); + } + + /** + * Sets array content. + * @param name the array name + * @param channel not used in pd + * @param values the array to set + */ + public static void poke(String name, int channel, float values[]) { + setArray(name, 0, values); + } + + /** + * Sets array content. + * @param name the array name + * @param channel not used in pd + * @param start the start index of the array + * @param values the array to set + */ + public static void poke(String name, int channel, long start, float values[]) { + setArray(name, start, values); + } + + /** + * Set a value in a array. + * @param name the array name + * @param channel not used in pd + * @param index the index in the array to set + * @param value the value to set in the array + */ + public static void poke(String name, int channel, long index, float value) { + float content[] = new float[1]; + content[0] = value; + setArray(name, index, content); + } + + /** + * Sets the array size. + * @param name the array name + * @param numchannel not used in pd + * @param size the new array size; + */ + public static native void setSize(String name, int numchannel, long size); + + /** + * Returns the array size + * @param name the array name + * @return the array size or -1 if not found + */ + public static native long getSize(String name); + + private static native float[] getArray(String name, long from, long size); + private static native void setArray(String name, long from, float[]content); + private MSPBuffer() {} + + /** + * Return the number of channel for this array. Useless in PD cause there + * is no channels in a array. + * @param name array name. + * @return always returns 1 on pd; unless the name is not defined. + */ + public static int getChannel(String name) { + // resolv the name + if ( getSize(name) != -1 ) + return 1; + return -1; + } +} diff --git a/src/java/com/cycling74/msp/MSPObject.java b/src/java/com/cycling74/msp/MSPObject.java new file mode 100644 index 0000000..be2d68b --- /dev/null +++ b/src/java/com/cycling74/msp/MSPObject.java @@ -0,0 +1,163 @@ +package com.cycling74.msp; + +import java.lang.reflect.Method; + +import com.cycling74.max.DataTypes; +import com.cycling74.max.MaxObject; + +/** + * Main object to extend to use dsp with pd. You will need to + * use the pdj~ external if you want to process signals. Before + * the dsp starts processing, the method dsp will be called and it must + * return the performer method that will process each dsp cycles. + * + *

Here is a basic guideline for using the MSPObject:

+ *

+ * import com.cycling74.max.*;
+ * import com.cycling74.msp.*;
+ * import java.lang.reflection.Method;
+ * 
+ * public class panner extends MSPObject {
+ *   float left = 1, right = 1;
+ *   
+ *   public panner() {
+ *       declareInlets( new int[] { SIGNAL, DataTypes.ANYTHING } );
+ *       declareOutlets( new int[] { SIGNAL, SIGNAL } );
+ *   }
+ *   
+ *   // From 0..127
+ *   public void inlet(float val) {
+ *       if ( val > 64 ) {
+ *           right = 1;
+ *           left = ((127-val) / 64);
+ *       } else {
+ *           left = 1;
+ *           right = val / 64;
+ *       }
+ *   }
+ *  
+ *   public Method dsp(MSPSignal[] ins, MSPSignal[] outs) {
+ *       return getPerformMethod("perform");
+ *   }
+ *
+ *   public void perform(MSPSignal[] ins, MSPSignal[] outs) {
+ *       for (int i=0;i<ins[0].n;i++) {
+ *           outs[0].vec[i] = ins[0].vec[i] * left;
+ *           outs[1].vec[i] = ins[0].vec[i] * right;
+ *       }
+ *   }
+ * }
+ * 

+ */ +public abstract class MSPObject extends MaxObject { + /** + * Use this value to indentify a signal intlet/outlet to + * the method declareInlet/declareOutlet. + */ + public final static int SIGNAL = 32; + + /** + * Initialize the dsp state. From the numbers of input/output this + * method must return a performing method. + * @param ins input signals + * @param outs output signals + * @return the method to execute at each dsp cycle + */ + public abstract Method dsp(MSPSignal[] ins, MSPSignal[] outs); + + /** + * Quicky method to be used with dsp(MSPSignal[], MSPSignal[]). It + * will return the method name methodName with signature + * (MSPSignal[], MSPSignal[]) in the current class. + * @param methodName the name of the method in the current class + * @return the method reflection + */ + protected Method getPerformMethod(String methodName) { + try { + Method m = getClass().getDeclaredMethod(methodName, new Class[] { + MSP_SIGNAL_ARRAY_CLZ, MSP_SIGNAL_ARRAY_CLZ }); + return m; + } catch ( NoSuchMethodException e ) { + error("pdj~: method: " + methodName + " not found in class in:" + getClass().toString()); + } + return null; + } + + /** + * Force to copy of each MSPBuffer when performer is called. Right now, + * on pdj the MSPBuffer is always copied. + * @param copyBuffer true if you need to copyBuffer + */ + protected void setNoInPlace(boolean copyBuffer) { + //this.copyBuffer = copyBuffer; + } + + /** + * This method is called when the dsp is start/stop. + * NOT USED ON PD. + * @param dspRunning true if the dsp is running + */ + protected void dspstate(boolean dspRunning) { + } + + + /** + * Declare the inlets used by this object. Use MSPObject.SIGNAL + * to define a signal inlet. Note: any signal inlet won't + * be able to process any atom messages. + */ + protected void declareInlets(int[] types) { + int i, inlets = 0; + for (i=0;i + +

Package for using using array and signals objects.

+ + \ No newline at end of file diff --git a/src/java/com/e1/pdj/ConsoleStream.java b/src/java/com/e1/pdj/ConsoleStream.java new file mode 100644 index 0000000..cf396c7 --- /dev/null +++ b/src/java/com/e1/pdj/ConsoleStream.java @@ -0,0 +1,35 @@ +package com.e1.pdj; + +import java.io.*; + +import com.cycling74.max.MaxSystem; + +public class ConsoleStream extends OutputStream { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(4096); + + protected void send(String message) { + MaxSystem.post("pdj: " + message); + } + + public void flush() { + String msg = buffer.toString(); + if ( msg.endsWith("\n") ) { + msg = msg.substring(0, msg.length()-1); + } + if ( !msg.equals("") ) + send(buffer.toString()); + buffer.reset(); + } + + public void write(byte[] b) throws IOException { + buffer.write(b); + } + + public void write(int b) throws IOException { + buffer.write(b); + } + + public void write(byte[] b, int off, int len) throws IOException { + buffer.write(b, off, len); + } +} diff --git a/src/java/com/e1/pdj/ConsoleStreamWin32.java b/src/java/com/e1/pdj/ConsoleStreamWin32.java new file mode 100644 index 0000000..80f767e --- /dev/null +++ b/src/java/com/e1/pdj/ConsoleStreamWin32.java @@ -0,0 +1,34 @@ +package com.e1.pdj; + +import com.cycling74.max.MaxSystem; + +/** + * Win32 has a special stream since it can contains /r/n that will + * be duplicated in the console + */ +public class ConsoleStreamWin32 extends ConsoleStream { + protected void send(String message) { + StringBuffer ret = new StringBuffer(); + + for (int i=0;i classFile.lastModified() ) { + compileClass(javaFile); + } else { + if ( verboseCL ) + MaxSystem.post("class: " + name + " is already compiled and younger than the source .java file"); + } + } else { + compileClass(javaFile); + } + } + + resetClassloader(); + + try { + return instance.loadClass(name); + } catch (ClassNotFoundException e) { + throw new PDJClassLoaderException(e); + } + } + + protected static void compileClass(File javaFile) throws PDJClassLoaderException { + String str_compiler = System.getProperty("pdj.compiler"); + GenericCompiler compiler = null; + + if ( str_compiler.equals("jikes") ) { + compiler = new JikesCompiler(); + } else { + if ( !str_compiler.equals("javac") ) { + System.err.println("pdj: unknown compiler:" + str_compiler + ", using javac."); + } + compiler = new JavacCompiler(); + } + compiler.javaFile = javaFile; + compiler.compileClass(); + } +} diff --git a/src/java/com/e1/pdj/PDJClassLoaderException.java b/src/java/com/e1/pdj/PDJClassLoaderException.java new file mode 100644 index 0000000..547853c --- /dev/null +++ b/src/java/com/e1/pdj/PDJClassLoaderException.java @@ -0,0 +1,17 @@ +package com.e1.pdj; + +public class PDJClassLoaderException extends Exception { + private static final long serialVersionUID = 8714491904913363787L; + + public PDJClassLoaderException(String msg) { + super(msg); + } + + public PDJClassLoaderException(String msg, Exception root) { + super(msg, root); + } + + public PDJClassLoaderException(Exception root) { + super(root); + } +} diff --git a/src/java/com/e1/pdj/PDJError.java b/src/java/com/e1/pdj/PDJError.java new file mode 100644 index 0000000..7d89f1a --- /dev/null +++ b/src/java/com/e1/pdj/PDJError.java @@ -0,0 +1,15 @@ +package com.e1.pdj; + + +public class PDJError extends Error { + private static final long serialVersionUID = 1264000707984047887L; + + public PDJError(String msg) { + super(msg); + } + public PDJError(Throwable t) { + initCause(t); + } + public PDJError() { + } +} diff --git a/src/java/com/e1/pdj/PDJSystem.java b/src/java/com/e1/pdj/PDJSystem.java new file mode 100644 index 0000000..3d30a53 --- /dev/null +++ b/src/java/com/e1/pdj/PDJSystem.java @@ -0,0 +1,142 @@ +package com.e1.pdj; + +import com.cycling74.max.MaxSystem; + +import java.awt.Component; +import java.awt.Frame; +import java.awt.Toolkit; +import java.io.*; + +/** + * Startup class for pdj. + */ +public class PDJSystem { + private static int loaded = 0; + + public static PrintStream err; + + public static PrintStream out; + + /** + * Called by the pdj external when the JVM is initializing. + */ + public static void _init_system() { + if ( loaded == 1 ) + return; + linknative(); + initIO(); + } + + static void resolvRtJar() { + char ps = File.separatorChar; + String systemCpJar = System.getProperty("pdj.JAVA_HOME"); + if ( systemCpJar == null ) { + systemCpJar = System.getProperty("JAVA_HOME"); + if ( systemCpJar == null ) { + systemCpJar = System.getenv("JAVA_HOME"); + } + } + + System.setProperty("pdj.JAVA_HOME", systemCpJar); + GenericCompiler.rtJar = systemCpJar + ps + "jre" + ps + "lib" + ps + "rt.jar" + File.pathSeparator; + } + + /** + * Link the Java native classes + */ + static void linknative() { + String pdjHome = System.getProperty("pdj.home"); + + // this is a hack to be sure that statics of MaxSystem are loaded + // before everything + Class cls = MaxSystem.class; + + String osname = System.getProperty("os.name"); + + if ( osname.indexOf("Linux") != -1 ) { + // maps PD object as a JVM native library + Runtime.getRuntime().load(pdjHome + "/pdj.pd_linux"); + loaded = 1; + resolvRtJar(); + return; + } + + if ( osname.indexOf("Windows") != -1 ) { + // maps PD object as a JVM native library + Runtime.getRuntime().load(pdjHome + "/pdj.dll"); + loaded = 1; + resolvRtJar(); + return; + } + + if ( osname.indexOf("OS X") != -1 ) { + // maps PD object as a JVM native library + try { + Runtime.getRuntime().load(pdjHome + "/pdj.pd_darwin"); + } catch (UnsatisfiedLinkError e ) { + Runtime.getRuntime().load(pdjHome + "/pdj.pd_imac"); + } + loaded = 1; + + // this will initialize the AWT component in another thread + new Thread(new Runnable() { + public void run() { + Class clz = Component.class; + } + }).start(); + + GenericCompiler.rtJar = "/System/Library/Frameworks/JavaVM.framework/Classes/classes.jar:"; + + return; + } + + System.err.println("pdj: operating system type not found, the native link has not been made"); + } + + static boolean redirectIO() { + String prop = System.getProperty("pdj.redirect-pdio"); + + if ( prop == null ) + return true; + + if ( prop.charAt(0) == '0' ) + return false; + + if ( prop.equals("false") ) + return false; + + return true; + } + + static void initIO() { + if ( redirectIO() ) { + if ( System.getProperty("os.name").indexOf("Windows") == -1 ) { + out = new PrintStream(new ConsoleStream(), true); + err = new PrintStream(new ConsoleStream(), true); + } else { + out = new PrintStream(new ConsoleStreamWin32(), true); + err = new PrintStream(new ConsoleStreamWin32(), true); + } + System.setOut(out); + System.setErr(err); + } else { + out = System.out; + err = System.err; + } + } + + public static boolean isSystemPropertyTrue(String name) { + String value = System.getProperty(name); + + if ( value == null ) + return false; + + if ( value.toLowerCase().equals("true") ) + return true; + + if ( value.equals("1") ) + return true; + + return false; + } +} diff --git a/src/java/com/e1/pdj/PriorityQueue.java b/src/java/com/e1/pdj/PriorityQueue.java new file mode 100644 index 0000000..59a69a4 --- /dev/null +++ b/src/java/com/e1/pdj/PriorityQueue.java @@ -0,0 +1,68 @@ +package com.e1.pdj; + +import java.util.*; +import com.cycling74.max.Executable; + +public class PriorityQueue implements Runnable { + List list = new ArrayList(); + private Thread thread; + boolean tostop = false; + + public PriorityQueue(int priority) { + thread = new Thread(this); + + switch( priority ) { + case Thread.MIN_PRIORITY : + thread.setName("PriorityQueue:low"); + break; + case Thread.NORM_PRIORITY : + thread.setName("PriorityQueue:norm"); + break; + case Thread.MAX_PRIORITY : + thread.setName("PriorityQueue:max"); + break; + } + thread.setPriority(priority); + thread.setDaemon(true); + thread.start(); + } + + public void shutdown() { + synchronized(this) { + tostop = true; + notify(); + } + } + + public void run() { + Executable exec; + + while(true) { + try { + synchronized(this) { + if ( list.size() == 0 ) + wait(); + + if ( tostop ) + break; + exec = (Executable) list.remove(0); + } + try { + exec.execute(); + } catch (Exception e) { + e.printStackTrace(); + } + } catch (InterruptedException e) { + break; + } + } + } + + public void defer(Executable e) { + synchronized(this) { + list.add(e); + notify(); + } + } + +} diff --git a/src/java/com/e1/pdj/test/AtomTest.java b/src/java/com/e1/pdj/test/AtomTest.java new file mode 100644 index 0000000..c900ed8 --- /dev/null +++ b/src/java/com/e1/pdj/test/AtomTest.java @@ -0,0 +1,92 @@ +package com.e1.pdj.test; + +import junit.framework.TestCase; +import com.cycling74.max.Atom; + +public class AtomTest extends TestCase { + + public void testIsIn() { + Atom[] list = new Atom[] { Atom.newAtom(1), Atom.newAtom("ok"), Atom.newAtom(5) }; + + assertEquals(Atom.isIn(Atom.newAtom("ok"), list), 1); + assertEquals(Atom.isIn(Atom.newAtom(5), list), 2); + assertEquals(Atom.isIn(Atom.newAtom(0), list), -1); + assertEquals(Atom.isIn(Atom.newAtom(1), list, 1 ,2), -1); + assertEquals(Atom.isIn(Atom.newAtom("ok"), list, 1 ,2), 1); + } + + public void testRemoveSome() { + Atom[] list = new Atom[] { Atom.newAtom(1), Atom.newAtom("ok"), Atom.newAtom(5) }; + + Atom[] test = Atom.removeSome(list, 1, 2); + assertEquals(1, test.length); + assertEquals(Atom.newAtom(1), test[0]); + } + + public void testReverse() { + Atom[] list = new Atom[] { Atom.newAtom(1), Atom.newAtom("ok"), Atom.newAtom(5) }; + + Atom[] test = Atom.reverse(list); + assertEquals(Atom.newAtom(5), test[0]); + assertEquals(Atom.newAtom("ok"), test[1]); + assertEquals(Atom.newAtom(1),test[2]); + } + + public void testRotate() { + Atom[] list = new Atom[] { Atom.newAtom(1), Atom.newAtom("ok"), Atom.newAtom(5) }; + + Atom[] test = Atom.rotate(list, 2); + assertEquals(Atom.newAtom("ok"), test[0]); + assertEquals(Atom.newAtom(5), test[1]); + assertEquals(Atom.newAtom(1), test[2]); + + test = Atom.rotate(list, 5); + assertEquals(Atom.newAtom("ok"), test[0]); + assertEquals(Atom.newAtom(5), test[1]); + assertEquals(Atom.newAtom(1), test[2]); + + test = Atom.rotate(list, 1); + assertEquals(Atom.newAtom(5), test[0]); + assertEquals(Atom.newAtom(1), test[1]); + assertEquals(Atom.newAtom("ok"), test[2]); + } + + public void testRemoveFirst() { + Atom[] list = new Atom[] { Atom.newAtom(1), Atom.newAtom("ok"), Atom.newAtom(5) }; + + Atom[] test = Atom.removeFirst(list, 2); + assertEquals(1, test.length); + assertEquals(Atom.newAtom(5), test[0]); + + test = Atom.removeFirst(list); + assertEquals(2, test.length); + assertEquals(Atom.newAtom("ok"), test[0]); + assertEquals(Atom.newAtom(5), test[1]); + } + + public void testRemoveLast() { + Atom[] list = new Atom[] { Atom.newAtom(1), Atom.newAtom("ok"), Atom.newAtom(5) }; + + Atom[] test = Atom.removeLast(list, 2); + assertEquals(1, test.length); + assertEquals(Atom.newAtom(1), test[0]); + + test = Atom.removeLast(list); + assertEquals(2, test.length); + assertEquals(Atom.newAtom(1), test[0]); + assertEquals(Atom.newAtom("ok"), test[1]); + } + + public void testUnion() { + assertTrue("union not implemented", false); + } + + public void testIntersection() { + assertTrue("intersection not implementated", false); + } + + public void testInt() { + Atom test = Atom.newAtom(0x90); + assertEquals(0x90, test.getInt()); + } +} diff --git a/src/java/com/e1/pdj/test/CallbackTest.java b/src/java/com/e1/pdj/test/CallbackTest.java new file mode 100644 index 0000000..4105479 --- /dev/null +++ b/src/java/com/e1/pdj/test/CallbackTest.java @@ -0,0 +1,10 @@ +package com.e1.pdj.test; + +import junit.framework.TestCase; + +public class CallbackTest extends TestCase { + + public void testSomething() { + + } +} diff --git a/src/java/com/e1/pdj/test/MaxQelemTest.java b/src/java/com/e1/pdj/test/MaxQelemTest.java new file mode 100644 index 0000000..a55fef9 --- /dev/null +++ b/src/java/com/e1/pdj/test/MaxQelemTest.java @@ -0,0 +1,7 @@ +package com.e1.pdj.test; + +import junit.framework.TestCase; + +public class MaxQelemTest extends TestCase { + +} diff --git a/src/java/help_class.java b/src/java/help_class.java new file mode 100644 index 0000000..3410c70 --- /dev/null +++ b/src/java/help_class.java @@ -0,0 +1,26 @@ +import com.cycling74.max.*; + +public class help_class extends MaxObject { + int attr1; + + public help_class() { + declareOutlets(new int[] { DataTypes.FLOAT, DataTypes.ALL }); + } + + public void inlet(float x) { + outlet(0, x); + } + + public void bang() { + outlet(1, "BANG! received"); + } + + public void callme(Atom args[]) { + outlet(1, "callme has called with arg1:" + args[0].toString()); + } + + public void dynamic_method() { + outlet(1, "dynamic_method has been called"); + } + +} diff --git a/src/java/panner.java b/src/java/panner.java new file mode 100644 index 0000000..34fa77b --- /dev/null +++ b/src/java/panner.java @@ -0,0 +1,37 @@ +import java.lang.reflect.Method; + +import com.cycling74.max.*; +import com.cycling74.msp.*; + +public class panner extends MSPObject { + float left = 1, right = 1; + + public panner() { + declareInlets( new int[] { SIGNAL, DataTypes.ANYTHING } ); + declareOutlets( new int[] { SIGNAL, SIGNAL } ); + } + + /** + * From 0..127 + */ + public void inlet(float val) { + if ( val > 64 ) { + right = 1; + left = ((127-val) / 64); + } else { + left = 1; + right = val / 64; + } + } + + public Method dsp(MSPSignal[] ins, MSPSignal[] outs) { + return getPerformMethod("perform"); + } + + public void perform(MSPSignal[] ins, MSPSignal[] outs) { + for (int i=0;i +#include +#include +#include +#include "pdj.h" + +int getuglylibpath(char *path) { + char buffer[BUFFER_SIZE]; + FILE *f; + + sprintf(buffer, "/proc/%d/maps", getpid()); + f = fopen(buffer, "r"); + if ( f == NULL ) { + perror("pdj: unable to map :"); + strcpy(path, "."); + return 1; + } + + while(!feof(f)) { + fgets(buffer, BUFFER_SIZE-1, f); + if ( strstr(buffer, "pdj.pd_linux") != NULL ) { + buffer[strlen(buffer) - 14] = 0; + strcpy(path, buffer+49); + fclose(f); + return 0; + } + } + + /* not found, check in the current dir :( */ + post("pdj: humm... pdj path library not found, setting current path"); + strcpy(path, "."); + fclose(f); + return 1; +} + + +JNI_CreateJavaVM_func *linkjvm(char *vm_type) { + JNI_CreateJavaVM_func *func; + char work[BUFFER_SIZE]; + char *javahome = pdj_getProperty("pdj.JAVA_HOME"); + void *libVM; + + if ( javahome == NULL ) { + javahome = getenv("JAVA_HOME"); + } else { + sprintf(work, "%s/jre/lib/i386/%s/libjvm.so", javahome, vm_type); + libVM = dlopen(work, RTLD_LAZY); + + if ( libVM == NULL ) { + post("pdj: unable to use the JVM specified at pdj.JAVA_HOME"); + javahome = getenv("JAVA_HOME"); + } + } + + if ( javahome == NULL ) { + post("pdj: using JVM from the LD_LIBRARY_PATH"); + libVM = dlopen("libjava.so", RTLD_LAZY); + } else { + post("pdj: using JVM %s", javahome); + /* using LD_LIBRARY_PATH + putenv doesn't work, load std jvm libs + * with absolute path. order is important. + */ + sprintf(work, "%s/jre/lib/i386/%s/libjvm.so", javahome, vm_type); + dlopen(work, RTLD_LAZY); + + sprintf(work, "%s/jre/lib/i386/libverify.so", javahome); + dlopen(work, RTLD_LAZY); + + sprintf(work, "%s/jre/lib/i386/libjava.so", javahome); + dlopen(work, RTLD_LAZY); + + sprintf(work, "%s/jre/lib/i386/libmlib_image.so", javahome); + dlopen(work, RTLD_LAZY); + + /* ELF should support dynamic LD_LIBRARY_PATH :( :( :( */ + sprintf(work, "%s/jre/lib/i386/libjava.so", javahome); + libVM = dlopen(work, RTLD_LAZY); + } + + if ( libVM == 0 ) { + error("pdj: %s", dlerror()); + return NULL; + } + + func = dlsym(libVM, "JNI_CreateJavaVM"); + if ( func == 0 ) { + error("pdj: %s", dlerror()); + return NULL; + } + return func; +} diff --git a/src/pdj-osx.c b/src/pdj-osx.c new file mode 100644 index 0000000..bd2be18 --- /dev/null +++ b/src/pdj-osx.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +#include "pdj.h" + +int getuglylibpath(char *path) { + char fullpath[MAXPDSTRING], *pfullpath; + FILE *fd; + + fd = (FILE *) open_via_path("", "pdj.properties", "", fullpath, &pfullpath, MAXPDSTRING, 0); + if ( fd != NULL ) { + close(fd); + if ( fullpath[0] != 0 ) { + if ( pfullpath == ((char *) &fullpath) ) { + getcwd(path, MAXPDSTRING); + } else { + strcpy(path, fullpath); + } + return 0; + } + } + + error("unable to find pdj directory, please add it in your pure-data path settings"); + return 1; +} + +JNI_CreateJavaVM_func *linkjvm(char *vmtype) { + char *jvmVersion = pdj_getProperty("pdj.osx.JAVA_JVM_VERSION"); + + if ( jvmVersion != NULL ) { + setenv("JAVA_JVM_VERSION", jvmVersion, 1); + } + + return (JNI_CreateJavaVM_func *) &JNI_CreateJavaVM; +} \ No newline at end of file diff --git a/src/pdj-win32.c b/src/pdj-win32.c new file mode 100644 index 0000000..c93e53a --- /dev/null +++ b/src/pdj-win32.c @@ -0,0 +1,107 @@ +#include +#include +#include +#include "pdj.h" + +int getuglylibpath(char *path) { + HMODULE hmodule = GetModuleHandle("pdj.dll"); + char dllPath[BUFFER_SIZE]; + int rc; + + if ( hmodule == NULL ) { + post("pdj: can't get windows dll handle"); + strcpy(path, "."); + return 1; + } + + rc = GetModuleFileName(hmodule, dllPath, 1023); + if ( rc == 0 || rc == 1023 ) { + post("pdj: can't get windows dll path"); + strcpy(path, "."); + return 1; + } + + dllPath[strlen(dllPath)-8] = 0; + strcpy(path, dllPath); + return 0; +} + + +static char *getJavaHomeFromReg(char *javaHome) { + char keyName[BUFFER_SIZE] = "Software\\JavaSoft\\Java Development Kit"; + char *dest; + int size = BUFFER_SIZE; + HKEY hKey; + long rc; + + rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &hKey); + if ( rc != ERROR_SUCCESS ) + return NULL; + + strcat(keyName, "\\"); + dest = strlen(keyName) + keyName; + rc = RegQueryValueEx(hKey, "CurrentVersion", NULL, NULL, + (LPBYTE) dest, &size); + + RegCloseKey(hKey); + + if ( rc != ERROR_SUCCESS ) + return NULL; + + rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &hKey); + if ( rc != ERROR_SUCCESS ) + return NULL; + + size = BUFFER_SIZE; + rc = RegQueryValueEx(hKey, "JavaHome", NULL, NULL, + (LPBYTE) javaHome, &size); + + RegCloseKey(hKey); + + if ( rc != ERROR_SUCCESS ) + return NULL; + + return javaHome; +} + + +JNI_CreateJavaVM_func *linkjvm(char *vm_type) { + HINSTANCE jvmDll = (HINSTANCE)NULL; + JNI_CreateJavaVM_func *func; + char work[BUFFER_SIZE]; + char *javahome = pdj_getProperty("pdj.JAVA_HOME"); + void *libVM; + + // default config or no config ? try + if ( javahome == NULL || javahome[0] == '/' ) { + javahome = NULL; + javahome = getenv("JAVA_HOME"); + if ( javahome == NULL ) { + javahome = getJavaHomeFromReg(work); + if ( javahome == NULL ) { + post("pdj: unable to find any java VM. Please set JAVA_HOME environment variable"); + return NULL; + } + } + } + + strcpy(work, javahome); + strcat(work, "\\jre\\bin\\"); + strcat(work, vm_type); + strcat(work, "\\jvm.dll"); + jvmDll = LoadLibrary(work); + + if ( jvmDll == NULL ) { + post("post: unable to find jvm.dll in %s", work); + return NULL; + } + func = GetProcAddress(jvmDll, "JNI_CreateJavaVM"); + if ( func == NULL ) { + post("pdj: unable to find symbol: JNI_CreateJavaVM in jvm.dll ??"); + return NULL; + } + + post("pdj: using JVM %s", javahome); + + return func; +} diff --git a/src/pdj.c b/src/pdj.c new file mode 100644 index 0000000..d76c4f9 --- /dev/null +++ b/src/pdj.c @@ -0,0 +1,396 @@ +#include +#include "pdj.h" +#include "type_handler.h" + +JavaVM *jni_jvm = NULL; +t_class *inlet_proxy; +t_class *pdj_class; + +#define PDTHREAD_STACKSIZE 1000000 +static JNIEnv *pdthread_jnienv = NULL; +static char *pdthread_stackaddr; + +#define PROF_MIN 999 + +double prof_max = 0, prof_min = PROF_MIN, prof_tot = 0, prof_nb = 0, prof_tmp; + +JNIEnv *pdjAttachVM() { + const int N_REFS = 16; + JNIEnv *env; + char stack_pos; /* the position of this variable is relative to the + position in the stack of the main thread */ + + PROF(prof_tmp = sys_getrealtime();); + + /* this avoids getting the JNIEnv when we are in the main thread. so + * if the current stack is more far than 1 meg of distance between the + * main thread stack when pdj was initialized, it is considered a + * "external thread" + */ + if ( abs(pdthread_stackaddr - (&stack_pos)) > PDTHREAD_STACKSIZE ) { + (*jni_jvm)->AttachCurrentThread(jni_jvm, (void **)&env, NULL); + ASSERT(env); + } else { + env = pdthread_jnienv; + } + + if ( (*env)->PushLocalFrame(env, N_REFS) < 0 ) { + SHOWEXC; + bug("pdj: java: out of memory!?!"); + } + + return env; +} + + +void pdjDetachVM(JNIEnv *env) { +#ifdef DEBUG + if ( (*env)->ExceptionOccurred(env) ) { + error("pdj: unhandled exception in JNI interface:"); + (*env)->ExceptionDescribe(env); + } +#endif + (*env)->PopLocalFrame(env, NULL); + + PROF(prof_tmp = sys_getrealtime() - prof_tmp;); + PROF(prof_max = prof_tmp > prof_max ? prof_tmp : prof_max;); + PROF(prof_min = prof_tmp < prof_min ? prof_tmp : prof_min;); + PROF(prof_nb++;); + PROF(prof_tot += prof_tmp;); +} + + +static void pdj_mapmethods(JNIEnv *env, t_pdj *pdj) { + jclass base = pdjCaching.cls_MaxObject; + jmethodID idBase, id; + JASSERT(base); + + id = (*env)->GetMethodID(env, pdj->cls, "bang", "()V"); + idBase = (*env)->GetMethodID(env, base, "bang", "()V"); + (*env)->ExceptionClear(env); + if ( id != idBase ) { + pdj->MIDbang = id; + } else { + pdj->MIDbang = NULL; + } + + id = (*env)->GetMethodID(env, pdj->cls, "inlet", "(F)V"); + idBase = (*env)->GetMethodID(env, base, "inlet", "(F)V"); + (*env)->ExceptionClear(env); + if ( id != idBase ) { + pdj->MIDfloat = id; + } else { + pdj->MIDfloat = NULL; + id = (*env)->GetMethodID(env, pdj->cls, "inlet", "(I)V"); + idBase = (*env)->GetMethodID(env, base, "inlet", "(I)V"); + (*env)->ExceptionClear(env); + if ( id != idBase ) { + pdj->MIDint = id; + } else { + pdj->MIDint = NULL; + } + } + + id = (*env)->GetMethodID(env, pdj->cls, "list", "([com/cycling74/max/Atom;)V"); + idBase = (*env)->GetMethodID(env, base, "list", "([com/cycling74/max/Atom;)V"); + (*env)->ExceptionClear(env); + if ( id != idBase ) { + pdj->MIDlist = id; + } else { + pdj->MIDlist = NULL; + } + + idBase = (*env)->GetMethodID(env, pdj->cls, "anything", "(Ljava/lang/String;[Lcom/cycling74/max/Atom;)V"); + pdj->MIDanything = idBase; +} + + +static jobject init_pdj_class(JNIEnv *env, char *name, long cobj, int argc, t_atom *argv) { + char *sig = "(Ljava/lang/String;J[Lcom/cycling74/max/Atom;)Lcom/cycling74/max/MaxObject;"; + jstring objectName; + jmethodID id; + jobject newMaxObject; + jobjectArray args; + jlong jval = cobj; + + id = (*env)->GetStaticMethodID(env, pdjCaching.cls_MaxObject, "registerObject", sig); + JASSERT(id); + + objectName = (*env)->NewStringUTF(env, name); + JASSERT(objectName); + + args = atoms2jatoms(env, argc, argv); + JASSERT(args); + + newMaxObject = (*env)->CallStaticObjectMethod(env, pdjCaching.cls_MaxObject, id, objectName, jval, args); + if ( (*env)->ExceptionOccurred(env) ) { + (*env)->ExceptionDescribe(env); + } + if ( newMaxObject != NULL ) { + jobject global = (*env)->NewGlobalRef(env, newMaxObject); + (*env)->DeleteLocalRef(env, newMaxObject); + return global; + } + return NULL; +} + + +void *pdj_new(t_symbol *s, int argc, t_atom *argv) { + JNIEnv *env = NULL; + t_pdj *x; + + if ( argc < 1 ) { + post("pdj: no class specified"); + return NULL; + } + + if ( argv[0].a_type != A_SYMBOL ) { + post("pdj: first argument must be a class name"); + return NULL; + } + + if ( jni_jvm == NULL ) { + env = init_jvm(); + if ( env == NULL ) + return NULL; + } else { + (*jni_jvm)->AttachCurrentThread(jni_jvm, (void **)&env, NULL); + ASSERT(env); + } + + pdthread_jnienv = env; + + if ( s == gensym("pdj~") ) { + x = (t_pdj *)pd_new(pdj_tilde_class); + } else { + x = (t_pdj *)pd_new(pdj_class); + } + ASSERT(x); + x->jobject_name = argv[0].a_w.w_symbol->s_name; + x->nb_inlet = 0; + x->patch_path = canvas_getcurrentdir()->s_name; + + x->cache = NULL; + x->obj = init_pdj_class(env, x->jobject_name, (long) x, argc, argv); + + if ( x->obj != NULL ) { + jclass cls = (*env)->GetObjectClass(env, x->obj); + JASSERT(cls); + + x->cls = (*env)->NewGlobalRef(env, cls); + pdj_mapmethods(env, x); + return x; + } + + return NULL; +} + + +static void pdj_profiler(t_pdj *pdj) { + if ( prof_nb != 0 ) + post("pdj-profiler: %f min %f max %f avg", prof_min, prof_max, prof_tot / prof_nb); + prof_min = PROF_MIN; + prof_max = 0; + prof_tot = 0; + prof_nb = 0; +} + + +void pdj_free(t_pdj *pdj) { + JNIEnv *env = pdjAttachVM(); + jmethodID id = (*env)->GetMethodID(env, pdj->cls, "notifyDeleted", "()V"); + JASSERT(id); + + (*env)->CallVoidMethod(env, pdj->obj, id); + SHOWEXC; + + PROF(pdj_profiler(pdj);); + + while (pdj->cache != NULL) { + t_pdjcached_sym *next = pdj->cache->next; + free(pdj->cache); + pdj->cache = next; + } + + (*env)->DeleteGlobalRef(env, pdj->cls); + (*env)->DeleteGlobalRef(env, pdj->obj); + + pdjDetachVM(env); +} + + +static void pdj_addcache_sym(t_pdj *pdj, t_symbol *s, jmethodID id, int arged) { + t_pdjcached_sym *n = malloc(sizeof(t_pdjcached_sym)); + + n->sym = s; + n->mid = id; + n->arged = arged; + n->next = pdj->cache; + + pdj->cache = n; +} + + +static void pdj_process_inlet(int idx, t_pdj *pdj, t_symbol *s, int argc, t_atom atoms[]){ + JNIEnv *env = pdjAttachVM(); + jobjectArray args = NULL; + t_pdjcached_sym *cache; + jmethodID id; + jboolean rc; + jstring s_name; + + (*env)->SetIntField(env, pdj->obj, pdjCaching.FIDMaxObject_activity_inlet, idx); + + if ( s == &s_bang ) { + if ( pdj->MIDbang != NULL ) { + (*env)->CallVoidMethod(env, pdj->obj, pdj->MIDbang); + SHOWEXC; + pdjDetachVM(env); + return; + } + } else if ( s == &s_float ) { + if ( pdj->MIDfloat != NULL ) { + (*env)->CallVoidMethod(env, pdj->obj, pdj->MIDfloat, atom_getfloatarg(0, argc, atoms)); + SHOWEXC; + pdjDetachVM(env); + return; + } + if ( pdj->MIDint != NULL ) { + (*env)->CallVoidMethod(env, pdj->obj, pdj->MIDint, atom_getintarg(0, argc, atoms)); + SHOWEXC; + pdjDetachVM(env); + return; + } + } else if ( s == &s_list ) { + if ( pdj->MIDlist != NULL ) { + args = atoms2jatoms(env, argc, atoms); + JASSERT(args); + (*env)->CallVoidMethod(env, pdj->obj, pdj->MIDlist, args); + SHOWEXC; + pdjDetachVM(env); + return; + } + } + + /* is the symbol in cache ? */ + cache = pdj->cache; + while( cache != NULL ) { + if ( cache->sym == s ) { + if ( ! cache->arged ) { + (*env)->CallVoidMethod(env, pdj->obj, cache->mid, NULL); + } else { + args = atoms2jatoms(env, argc, atoms); + JASSERT(args); + (*env)->CallVoidMethod(env, pdj->obj, cache->mid, args); + } + SHOWEXC; + pdjDetachVM(env); + return; + } + cache = cache->next; + } + + /* the last tries will require a Atom[] arguments */ + args = atoms2jatoms(env, argc, atoms); + JASSERT(args); + + /* try with [name](Atom []) */ + id = (*env)->GetMethodID(env, pdj->cls, s->s_name, "([Lcom/cycling74/max/Atom;)V"); + (*env)->ExceptionClear(env); + if ( id != NULL ) { + (*env)->CallVoidMethod(env, pdj->obj, id, args); + SHOWEXC; + pdjDetachVM(env); + pdj_addcache_sym(pdj, s, id, 1); + return; + } + + /* try again with [name]() */ + id = (*env)->GetMethodID(env, pdj->cls, s->s_name, "()V"); + (*env)->ExceptionClear(env); + if ( id != NULL ) { + (*env)->CallVoidMethod(env, pdj->obj, id, NULL); + SHOWEXC; + pdjDetachVM(env); + pdj_addcache_sym(pdj, s, id, 0); + return; + } + + s_name = (*env)->NewStringUTF(env, s->s_name); + JASSERT(s_name); + + /* try with the setter */ + rc = (*env)->CallBooleanMethod(env, pdj->obj, pdjCaching.MIDMaxObject_trySetter, s_name, args); + if ( (*env)->ExceptionCheck(env) == 1 ) { + /* we got an exception, the class do have a setter, but it trowed an + * exception: log it and don't try with anything() + */ + (*env)->ExceptionDescribe(env); + rc = 1; + } + + if ( rc == 0 ) { + /* nothing... call the anything method anything... */ + (*env)->CallVoidMethod(env, pdj->obj, pdj->MIDanything, s_name, args); + SHOWEXC; + } + + // TODO: setters should be cached too + pdjDetachVM(env); +} + + +static void pdj_anything(t_pdj *pdj, t_symbol *s, int argc, t_atom atoms[]){ + pdj_process_inlet(0, pdj, s, argc, atoms); +} + + +static void inlet_proxy_anything(t_inlet_proxy *proxy, t_symbol *s, int argc, t_atom atoms[]){ + pdj_process_inlet(proxy->idx, proxy->peer, s, argc, atoms); +} + + +static void pdj_loadbang(t_pdj *pdj) { + JNIEnv *env = pdjAttachVM(); + jmethodID id = (*env)->GetMethodID(env, pdj->cls, "loadbang", "()V"); + JASSERT(id); + + (*env)->CallVoidMethod(env, pdj->obj, id, NULL); + SHOWEXC; + + pdjDetachVM(env); +} + + +void pdj_setup(void) { + char stack_pos; + + pdj_class = class_new(gensym("pdj"), + (t_newmethod)pdj_new, (t_method)pdj_free, + sizeof(t_pdj), CLASS_DEFAULT|CLASS_NOINLET, A_GIMME, 0); + + class_addmethod(pdj_class, (t_method)pdj_loadbang, gensym("loadbang"), + A_CANT, A_NULL); + class_addanything(pdj_class, (t_method)pdj_anything); + + /* pdj~ things */ + pdj_tilde_class = class_new(gensym("pdj~"), + (t_newmethod)pdj_tilde_new, (t_method)pdj_tilde_free, + sizeof(t_pdj_tilde), CLASS_DEFAULT|CLASS_NOINLET, A_GIMME, 0); + + class_addmethod(pdj_tilde_class, (t_method)pdj_loadbang, gensym("loadbang"), + A_CANT, A_NULL); + class_addanything(pdj_tilde_class, (t_method)pdj_anything); + class_addmethod(pdj_tilde_class, (t_method)pdj_tilde_dsp, gensym("dsp"), 0); + CLASS_MAINSIGNALIN(pdj_tilde_class, t_pdj_tilde, _dummy_f); + + /* inlet_proxy: we create a dummy class to catch all messages from cold + * inlets */ + inlet_proxy = class_new(gensym("pdj_inlet_proxy"), + NULL,NULL, sizeof(t_inlet_proxy), + CLASS_PD|CLASS_NOINLET, A_NULL); + class_addanything(inlet_proxy, (t_method)inlet_proxy_anything); + + /* main thread stack address */ + pdthread_stackaddr = (void *) &(stack_pos); +} diff --git a/src/pdj.h b/src/pdj.h new file mode 100644 index 0000000..d318ded --- /dev/null +++ b/src/pdj.h @@ -0,0 +1,140 @@ +#include +#include + +#ifdef DEBUG + #define ASSERT(v) { if ( v == NULL ) {bug("ouch, assertion failed %s:%d\n", __FILE__, __LINE__);}} + #define JASSERT(v) { if ( v == NULL ){(*env)->ExceptionDescribe(env);bug("ouch, assertion failed %s:%d\n", __FILE__, __LINE__);}} + #undef DEBUG + #define DEBUG(X) {X}; +#else + #define ASSERT(v) + #define DEBUG(X) + #define JASSERT(v) +#endif + +#define SHOWEXC { if ((*env)->ExceptionOccurred(env)) (*env)->ExceptionDescribe(env); } + +#ifdef PROFILER + #define PROF(v) { v } +#else + #define PROF(v) +#endif + +#ifdef MSW + #define DIR_SEP "\\" + #define PATH_SEP ";" +#else + #define DIR_SEP "/" + #define PATH_SEP ":" +#endif + +// the JVM takes 50M; I don't care taking 4K... +#define BUFFER_SIZE 4096 + +// MAXIMUM atom[x] size for type casting from jatoms to atoms +#define MAX_ATOMS_STACK 32 + +typedef struct PdjCaching { + jclass cls_Atom; + jclass cls_AtomString; + jclass cls_AtomFloat; + jclass cls_MaxClock; + jclass cls_MaxObject; + jclass cls_MSPObject; + jclass cls_MSPSignal; + jmethodID MIDAtom_newAtom_String; + jmethodID MIDAtom_newAtom_Float; + jmethodID MIDMaxObject_trySetter; + jmethodID MIDMSPObject_dspinit; + jmethodID MIDMSPObject_emptyPerformer; + jfieldID FIDAtom_type; + jfieldID FIDMaxObject_pdobj_ptr; + jfieldID FIDMaxObject_activity_inlet; + jfieldID FIDMaxClock_clock_ptr; + jfieldID FIDAtomFloat_value; + jfieldID FIDAtomString_value; + jfieldID FIDMSPObject_used_inputs; + jfieldID FIDMSPObject_used_outputs; + jfieldID FIDMSPSignal_vec; +} PdjCaching; +extern PdjCaching pdjCaching; + +typedef struct _pdjcached_sym { + t_symbol *sym; + jmethodID mid; + int arged; + struct _pdjcached_sym *next; +} t_pdjcached_sym; + +extern t_class *pdj_class; +typedef struct _pdj { + t_object x_obj; + char *jobject_name; + int nb_inlet; + char *patch_path; + + /* already resolved symbol to method id */ + t_pdjcached_sym *cache; + + /* java object instance and class definition */ + jobject obj; + jclass cls; + + /* object method binder */ + jmethodID MIDbang; + jmethodID MIDfloat; + jmethodID MIDint; + jmethodID MIDlist; + jmethodID MIDanything; +} t_pdj; + +extern t_class *pdj_tilde_class; +typedef struct _pdj_tilde { + t_pdj pdj; + t_sample _dummy_f; + + /* performer method */ + jmethodID performer; + + /* pointer to private field _used_inputs/outputs */ + jobject _used_inputs; + jobject _used_outputs; + + /* C array to the java float vector */ + jobject *ins; + jobject *outs; + + int ins_count; + int outs_count; + + /* number of arguments sended to the performer */ + int argc; +} t_pdj_tilde; + +extern t_class *inlet_proxy; +typedef struct _inlet_proxy { + t_object x_obj; + t_pdj *peer; + int idx; +} t_inlet_proxy; + + +typedef int JNICALL JNI_CreateJavaVM_func(JavaVM**, JNIEnv**, JavaVMInitArgs*); +int getuglylibpath(char *path); +JNI_CreateJavaVM_func *linkjvm(char *vm_type); +char *pdj_getProperty(char *name); + +JNIEnv *init_jvm(); + +void *pdj_new(t_symbol *s, int argc, t_atom *argv); +void pdj_free(t_pdj *obj); + +void pdj_tilde_dsp(t_pdj_tilde *obj, t_signal **sp); +void *pdj_tilde_new(t_symbol *s, int argc, t_atom *argv); +void pdj_tilde_free(t_pdj_tilde *pdjt); + +JNIEnv *pdjAttachVM(); +void pdjDetachVM(JNIEnv *env); + +extern int REDIRECT_PD_IO; +extern JavaVM *jni_jvm; diff --git a/src/pdj~.c b/src/pdj~.c new file mode 100644 index 0000000..d2f2a36 --- /dev/null +++ b/src/pdj~.c @@ -0,0 +1,206 @@ +#include +#include +#include "pdj.h" + +t_class *pdj_tilde_class; + + +t_int *pdj_tilde_perform(t_int *w) { + JNIEnv *env = pdjAttachVM(); + t_pdj_tilde *pdjt = (t_pdj_tilde *) (w[1]); + int sz = (int) (w[2]); + int i, work; + + /* copy buffer in */ + for (i=0;iins_count;i++) { + t_sample *in = (t_sample *)(w[i+3]); + (*env)->SetFloatArrayRegion(env, pdjt->ins[i], 0, sz, in); + } + + /* call the performer */ + (*env)->CallVoidMethod(env, pdjt->pdj.obj, pdjt->performer, + pdjt->_used_inputs, pdjt->_used_outputs); + + /* if an exception occured, stop the dsp processing for this object */ + if ( (*env)->ExceptionOccurred(env) ) { + int tmp; + + work = i + 3; + + /* insert silence */ + for (tmp=0;tmpouts_count;tmp++) { + t_sample *out = (t_sample *)(w[work+tmp]); + memset(out, 0, sizeof(float) * sz); + } + + error("pdj~: exception occured in dsp processing of: %s", pdjt->pdj.jobject_name); + (*env)->ExceptionDescribe(env); + + /* cancels current dsp processing */ + pdjt->performer = pdjCaching.MIDMSPObject_emptyPerformer; + } + + /* copy buffer out */ + work = i + 3; + for (i=0;iouts_count;i++) { + t_sample *out = (t_sample *)(w[work+i]); + (*env)->GetFloatArrayRegion(env, pdjt->outs[i], 0, sz, out); + } + + pdjDetachVM(env); + + return (w+pdjt->argc+1); +} + + +void pdj_tilde_dsp(t_pdj_tilde *pdjt, t_signal **sp) { + JNIEnv *env = pdjAttachVM(); + jobject perfReflection = (*env)->CallObjectMethod(env, pdjt->pdj.obj, + pdjCaching.MIDMSPObject_dspinit, (jfloat) sp[0]->s_sr, sp[0]->s_n); + int i; + + SHOWEXC; + if ( perfReflection == NULL ) { + post("pdj~: can't bind MSPObject to dsp chain, dsp(MSPSignal[], MSPSignal[]) returned null"); + return; + } + + pdjt->performer = (*env)->FromReflectedMethod(env, perfReflection); + JASSERT(pdjt->performer); + + for(i=0;iins_count;i++) { + jobject tmp, obj = (*env)->GetObjectArrayElement(env, pdjt->_used_inputs, i); + JASSERT(obj); + if ( pdjt->ins[i] != NULL ) + (*env)->DeleteGlobalRef(env, pdjt->ins[i]); + tmp = (*env)->GetObjectField(env, obj, pdjCaching.FIDMSPSignal_vec); + pdjt->ins[i] = (*env)->NewGlobalRef(env, tmp); + (*env)->DeleteLocalRef(env, tmp); + JASSERT(pdjt->ins[i]); + } + + for(i=0;iouts_count;i++) { + jobject tmp, obj = (*env)->GetObjectArrayElement(env, pdjt->_used_outputs, i); + JASSERT(obj); + if ( pdjt->outs[i] != NULL ) + (*env)->DeleteGlobalRef(env, pdjt->outs[i]); + tmp = (*env)->GetObjectField(env, obj, pdjCaching.FIDMSPSignal_vec); + pdjt->outs[i] = (*env)->NewGlobalRef(env, tmp); + (*env)->DeleteLocalRef(env, tmp); + JASSERT(pdjt->outs[i]); + } + pdjDetachVM(env); + + switch(pdjt->argc) { + case 2: + // no inlets/outlets == do nothing + break; + case 3: + dsp_add(pdj_tilde_perform, pdjt->argc, pdjt, sp[0]->s_n, + sp[0]->s_vec); + break; + case 4: + dsp_add(pdj_tilde_perform, pdjt->argc, pdjt, sp[0]->s_n, + sp[0]->s_vec, sp[1]->s_vec); + break; + case 5: + dsp_add(pdj_tilde_perform, pdjt->argc, pdjt, sp[0]->s_n, + sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec); + break; + case 6: + dsp_add(pdj_tilde_perform, pdjt->argc, pdjt, sp[0]->s_n, + sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec, sp[3]->s_vec); + break; + case 7: + dsp_add(pdj_tilde_perform, pdjt->argc, pdjt, sp[0]->s_n, + sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec, sp[3]->s_vec, + sp[4]->s_vec); + break; + case 8: + dsp_add(pdj_tilde_perform, pdjt->argc, pdjt, sp[0]->s_n, + sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec, sp[3]->s_vec, + sp[4]->s_vec, sp[5]->s_vec); + break; + case 9: + dsp_add(pdj_tilde_perform, pdjt->argc, pdjt, sp[0]->s_n, + sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec, sp[3]->s_vec, + sp[4]->s_vec, sp[5]->s_vec, sp[6]->s_vec); + break; + case 10: + dsp_add(pdj_tilde_perform, pdjt->argc, pdjt, sp[0]->s_n, + sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec, sp[3]->s_vec, + sp[4]->s_vec, sp[5]->s_vec, sp[6]->s_vec, sp[7]->s_vec); + break; + default: + error("pdj~: too much ~ inlets/outlets, go see the source luke"); + break; + } +} + + +void *pdj_tilde_new(t_symbol *s, int argc, t_atom *argv) { + t_pdj_tilde *pdjt = pdj_new(s, argc, argv); + JNIEnv *env; + jobject tmp; + + if ( pdjt == NULL ) + return NULL; + + env = pdjAttachVM(); + + /* check if we are really a MSPObject */ + if ( !(*env)->IsInstanceOf(env, pdjt->pdj.obj, pdjCaching.cls_MSPObject) ) { + error("pdj~: class must be a superclass of MSPObject"); + pdj_free((t_pdj*)pdjt); + return NULL; + } + + /* initialize inlets/outlets */ + tmp = (*env)->GetObjectField(env, pdjt->pdj.obj, pdjCaching.FIDMSPObject_used_inputs); + JASSERT(tmp); + pdjt->_used_inputs = (*env)->NewGlobalRef(env, tmp); + (*env)->DeleteLocalRef(env, tmp); + + tmp = (*env)->GetObjectField(env, pdjt->pdj.obj, pdjCaching.FIDMSPObject_used_outputs); + JASSERT(tmp); + pdjt->_used_outputs = (*env)->NewGlobalRef(env, tmp); + (*env)->DeleteLocalRef(env, tmp); + + pdjt->ins_count = (*env)->GetArrayLength(env, pdjt->_used_inputs); + pdjt->outs_count = (*env)->GetArrayLength(env, pdjt->_used_outputs); + + pdjDetachVM(env); + + pdjt->ins = malloc(sizeof(jobject) * pdjt->ins_count); + memset(pdjt->ins, 0, sizeof(jobject) * pdjt->ins_count); + pdjt->outs = malloc(sizeof(jobject) * pdjt->outs_count); + memset(pdjt->outs, 0, sizeof(jobject) * pdjt->outs_count); + + // pdj object pointer + signal size + nb inlets + nb outlets + pdjt->argc = 2 + pdjt->ins_count + pdjt->outs_count; + + return pdjt; +} + + +void pdj_tilde_free(t_pdj_tilde *pdjt) { + JNIEnv *env = pdjAttachVM(); + int i; + + (*env)->DeleteGlobalRef(env, pdjt->_used_inputs); + (*env)->DeleteGlobalRef(env, pdjt->_used_outputs); + for(i=0;iins_count;i++) { + if ( pdjt->ins[i] != NULL ) + (*env)->DeleteGlobalRef(env, pdjt->ins[i]); + } + for(i=0;iouts_count;i++) { + if ( pdjt->outs[i] != NULL ) + (*env)->DeleteGlobalRef(env, pdjt->outs[i]); + } + pdjDetachVM(env); + + free(pdjt->ins); + free(pdjt->outs); + + pdj_free((t_pdj*)pdjt); +} diff --git a/src/type_handler.c b/src/type_handler.c new file mode 100644 index 0000000..7089a31 --- /dev/null +++ b/src/type_handler.c @@ -0,0 +1,151 @@ +#include +#include "pdj.h" +#include "type_handler.h" + +int jatom2atom(JNIEnv *env, jobject jatom, t_atom *atom) { + int type = (*env)->GetIntField(env, jatom, pdjCaching.FIDAtom_type); + const jbyte *symbolValue; + jstring value; + + switch(type) { + case DataTypes_INT: + case DataTypes_FLOAT: + atom->a_type = A_FLOAT; + atom->a_w.w_float = (*env)->GetFloatField(env, jatom, pdjCaching.FIDAtomFloat_value); + return 0; + + case DataTypes_MESSAGE : + atom->a_type = A_SYMBOL; + value = (*env)->GetObjectField(env, jatom, pdjCaching.FIDAtomString_value); + symbolValue = (*env)->GetStringUTFChars(env, value, NULL); + atom->a_w.w_symbol = gensym((char *)symbolValue); + (*env)->ReleaseStringUTFChars(env, value, symbolValue); + return 0; + + default: + error("pdj: java unhandled datatype: %d ", type); + atom->a_type = A_CANT; + return 1; + } +} + + +int jatoms2atoms(JNIEnv *env, jobjectArray jatoms, int *nb_atoms, t_atom *atoms) { + jobject obj; + int i, rc = 0; + + *nb_atoms = (*env)->GetArrayLength(env, jatoms); + + if ( *nb_atoms >= MAX_ATOMS_STACK ) { + error("pdj: array of atoms truncated, original size: %d, new size: %d", + *nb_atoms, MAX_ATOMS_STACK); + *nb_atoms = MAX_ATOMS_STACK; + } + + for(i=0;i<*nb_atoms;i++) { + obj = (*env)->GetObjectArrayElement(env, jatoms, i); + rc |= jatom2atom(env, obj, atoms+i); + } + + return rc; +} + + +jobject atom2jatom(JNIEnv *env, t_atom *atom) { + jstring arg; + jobject ret; + + ASSERT(atom); + switch(atom->a_type) { + case A_NULL: + ret = (*env)->CallStaticObjectMethod(env, pdjCaching.cls_Atom, + pdjCaching.MIDAtom_newAtom_String, NULL); + if ( ret == NULL ) { + SHOWEXC; + return NULL; + } + return ret; + + case A_SYMBOL: + arg = (*env)->NewStringUTF(env, atom->a_w.w_symbol->s_name); + JASSERT(arg); + ret = (*env)->CallStaticObjectMethod(env, pdjCaching.cls_Atom, + pdjCaching.MIDAtom_newAtom_String, arg); + if ( ret == NULL ) { + SHOWEXC; + return NULL; + } + return ret; + + case A_FLOAT: + ret = (*env)->CallStaticObjectMethod(env, pdjCaching.cls_Atom, + pdjCaching.MIDAtom_newAtom_Float, atom->a_w.w_float); + if ( ret == NULL ) { + SHOWEXC; + return NULL; + } + return ret; + + default: + error("pdj: don't know how to handle atom! type=%d", atom->a_type); + return NULL; + } +} + + +jobjectArray atoms2jatoms(JNIEnv *env, int argc, t_atom *argv) { + jobjectArray ret; + int i; + + ret = (*env)->NewObjectArray(env, argc, pdjCaching.cls_Atom, NULL); + JASSERT(ret); + + for(i=0;iDeleteLocalRef(env, ret); + return NULL; + } + + (*env)->SetObjectArrayElement(env, ret, i, elem); + if ( (*env)->ExceptionOccurred(env) ) { + (*env)->ExceptionDescribe(env); + (*env)->DeleteLocalRef(env, ret); + return NULL; + } + } + + return ret; +} + + +t_symbol *jstring2symbol(JNIEnv *env, jstring strvalue) { + const jbyte *tmp = (*env)->GetStringUTFChars(env, strvalue, NULL); + t_symbol *value; + + JASSERT(tmp); + // TODO : do we have a valid symbol name ??? + value = gensym((char *) tmp); + (*env)->ReleaseStringUTFChars(env, strvalue, tmp); + + return value; +} + + +/** + * Returns the first elements with symbol name + */ +t_pd *findPDObject(t_symbol *name) { + void *bindlist_class = gensym("bindlist")->s_thing; + + if ( !name->s_thing ) + return NULL; + if ( *name->s_thing != bindlist_class ) + return name->s_thing; + + // TODO: work with bindlist to find first object + error("pdj: duplicate named objects not supported: %s", name->s_name); + + return NULL; +} diff --git a/src/type_handler.h b/src/type_handler.h new file mode 100644 index 0000000..621f618 --- /dev/null +++ b/src/type_handler.h @@ -0,0 +1,18 @@ +#include +#include "m_pd.h" + +#define DataTypes_ALL 15 +#define DataTypes_ANYTHING 15 +#define DataTypes_FLOAT 2 +#define DataTypes_INT 1 +#define DataTypes_LIST 4 +#define DataTypes_MESSAGE 8 + +int jatom2atom(JNIEnv *env, jobject jatom, t_atom *atom); +int jatoms2atoms(JNIEnv *env, jobjectArray jatoms, int *nb_atoms, t_atom *atoms); + +jobject atom2jatom(JNIEnv *env, t_atom *atom); +jobjectArray atoms2jatoms(JNIEnv *env, int argc, t_atom *argv); + +t_symbol *jstring2symbol(JNIEnv *env, jstring strvalue); +t_pd *findPDObject(t_symbol *name); diff --git a/www/index.html b/www/index.html new file mode 100644 index 0000000..219bed2 --- /dev/null +++ b/www/index.html @@ -0,0 +1,49 @@ + + + + java plug-in for pure-data + + + +

pdj-logo java +external plugin for pure-data

+

PDJ enables you to write java code to interact with pure-data +objects. The +API is totally based on Cycling74 Max/MSP 'mxj' object implementation. +This will enable java mxj objects to run on pure-data with pdj.

+

This is a work in progress and some features are missing and some +might not even be ported. I am trying to do my best to release soon and +often! If you find any bugs feel free to contact me.

+

Is it compatible with mxj on Max/MSP ?

+

On the I/O level it is. Anything that deals on receiving/sending atoms +will try to match extact mxj behavior. Off course, mxj is acting like a +reference implementation. The API complience is based on Max/MSP 4.5.5 with +MSP signal support. +

+

How does it looks ?

+

This overview : +

import com.cycling74.max.*;

public class help_class extends MaxObject {

public help_class() {
declareOutlets(new int[] { DataTypes.FLOAT, DataTypes.ALL });
}

public void inlet(float x) {
outlet(0, x);
}

public void bang() {
outlet(1, "BANG! received");
}

public void callme(Atom args[]) {
outlet(1, "callme has called with arg1:" + args[0].toString());
}

public void dynamic_method() {
outlet(1, "dynamic_method has been called");
}

}
+Looks like this:
+
+pdj-help example +

Please see the javadoc for more information.

+

How to write pdj externals:

+

Edit your .java in the /classes directory (in the pdj home) with your +favorite editor. Type the name of the .java file (without the extension) +into the pdj object argument. If pdj finds out that the .java is younger +than the .class, pdj will compile it for you.

+

Requirements:

+
  • pure-data +0.38.x or better
  • +
  • java jdk 1.4.x or better; use java 1.5 !!!
  • +
  • apache ant 1.5.x or better for +building
  • +
  • works on linux, os x (tested on 10.4) and windows
  • +

    Download:

    +

    Download latest release here: pdj-0.8.3.tar.gz +/ pdj-0.8.3-win32.zip +(September 9 2006) +

    +Contact: asb2m10 .at. users.sourceforge.net + + diff --git a/www/pdj-help.png b/www/pdj-help.png new file mode 100644 index 0000000..d384b2b Binary files /dev/null and b/www/pdj-help.png differ diff --git a/www/pdj-logo.png b/www/pdj-logo.png new file mode 100644 index 0000000..5930dfa Binary files /dev/null and b/www/pdj-logo.png differ diff --git a/www/stylesheet.css b/www/stylesheet.css new file mode 100644 index 0000000..6ddda95 --- /dev/null +++ b/www/stylesheet.css @@ -0,0 +1,35 @@ +body { + margin:5px; + padding:5px; + background-color:white; + font-size : 11px; + font-family : verdana, arial, helvetica, sans-serif; + width: 785px; + color:black; + } +h1 { + font-size : 28px; + padding:5px; + border:1px dashed black; +} + +h2 { + font-size : 16px; + padding:2px; +} + +h3 { + font-size : 16px; + padding:2px; +} + +a { + color:#09C; + font-size:11px; + text-decoration:none; + font-weight:600; + font-family:verdana, arial, helvetica, sans-serif; + } +a:link {color:#09c;} +a:visited {color:#07a;} +a:hover {background-color:white;} -- cgit v1.2.1