diff options
-rw-r--r-- | .gitattributes | 1 | ||||
-rw-r--r-- | AUTHORS | 11 | ||||
-rw-r--r-- | COPYING | 38 | ||||
-rw-r--r-- | INSTALL | 66 | ||||
-rw-r--r-- | NEWS | 228 | ||||
-rw-r--r-- | README | 8 | ||||
-rw-r--r-- | bindings/lilv.i | 66 | ||||
-rw-r--r-- | bindings/numpy.i | 1746 | ||||
-rw-r--r-- | bindings/python/Makefile | 186 | ||||
-rw-r--r-- | bindings/python/conf.py | 263 | ||||
-rw-r--r-- | bindings/python/index.rst | 9 | ||||
-rw-r--r-- | bindings/python/lilv.py | 1775 | ||||
-rwxr-xr-x | bindings/python/lv2_apply.py | 159 | ||||
-rwxr-xr-x | bindings/python/lv2_list.py | 9 | ||||
-rw-r--r-- | bindings/test/bindings_test_plugin.c | 196 | ||||
-rw-r--r-- | bindings/test/bindings_test_plugin.ttl.in | 62 | ||||
-rw-r--r-- | bindings/test/manifest.ttl.in | 7 | ||||
-rw-r--r-- | bindings/test/python/test_api.py | 290 | ||||
-rw-r--r-- | doc/layout.xml | 187 | ||||
-rw-r--r-- | doc/lv2apply.1 | 34 | ||||
-rw-r--r-- | doc/lv2info.1 | 33 | ||||
-rw-r--r-- | doc/lv2ls.1 | 30 | ||||
-rw-r--r-- | doc/reference.doxygen.in | 2415 | ||||
-rw-r--r-- | doc/style.css | 691 | ||||
-rw-r--r-- | lilv.pc.in | 11 | ||||
-rw-r--r-- | lilv.ttl | 29 | ||||
-rw-r--r-- | lilv/lilv.h | 1844 | ||||
-rw-r--r-- | lilv/lilvmm.hpp | 345 | ||||
-rw-r--r-- | src/collections.c | 224 | ||||
-rw-r--r-- | src/instance.c | 110 | ||||
-rw-r--r-- | src/lib.c | 112 | ||||
-rw-r--r-- | src/lilv_internal.h | 444 | ||||
-rw-r--r-- | src/node.c | 397 | ||||
-rw-r--r-- | src/plugin.c | 1138 | ||||
-rw-r--r-- | src/pluginclass.c | 88 | ||||
-rw-r--r-- | src/port.c | 268 | ||||
-rw-r--r-- | src/query.c | 139 | ||||
-rw-r--r-- | src/scalepoint.c | 49 | ||||
-rw-r--r-- | src/state.c | 1329 | ||||
-rw-r--r-- | src/ui.c | 111 | ||||
-rw-r--r-- | src/util.c | 640 | ||||
-rw-r--r-- | src/world.c | 1212 | ||||
-rw-r--r-- | src/zix/common.h | 88 | ||||
-rw-r--r-- | src/zix/tree.c | 716 | ||||
-rw-r--r-- | src/zix/tree.h | 148 | ||||
-rw-r--r-- | test/bad_syntax.lv2/bad_syntax.c | 93 | ||||
-rw-r--r-- | test/bad_syntax.lv2/bad_syntax.ttl.in | 22 | ||||
-rw-r--r-- | test/bad_syntax.lv2/manifest.ttl.in | 7 | ||||
-rw-r--r-- | test/bad_syntax.lv2/test_bad_syntax.c | 45 | ||||
-rw-r--r-- | test/failed_instantiation.lv2/failed_instantiation.c | 70 | ||||
-rw-r--r-- | test/failed_instantiation.lv2/failed_instantiation.ttl.in | 40 | ||||
-rw-r--r-- | test/failed_instantiation.lv2/manifest.ttl.in | 7 | ||||
-rw-r--r-- | test/failed_instantiation.lv2/test_failed_instantiation.c | 45 | ||||
-rw-r--r-- | test/failed_lib_descriptor.lv2/failed_lib_descriptor.c | 30 | ||||
-rw-r--r-- | test/failed_lib_descriptor.lv2/failed_lib_descriptor.ttl.in | 38 | ||||
-rw-r--r-- | test/failed_lib_descriptor.lv2/manifest.ttl.in | 7 | ||||
-rw-r--r-- | test/failed_lib_descriptor.lv2/test_failed_lib_descriptor.c | 46 | ||||
-rw-r--r-- | test/lib_descriptor.lv2/lib_descriptor.c | 112 | ||||
-rw-r--r-- | test/lib_descriptor.lv2/lib_descriptor.ttl.in | 41 | ||||
-rw-r--r-- | test/lib_descriptor.lv2/manifest.ttl.in | 7 | ||||
-rw-r--r-- | test/lib_descriptor.lv2/test_lib_descriptor.c | 59 | ||||
-rw-r--r-- | test/lilv_cxx_test.cpp | 23 | ||||
-rw-r--r-- | test/lilv_test.c | 2296 | ||||
-rw-r--r-- | test/missing_descriptor.lv2/manifest.ttl.in | 7 | ||||
-rw-r--r-- | test/missing_descriptor.lv2/missing_descriptor.c | 21 | ||||
-rw-r--r-- | test/missing_descriptor.lv2/missing_descriptor.ttl.in | 38 | ||||
-rw-r--r-- | test/missing_descriptor.lv2/test_missing_descriptor.c | 46 | ||||
-rw-r--r-- | test/missing_name.lv2/manifest.ttl.in | 7 | ||||
-rw-r--r-- | test/missing_name.lv2/missing_name.c | 93 | ||||
-rw-r--r-- | test/missing_name.lv2/missing_name.ttl.in | 37 | ||||
-rw-r--r-- | test/missing_name.lv2/test_missing_name.c | 47 | ||||
-rw-r--r-- | test/missing_plugin.lv2/manifest.ttl.in | 7 | ||||
-rw-r--r-- | test/missing_plugin.lv2/missing_plugin.c | 43 | ||||
-rw-r--r-- | test/missing_plugin.lv2/missing_plugin.ttl.in | 38 | ||||
-rw-r--r-- | test/missing_plugin.lv2/test_missing_plugin.c | 46 | ||||
-rw-r--r-- | test/missing_port.lv2/manifest.ttl.in | 7 | ||||
-rw-r--r-- | test/missing_port.lv2/missing_port.c | 93 | ||||
-rw-r--r-- | test/missing_port.lv2/missing_port.ttl.in | 31 | ||||
-rw-r--r-- | test/missing_port.lv2/test_missing_port.c | 45 | ||||
-rw-r--r-- | test/missing_port_name.lv2/manifest.ttl.in | 7 | ||||
-rw-r--r-- | test/missing_port_name.lv2/missing_port_name.c | 93 | ||||
-rw-r--r-- | test/missing_port_name.lv2/missing_port_name.ttl.in | 30 | ||||
-rw-r--r-- | test/missing_port_name.lv2/test_missing_port_name.c | 49 | ||||
-rw-r--r-- | test/new_version.lv2/manifest.ttl.in | 7 | ||||
-rw-r--r-- | test/new_version.lv2/new_version.c | 93 | ||||
-rw-r--r-- | test/new_version.lv2/new_version.ttl.in | 40 | ||||
-rw-r--r-- | test/old_version.lv2/manifest.ttl.in | 7 | ||||
-rw-r--r-- | test/old_version.lv2/old_version.c | 93 | ||||
-rw-r--r-- | test/old_version.lv2/old_version.ttl.in | 40 | ||||
-rw-r--r-- | test/test.lv2/manifest.ttl.in | 7 | ||||
-rw-r--r-- | test/test.lv2/test.c | 394 | ||||
-rw-r--r-- | test/test.lv2/test.ttl.in | 46 | ||||
-rw-r--r-- | utils/bench.h | 52 | ||||
-rw-r--r-- | utils/lilv-bench.c | 38 | ||||
-rw-r--r-- | utils/lilv.bash_completion | 59 | ||||
-rw-r--r-- | utils/lv2apply.c | 354 | ||||
-rw-r--r-- | utils/lv2bench.c | 268 | ||||
-rw-r--r-- | utils/lv2info.c | 460 | ||||
-rw-r--r-- | utils/lv2ls.c | 93 | ||||
-rw-r--r-- | utils/uri_table.h | 73 | ||||
-rwxr-xr-x | waf | 171 | ||||
-rw-r--r-- | waflib/.gitignore (renamed from .gitignore) | 0 | ||||
-rw-r--r-- | waflib/Build.py (renamed from Build.py) | 0 | ||||
-rw-r--r-- | waflib/COPYING | 25 | ||||
-rw-r--r-- | waflib/ConfigSet.py (renamed from ConfigSet.py) | 0 | ||||
-rw-r--r-- | waflib/Configure.py (renamed from Configure.py) | 0 | ||||
-rw-r--r-- | waflib/Context.py (renamed from Context.py) | 0 | ||||
-rw-r--r-- | waflib/Errors.py (renamed from Errors.py) | 0 | ||||
-rw-r--r-- | waflib/Logs.py (renamed from Logs.py) | 0 | ||||
-rw-r--r-- | waflib/Node.py (renamed from Node.py) | 0 | ||||
-rw-r--r-- | waflib/Options.py (renamed from Options.py) | 0 | ||||
-rw-r--r-- | waflib/README.md (renamed from README.md) | 0 | ||||
-rw-r--r-- | waflib/Runner.py (renamed from Runner.py) | 0 | ||||
-rw-r--r-- | waflib/Scripting.py (renamed from Scripting.py) | 0 | ||||
-rw-r--r-- | waflib/Task.py (renamed from Task.py) | 0 | ||||
-rw-r--r-- | waflib/TaskGen.py (renamed from TaskGen.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/__init__.py (renamed from Tools/__init__.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/ar.py (renamed from Tools/ar.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/asm.py (renamed from Tools/asm.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/bison.py (renamed from Tools/bison.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/c.py (renamed from Tools/c.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/c_aliases.py (renamed from Tools/c_aliases.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/c_config.py (renamed from Tools/c_config.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/c_osx.py (renamed from Tools/c_osx.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/c_preproc.py (renamed from Tools/c_preproc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/c_tests.py (renamed from Tools/c_tests.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/ccroot.py (renamed from Tools/ccroot.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/clang.py (renamed from Tools/clang.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/clangxx.py (renamed from Tools/clangxx.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/compiler_c.py (renamed from Tools/compiler_c.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/compiler_cxx.py (renamed from Tools/compiler_cxx.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/compiler_d.py (renamed from Tools/compiler_d.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/compiler_fc.py (renamed from Tools/compiler_fc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/cs.py (renamed from Tools/cs.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/cxx.py (renamed from Tools/cxx.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/d.py (renamed from Tools/d.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/d_config.py (renamed from Tools/d_config.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/d_scan.py (renamed from Tools/d_scan.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/dbus.py (renamed from Tools/dbus.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/dmd.py (renamed from Tools/dmd.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/errcheck.py (renamed from Tools/errcheck.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/fc.py (renamed from Tools/fc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/fc_config.py (renamed from Tools/fc_config.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/fc_scan.py (renamed from Tools/fc_scan.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/flex.py (renamed from Tools/flex.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/g95.py (renamed from Tools/g95.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/gas.py (renamed from Tools/gas.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/gcc.py (renamed from Tools/gcc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/gdc.py (renamed from Tools/gdc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/gfortran.py (renamed from Tools/gfortran.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/glib2.py (renamed from Tools/glib2.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/gnu_dirs.py (renamed from Tools/gnu_dirs.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/gxx.py (renamed from Tools/gxx.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/icc.py (renamed from Tools/icc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/icpc.py (renamed from Tools/icpc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/ifort.py (renamed from Tools/ifort.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/intltool.py (renamed from Tools/intltool.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/irixcc.py (renamed from Tools/irixcc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/javaw.py (renamed from Tools/javaw.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/ldc2.py (renamed from Tools/ldc2.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/lua.py (renamed from Tools/lua.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/md5_tstamp.py (renamed from Tools/md5_tstamp.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/msvc.py (renamed from Tools/msvc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/nasm.py (renamed from Tools/nasm.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/nobuild.py (renamed from Tools/nobuild.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/perl.py (renamed from Tools/perl.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/python.py (renamed from Tools/python.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/qt5.py (renamed from Tools/qt5.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/ruby.py (renamed from Tools/ruby.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/suncc.py (renamed from Tools/suncc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/suncxx.py (renamed from Tools/suncxx.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/tex.py (renamed from Tools/tex.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/vala.py (renamed from Tools/vala.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/waf_unit_test.py (renamed from Tools/waf_unit_test.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/winres.py (renamed from Tools/winres.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/xlc.py (renamed from Tools/xlc.py) | 0 | ||||
-rw-r--r-- | waflib/Tools/xlcxx.py (renamed from Tools/xlcxx.py) | 0 | ||||
-rw-r--r-- | waflib/Utils.py (renamed from Utils.py) | 0 | ||||
-rw-r--r-- | waflib/__init__.py (renamed from __init__.py) | 0 | ||||
-rw-r--r-- | waflib/ansiterm.py (renamed from ansiterm.py) | 0 | ||||
-rw-r--r-- | waflib/extras/__init__.py (renamed from extras/__init__.py) | 0 | ||||
-rw-r--r-- | waflib/extras/autowaf.py (renamed from extras/autowaf.py) | 0 | ||||
-rw-r--r-- | waflib/extras/batched_cc.py (renamed from extras/batched_cc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/biber.py (renamed from extras/biber.py) | 0 | ||||
-rw-r--r-- | waflib/extras/bjam.py (renamed from extras/bjam.py) | 0 | ||||
-rw-r--r-- | waflib/extras/blender.py (renamed from extras/blender.py) | 0 | ||||
-rw-r--r-- | waflib/extras/boo.py (renamed from extras/boo.py) | 0 | ||||
-rw-r--r-- | waflib/extras/boost.py (renamed from extras/boost.py) | 0 | ||||
-rw-r--r-- | waflib/extras/build_file_tracker.py (renamed from extras/build_file_tracker.py) | 0 | ||||
-rw-r--r-- | waflib/extras/build_logs.py (renamed from extras/build_logs.py) | 0 | ||||
-rw-r--r-- | waflib/extras/buildcopy.py (renamed from extras/buildcopy.py) | 0 | ||||
-rw-r--r-- | waflib/extras/c_bgxlc.py (renamed from extras/c_bgxlc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/c_dumbpreproc.py (renamed from extras/c_dumbpreproc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/c_emscripten.py (renamed from extras/c_emscripten.py) | 0 | ||||
-rw-r--r-- | waflib/extras/c_nec.py (renamed from extras/c_nec.py) | 0 | ||||
-rw-r--r-- | waflib/extras/cabal.py (renamed from extras/cabal.py) | 0 | ||||
-rw-r--r-- | waflib/extras/cfg_altoptions.py (renamed from extras/cfg_altoptions.py) | 0 | ||||
-rw-r--r-- | waflib/extras/clang_compilation_database.py (renamed from extras/clang_compilation_database.py) | 0 | ||||
-rw-r--r-- | waflib/extras/codelite.py (renamed from extras/codelite.py) | 0 | ||||
-rw-r--r-- | waflib/extras/color_gcc.py (renamed from extras/color_gcc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/color_rvct.py (renamed from extras/color_rvct.py) | 0 | ||||
-rw-r--r-- | waflib/extras/compat15.py (renamed from extras/compat15.py) | 0 | ||||
-rw-r--r-- | waflib/extras/cppcheck.py (renamed from extras/cppcheck.py) | 0 | ||||
-rw-r--r-- | waflib/extras/cpplint.py (renamed from extras/cpplint.py) | 0 | ||||
-rw-r--r-- | waflib/extras/cross_gnu.py (renamed from extras/cross_gnu.py) | 0 | ||||
-rw-r--r-- | waflib/extras/cython.py (renamed from extras/cython.py) | 0 | ||||
-rw-r--r-- | waflib/extras/dcc.py (renamed from extras/dcc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/distnet.py (renamed from extras/distnet.py) | 0 | ||||
-rw-r--r-- | waflib/extras/doxygen.py (renamed from extras/doxygen.py) | 0 | ||||
-rw-r--r-- | waflib/extras/dpapi.py (renamed from extras/dpapi.py) | 0 | ||||
-rw-r--r-- | waflib/extras/eclipse.py (renamed from extras/eclipse.py) | 0 | ||||
-rw-r--r-- | waflib/extras/erlang.py (renamed from extras/erlang.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fast_partial.py (renamed from extras/fast_partial.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_bgxlf.py (renamed from extras/fc_bgxlf.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_cray.py (renamed from extras/fc_cray.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_nag.py (renamed from extras/fc_nag.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_nec.py (renamed from extras/fc_nec.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_open64.py (renamed from extras/fc_open64.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_pgfortran.py (renamed from extras/fc_pgfortran.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_solstudio.py (renamed from extras/fc_solstudio.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fc_xlf.py (renamed from extras/fc_xlf.py) | 0 | ||||
-rw-r--r-- | waflib/extras/file_to_object.py (renamed from extras/file_to_object.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fluid.py (renamed from extras/fluid.py) | 0 | ||||
-rw-r--r-- | waflib/extras/freeimage.py (renamed from extras/freeimage.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fsb.py (renamed from extras/fsb.py) | 0 | ||||
-rw-r--r-- | waflib/extras/fsc.py (renamed from extras/fsc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/gccdeps.py (renamed from extras/gccdeps.py) | 0 | ||||
-rw-r--r-- | waflib/extras/gdbus.py (renamed from extras/gdbus.py) | 0 | ||||
-rw-r--r-- | waflib/extras/gob2.py (renamed from extras/gob2.py) | 0 | ||||
-rw-r--r-- | waflib/extras/halide.py (renamed from extras/halide.py) | 0 | ||||
-rwxr-xr-x | waflib/extras/javatest.py (renamed from extras/javatest.py) | 0 | ||||
-rw-r--r-- | waflib/extras/kde4.py (renamed from extras/kde4.py) | 0 | ||||
-rw-r--r-- | waflib/extras/local_rpath.py (renamed from extras/local_rpath.py) | 0 | ||||
-rw-r--r-- | waflib/extras/lv2.py (renamed from extras/lv2.py) | 0 | ||||
-rw-r--r-- | waflib/extras/make.py (renamed from extras/make.py) | 0 | ||||
-rw-r--r-- | waflib/extras/midl.py (renamed from extras/midl.py) | 0 | ||||
-rw-r--r-- | waflib/extras/msvcdeps.py (renamed from extras/msvcdeps.py) | 0 | ||||
-rw-r--r-- | waflib/extras/msvs.py (renamed from extras/msvs.py) | 0 | ||||
-rw-r--r-- | waflib/extras/netcache_client.py (renamed from extras/netcache_client.py) | 0 | ||||
-rw-r--r-- | waflib/extras/objcopy.py (renamed from extras/objcopy.py) | 0 | ||||
-rw-r--r-- | waflib/extras/ocaml.py (renamed from extras/ocaml.py) | 0 | ||||
-rw-r--r-- | waflib/extras/package.py (renamed from extras/package.py) | 0 | ||||
-rw-r--r-- | waflib/extras/parallel_debug.py (renamed from extras/parallel_debug.py) | 0 | ||||
-rw-r--r-- | waflib/extras/pch.py (renamed from extras/pch.py) | 0 | ||||
-rw-r--r-- | waflib/extras/pep8.py (renamed from extras/pep8.py) | 0 | ||||
-rw-r--r-- | waflib/extras/pgicc.py (renamed from extras/pgicc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/pgicxx.py (renamed from extras/pgicxx.py) | 0 | ||||
-rw-r--r-- | waflib/extras/proc.py (renamed from extras/proc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/protoc.py (renamed from extras/protoc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/pyqt5.py (renamed from extras/pyqt5.py) | 0 | ||||
-rw-r--r-- | waflib/extras/pytest.py (renamed from extras/pytest.py) | 0 | ||||
-rw-r--r-- | waflib/extras/qnxnto.py (renamed from extras/qnxnto.py) | 0 | ||||
-rw-r--r-- | waflib/extras/qt4.py (renamed from extras/qt4.py) | 0 | ||||
-rw-r--r-- | waflib/extras/relocation.py (renamed from extras/relocation.py) | 0 | ||||
-rw-r--r-- | waflib/extras/remote.py (renamed from extras/remote.py) | 0 | ||||
-rw-r--r-- | waflib/extras/resx.py (renamed from extras/resx.py) | 0 | ||||
-rw-r--r-- | waflib/extras/review.py (renamed from extras/review.py) | 0 | ||||
-rw-r--r-- | waflib/extras/rst.py (renamed from extras/rst.py) | 0 | ||||
-rw-r--r-- | waflib/extras/run_do_script.py (renamed from extras/run_do_script.py) | 0 | ||||
-rw-r--r-- | waflib/extras/run_m_script.py (renamed from extras/run_m_script.py) | 0 | ||||
-rw-r--r-- | waflib/extras/run_py_script.py (renamed from extras/run_py_script.py) | 0 | ||||
-rw-r--r-- | waflib/extras/run_r_script.py (renamed from extras/run_r_script.py) | 0 | ||||
-rw-r--r-- | waflib/extras/sas.py (renamed from extras/sas.py) | 0 | ||||
-rw-r--r-- | waflib/extras/satellite_assembly.py (renamed from extras/satellite_assembly.py) | 0 | ||||
-rw-r--r-- | waflib/extras/scala.py (renamed from extras/scala.py) | 0 | ||||
-rw-r--r-- | waflib/extras/slow_qt4.py (renamed from extras/slow_qt4.py) | 0 | ||||
-rw-r--r-- | waflib/extras/softlink_libs.py (renamed from extras/softlink_libs.py) | 0 | ||||
-rw-r--r-- | waflib/extras/stale.py (renamed from extras/stale.py) | 0 | ||||
-rw-r--r-- | waflib/extras/stracedeps.py (renamed from extras/stracedeps.py) | 0 | ||||
-rw-r--r-- | waflib/extras/swig.py (renamed from extras/swig.py) | 0 | ||||
-rw-r--r-- | waflib/extras/syms.py (renamed from extras/syms.py) | 0 | ||||
-rw-r--r-- | waflib/extras/ticgt.py (renamed from extras/ticgt.py) | 0 | ||||
-rw-r--r-- | waflib/extras/unity.py (renamed from extras/unity.py) | 0 | ||||
-rw-r--r-- | waflib/extras/use_config.py (renamed from extras/use_config.py) | 0 | ||||
-rw-r--r-- | waflib/extras/valadoc.py (renamed from extras/valadoc.py) | 0 | ||||
-rw-r--r-- | waflib/extras/waf_xattr.py (renamed from extras/waf_xattr.py) | 0 | ||||
-rw-r--r-- | waflib/extras/why.py (renamed from extras/why.py) | 0 | ||||
-rw-r--r-- | waflib/extras/win32_opts.py (renamed from extras/win32_opts.py) | 0 | ||||
-rw-r--r-- | waflib/extras/wix.py (renamed from extras/wix.py) | 0 | ||||
-rw-r--r-- | waflib/extras/xcode6.py (renamed from extras/xcode6.py) | 0 | ||||
-rw-r--r-- | waflib/fixpy2.py (renamed from fixpy2.py) | 0 | ||||
-rwxr-xr-x | waflib/processor.py (renamed from processor.py) | 0 | ||||
-rwxr-xr-x | waflib/waf | 16 | ||||
-rw-r--r-- | wscript | 506 |
284 files changed, 24533 insertions, 34 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f063da3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +waf binary
\ No newline at end of file @@ -0,0 +1,11 @@ +Author: + David Robillard <d@drobilla.net> + +GTK2 GUI and I18N support: + Lars Luthman <lars.luthman@gmail.com> + +Dynamic manifest support: + Stefano D'Angelo + +Plugin execution via Python bindings: + Kaspar Emanuel <kaspar.emanuel@gmail.com>
\ No newline at end of file @@ -1,25 +1,13 @@ -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. 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. - -3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR "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 AUTHOR 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. +Copyright 2011-2017 David Robillard <http://drobilla.net> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
\ No newline at end of file @@ -0,0 +1,66 @@ +Installation Instructions +========================= + +Basic Installation +------------------ + +Building this software requires only Python. To install with default options: + + ./waf configure + ./waf + ./waf install # or sudo ./waf install + +Configuration Options +--------------------- + +All supported options can be viewed using the command: + + ./waf --help + +Most options only need to be passed during the configure stage, for example: + + ./waf configure --prefix=/usr + ./waf + ./waf install + +Compiler Configuration +---------------------- + +Several standard environment variables can be used to control how compilers are +invoked: + + * CC: Path to C compiler + * CFLAGS: C compiler options + * CXX: Path to C++ compiler + * CXXFLAGS: C++ compiler options + * CPPFLAGS: C preprocessor options + * LINKFLAGS: Linker options + +Library Versioning +------------------ + +This library uses semantic versioning <http://semver.org/>. + +Several major versions can be installed in parallel. The shared library name, +include directory, and pkg-config file are suffixed with the major version +number. For example, a library named "foo" at version 1.x.y might install: + + /usr/include/foo-1/foo/foo.h + /usr/lib/foo-1.so.1.x.y + /usr/lib/pkgconfig/foo-1.pc + +Dependencies can check for the package "foo-1" with pkg-config. + +Packaging +--------- + +Everything can be installed to a specific root directory by passing a --destdir +option to the install stage (or setting the DESTDIR environment variable), +which adds a prefix to all install paths. For example: + + ./waf configure --prefix=/usr + ./waf + ./waf install --destdir=/tmp/package + +Packages should allow parallel installation of several major versions. For +example, the above would be packaged as "foo-1".
\ No newline at end of file @@ -0,0 +1,228 @@ +lilv (0.24.5) unstable; + + * Fix GCC8 warnings + + -- David Robillard <d@drobilla.net> Tue, 04 Sep 2018 21:02:19 +0200 + +lilv (0.24.4) stable; + + * Fix saving state when broken links are encountered + * Don't attempt to load remote or non-Turtle files + * lv2apply: Activate plugin before running + * lv2apply: Use default values when they are not nan + * lv2bench: Improve support for plugins with sequence ports + * lv2bench: Support running a single plugin given on the command line + * Gracefully handle plugins with missing binary URIs + * Remove use of deprecated readdir_r + * Install Python bindings when configured without tests + (thanks Clement Skau) + + -- David Robillard <d@drobilla.net> Sun, 22 Jul 2018 20:42:00 +0200 + +lilv (0.24.2) stable; + + * Fix saving state to paths that contain URI delimiters (#, ?, etc) + * Fix comparison of restored states with paths + + -- David Robillard <d@drobilla.net> Wed, 04 Jan 2017 11:48:08 -0500 + +lilv (0.24.0) stable; + + * Add new hand-crafted Pythonic bindings with full test coverage + * Add lv2apply utility for applying plugins to audio files + * Add lilv_world_get_symbol() + * Add lilv_state_set_metadata() for adding state banks/comments/etc + (based on patch from Hanspeter Portner) + * Fix crash when state contains non-POD properties + * Fix crash when NULL predicate is passed to lilv_world_find_nodes() + * Fix state file versioning + * Unload contained resources when bundle is unloaded + * Do not instantiate plugin when data fails to parse + * Support re-loading plugins + * Replace bundles if bundle with newer plugin version is loaded + (based on patch from Robin Gareus) + * Fix loading dyn-manifest from bundles with spaces in their path + * Check lv2:binary predicate for UIs + * Add LILV_URI_ATOM_PORT and LILV_URI_CV_PORT defines + * Fix documentation installation + * Fix outdated comment references to lilv_uri_to_path() + + -- David Robillard <d@drobilla.net> Mon, 19 Sep 2016 22:24:57 -0400 + +lilv (0.22.0) stable; + + * Fix loading files with spaces in their path + * Add lilv_file_uri_parse() for correct URI to path conversion + * Add lilv_node_get_path() for convenient file URI path access + * Add lilv_state_emit_port_values() for special port value handling + * Add lilv_state_get_uri() + * Add lilv_state_delete() for deleting user saved presets + * Add lilv_free() for systems picky about such things + * Fix lilv_world_ask() to work with wildcards + * Fix creation of duplicate manifest entries when saving state + * Fix bindings for Python 3 + * Load discovered owl ontologies as specifications + * Expose lilv_world_load_specifications() and + lilv_world_load_plugin_classes() + * Tolerate passing NULL to lilv_state_restore() + * Preserve absolute paths in state if no link directory is given + * Fix a few minor/unlikely memory errors + * Configure based on compiler target OS for cross-compilation + * Fix lilv_realpath() on pre-POSIX-2008 systems + * Fix directory walking on some systems (thanks Matt Fischer) + * Windows fixes (thanks John Emmas) + * Minor documentation improvements + * Upgrade to waf 1.8.14 + + -- David Robillard <d@drobilla.net> Thu, 08 Oct 2015 15:39:29 -0400 + +lilv (0.20.0) stable; + + * Don't load files multiple times if they are listed as rdfs:seeAlso for + several plugins + * Call lv2_lib_descriptor separately for different bundle paths + (fix loading several dynamic plugins like Ingen at once) + * Tolerate calling lilv_node_as_uri or lilv_node_as_blank on NULL + * Add convenient lilv_new_file_uri for creating file URIs + * Fix use of lv2info -m and -p options to write plugin data + (useful for porting plugins bridges with NASPRO) + * Fix issues with lilv_plugin_get_author_name and friends + (thanks Filipe Coelho) + * Improved/working lv2_apply.py to apply plugin to a .wav + (thanks Joe Button) + * Add lilv_world_unload_bundle() and lilv_world_unload_resource() + * Fix several minor memory leaks + * Improve test coverage + * Upgrade to waf 1.7.16 + + -- David Robillard <d@drobilla.net> Fri, 08 Aug 2014 18:21:32 -0400 + +lilv (0.18.0) stable; + + * Allow lilv_state_restore() to be used without passing an instance, + for restoring port values via a callback only + * Fix unlikely memory leak in lilv_plugin_instantiate() + * Support denoting latency ports with lv2:designation lv2:latency + * Allow passing NULL port_class to lilv_plugin_get_port_by_designation + * Call GetProcAddress with correct calling convention on Windows + * Add support for running plugins from Python by Kaspar Emanuel + * Clean up after test suite so multiple runs are successful + * Add lilv_port_get_node() for using world query functions with ports + * lv2info: Don't display invalid control maxes and defaults + (patch from Robin Gareus) + * lilvmm.hpp: Add wrappers for UI API + + -- David Robillard <d@drobilla.net> Sat, 04 Jan 2014 16:06:42 -0500 + +lilv (0.16.0) stable; + + * Add lilv_world_ask() for easily checking if a statement exists + * Add lilv_world_get() and lilv_port_get() for easily getting one value + * Add lilv_nodes_merge() + * Make lilv_plugin_get_port_by_designation() return a const pointer + * Require a URI for lilv_state_to_string() and fail gracefully otherwise + * Fail gracefully when lilv_state_new_from_string() is called on NULL + * Make state loading functions fall back to lv2:default for port values, + so a plugin description can be loaded as default state + * Ignore state ports with no value instead of printing an error + * Support atom:supports in lilv_port_supports_event() + * Add va_list variant of lilv_plugin_get_num_ports_of_class() + * Fix several plugin functions that failed to load data if called first + * Correctly depend on serd at build time (fix compilation in odd cases) + * Disable timestamps in HTML documentation for reproducible build + * lilvmm.hpp: Support varargs for Plugin::get_num_ports_of_class() + * lilvmm.hpp: Add several missing methods + * Update to waf 1.7.8 and autowaf r90 (install docs to versioned directory) + + -- David Robillard <d@drobilla.net> Mon, 18 Feb 2013 16:43:10 -0500 + +lilv (0.14.4) stable; + + * Deprecate old flawed Lilv::Instance constructors + * Fix documentation for ui_type parameter of lilv_ui_is_supported() + * Fix crash when lv2info is run with an invalid URI argument + * Gracefully handle failure to save plugin state and print error message + * Reduce memory usage (per node) + * Simpler node implementation always backed by a SordNode + * Make all 'zix' symbols private to avoid symbol clashes in static builds + * Add lv2bench utility + * Fix various hyper-strict warnings + * Do not require a C++ compiler to build + * Add option to build utilities as static binaries + * Upgrade to waf 1.7.2 + * lilvmm.hpp: Make Lilv::Instance handle features and failed instantiations + * lilvmm.hpp: Add Lilv::Instance::get_handle() + * lilvmm.hpp: Add Lilv::Instance::get_extension_data() + + -- David Robillard <d@drobilla.net> Thu, 23 Aug 2012 01:38:29 -0400 + +lilv (0.14.2) stable; + + * Fix dynmanifest support + + -- David Robillard <d@drobilla.net> Thu, 19 Apr 2012 16:11:31 -0400 + +lilv (0.14.0) stable; + + * Add lilv_plugin_get_extension_data + * Use path variables in pkgconfig files + * Install man page to DATADIR (e.g. PREFIX/share/man, not PREFIX/man) + * Make Lilv::uri_to_path static inline (fix linking errors) + * Use correct URI for dcterms:replaces (for hiding old plugins): + "http://purl.org/dc/terms/replaces" + * Fix compilation on BSD + * Only load dynmanifest libraries once per bundle, not once per plugin + * Fix lilv_world_find_nodes to work with wildcard subjects + * Add lilv_plugin_get_related to get resources related to plugins that + are not directly rdfs:seeAlso linked (e.g. presets) + * Add lilv_world_load_resource for related resources (e.g. presets) + * Print presets in lv2info + * Remove locale smashing kludges and use new serd functions for converting + nodes to/from numbers. + * Add LilvState API for handling plugin state. This makes it simple to + save and restore plugin state both in memory and on disk, as well as + save presets in a host-sharable way since the disk format is identical + to the LV2 presets format. + * Update old references to lv2_list (now lv2ls) + * Support compilation as C++ under MSVC++. + * Remove use of wordexp. + * Add lilv_plugin_get_port_by_designation() and lilv_port_get_index() as an + improved generic alternative to lilv_plugin_get_latency_port_index(). + * Add lilv_plugin_get_project() and get author information from project if + it is not given directly on the plugin. + + -- David Robillard <d@drobilla.net> Wed, 18 Apr 2012 20:06:28 -0400 + +lilv (0.5.0) stable; + + * Remove glib dependency + * Add lv2core as a pkg-config dependency (for lv2.h header include) + * Obey prefix when installing bash completion script + * Support integer minimum, maximum, and default port values in + lilv_plugin_get_port_ranges_float + * Add ability to build static library + + -- David Robillard <d@drobilla.net> Thu, 29 Sep 2011 00:00:00 -0400 + +lilv (0.4.4) stable; + + * Fix building python bindings + * Make free methods tolerate being called on NULL + * Remove lv2jack (replaced by jalv) + * Fix parsing extra plugin data files in other bundles + * Fix lilv_ui_is_supported when Lilv is built independently + + -- David Robillard <d@drobilla.net> Sat, 11 Jun 2011 11:20:11 -0400 + +lilv (0.4.2) stable; + + * Fix compilation issues on some systems + * Fix build system Python 3 compatibility + + -- David Robillard <d@drobilla.net> Wed, 25 May 2011 19:00:00 -0400 + +lilv (0.4.0) stable; + + * Initial version (forked from SLV2) + + -- David Robillard <d@drobilla.net> Tue, 24 May 2011 23:00:00 -0400 @@ -0,0 +1,8 @@ +Lilv +==== + +Lilv is a C library to make the use of LV2 plugins as simple as possible for +applications. +For more information, see <http://drobilla.net/software/lilv>. + + -- David Robillard <d@drobilla.net> diff --git a/bindings/lilv.i b/bindings/lilv.i new file mode 100644 index 0000000..f6254a7 --- /dev/null +++ b/bindings/lilv.i @@ -0,0 +1,66 @@ +%module lilv +%typedef unsigned uint32_t; +%{ +#define SWIG_FILE_WITH_INIT +#include "lilv/lilv.h" +#include "lilv/lilvmm.hpp" +%} + +%include "numpy.i" +%init %{ + import_array(); +%} +%apply (float* INPLACE_ARRAY1) {(void* data_location)} + +%feature("compactdefaultargs") %{ + lilv_plugin_get_num_ports_of_class; + get_num_ports_of_class; +%} +%varargs(3, LilvNode* node = NULL) lilv_plugin_get_num_ports_of_class; +%varargs(3, LilvNode* node = NULL) get_num_ports_of_class; +%typemap(in, numinputs=0) LilvNode *node3 ""; // Make sure it's NULL terminated + +%include "lilv/lilv.h" +%include "lilv/lilvmm.hpp" + +namespace Lilv { + +%extend Plugins { +%pythoncode %{ + def __iter__(self): + class Iterator(object): + def __init__(self, plugins): + self.plugins = plugins + self.iter = plugins.begin() + + def __next__(self): + if self.plugins.is_end(self.iter): + raise StopIteration + plugin = self.plugins.get(self.iter) + self.iter = self.plugins.next(self.iter) + return plugin + + def next(self): + "Python 2 iterator protocol" + return Iterator.__next__(self) + + return Iterator(self) + + def get_by_uri(self, *args): + """get_by_uri(self, LilvNode uri) -> PluginClass""" + ret = _lilv.Plugins_get_by_uri(self, *args) + if ret.me is None: + return None + else: + return ret +%} +}; + +%extend Node { +%pythoncode %{ + def __str__(self): + return self.get_turtle_token() +%} +}; + +} /* namespace Lilv */ diff --git a/bindings/numpy.i b/bindings/numpy.i new file mode 100644 index 0000000..d695b36 --- /dev/null +++ b/bindings/numpy.i @@ -0,0 +1,1746 @@ +/* -*- C -*- (not really, but good for syntax highlighting) */ +#ifdef SWIGPYTHON + +%{ +#ifndef SWIG_FILE_WITH_INIT +# define NO_IMPORT_ARRAY +#endif +#include "stdio.h" +#include <numpy/arrayobject.h> +%} + +/**********************************************************************/ + +%fragment("NumPy_Backward_Compatibility", "header") +{ +/* Support older NumPy data type names +*/ +%#if NDARRAY_VERSION < 0x01000000 +%#define NPY_BOOL PyArray_BOOL +%#define NPY_BYTE PyArray_BYTE +%#define NPY_UBYTE PyArray_UBYTE +%#define NPY_SHORT PyArray_SHORT +%#define NPY_USHORT PyArray_USHORT +%#define NPY_INT PyArray_INT +%#define NPY_UINT PyArray_UINT +%#define NPY_LONG PyArray_LONG +%#define NPY_ULONG PyArray_ULONG +%#define NPY_LONGLONG PyArray_LONGLONG +%#define NPY_ULONGLONG PyArray_ULONGLONG +%#define NPY_FLOAT PyArray_FLOAT +%#define NPY_DOUBLE PyArray_DOUBLE +%#define NPY_LONGDOUBLE PyArray_LONGDOUBLE +%#define NPY_CFLOAT PyArray_CFLOAT +%#define NPY_CDOUBLE PyArray_CDOUBLE +%#define NPY_CLONGDOUBLE PyArray_CLONGDOUBLE +%#define NPY_OBJECT PyArray_OBJECT +%#define NPY_STRING PyArray_STRING +%#define NPY_UNICODE PyArray_UNICODE +%#define NPY_VOID PyArray_VOID +%#define NPY_NTYPES PyArray_NTYPES +%#define NPY_NOTYPE PyArray_NOTYPE +%#define NPY_CHAR PyArray_CHAR +%#define NPY_USERDEF PyArray_USERDEF +%#define npy_intp intp + +%#define NPY_MAX_BYTE MAX_BYTE +%#define NPY_MIN_BYTE MIN_BYTE +%#define NPY_MAX_UBYTE MAX_UBYTE +%#define NPY_MAX_SHORT MAX_SHORT +%#define NPY_MIN_SHORT MIN_SHORT +%#define NPY_MAX_USHORT MAX_USHORT +%#define NPY_MAX_INT MAX_INT +%#define NPY_MIN_INT MIN_INT +%#define NPY_MAX_UINT MAX_UINT +%#define NPY_MAX_LONG MAX_LONG +%#define NPY_MIN_LONG MIN_LONG +%#define NPY_MAX_ULONG MAX_ULONG +%#define NPY_MAX_LONGLONG MAX_LONGLONG +%#define NPY_MIN_LONGLONG MIN_LONGLONG +%#define NPY_MAX_ULONGLONG MAX_ULONGLONG +%#define NPY_MAX_INTP MAX_INTP +%#define NPY_MIN_INTP MIN_INTP + +%#define NPY_FARRAY FARRAY +%#define NPY_F_CONTIGUOUS F_CONTIGUOUS +%#endif +} + +/**********************************************************************/ + +/* The following code originally appeared in + * enthought/kiva/agg/src/numeric.i written by Eric Jones. It was + * translated from C++ to C by John Hunter. Bill Spotz has modified + * it to fix some minor bugs, upgrade from Numeric to numpy (all + * versions), add some comments and functionality, and convert from + * direct code insertion to SWIG fragments. + */ + +%fragment("NumPy_Macros", "header") +{ +/* Macros to extract array attributes. + */ +%#define is_array(a) ((a) && PyArray_Check((PyArrayObject *)a)) +%#define array_type(a) (int)(PyArray_TYPE(a)) +%#define array_numdims(a) (((PyArrayObject *)a)->nd) +%#define array_dimensions(a) (((PyArrayObject *)a)->dimensions) +%#define array_size(a,i) (((PyArrayObject *)a)->dimensions[i]) +%#define array_data(a) (((PyArrayObject *)a)->data) +%#define array_is_contiguous(a) (PyArray_ISCONTIGUOUS(a)) +%#define array_is_native(a) (PyArray_ISNOTSWAPPED(a)) +%#define array_is_fortran(a) (PyArray_ISFORTRAN(a)) +} + +/**********************************************************************/ + +%fragment("NumPy_Utilities", "header") +{ + /* Given a PyObject, return a string describing its type. + */ + const char* pytype_string(PyObject* py_obj) { + if (py_obj == NULL ) return "C NULL value"; + if (py_obj == Py_None ) return "Python None" ; + if (PyCallable_Check(py_obj)) return "callable" ; + if (PyString_Check( py_obj)) return "string" ; + if (PyInt_Check( py_obj)) return "int" ; + if (PyFloat_Check( py_obj)) return "float" ; + if (PyDict_Check( py_obj)) return "dict" ; + if (PyList_Check( py_obj)) return "list" ; + if (PyTuple_Check( py_obj)) return "tuple" ; + if (PyModule_Check( py_obj)) return "module" ; +%#if PY_MAJOR_VERSION < 3 + if (PyFile_Check( py_obj)) return "file" ; + if (PyInstance_Check(py_obj)) return "instance" ; +%#endif + + return "unkown type"; + } + + /* Given a NumPy typecode, return a string describing the type. + */ + const char* typecode_string(int typecode) { + static const char* type_names[25] = {"bool", "byte", "unsigned byte", + "short", "unsigned short", "int", + "unsigned int", "long", "unsigned long", + "long long", "unsigned long long", + "float", "double", "long double", + "complex float", "complex double", + "complex long double", "object", + "string", "unicode", "void", "ntypes", + "notype", "char", "unknown"}; + return typecode < 24 ? type_names[typecode] : type_names[24]; + } + + /* Make sure input has correct numpy type. Allow character and byte + * to match. Also allow int and long to match. This is deprecated. + * You should use PyArray_EquivTypenums() instead. + */ + int type_match(int actual_type, int desired_type) { + return PyArray_EquivTypenums(actual_type, desired_type); + } +} + +/**********************************************************************/ + +%fragment("NumPy_Object_to_Array", "header", + fragment="NumPy_Backward_Compatibility", + fragment="NumPy_Macros", + fragment="NumPy_Utilities") +{ + /* Given a PyObject pointer, cast it to a PyArrayObject pointer if + * legal. If not, set the python error string appropriately and + * return NULL. + */ + PyArrayObject* obj_to_array_no_conversion(PyObject* input, int typecode) + { + PyArrayObject* ary = NULL; + if (is_array(input) && (typecode == NPY_NOTYPE || + PyArray_EquivTypenums(array_type(input), typecode))) + { + ary = (PyArrayObject*) input; + } + else if is_array(input) + { + const char* desired_type = typecode_string(typecode); + const char* actual_type = typecode_string(array_type(input)); + PyErr_Format(PyExc_TypeError, + "Array of type '%s' required. Array of type '%s' given", + desired_type, actual_type); + ary = NULL; + } + else + { + const char * desired_type = typecode_string(typecode); + const char * actual_type = pytype_string(input); + PyErr_Format(PyExc_TypeError, + "Array of type '%s' required. A '%s' was given", + desired_type, actual_type); + ary = NULL; + } + return ary; + } + + /* Convert the given PyObject to a NumPy array with the given + * typecode. On success, return a valid PyArrayObject* with the + * correct type. On failure, the python error string will be set and + * the routine returns NULL. + */ + PyArrayObject* obj_to_array_allow_conversion(PyObject* input, int typecode, + int* is_new_object) + { + PyArrayObject* ary = NULL; + PyObject* py_obj; + if (is_array(input) && (typecode == NPY_NOTYPE || + PyArray_EquivTypenums(array_type(input),typecode))) + { + ary = (PyArrayObject*) input; + *is_new_object = 0; + } + else + { + py_obj = PyArray_FROMANY(input, typecode, 0, 0, NPY_DEFAULT); + /* If NULL, PyArray_FromObject will have set python error value.*/ + ary = (PyArrayObject*) py_obj; + *is_new_object = 1; + } + return ary; + } + + /* Given a PyArrayObject, check to see if it is contiguous. If so, + * return the input pointer and flag it as not a new object. If it is + * not contiguous, create a new PyArrayObject using the original data, + * flag it as a new object and return the pointer. + */ + PyArrayObject* make_contiguous(PyArrayObject* ary, int* is_new_object, + int min_dims, int max_dims) + { + PyArrayObject* result; + if (array_is_contiguous(ary)) + { + result = ary; + *is_new_object = 0; + } + else + { + result = (PyArrayObject*) PyArray_ContiguousFromObject((PyObject*)ary, + array_type(ary), + min_dims, + max_dims); + *is_new_object = 1; + } + return result; + } + + /* Given a PyArrayObject, check to see if it is Fortran-contiguous. + * If so, return the input pointer, but do not flag it as not a new + * object. If it is not Fortran-contiguous, create a new + * PyArrayObject using the original data, flag it as a new object + * and return the pointer. + */ + PyArrayObject* make_fortran(PyArrayObject* ary, int* is_new_object, + int min_dims, int max_dims) + { + PyArrayObject* result; + if (array_is_fortran(ary)) + { + result = ary; + *is_new_object = 0; + } + else + { + Py_INCREF(ary->descr); + result = (PyArrayObject*) PyArray_FromArray(ary, ary->descr, NPY_FORTRAN); + *is_new_object = 1; + } + return result; + } + + /* Convert a given PyObject to a contiguous PyArrayObject of the + * specified type. If the input object is not a contiguous + * PyArrayObject, a new one will be created and the new object flag + * will be set. + */ + PyArrayObject* obj_to_array_contiguous_allow_conversion(PyObject* input, + int typecode, + int* is_new_object) + { + int is_new1 = 0; + int is_new2 = 0; + PyArrayObject* ary2; + PyArrayObject* ary1 = obj_to_array_allow_conversion(input, typecode, + &is_new1); + if (ary1) + { + ary2 = make_contiguous(ary1, &is_new2, 0, 0); + if ( is_new1 && is_new2) + { + Py_DECREF(ary1); + } + ary1 = ary2; + } + *is_new_object = is_new1 || is_new2; + return ary1; + } + + /* Convert a given PyObject to a Fortran-ordered PyArrayObject of the + * specified type. If the input object is not a Fortran-ordered + * PyArrayObject, a new one will be created and the new object flag + * will be set. + */ + PyArrayObject* obj_to_array_fortran_allow_conversion(PyObject* input, + int typecode, + int* is_new_object) + { + int is_new1 = 0; + int is_new2 = 0; + PyArrayObject* ary2; + PyArrayObject* ary1 = obj_to_array_allow_conversion(input, typecode, + &is_new1); + if (ary1) + { + ary2 = make_fortran(ary1, &is_new2, 0, 0); + if (is_new1 && is_new2) + { + Py_DECREF(ary1); + } + ary1 = ary2; + } + *is_new_object = is_new1 || is_new2; + return ary1; + } + + /* The following code was added by Ilmar M. Wilbers for forcing a copy of the + * object even when it is a NumPy array. This is meant for use with the + * IN_ARRAY typemaps, and allows the user to perform changes on an array + * without these chenges being reflected in the calling code. + */ + + /* Convert the given PyObject to a NumPy array with the given + * typecode as a copy. On success, return a valid PyArrayObject* with the + * correct type. On failure, the python error string will be set and + * the routine returns NULL. + */ + PyArrayObject* obj_to_array_force_conversion(PyObject* input, int typecode, + int* is_new_object) + { + PyArrayObject* ary = NULL; + PyObject* py_obj; + if (is_array(input) && (typecode == NPY_NOTYPE || + PyArray_EquivTypenums(array_type(input),typecode))) + { + py_obj = PyArray_Copy((PyArrayObject*) input); + ary = (PyArrayObject*) py_obj; + *is_new_object = 1; + } + else + { + py_obj = PyArray_FROMANY(input, typecode, 0, 0, NPY_DEFAULT); + /* If NULL, PyArray_FromObject will have set python error value.*/ + ary = (PyArrayObject*) py_obj; + *is_new_object = 1; + } + return ary; + } + + /* Convert a given PyObject to a contiguous PyArrayObject of the + * specified type. If the input object is not a contiguous + * PyArrayObject, a new one will be created and the new object flag + * will be set. + */ + PyArrayObject* obj_to_array_contiguous_force_conversion(PyObject* input, + int typecode, + int* is_new_object) + { + int is_new1 = 0; + int is_new2 = 0; + PyArrayObject* ary2; + PyArrayObject* ary1 = obj_to_array_force_conversion(input, typecode, + &is_new1); + if (ary1) + { + ary2 = make_contiguous(ary1, &is_new2, 0, 0); + if ( is_new1 && is_new2) + { + Py_DECREF(ary1); + } + ary1 = ary2; + } + *is_new_object = is_new1 || is_new2; + return ary1; + } + + /* Convert a given PyObject to a Fortran-ordered PyArrayObject of the + * specified type. If the input object is not a Fortran-ordered + * PyArrayObject, a new one will be created and the new object flag + * will be set. + */ + PyArrayObject* obj_to_array_fortran_force_conversion(PyObject* input, + int typecode, + int* is_new_object) + { + int is_new1 = 0; + int is_new2 = 0; + PyArrayObject* ary2; + PyArrayObject* ary1 = obj_to_array_force_conversion(input, typecode, + &is_new1); + if (ary1) + { + ary2 = make_fortran(ary1, &is_new2, 0, 0); + if (is_new1 && is_new2) + { + Py_DECREF(ary1); + } + ary1 = ary2; + } + *is_new_object = is_new1 || is_new2; + return ary1; + } + /* End modifications by Ilmar M. Wilbers + */ + +} /* end fragment */ + + +/**********************************************************************/ + +%fragment("NumPy_Array_Requirements", "header", + fragment="NumPy_Backward_Compatibility", + fragment="NumPy_Macros") +{ + /* Test whether a python object is contiguous. If array is + * contiguous, return 1. Otherwise, set the python error string and + * return 0. + */ + int require_contiguous(PyArrayObject* ary) + { + int contiguous = 1; + if (!array_is_contiguous(ary)) + { + PyErr_SetString(PyExc_TypeError, + "Array must be contiguous. A non-contiguous array was given"); + contiguous = 0; + } + return contiguous; + } + + /* Require that a numpy array is not byte-swapped. If the array is + * not byte-swapped, return 1. Otherwise, set the python error string + * and return 0. + */ + int require_native(PyArrayObject* ary) + { + int native = 1; + if (!array_is_native(ary)) + { + PyErr_SetString(PyExc_TypeError, + "Array must have native byteorder. " + "A byte-swapped array was given"); + native = 0; + } + return native; + } + + /* Require the given PyArrayObject to have a specified number of + * dimensions. If the array has the specified number of dimensions, + * return 1. Otherwise, set the python error string and return 0. + */ + int require_dimensions(PyArrayObject* ary, int exact_dimensions) + { + int success = 1; + if (array_numdims(ary) != exact_dimensions) + { + PyErr_Format(PyExc_TypeError, + "Array must have %d dimensions. Given array has %d dimensions", + exact_dimensions, array_numdims(ary)); + success = 0; + } + return success; + } + + /* Require the given PyArrayObject to have one of a list of specified + * number of dimensions. If the array has one of the specified number + * of dimensions, return 1. Otherwise, set the python error string + * and return 0. + */ + int require_dimensions_n(PyArrayObject* ary, int* exact_dimensions, int n) + { + int success = 0; + int i; + char dims_str[255] = ""; + char s[255]; + for (i = 0; i < n && !success; i++) + { + if (array_numdims(ary) == exact_dimensions[i]) + { + success = 1; + } + } + if (!success) + { + for (i = 0; i < n-1; i++) + { + sprintf(s, "%d, ", exact_dimensions[i]); + strcat(dims_str,s); + } + sprintf(s, " or %d", exact_dimensions[n-1]); + strcat(dims_str,s); + PyErr_Format(PyExc_TypeError, + "Array must have %s dimensions. Given array has %d dimensions", + dims_str, array_numdims(ary)); + } + return success; + } + + /* Require the given PyArrayObject to have a specified shape. If the + * array has the specified shape, return 1. Otherwise, set the python + * error string and return 0. + */ + int require_size(PyArrayObject* ary, npy_intp* size, int n) + { + int i; + int success = 1; + int len; + char desired_dims[255] = "["; + char s[255]; + char actual_dims[255] = "["; + for(i=0; i < n;i++) + { + if (size[i] != -1 && size[i] != array_size(ary,i)) + { + success = 0; + } + } + if (!success) + { + for (i = 0; i < n; i++) + { + if (size[i] == -1) + { + sprintf(s, "*,"); + } + else + { + sprintf(s, "%ld,", (long int)size[i]); + } + strcat(desired_dims,s); + } + len = strlen(desired_dims); + desired_dims[len-1] = ']'; + for (i = 0; i < n; i++) + { + sprintf(s, "%ld,", (long int)array_size(ary,i)); + strcat(actual_dims,s); + } + len = strlen(actual_dims); + actual_dims[len-1] = ']'; + PyErr_Format(PyExc_TypeError, + "Array must have shape of %s. Given array has shape of %s", + desired_dims, actual_dims); + } + return success; + } + + /* Require the given PyArrayObject to to be FORTRAN ordered. If the + * the PyArrayObject is already FORTRAN ordered, do nothing. Else, + * set the FORTRAN ordering flag and recompute the strides. + */ + int require_fortran(PyArrayObject* ary) + { + int success = 1; + int nd = array_numdims(ary); + int i; + if (array_is_fortran(ary)) return success; + /* Set the FORTRAN ordered flag */ + ary->flags = NPY_FARRAY; + /* Recompute the strides */ + ary->strides[0] = ary->strides[nd-1]; + for (i=1; i < nd; ++i) + ary->strides[i] = ary->strides[i-1] * array_size(ary,i-1); + return success; + } +} + +/* Combine all NumPy fragments into one for convenience */ +%fragment("NumPy_Fragments", "header", + fragment="NumPy_Backward_Compatibility", + fragment="NumPy_Macros", + fragment="NumPy_Utilities", + fragment="NumPy_Object_to_Array", + fragment="NumPy_Array_Requirements") { } + +/* End John Hunter translation (with modifications by Bill Spotz) + */ + +/* %numpy_typemaps() macro + * + * This macro defines a family of 42 typemaps that allow C arguments + * of the form + * + * (DATA_TYPE IN_ARRAY1[ANY]) + * (DATA_TYPE* IN_ARRAY1, DIM_TYPE DIM1) + * (DIM_TYPE DIM1, DATA_TYPE* IN_ARRAY1) + * + * (DATA_TYPE IN_ARRAY2[ANY][ANY]) + * (DATA_TYPE* IN_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) + * (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_ARRAY2) + * (DATA_TYPE* IN_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) + * (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_FARRAY2) + * + * (DATA_TYPE IN_ARRAY3[ANY][ANY][ANY]) + * (DATA_TYPE* IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3) + * (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_ARRAY3) + * (DATA_TYPE* IN_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3) + * (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_FARRAY3) + * + * (DATA_TYPE INPLACE_ARRAY1[ANY]) + * (DATA_TYPE* INPLACE_ARRAY1) + * (DATA_TYPE* INPLACE_ARRAY1, DIM_TYPE DIM1) + * (DIM_TYPE DIM1, DATA_TYPE* INPLACE_ARRAY1) + * + * (DATA_TYPE INPLACE_ARRAY2[ANY][ANY]) + * (DATA_TYPE* INPLACE_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) + * (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_ARRAY2) + * (DATA_TYPE* INPLACE_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) + * (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_FARRAY2) + * + * (DATA_TYPE INPLACE_ARRAY3[ANY][ANY][ANY]) + * (DATA_TYPE* INPLACE_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3) + * (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* INPLACE_ARRAY3) + * (DATA_TYPE* INPLACE_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3) + * (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* INPLACE_FARRAY3) + * + * (DATA_TYPE ARGOUT_ARRAY1[ANY]) + * (DATA_TYPE* ARGOUT_ARRAY1, DIM_TYPE DIM1) + * (DIM_TYPE DIM1, DATA_TYPE* ARGOUT_ARRAY1) + * + * (DATA_TYPE ARGOUT_ARRAY2[ANY][ANY]) + * + * (DATA_TYPE ARGOUT_ARRAY3[ANY][ANY][ANY]) + * + * (DATA_TYPE** ARGOUTVIEW_ARRAY1, DIM_TYPE* DIM1) + * (DIM_TYPE* DIM1, DATA_TYPE** ARGOUTVIEW_ARRAY1) + * + * (DATA_TYPE** ARGOUTVIEW_ARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2) + * (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_ARRAY2) + * (DATA_TYPE** ARGOUTVIEW_FARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2) + * (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_FARRAY2) + * + * (DATA_TYPE** ARGOUTVIEW_ARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3) + * (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEW_ARRAY3) + * (DATA_TYPE** ARGOUTVIEW_FARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3) + * (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEW_FARRAY3) + * + * where "DATA_TYPE" is any type supported by the NumPy module, and + * "DIM_TYPE" is any int-like type suitable for specifying dimensions. + * The difference between "ARRAY" typemaps and "FARRAY" typemaps is + * that the "FARRAY" typemaps expect FORTRAN ordering of + * multidimensional arrays. In python, the dimensions will not need + * to be specified (except for the "DATA_TYPE* ARGOUT_ARRAY1" + * typemaps). The IN_ARRAYs can be a numpy array or any sequence that + * can be converted to a numpy array of the specified type. The + * INPLACE_ARRAYs must be numpy arrays of the appropriate type. The + * ARGOUT_ARRAYs will be returned as new numpy arrays of the + * appropriate type. + * + * These typemaps can be applied to existing functions using the + * %apply directive. For example: + * + * %apply (double* IN_ARRAY1, int DIM1) {(double* series, int length)}; + * double prod(double* series, int length); + * + * %apply (int DIM1, int DIM2, double* INPLACE_ARRAY2) + * {(int rows, int cols, double* matrix )}; + * void floor(int rows, int cols, double* matrix, double f); + * + * %apply (double IN_ARRAY3[ANY][ANY][ANY]) + * {(double tensor[2][2][2] )}; + * %apply (double ARGOUT_ARRAY3[ANY][ANY][ANY]) + * {(double low[2][2][2] )}; + * %apply (double ARGOUT_ARRAY3[ANY][ANY][ANY]) + * {(double upp[2][2][2] )}; + * void luSplit(double tensor[2][2][2], + * double low[2][2][2], + * double upp[2][2][2] ); + * + * or directly with + * + * double prod(double* IN_ARRAY1, int DIM1); + * + * void floor(int DIM1, int DIM2, double* INPLACE_ARRAY2, double f); + * + * void luSplit(double IN_ARRAY3[ANY][ANY][ANY], + * double ARGOUT_ARRAY3[ANY][ANY][ANY], + * double ARGOUT_ARRAY3[ANY][ANY][ANY]); + */ + +%define %numpy_typemaps(DATA_TYPE, DATA_TYPECODE, DIM_TYPE) + +/************************/ +/* Input Array Typemaps */ +/************************/ + +/* Typemap suite for (DATA_TYPE IN_ARRAY1[ANY]) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE IN_ARRAY1[ANY]) +{ + $1 = is_array($input) || PySequence_Check($input); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE IN_ARRAY1[ANY]) + (PyArrayObject* array=NULL, int is_new_object=0) +{ + npy_intp size[1] = { $1_dim0 }; + array = obj_to_array_contiguous_force_conversion($input, DATA_TYPECODE, + &is_new_object); + if (!array || !require_dimensions(array, 1) || + !require_size(array, size, 1)) SWIG_fail; + $1 = ($1_ltype) array_data(array); +} +%typemap(freearg) + (DATA_TYPE IN_ARRAY1[ANY]) +{ + if (is_new_object$argnum && array$argnum) + { Py_DECREF(array$argnum); } +} + +/* Typemap suite for (DATA_TYPE* IN_ARRAY1, DIM_TYPE DIM1) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE* IN_ARRAY1, DIM_TYPE DIM1) +{ + $1 = is_array($input) || PySequence_Check($input); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE* IN_ARRAY1, DIM_TYPE DIM1) + (PyArrayObject* array=NULL, int is_new_object=0) +{ + npy_intp size[1] = { -1 }; + array = obj_to_array_contiguous_force_conversion($input, DATA_TYPECODE, + &is_new_object); + if (!array || !require_dimensions(array, 1) || + !require_size(array, size, 1)) SWIG_fail; + $1 = (DATA_TYPE*) array_data(array); + $2 = (DIM_TYPE) array_size(array,0); +} +%typemap(freearg) + (DATA_TYPE* IN_ARRAY1, DIM_TYPE DIM1) +{ + if (is_new_object$argnum && array$argnum) + { Py_DECREF(array$argnum); } +} + +/* Typemap suite for (DIM_TYPE DIM1, DATA_TYPE* IN_ARRAY1) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DIM_TYPE DIM1, DATA_TYPE* IN_ARRAY1) +{ + $1 = is_array($input) || PySequence_Check($input); +} +%typemap(in, + fragment="NumPy_Fragments") + (DIM_TYPE DIM1, DATA_TYPE* IN_ARRAY1) + (PyArrayObject* array=NULL, int is_new_object=0) +{ + npy_intp size[1] = {-1}; + array = obj_to_array_contiguous_force_conversion($input, DATA_TYPECODE, + &is_new_object); + if (!array || !require_dimensions(array, 1) || + !require_size(array, size, 1)) SWIG_fail; + $1 = (DIM_TYPE) array_size(array,0); + $2 = (DATA_TYPE*) array_data(array); +} +%typemap(freearg) + (DIM_TYPE DIM1, DATA_TYPE* IN_ARRAY1) +{ + if (is_new_object$argnum && array$argnum) + { Py_DECREF(array$argnum); } +} + +/* Typemap suite for (DATA_TYPE IN_ARRAY2[ANY][ANY]) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE IN_ARRAY2[ANY][ANY]) +{ + $1 = is_array($input) || PySequence_Check($input); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE IN_ARRAY2[ANY][ANY]) + (PyArrayObject* array=NULL, int is_new_object=0) +{ + npy_intp size[2] = { $1_dim0, $1_dim1 }; + array = obj_to_array_contiguous_force_conversion($input, DATA_TYPECODE, + &is_new_object); + if (!array || !require_dimensions(array, 2) || + !require_size(array, size, 2)) SWIG_fail; + $1 = ($1_ltype) array_data(array); +} +%typemap(freearg) + (DATA_TYPE IN_ARRAY2[ANY][ANY]) +{ + if (is_new_object$argnum && array$argnum) + { Py_DECREF(array$argnum); } +} + +/* Typemap suite for (DATA_TYPE* IN_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE* IN_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) +{ + $1 = is_array($input) || PySequence_Check($input); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE* IN_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) + (PyArrayObject* array=NULL, int is_new_object=0) +{ + npy_intp size[2] = { -1, -1 }; + array = obj_to_array_contiguous_force_conversion($input, DATA_TYPECODE, + &is_new_object); + if (!array || !require_dimensions(array, 2) || + !require_size(array, size, 2)) SWIG_fail; + $1 = (DATA_TYPE*) array_data(array); + $2 = (DIM_TYPE) array_size(array,0); + $3 = (DIM_TYPE) array_size(array,1); +} +%typemap(freearg) + (DATA_TYPE* IN_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) +{ + if (is_new_object$argnum && array$argnum) + { Py_DECREF(array$argnum); } +} + +/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_ARRAY2) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_ARRAY2) +{ + $1 = is_array($input) || PySequence_Check($input); +} +%typemap(in, + fragment="NumPy_Fragments") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_ARRAY2) + (PyArrayObject* array=NULL, int is_new_object=0) +{ + npy_intp size[2] = { -1, -1 }; + array = obj_to_array_contiguous_force_conversion($input, DATA_TYPECODE, + &is_new_object); + if (!array || !require_dimensions(array, 2) || + !require_size(array, size, 2)) SWIG_fail; + $1 = (DIM_TYPE) array_size(array,0); + $2 = (DIM_TYPE) array_size(array,1); + $3 = (DATA_TYPE*) array_data(array); +} +%typemap(freearg) + (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_ARRAY2) +{ + if (is_new_object$argnum && array$argnum) + { Py_DECREF(array$argnum); } +} + +/* Typemap suite for (DATA_TYPE* IN_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE* IN_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) +{ + $1 = is_array($input) || PySequence_Check($input); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE* IN_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) + (PyArrayObject* array=NULL, int is_new_object=0) +{ + npy_intp size[2] = { -1, -1 }; + array = obj_to_array_fortran_force_conversion($input, DATA_TYPECODE, + &is_new_object); + if (!array || !require_dimensions(array, 2) || + !require_size(array, size, 2) || !require_fortran(array)) SWIG_fail; + $1 = (DATA_TYPE*) array_data(array); + $2 = (DIM_TYPE) array_size(array,0); + $3 = (DIM_TYPE) array_size(array,1); +} +%typemap(freearg) + (DATA_TYPE* IN_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) +{ + if (is_new_object$argnum && array$argnum) + { Py_DECREF(array$argnum); } +} + +/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_FARRAY2) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_FARRAY2) +{ + $1 = is_array($input) || PySequence_Check($input); +} +%typemap(in, + fragment="NumPy_Fragments") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_FARRAY2) + (PyArrayObject* array=NULL, int is_new_object=0) +{ + npy_intp size[2] = { -1, -1 }; + array = obj_to_array_contiguous_force_conversion($input, DATA_TYPECODE, + &is_new_object); + if (!array || !require_dimensions(array, 2) || + !require_size(array, size, 2) || !require_fortran(array)) SWIG_fail; + $1 = (DIM_TYPE) array_size(array,0); + $2 = (DIM_TYPE) array_size(array,1); + $3 = (DATA_TYPE*) array_data(array); +} +%typemap(freearg) + (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* IN_FARRAY2) +{ + if (is_new_object$argnum && array$argnum) + { Py_DECREF(array$argnum); } +} + +/* Typemap suite for (DATA_TYPE IN_ARRAY3[ANY][ANY][ANY]) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE IN_ARRAY3[ANY][ANY][ANY]) +{ + $1 = is_array($input) || PySequence_Check($input); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE IN_ARRAY3[ANY][ANY][ANY]) + (PyArrayObject* array=NULL, int is_new_object=0) +{ + npy_intp size[3] = { $1_dim0, $1_dim1, $1_dim2 }; + array = obj_to_array_contiguous_force_conversion($input, DATA_TYPECODE, + &is_new_object); + if (!array || !require_dimensions(array, 3) || + !require_size(array, size, 3)) SWIG_fail; + $1 = ($1_ltype) array_data(array); +} +%typemap(freearg) + (DATA_TYPE IN_ARRAY3[ANY][ANY][ANY]) +{ + if (is_new_object$argnum && array$argnum) + { Py_DECREF(array$argnum); } +} + +/* Typemap suite for (DATA_TYPE* IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, + * DIM_TYPE DIM3) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE* IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3) +{ + $1 = is_array($input) || PySequence_Check($input); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE* IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3) + (PyArrayObject* array=NULL, int is_new_object=0) +{ + npy_intp size[3] = { -1, -1, -1 }; + array = obj_to_array_contiguous_force_conversion($input, DATA_TYPECODE, + &is_new_object); + if (!array || !require_dimensions(array, 3) || + !require_size(array, size, 3)) SWIG_fail; + $1 = (DATA_TYPE*) array_data(array); + $2 = (DIM_TYPE) array_size(array,0); + $3 = (DIM_TYPE) array_size(array,1); + $4 = (DIM_TYPE) array_size(array,2); +} +%typemap(freearg) + (DATA_TYPE* IN_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3) +{ + if (is_new_object$argnum && array$argnum) + { Py_DECREF(array$argnum); } +} + +/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, + * DATA_TYPE* IN_ARRAY3) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_ARRAY3) +{ + $1 = is_array($input) || PySequence_Check($input); +} +%typemap(in, + fragment="NumPy_Fragments") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_ARRAY3) + (PyArrayObject* array=NULL, int is_new_object=0) +{ + npy_intp size[3] = { -1, -1, -1 }; + array = obj_to_array_contiguous_force_conversion($input, DATA_TYPECODE, + &is_new_object); + if (!array || !require_dimensions(array, 3) || + !require_size(array, size, 3)) SWIG_fail; + $1 = (DIM_TYPE) array_size(array,0); + $2 = (DIM_TYPE) array_size(array,1); + $3 = (DIM_TYPE) array_size(array,2); + $4 = (DATA_TYPE*) array_data(array); +} +%typemap(freearg) + (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_ARRAY3) +{ + if (is_new_object$argnum && array$argnum) + { Py_DECREF(array$argnum); } +} + +/* Typemap suite for (DATA_TYPE* IN_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, + * DIM_TYPE DIM3) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE* IN_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3) +{ + $1 = is_array($input) || PySequence_Check($input); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE* IN_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3) + (PyArrayObject* array=NULL, int is_new_object=0) +{ + npy_intp size[3] = { -1, -1, -1 }; + array = obj_to_array_fortran_force_conversion($input, DATA_TYPECODE, + &is_new_object); + if (!array || !require_dimensions(array, 3) || + !require_size(array, size, 3) | !require_fortran(array)) SWIG_fail; + $1 = (DATA_TYPE*) array_data(array); + $2 = (DIM_TYPE) array_size(array,0); + $3 = (DIM_TYPE) array_size(array,1); + $4 = (DIM_TYPE) array_size(array,2); +} +%typemap(freearg) + (DATA_TYPE* IN_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3) +{ + if (is_new_object$argnum && array$argnum) + { Py_DECREF(array$argnum); } +} + +/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, + * DATA_TYPE* IN_FARRAY3) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_FARRAY3) +{ + $1 = is_array($input) || PySequence_Check($input); +} +%typemap(in, + fragment="NumPy_Fragments") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_FARRAY3) + (PyArrayObject* array=NULL, int is_new_object=0) +{ + npy_intp size[3] = { -1, -1, -1 }; + array = obj_to_array_contiguous_force_conversion($input, DATA_TYPECODE, + &is_new_object); + if (!array || !require_dimensions(array, 3) || + !require_size(array, size, 3) || !require_fortran(array)) SWIG_fail; + $1 = (DIM_TYPE) array_size(array,0); + $2 = (DIM_TYPE) array_size(array,1); + $3 = (DIM_TYPE) array_size(array,2); + $4 = (DATA_TYPE*) array_data(array); +} +%typemap(freearg) + (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* IN_FARRAY3) +{ + if (is_new_object$argnum && array$argnum) + { Py_DECREF(array$argnum); } +} + +/***************************/ +/* In-Place Array Typemaps */ +/***************************/ + +/* Typemap suite for (DATA_TYPE INPLACE_ARRAY1[ANY]) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE INPLACE_ARRAY1[ANY]) +{ + $1 = is_array($input) && PyArray_EquivTypenums(array_type($input), + DATA_TYPECODE); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE INPLACE_ARRAY1[ANY]) + (PyArrayObject* array=NULL) +{ + npy_intp size[1] = { $1_dim0 }; + array = obj_to_array_no_conversion($input, DATA_TYPECODE); + if (!array || !require_dimensions(array,1) || !require_size(array, size, 1) || + !require_contiguous(array) || !require_native(array)) SWIG_fail; + $1 = ($1_ltype) array_data(array); +} + +/* Typemap suite for (DATA_TYPE* INPLACE_ARRAY1) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE* INPLACE_ARRAY1) +{ + $1 = is_array($input) && PyArray_EquivTypenums(array_type($input), + DATA_TYPECODE); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE* INPLACE_ARRAY1) + (PyArrayObject* array=NULL) +{ + array = obj_to_array_no_conversion($input, DATA_TYPECODE); + if (!array || !require_dimensions(array,1) || !require_contiguous(array) + || !require_native(array)) SWIG_fail; + $1 = (DATA_TYPE*) array_data(array); +} + +/* Typemap suite for (DATA_TYPE* INPLACE_ARRAY1, DIM_TYPE DIM1) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE* INPLACE_ARRAY1, DIM_TYPE DIM1) +{ + $1 = is_array($input) && PyArray_EquivTypenums(array_type($input), + DATA_TYPECODE); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE* INPLACE_ARRAY1, DIM_TYPE DIM1) + (PyArrayObject* array=NULL, int i=1) +{ + array = obj_to_array_no_conversion($input, DATA_TYPECODE); + if (!array || !require_dimensions(array,1) || !require_contiguous(array) + || !require_native(array)) SWIG_fail; + $1 = (DATA_TYPE*) array_data(array); + $2 = 1; + for (i=0; i < array_numdims(array); ++i) $2 *= array_size(array,i); +} + +/* Typemap suite for (DIM_TYPE DIM1, DATA_TYPE* INPLACE_ARRAY1) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DIM_TYPE DIM1, DATA_TYPE* INPLACE_ARRAY1) +{ + $1 = is_array($input) && PyArray_EquivTypenums(array_type($input), + DATA_TYPECODE); +} +%typemap(in, + fragment="NumPy_Fragments") + (DIM_TYPE DIM1, DATA_TYPE* INPLACE_ARRAY1) + (PyArrayObject* array=NULL, int i=0) +{ + array = obj_to_array_no_conversion($input, DATA_TYPECODE); + if (!array || !require_dimensions(array,1) || !require_contiguous(array) + || !require_native(array)) SWIG_fail; + $1 = 1; + for (i=0; i < array_numdims(array); ++i) $1 *= array_size(array,i); + $2 = (DATA_TYPE*) array_data(array); +} + +/* Typemap suite for (DATA_TYPE INPLACE_ARRAY2[ANY][ANY]) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE INPLACE_ARRAY2[ANY][ANY]) +{ + $1 = is_array($input) && PyArray_EquivTypenums(array_type($input), + DATA_TYPECODE); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE INPLACE_ARRAY2[ANY][ANY]) + (PyArrayObject* array=NULL) +{ + npy_intp size[2] = { $1_dim0, $1_dim1 }; + array = obj_to_array_no_conversion($input, DATA_TYPECODE); + if (!array || !require_dimensions(array,2) || !require_size(array, size, 2) || + !require_contiguous(array) || !require_native(array)) SWIG_fail; + $1 = ($1_ltype) array_data(array); +} + +/* Typemap suite for (DATA_TYPE* INPLACE_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE* INPLACE_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) +{ + $1 = is_array($input) && PyArray_EquivTypenums(array_type($input), + DATA_TYPECODE); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE* INPLACE_ARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) + (PyArrayObject* array=NULL) +{ + array = obj_to_array_no_conversion($input, DATA_TYPECODE); + if (!array || !require_dimensions(array,2) || !require_contiguous(array) + || !require_native(array)) SWIG_fail; + $1 = (DATA_TYPE*) array_data(array); + $2 = (DIM_TYPE) array_size(array,0); + $3 = (DIM_TYPE) array_size(array,1); +} + +/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_ARRAY2) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_ARRAY2) +{ + $1 = is_array($input) && PyArray_EquivTypenums(array_type($input), + DATA_TYPECODE); +} +%typemap(in, + fragment="NumPy_Fragments") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_ARRAY2) + (PyArrayObject* array=NULL) +{ + array = obj_to_array_no_conversion($input, DATA_TYPECODE); + if (!array || !require_dimensions(array,2) || !require_contiguous(array) || + !require_native(array)) SWIG_fail; + $1 = (DIM_TYPE) array_size(array,0); + $2 = (DIM_TYPE) array_size(array,1); + $3 = (DATA_TYPE*) array_data(array); +} + +/* Typemap suite for (DATA_TYPE* INPLACE_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE* INPLACE_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) +{ + $1 = is_array($input) && PyArray_EquivTypenums(array_type($input), + DATA_TYPECODE); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE* INPLACE_FARRAY2, DIM_TYPE DIM1, DIM_TYPE DIM2) + (PyArrayObject* array=NULL) +{ + array = obj_to_array_no_conversion($input, DATA_TYPECODE); + if (!array || !require_dimensions(array,2) || !require_contiguous(array) + || !require_native(array) || !require_fortran(array)) SWIG_fail; + $1 = (DATA_TYPE*) array_data(array); + $2 = (DIM_TYPE) array_size(array,0); + $3 = (DIM_TYPE) array_size(array,1); +} + +/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_FARRAY2) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_FARRAY2) +{ + $1 = is_array($input) && PyArray_EquivTypenums(array_type($input), + DATA_TYPECODE); +} +%typemap(in, + fragment="NumPy_Fragments") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DATA_TYPE* INPLACE_FARRAY2) + (PyArrayObject* array=NULL) +{ + array = obj_to_array_no_conversion($input, DATA_TYPECODE); + if (!array || !require_dimensions(array,2) || !require_contiguous(array) || + !require_native(array) || !require_fortran(array)) SWIG_fail; + $1 = (DIM_TYPE) array_size(array,0); + $2 = (DIM_TYPE) array_size(array,1); + $3 = (DATA_TYPE*) array_data(array); +} + +/* Typemap suite for (DATA_TYPE INPLACE_ARRAY3[ANY][ANY][ANY]) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE INPLACE_ARRAY3[ANY][ANY][ANY]) +{ + $1 = is_array($input) && PyArray_EquivTypenums(array_type($input), + DATA_TYPECODE); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE INPLACE_ARRAY3[ANY][ANY][ANY]) + (PyArrayObject* array=NULL) +{ + npy_intp size[3] = { $1_dim0, $1_dim1, $1_dim2 }; + array = obj_to_array_no_conversion($input, DATA_TYPECODE); + if (!array || !require_dimensions(array,3) || !require_size(array, size, 3) || + !require_contiguous(array) || !require_native(array)) SWIG_fail; + $1 = ($1_ltype) array_data(array); +} + +/* Typemap suite for (DATA_TYPE* INPLACE_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, + * DIM_TYPE DIM3) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE* INPLACE_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3) +{ + $1 = is_array($input) && PyArray_EquivTypenums(array_type($input), + DATA_TYPECODE); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE* INPLACE_ARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3) + (PyArrayObject* array=NULL) +{ + array = obj_to_array_no_conversion($input, DATA_TYPECODE); + if (!array || !require_dimensions(array,3) || !require_contiguous(array) || + !require_native(array)) SWIG_fail; + $1 = (DATA_TYPE*) array_data(array); + $2 = (DIM_TYPE) array_size(array,0); + $3 = (DIM_TYPE) array_size(array,1); + $4 = (DIM_TYPE) array_size(array,2); +} + +/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, + * DATA_TYPE* INPLACE_ARRAY3) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* INPLACE_ARRAY3) +{ + $1 = is_array($input) && PyArray_EquivTypenums(array_type($input), + DATA_TYPECODE); +} +%typemap(in, + fragment="NumPy_Fragments") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* INPLACE_ARRAY3) + (PyArrayObject* array=NULL) +{ + array = obj_to_array_no_conversion($input, DATA_TYPECODE); + if (!array || !require_dimensions(array,3) || !require_contiguous(array) + || !require_native(array)) SWIG_fail; + $1 = (DIM_TYPE) array_size(array,0); + $2 = (DIM_TYPE) array_size(array,1); + $3 = (DIM_TYPE) array_size(array,2); + $4 = (DATA_TYPE*) array_data(array); +} + +/* Typemap suite for (DATA_TYPE* INPLACE_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, + * DIM_TYPE DIM3) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DATA_TYPE* INPLACE_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3) +{ + $1 = is_array($input) && PyArray_EquivTypenums(array_type($input), + DATA_TYPECODE); +} +%typemap(in, + fragment="NumPy_Fragments") + (DATA_TYPE* INPLACE_FARRAY3, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3) + (PyArrayObject* array=NULL) +{ + array = obj_to_array_no_conversion($input, DATA_TYPECODE); + if (!array || !require_dimensions(array,3) || !require_contiguous(array) || + !require_native(array) || !require_fortran(array)) SWIG_fail; + $1 = (DATA_TYPE*) array_data(array); + $2 = (DIM_TYPE) array_size(array,0); + $3 = (DIM_TYPE) array_size(array,1); + $4 = (DIM_TYPE) array_size(array,2); +} + +/* Typemap suite for (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, + * DATA_TYPE* INPLACE_FARRAY3) + */ +%typecheck(SWIG_TYPECHECK_DOUBLE_ARRAY, + fragment="NumPy_Macros") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* INPLACE_FARRAY3) +{ + $1 = is_array($input) && PyArray_EquivTypenums(array_type($input), + DATA_TYPECODE); +} +%typemap(in, + fragment="NumPy_Fragments") + (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DATA_TYPE* INPLACE_FARRAY3) + (PyArrayObject* array=NULL) +{ + array = obj_to_array_no_conversion($input, DATA_TYPECODE); + if (!array || !require_dimensions(array,3) || !require_contiguous(array) + || !require_native(array) || !require_fortran(array)) SWIG_fail; + $1 = (DIM_TYPE) array_size(array,0); + $2 = (DIM_TYPE) array_size(array,1); + $3 = (DIM_TYPE) array_size(array,2); + $4 = (DATA_TYPE*) array_data(array); +} + +/*************************/ +/* Argout Array Typemaps */ +/*************************/ + +/* Typemap suite for (DATA_TYPE ARGOUT_ARRAY1[ANY]) + */ +%typemap(in,numinputs=0, + fragment="NumPy_Backward_Compatibility,NumPy_Macros") + (DATA_TYPE ARGOUT_ARRAY1[ANY]) + (PyObject * array = NULL) +{ + npy_intp dims[1] = { $1_dim0 }; + array = PyArray_SimpleNew(1, dims, DATA_TYPECODE); + if (!array) SWIG_fail; + $1 = ($1_ltype) array_data(array); +} +%typemap(argout) + (DATA_TYPE ARGOUT_ARRAY1[ANY]) +{ + $result = SWIG_Python_AppendOutput($result,array$argnum); +} + +/* Typemap suite for (DATA_TYPE* ARGOUT_ARRAY1, DIM_TYPE DIM1) + */ +%typemap(in,numinputs=1, + fragment="NumPy_Fragments") + (DATA_TYPE* ARGOUT_ARRAY1, DIM_TYPE DIM1) + (PyObject * array = NULL) +{ + npy_intp dims[1]; + if (!PyInt_Check($input)) + { + const char* typestring = pytype_string($input); + PyErr_Format(PyExc_TypeError, + "Int dimension expected. '%s' given.", + typestring); + SWIG_fail; + } + $2 = (DIM_TYPE) PyInt_AsLong($input); + dims[0] = (npy_intp) $2; + array = PyArray_SimpleNew(1, dims, DATA_TYPECODE); + if (!array) SWIG_fail; + $1 = (DATA_TYPE*) array_data(array); +} +%typemap(argout) + (DATA_TYPE* ARGOUT_ARRAY1, DIM_TYPE DIM1) +{ + $result = SWIG_Python_AppendOutput($result,array$argnum); +} + +/* Typemap suite for (DIM_TYPE DIM1, DATA_TYPE* ARGOUT_ARRAY1) + */ +%typemap(in,numinputs=1, + fragment="NumPy_Fragments") + (DIM_TYPE DIM1, DATA_TYPE* ARGOUT_ARRAY1) + (PyObject * array = NULL) +{ + npy_intp dims[1]; + if (!PyInt_Check($input)) + { + const char* typestring = pytype_string($input); + PyErr_Format(PyExc_TypeError, + "Int dimension expected. '%s' given.", + typestring); + SWIG_fail; + } + $1 = (DIM_TYPE) PyInt_AsLong($input); + dims[0] = (npy_intp) $1; + array = PyArray_SimpleNew(1, dims, DATA_TYPECODE); + if (!array) SWIG_fail; + $2 = (DATA_TYPE*) array_data(array); +} +%typemap(argout) + (DIM_TYPE DIM1, DATA_TYPE* ARGOUT_ARRAY1) +{ + $result = SWIG_Python_AppendOutput($result,array$argnum); +} + +/* Typemap suite for (DATA_TYPE ARGOUT_ARRAY2[ANY][ANY]) + */ +%typemap(in,numinputs=0, + fragment="NumPy_Backward_Compatibility,NumPy_Macros") + (DATA_TYPE ARGOUT_ARRAY2[ANY][ANY]) + (PyObject * array = NULL) +{ + npy_intp dims[2] = { $1_dim0, $1_dim1 }; + array = PyArray_SimpleNew(2, dims, DATA_TYPECODE); + if (!array) SWIG_fail; + $1 = ($1_ltype) array_data(array); +} +%typemap(argout) + (DATA_TYPE ARGOUT_ARRAY2[ANY][ANY]) +{ + $result = SWIG_Python_AppendOutput($result,array$argnum); +} + +/* Typemap suite for (DATA_TYPE ARGOUT_ARRAY3[ANY][ANY][ANY]) + */ +%typemap(in,numinputs=0, + fragment="NumPy_Backward_Compatibility,NumPy_Macros") + (DATA_TYPE ARGOUT_ARRAY3[ANY][ANY][ANY]) + (PyObject * array = NULL) +{ + npy_intp dims[3] = { $1_dim0, $1_dim1, $1_dim2 }; + array = PyArray_SimpleNew(3, dims, DATA_TYPECODE); + if (!array) SWIG_fail; + $1 = ($1_ltype) array_data(array); +} +%typemap(argout) + (DATA_TYPE ARGOUT_ARRAY3[ANY][ANY][ANY]) +{ + $result = SWIG_Python_AppendOutput($result,array$argnum); +} + +/*****************************/ +/* Argoutview Array Typemaps */ +/*****************************/ + +/* Typemap suite for (DATA_TYPE** ARGOUTVIEW_ARRAY1, DIM_TYPE* DIM1) + */ +%typemap(in,numinputs=0) + (DATA_TYPE** ARGOUTVIEW_ARRAY1, DIM_TYPE* DIM1 ) + (DATA_TYPE* data_temp , DIM_TYPE dim_temp) +{ + $1 = &data_temp; + $2 = &dim_temp; +} +%typemap(argout, + fragment="NumPy_Backward_Compatibility") + (DATA_TYPE** ARGOUTVIEW_ARRAY1, DIM_TYPE* DIM1) +{ + npy_intp dims[1] = { *$2 }; + PyObject * array = PyArray_SimpleNewFromData(1, dims, DATA_TYPECODE, (void*)(*$1)); + if (!array) SWIG_fail; + $result = SWIG_Python_AppendOutput($result,array); +} + +/* Typemap suite for (DIM_TYPE* DIM1, DATA_TYPE** ARGOUTVIEW_ARRAY1) + */ +%typemap(in,numinputs=0) + (DIM_TYPE* DIM1 , DATA_TYPE** ARGOUTVIEW_ARRAY1) + (DIM_TYPE dim_temp, DATA_TYPE* data_temp ) +{ + $1 = &dim_temp; + $2 = &data_temp; +} +%typemap(argout, + fragment="NumPy_Backward_Compatibility") + (DIM_TYPE* DIM1, DATA_TYPE** ARGOUTVIEW_ARRAY1) +{ + npy_intp dims[1] = { *$1 }; + PyObject * array = PyArray_SimpleNewFromData(1, dims, DATA_TYPECODE, (void*)(*$2)); + if (!array) SWIG_fail; + $result = SWIG_Python_AppendOutput($result,array); +} + +/* Typemap suite for (DATA_TYPE** ARGOUTVIEW_ARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2) + */ +%typemap(in,numinputs=0) + (DATA_TYPE** ARGOUTVIEW_ARRAY2, DIM_TYPE* DIM1 , DIM_TYPE* DIM2 ) + (DATA_TYPE* data_temp , DIM_TYPE dim1_temp, DIM_TYPE dim2_temp) +{ + $1 = &data_temp; + $2 = &dim1_temp; + $3 = &dim2_temp; +} +%typemap(argout, + fragment="NumPy_Backward_Compatibility") + (DATA_TYPE** ARGOUTVIEW_ARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2) +{ + npy_intp dims[2] = { *$2, *$3 }; + PyObject * array = PyArray_SimpleNewFromData(2, dims, DATA_TYPECODE, (void*)(*$1)); + if (!array) SWIG_fail; + $result = SWIG_Python_AppendOutput($result,array); +} + +/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_ARRAY2) + */ +%typemap(in,numinputs=0) + (DIM_TYPE* DIM1 , DIM_TYPE* DIM2 , DATA_TYPE** ARGOUTVIEW_ARRAY2) + (DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DATA_TYPE* data_temp ) +{ + $1 = &dim1_temp; + $2 = &dim2_temp; + $3 = &data_temp; +} +%typemap(argout, + fragment="NumPy_Backward_Compatibility") + (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_ARRAY2) +{ + npy_intp dims[2] = { *$1, *$2 }; + PyObject * array = PyArray_SimpleNewFromData(2, dims, DATA_TYPECODE, (void*)(*$3)); + if (!array) SWIG_fail; + $result = SWIG_Python_AppendOutput($result,array); +} + +/* Typemap suite for (DATA_TYPE** ARGOUTVIEW_FARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2) + */ +%typemap(in,numinputs=0) + (DATA_TYPE** ARGOUTVIEW_FARRAY2, DIM_TYPE* DIM1 , DIM_TYPE* DIM2 ) + (DATA_TYPE* data_temp , DIM_TYPE dim1_temp, DIM_TYPE dim2_temp) +{ + $1 = &data_temp; + $2 = &dim1_temp; + $3 = &dim2_temp; +} +%typemap(argout, + fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements") + (DATA_TYPE** ARGOUTVIEW_FARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2) +{ + npy_intp dims[2] = { *$2, *$3 }; + PyObject * obj = PyArray_SimpleNewFromData(2, dims, DATA_TYPECODE, (void*)(*$1)); + PyArrayObject * array = (PyArrayObject*) obj; + if (!array || !require_fortran(array)) SWIG_fail; + $result = SWIG_Python_AppendOutput($result,obj); +} + +/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_FARRAY2) + */ +%typemap(in,numinputs=0) + (DIM_TYPE* DIM1 , DIM_TYPE* DIM2 , DATA_TYPE** ARGOUTVIEW_FARRAY2) + (DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DATA_TYPE* data_temp ) +{ + $1 = &dim1_temp; + $2 = &dim2_temp; + $3 = &data_temp; +} +%typemap(argout, + fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements") + (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_FARRAY2) +{ + npy_intp dims[2] = { *$1, *$2 }; + PyObject * obj = PyArray_SimpleNewFromData(2, dims, DATA_TYPECODE, (void*)(*$3)); + PyArrayObject * array = (PyArrayObject*) obj; + if (!array || !require_fortran(array)) SWIG_fail; + $result = SWIG_Python_AppendOutput($result,obj); +} + +/* Typemap suite for (DATA_TYPE** ARGOUTVIEW_ARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, + DIM_TYPE* DIM3) + */ +%typemap(in,numinputs=0) + (DATA_TYPE** ARGOUTVIEW_ARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3) + (DATA_TYPE* data_temp, DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp) +{ + $1 = &data_temp; + $2 = &dim1_temp; + $3 = &dim2_temp; + $4 = &dim3_temp; +} +%typemap(argout, + fragment="NumPy_Backward_Compatibility") + (DATA_TYPE** ARGOUTVIEW_ARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3) +{ + npy_intp dims[3] = { *$2, *$3, *$4 }; + PyObject * array = PyArray_SimpleNewFromData(3, dims, DATA_TYPECODE, (void*)(*$1)); + if (!array) SWIG_fail; + $result = SWIG_Python_AppendOutput($result,array); +} + +/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, + DATA_TYPE** ARGOUTVIEW_ARRAY3) + */ +%typemap(in,numinputs=0) + (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEW_ARRAY3) + (DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DATA_TYPE* data_temp) +{ + $1 = &dim1_temp; + $2 = &dim2_temp; + $3 = &dim3_temp; + $4 = &data_temp; +} +%typemap(argout, + fragment="NumPy_Backward_Compatibility") + (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEW_ARRAY3) +{ + npy_intp dims[3] = { *$1, *$2, *$3 }; + PyObject * array = PyArray_SimpleNewFromData(3, dims, DATA_TYPECODE, (void*)(*$3)); + if (!array) SWIG_fail; + $result = SWIG_Python_AppendOutput($result,array); +} + +/* Typemap suite for (DATA_TYPE** ARGOUTVIEW_FARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, + DIM_TYPE* DIM3) + */ +%typemap(in,numinputs=0) + (DATA_TYPE** ARGOUTVIEW_FARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3) + (DATA_TYPE* data_temp, DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp) +{ + $1 = &data_temp; + $2 = &dim1_temp; + $3 = &dim2_temp; + $4 = &dim3_temp; +} +%typemap(argout, + fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements") + (DATA_TYPE** ARGOUTVIEW_FARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3) +{ + npy_intp dims[3] = { *$2, *$3, *$4 }; + PyObject * obj = PyArray_SimpleNewFromData(3, dims, DATA_TYPECODE, (void*)(*$1)); + PyArrayObject * array = (PyArrayObject*) obj; + if (!array || require_fortran(array)) SWIG_fail; + $result = SWIG_Python_AppendOutput($result,obj); +} + +/* Typemap suite for (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, + DATA_TYPE** ARGOUTVIEW_FARRAY3) + */ +%typemap(in,numinputs=0) + (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEW_FARRAY3) + (DIM_TYPE dim1_temp, DIM_TYPE dim2_temp, DIM_TYPE dim3_temp, DATA_TYPE* data_temp) +{ + $1 = &dim1_temp; + $2 = &dim2_temp; + $3 = &dim3_temp; + $4 = &data_temp; +} +%typemap(argout, + fragment="NumPy_Backward_Compatibility,NumPy_Array_Requirements") + (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEW_FARRAY3) +{ + npy_intp dims[3] = { *$1, *$2, *$3 }; + PyObject * obj = PyArray_SimpleNewFromData(3, dims, DATA_TYPECODE, (void*)(*$3)); + PyArrayObject * array = (PyArrayObject*) obj; + if (!array || require_fortran(array)) SWIG_fail; + $result = SWIG_Python_AppendOutput($result,obj); +} + +%enddef /* %numpy_typemaps() macro */ +/* *************************************************************** */ + +/* Concrete instances of the %numpy_typemaps() macro: Each invocation + * below applies all of the typemaps above to the specified data type. + */ +%numpy_typemaps(signed char , NPY_BYTE , int) +%numpy_typemaps(unsigned char , NPY_UBYTE , int) +%numpy_typemaps(short , NPY_SHORT , int) +%numpy_typemaps(unsigned short , NPY_USHORT , int) +%numpy_typemaps(int , NPY_INT , int) +%numpy_typemaps(unsigned int , NPY_UINT , int) +%numpy_typemaps(long , NPY_LONG , int) +%numpy_typemaps(unsigned long , NPY_ULONG , int) +%numpy_typemaps(long long , NPY_LONGLONG , int) +%numpy_typemaps(unsigned long long, NPY_ULONGLONG, int) +%numpy_typemaps(float , NPY_FLOAT , int) +%numpy_typemaps(double , NPY_DOUBLE , int) + +/* *************************************************************** + * The follow macro expansion does not work, because C++ bool is 4 + * bytes and NPY_BOOL is 1 byte + * + * %numpy_typemaps(bool, NPY_BOOL, int) + */ + +/* *************************************************************** + * On my Mac, I get the following warning for this macro expansion: + * 'swig/python detected a memory leak of type 'long double *', no destructor found.' + * + * %numpy_typemaps(long double, NPY_LONGDOUBLE, int) + */ + +/* *************************************************************** + * Swig complains about a syntax error for the following macro + * expansions: + * + * %numpy_typemaps(complex float, NPY_CFLOAT , int) + * + * %numpy_typemaps(complex double, NPY_CDOUBLE, int) + * + * %numpy_typemaps(complex long double, NPY_CLONGDOUBLE, int) + */ + +#endif /* SWIGPYTHON */ diff --git a/bindings/python/Makefile b/bindings/python/Makefile new file mode 100644 index 0000000..e63c124 --- /dev/null +++ b/bindings/python/Makefile @@ -0,0 +1,186 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make <target>' where <target> is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +modules.rst lilv.rst: + mkdir -p lilv + ln -s -t lilv ../lilv.py + sphinx-apidoc -o . lilv + +clean: + rm -rf $(BUILDDIR)/* + rm -f lilv/lilv.py + rm -rf lilv + rm -f lilv.rst + rm -f modules.rst + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: modules.rst lilv.rst + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Lilv.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Lilv.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Lilv" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Lilv" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/bindings/python/conf.py b/bindings/python/conf.py new file mode 100644 index 0000000..576919e --- /dev/null +++ b/bindings/python/conf.py @@ -0,0 +1,263 @@ +# -*- coding: utf-8 -*- +# +# Lilv documentation build configuration file, created by +# sphinx-quickstart on Sun Sep 4 18:25:58 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.ifconfig', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Lilv' +copyright = u'2016, David Robillard' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.24.2' +# The full version, including alpha/beta/rc tags. +release = '0.24.2' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +#html_theme = '' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +html_theme_options = { 'nosidebar': True } + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Lilvdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'Lilv.tex', u'Lilv Documentation', + u'David Robillard', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'lilv', u'Lilv Documentation', + [u'David Robillard'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'Lilv', u'Lilv Documentation', + u'David Robillard', 'Lilv', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/bindings/python/index.rst b/bindings/python/index.rst new file mode 100644 index 0000000..4616054 --- /dev/null +++ b/bindings/python/index.rst @@ -0,0 +1,9 @@ +Lilv Python Documentation +========================= + + +.. toctree:: + +.. automodule:: lilv + :noindex: + :members: diff --git a/bindings/python/lilv.py b/bindings/python/lilv.py new file mode 100644 index 0000000..024bfe7 --- /dev/null +++ b/bindings/python/lilv.py @@ -0,0 +1,1775 @@ +"""Lilv Python interface""" + +__author__ = "David Robillard" +__copyright__ = "Copyright 2016 David Robillard" +__license__ = "ISC" +__version__ = "0.22.1" +__maintainer__ = "David Robillard" +__email__ = "d@drobilla.net" +__status__ = "Production" + +import ctypes +import os +import sys + +from ctypes import Structure, CDLL, POINTER, CFUNCTYPE +from ctypes import c_bool, c_double, c_float, c_int, c_size_t, c_uint, c_uint32 +from ctypes import c_char, c_char_p, c_void_p +from ctypes import byref + +# Load lilv library + +_lib = CDLL("liblilv-0.so") + +# Set namespaced aliases for all lilv functions + +class String(str): + # Wrapper for string parameters to pass as raw C UTF-8 strings + def from_param(cls, obj): + return obj.encode('utf-8') + + from_param = classmethod(from_param) + +def _as_uri(obj): + if type(obj) in [Plugin, PluginClass, UI]: + return obj.get_uri() + else: + return obj + +free = _lib.lilv_free +# uri_to_path = _lib.lilv_uri_to_path +file_uri_parse = _lib.lilv_file_uri_parse +new_uri = _lib.lilv_new_uri +new_file_uri = _lib.lilv_new_file_uri +new_string = _lib.lilv_new_string +new_int = _lib.lilv_new_int +new_float = _lib.lilv_new_float +new_bool = _lib.lilv_new_bool +node_free = _lib.lilv_node_free +node_duplicate = _lib.lilv_node_duplicate +node_equals = _lib.lilv_node_equals +node_get_turtle_token = _lib.lilv_node_get_turtle_token +node_is_uri = _lib.lilv_node_is_uri +node_as_uri = _lib.lilv_node_as_uri +node_is_blank = _lib.lilv_node_is_blank +node_as_blank = _lib.lilv_node_as_blank +node_is_literal = _lib.lilv_node_is_literal +node_is_string = _lib.lilv_node_is_string +node_as_string = _lib.lilv_node_as_string +node_get_path = _lib.lilv_node_get_path +node_is_float = _lib.lilv_node_is_float +node_as_float = _lib.lilv_node_as_float +node_is_int = _lib.lilv_node_is_int +node_as_int = _lib.lilv_node_as_int +node_is_bool = _lib.lilv_node_is_bool +node_as_bool = _lib.lilv_node_as_bool +plugin_classes_free = _lib.lilv_plugin_classes_free +plugin_classes_size = _lib.lilv_plugin_classes_size +plugin_classes_begin = _lib.lilv_plugin_classes_begin +plugin_classes_get = _lib.lilv_plugin_classes_get +plugin_classes_next = _lib.lilv_plugin_classes_next +plugin_classes_is_end = _lib.lilv_plugin_classes_is_end +plugin_classes_get_by_uri = _lib.lilv_plugin_classes_get_by_uri +scale_points_free = _lib.lilv_scale_points_free +scale_points_size = _lib.lilv_scale_points_size +scale_points_begin = _lib.lilv_scale_points_begin +scale_points_get = _lib.lilv_scale_points_get +scale_points_next = _lib.lilv_scale_points_next +scale_points_is_end = _lib.lilv_scale_points_is_end +uis_free = _lib.lilv_uis_free +uis_size = _lib.lilv_uis_size +uis_begin = _lib.lilv_uis_begin +uis_get = _lib.lilv_uis_get +uis_next = _lib.lilv_uis_next +uis_is_end = _lib.lilv_uis_is_end +uis_get_by_uri = _lib.lilv_uis_get_by_uri +nodes_free = _lib.lilv_nodes_free +nodes_size = _lib.lilv_nodes_size +nodes_begin = _lib.lilv_nodes_begin +nodes_get = _lib.lilv_nodes_get +nodes_next = _lib.lilv_nodes_next +nodes_is_end = _lib.lilv_nodes_is_end +nodes_get_first = _lib.lilv_nodes_get_first +nodes_contains = _lib.lilv_nodes_contains +nodes_merge = _lib.lilv_nodes_merge +plugins_size = _lib.lilv_plugins_size +plugins_begin = _lib.lilv_plugins_begin +plugins_get = _lib.lilv_plugins_get +plugins_next = _lib.lilv_plugins_next +plugins_is_end = _lib.lilv_plugins_is_end +plugins_get_by_uri = _lib.lilv_plugins_get_by_uri +world_new = _lib.lilv_world_new +world_set_option = _lib.lilv_world_set_option +world_free = _lib.lilv_world_free +world_load_all = _lib.lilv_world_load_all +world_load_bundle = _lib.lilv_world_load_bundle +world_load_specifications = _lib.lilv_world_load_specifications +world_load_plugin_classes = _lib.lilv_world_load_plugin_classes +world_unload_bundle = _lib.lilv_world_unload_bundle +world_load_resource = _lib.lilv_world_load_resource +world_unload_resource = _lib.lilv_world_unload_resource +world_get_plugin_class = _lib.lilv_world_get_plugin_class +world_get_plugin_classes = _lib.lilv_world_get_plugin_classes +world_get_all_plugins = _lib.lilv_world_get_all_plugins +world_find_nodes = _lib.lilv_world_find_nodes +world_get = _lib.lilv_world_get +world_ask = _lib.lilv_world_ask +plugin_verify = _lib.lilv_plugin_verify +plugin_get_uri = _lib.lilv_plugin_get_uri +plugin_get_bundle_uri = _lib.lilv_plugin_get_bundle_uri +plugin_get_data_uris = _lib.lilv_plugin_get_data_uris +plugin_get_library_uri = _lib.lilv_plugin_get_library_uri +plugin_get_name = _lib.lilv_plugin_get_name +plugin_get_class = _lib.lilv_plugin_get_class +plugin_get_value = _lib.lilv_plugin_get_value +plugin_has_feature = _lib.lilv_plugin_has_feature +plugin_get_supported_features = _lib.lilv_plugin_get_supported_features +plugin_get_required_features = _lib.lilv_plugin_get_required_features +plugin_get_optional_features = _lib.lilv_plugin_get_optional_features +plugin_has_extension_data = _lib.lilv_plugin_has_extension_data +plugin_get_extension_data = _lib.lilv_plugin_get_extension_data +plugin_get_num_ports = _lib.lilv_plugin_get_num_ports +plugin_get_port_ranges_float = _lib.lilv_plugin_get_port_ranges_float +plugin_has_latency = _lib.lilv_plugin_has_latency +plugin_get_latency_port_index = _lib.lilv_plugin_get_latency_port_index +plugin_get_port_by_index = _lib.lilv_plugin_get_port_by_index +plugin_get_port_by_symbol = _lib.lilv_plugin_get_port_by_symbol +plugin_get_port_by_designation = _lib.lilv_plugin_get_port_by_designation +plugin_get_project = _lib.lilv_plugin_get_project +plugin_get_author_name = _lib.lilv_plugin_get_author_name +plugin_get_author_email = _lib.lilv_plugin_get_author_email +plugin_get_author_homepage = _lib.lilv_plugin_get_author_homepage +plugin_is_replaced = _lib.lilv_plugin_is_replaced +plugin_get_related = _lib.lilv_plugin_get_related +port_get_node = _lib.lilv_port_get_node +port_get_value = _lib.lilv_port_get_value +port_get = _lib.lilv_port_get +port_get_properties = _lib.lilv_port_get_properties +port_has_property = _lib.lilv_port_has_property +port_supports_event = _lib.lilv_port_supports_event +port_get_index = _lib.lilv_port_get_index +port_get_symbol = _lib.lilv_port_get_symbol +port_get_name = _lib.lilv_port_get_name +port_get_classes = _lib.lilv_port_get_classes +port_is_a = _lib.lilv_port_is_a +port_get_range = _lib.lilv_port_get_range +port_get_scale_points = _lib.lilv_port_get_scale_points +state_new_from_world = _lib.lilv_state_new_from_world +state_new_from_file = _lib.lilv_state_new_from_file +state_new_from_string = _lib.lilv_state_new_from_string +state_new_from_instance = _lib.lilv_state_new_from_instance +state_free = _lib.lilv_state_free +state_equals = _lib.lilv_state_equals +state_get_num_properties = _lib.lilv_state_get_num_properties +state_get_plugin_uri = _lib.lilv_state_get_plugin_uri +state_get_uri = _lib.lilv_state_get_uri +state_get_label = _lib.lilv_state_get_label +state_set_label = _lib.lilv_state_set_label +state_set_metadata = _lib.lilv_state_set_metadata +state_emit_port_values = _lib.lilv_state_emit_port_values +state_restore = _lib.lilv_state_restore +state_save = _lib.lilv_state_save +state_to_string = _lib.lilv_state_to_string +state_delete = _lib.lilv_state_delete +scale_point_get_label = _lib.lilv_scale_point_get_label +scale_point_get_value = _lib.lilv_scale_point_get_value +plugin_class_get_parent_uri = _lib.lilv_plugin_class_get_parent_uri +plugin_class_get_uri = _lib.lilv_plugin_class_get_uri +plugin_class_get_label = _lib.lilv_plugin_class_get_label +plugin_class_get_children = _lib.lilv_plugin_class_get_children +plugin_instantiate = _lib.lilv_plugin_instantiate +instance_free = _lib.lilv_instance_free +plugin_get_uis = _lib.lilv_plugin_get_uis +ui_get_uri = _lib.lilv_ui_get_uri +ui_get_classes = _lib.lilv_ui_get_classes +ui_is_a = _lib.lilv_ui_is_a +ui_is_supported = _lib.lilv_ui_is_supported +ui_get_bundle_uri = _lib.lilv_ui_get_bundle_uri +ui_get_binary_uri = _lib.lilv_ui_get_binary_uri + +## LV2 types + +LV2_Handle = POINTER(None) +LV2_URID_Map_Handle = POINTER(None) +LV2_URID_Unmap_Handle = POINTER(None) +LV2_URID = c_uint32 + +class LV2_Feature(Structure): + __slots__ = [ 'URI', 'data' ] + _fields_ = [('URI', c_char_p), + ('data', POINTER(None))] + +class LV2_Descriptor(Structure): + __slots__ = [ 'URI', + 'instantiate', + 'connect_port', + 'activate', + 'run', + 'deactivate', + 'cleanup', + 'extension_data' ] + +LV2_Descriptor._fields_ = [ + ('URI', c_char_p), + ('instantiate', CFUNCTYPE(LV2_Handle, POINTER(LV2_Descriptor), + c_double, c_char_p, POINTER(POINTER(LV2_Feature)))), + ('connect_port', CFUNCTYPE(None, LV2_Handle, c_uint32, POINTER(None))), + ('activate', CFUNCTYPE(None, LV2_Handle)), + ('run', CFUNCTYPE(None, LV2_Handle, c_uint32)), + ('deactivate', CFUNCTYPE(None, LV2_Handle)), + ('cleanup', CFUNCTYPE(None, LV2_Handle)), + ('extension_data', CFUNCTYPE(c_void_p, c_char_p)), +] + +class LV2_URID_Map(Structure): + __slots__ = [ 'handle', 'map' ] + _fields_ = [ + ('handle', LV2_URID_Map_Handle), + ('map', CFUNCTYPE(LV2_URID, LV2_URID_Map_Handle, c_char_p)), + ] + +class LV2_URID_Unmap(Structure): + __slots__ = [ 'handle', 'unmap' ] + _fields_ = [ + ('handle', LV2_URID_Unmap_Handle), + ('unmap', CFUNCTYPE(c_char_p, LV2_URID_Unmap_Handle, LV2_URID)), + ] + +# Lilv types + +class Plugin(Structure): + """LV2 Plugin.""" + def __init__(self, world, plugin): + self.world = world + self.plugin = plugin + + def __eq__(self, other): + return self.get_uri() == other.get_uri() + + def verify(self): + """Check if `plugin` is valid. + + This is not a rigorous validator, but can be used to reject some malformed + plugins that could cause bugs (e.g. plugins with missing required fields). + + Note that normal hosts do NOT need to use this - lilv does not + load invalid plugins into plugin lists. This is included for plugin + testing utilities, etc. + """ + return plugin_verify(self.plugin) + + def get_uri(self): + """Get the URI of `plugin`. + + Any serialization that refers to plugins should refer to them by this. + Hosts SHOULD NOT save any filesystem paths, plugin indexes, etc. in saved + files pass save only the URI. + + The URI is a globally unique identifier for one specific plugin. Two + plugins with the same URI are compatible in port signature, and should + be guaranteed to work in a compatible and consistent way. If a plugin + is upgraded in an incompatible way (eg if it has different ports), it + MUST have a different URI than it's predecessor. + """ + return Node.wrap(node_duplicate(plugin_get_uri(self.plugin))) + + def get_bundle_uri(self): + """Get the (resolvable) URI of the plugin's "main" bundle. + + This returns the URI of the bundle where the plugin itself was found. Note + that the data for a plugin may be spread over many bundles, that is, + get_data_uris() may return URIs which are not within this bundle. + + Typical hosts should not need to use this function. + Note this always returns a fully qualified URI. If you want a local + filesystem path, use lilv.file_uri_parse(). + """ + return Node.wrap(node_duplicate(plugin_get_bundle_uri(self.plugin))) + + def get_data_uris(self): + """Get the (resolvable) URIs of the RDF data files that define a plugin. + + Typical hosts should not need to use this function. + Note this always returns fully qualified URIs. If you want local + filesystem paths, use lilv.file_uri_parse(). + """ + return Nodes(plugin_get_data_uris(self.plugin)) + + def get_library_uri(self): + """Get the (resolvable) URI of the shared library for `plugin`. + + Note this always returns a fully qualified URI. If you want a local + filesystem path, use lilv.file_uri_parse(). + """ + return Node.wrap(node_duplicate(plugin_get_library_uri(self.plugin))) + + def get_name(self): + """Get the name of `plugin`. + + This returns the name (doap:name) of the plugin. The name may be + translated according to the current locale, this value MUST NOT be used + as a plugin identifier (use the URI for that). + """ + return Node.wrap(plugin_get_name(self.plugin)) + + def get_class(self): + """Get the class this plugin belongs to (e.g. Filters).""" + return PluginClass(plugin_get_class(self.plugin)) + + def get_value(self, predicate): + """Get a value associated with the plugin in a plugin's data files. + + `predicate` must be either a URI or a QName. + + Returns the ?object of all triples found of the form: + + plugin-uri predicate ?object + + May return None if the property was not found, or if object(s) is not + sensibly represented as a LilvNodes (e.g. blank nodes). + """ + return Nodes(plugin_get_value(self.plugin, predicate.node)) + + def has_feature(self, feature_uri): + """Return whether a feature is supported by a plugin. + + This will return true if the feature is an optional or required feature + of the plugin. + """ + return plugin_has_feature(self.plugin, feature_uri.node) + + def get_supported_features(self): + """Get the LV2 Features supported (required or optionally) by a plugin. + + A feature is "supported" by a plugin if it is required OR optional. + + Since required features have special rules the host must obey, this function + probably shouldn't be used by normal hosts. Using get_optional_features() + and get_required_features() separately is best in most cases. + """ + return Nodes(plugin_get_supported_features(self.plugin)) + + def get_required_features(self): + """Get the LV2 Features required by a plugin. + + If a feature is required by a plugin, hosts MUST NOT use the plugin if they do not + understand (or are unable to support) that feature. + + All values returned here MUST be return plugin_(self.plugin)ed to the plugin's instantiate method + (along with data, if necessary, as defined by the feature specification) + or plugin instantiation will fail. + """ + return Nodes(plugin_get_required_features(self.plugin)) + + def get_optional_features(self): + """Get the LV2 Features optionally supported by a plugin. + + Hosts MAY ignore optional plugin features for whatever reasons. Plugins + MUST operate (at least somewhat) if they are instantiated without being + passed optional features. + """ + return Nodes(plugin_get_optional_features(self.plugin)) + + def has_extension_data(self, uri): + """Return whether or not a plugin provides a specific extension data.""" + return plugin_has_extension_data(self.plugin, uri.node) + + def get_extension_data(self): + """Get a sequence of all extension data provided by a plugin. + + This can be used to find which URIs get_extension_data() + will return a value for without instantiating the plugin. + """ + return Nodes(plugin_get_extension_data(self.plugin)) + + def get_num_ports(self): + """Get the number of ports on this plugin.""" + return plugin_get_num_ports(self.plugin) + + # def get_port_ranges_float(self, min_values, max_values, def_values): + # """Get the port ranges (minimum, maximum and default values) for all ports. + + # `min_values`, `max_values` and `def_values` must either point to an array + # of N floats, where N is the value returned by get_num_ports() + # for this plugin, or None. The elements of the array will be set to the + # the minimum, maximum and default values of the ports on this plugin, + # with array index corresponding to port index. If a port doesn't have a + # minimum, maximum or default value, or the port's type is not float, the + # corresponding array element will be set to NAN. + + # This is a convenience method for the common case of getting the range of + # all float ports on a plugin, and may be significantly faster than + # repeated calls to Port.get_range(). + # """ + # plugin_get_port_ranges_float(self.plugin, min_values, max_values, def_values) + + def get_num_ports_of_class(self, *args): + """Get the number of ports on this plugin that are members of some class(es).""" + args = list(map(lambda x: x.node, args)) + args += (None,) + return plugin_get_num_ports_of_class(self.plugin, *args) + + def has_latency(self): + """Return whether or not the plugin introduces (and reports) latency. + + The index of the latency port can be found with + get_latency_port() ONLY if this function returns true. + """ + return plugin_has_latency(self.plugin) + + def get_latency_port_index(self): + """Return the index of the plugin's latency port. + + Returns None if the plugin has no latency port. + + Any plugin that introduces unwanted latency that should be compensated for + (by hosts with the ability/need) MUST provide this port, which is a control + rate output port that reports the latency for each cycle in frames. + """ + return plugin_get_latency_port_index(self.plugin) if self.has_latency() else None + + def get_port(self, key): + """Get a port on `plugin` by index or symbol.""" + if type(key) == int: + return self.get_port_by_index(key) + else: + return self.get_port_by_symbol(key) + + def get_port_by_index(self, index): + """Get a port on `plugin` by `index`.""" + return Port.wrap(self, plugin_get_port_by_index(self.plugin, index)) + + def get_port_by_symbol(self, symbol): + """Get a port on `plugin` by `symbol`. + + Note this function is slower than get_port_by_index(), + especially on plugins with a very large number of ports. + """ + if type(symbol) == str: + symbol = self.world.new_string(symbol) + return Port.wrap(self, plugin_get_port_by_symbol(self.plugin, symbol.node)) + + def get_port_by_designation(self, port_class, designation): + """Get a port on `plugin` by its lv2:designation. + + The designation of a port describes the meaning, assignment, allocation or + role of the port, e.g. "left channel" or "gain". If found, the port with + matching `port_class` and `designation` is be returned, otherwise None is + returned. The `port_class` can be used to distinguish the input and output + ports for a particular designation. If `port_class` is None, any port with + the given designation will be returned. + """ + return Port.wrap(self, + plugin_get_port_by_designation(self.plugin, + port_class.node, + designation.node)) + + def get_project(self): + """Get the project the plugin is a part of. + + More information about the project can be read via find_nodes(), + typically using properties from DOAP (e.g. doap:name). + """ + return Node.wrap(plugin_get_project(self.plugin)) + + def get_author_name(self): + """Get the full name of the plugin's author. + + Returns None if author name is not present. + """ + return Node.wrap(plugin_get_author_name(self.plugin)) + + def get_author_email(self): + """Get the email address of the plugin's author. + + Returns None if author email address is not present. + """ + return Node.wrap(plugin_get_author_email(self.plugin)) + + def get_author_homepage(self): + """Get the address of the plugin author's home page. + + Returns None if author homepage is not present. + """ + return Node.wrap(plugin_get_author_homepage(self.plugin)) + + def is_replaced(self): + """Return true iff `plugin` has been replaced by another plugin. + + The plugin will still be usable, but hosts should hide them from their + user interfaces to prevent users from using deprecated plugins. + """ + return plugin_is_replaced(self.plugin) + + def get_related(self, resource_type): + """Get the resources related to `plugin` with lv2:appliesTo. + + Some plugin-related resources are not linked directly to the plugin with + rdfs:seeAlso and thus will not be automatically loaded along with the plugin + data (usually for performance reasons). All such resources of the given @c + type related to `plugin` can be accessed with this function. + + If `resource_type` is None, all such resources will be returned, regardless of type. + + To actually load the data for each returned resource, use world.load_resource(). + """ + return Nodes(plugin_get_related(self.plugin, resource_type)) + + def get_uis(self): + """Get all UIs for `plugin`.""" + return UIs(plugin_get_uis(self.plugin)) + +class PluginClass(Structure): + """Plugin Class (type/category).""" + def __init__(self, plugin_class): + self.plugin_class = plugin_class + + def __str__(self): + return self.get_uri().__str__() + + def get_parent_uri(self): + """Get the URI of this class' superclass. + + May return None if class has no parent. + """ + return Node.wrap(node_duplicate(plugin_class_get_parent_uri(self.plugin_class))) + + def get_uri(self): + """Get the URI of this plugin class.""" + return Node.wrap(node_duplicate(plugin_class_get_uri(self.plugin_class))) + + def get_label(self): + """Get the label of this plugin class, ie "Oscillators".""" + return Node.wrap(node_duplicate(plugin_class_get_label(self.plugin_class))) + + def get_children(self): + """Get the subclasses of this plugin class.""" + return PluginClasses(plugin_class_get_children(self.plugin_class)) + +class Port(Structure): + """Port on a Plugin.""" + @classmethod + def wrap(cls, plugin, port): + return Port(plugin, port) if plugin and port else None + + def __init__(self, plugin, port): + self.plugin = plugin + self.port = port + + def get_node(self): + """Get the RDF node of `port`. + + Ports nodes may be may be URIs or blank nodes. + """ + return Node.wrap(node_duplicate(port_get_node(self.plugin, self.port))) + + def get_value(self, predicate): + """Port analog of Plugin.get_value().""" + return Nodes(port_get_value(self.plugin.plugin, self.port, predicate.node)) + + def get(self, predicate): + """Get a single property value of a port. + + This is equivalent to lilv_nodes_get_first(lilv_port_get_value(...)) but is + simpler to use in the common case of only caring about one value. The + caller is responsible for freeing the returned node. + """ + return Node.wrap(port_get(self.plugin.plugin, self.port, predicate.node)) + + def get_properties(self): + """Return the LV2 port properties of a port.""" + return Nodes(port_get_properties(self.plugin.plugin, self.port)) + + def has_property(self, property_uri): + """Return whether a port has a certain property.""" + return port_has_property(self.plugin.plugin, self.port, property_uri.node) + + def supports_event(self, event_type): + """Return whether a port supports a certain event type. + + More precisely, this returns true iff the port has an atom:supports or an + ev:supportsEvent property with `event_type` as the value. + """ + return port_supports_event(self.plugin.plugin, self.port, event_type.node) + + def get_index(self): + """Get the index of a port. + + The index is only valid for the life of the plugin and may change between + versions. For a stable identifier, use the symbol. + """ + return port_get_index(self.plugin.plugin, self.port) + + def get_symbol(self): + """Get the symbol of a port. + + The 'symbol' is a short string, a valid C identifier. + """ + return Node.wrap(node_duplicate(port_get_symbol(self.plugin.plugin, self.port))) + + def get_name(self): + """Get the name of a port. + + This is guaranteed to return the untranslated name (the doap:name in the + data file without a language tag). + """ + return Node.wrap(port_get_name(self.plugin.plugin, self.port)) + + def get_classes(self): + """Get all the classes of a port. + + This can be used to determine if a port is an input, output, audio, + control, midi, etc, etc, though it's simpler to use is_a(). + The returned list does not include lv2:Port, which is implied. + Returned value is shared and must not be destroyed by caller. + """ + return Nodes(port_get_classes(self.plugin.plugin, self.port)) + + def is_a(self, port_class): + """Determine if a port is of a given class (input, output, audio, etc). + + For convenience/performance/extensibility reasons, hosts are expected to + create a LilvNode for each port class they "care about". Well-known type + URI strings are defined (e.g. LILV_URI_INPUT_PORT) for convenience, but + this function is designed so that Lilv is usable with any port types + without requiring explicit support in Lilv. + """ + return port_is_a(self.plugin.plugin, self.port, port_class.node) + + def get_range(self): + """Return the default, minimum, and maximum values of a port as a tuple.""" + pdef = POINTER(Node)() + pmin = POINTER(Node)() + pmax = POINTER(Node)() + port_get_range(self.plugin.plugin, self.port, byref(pdef), byref(pmin), byref(pmax)) + return (Node(pdef.contents) if pdef else None, + Node(pmin.contents) if pmin else None, + Node(pmax.contents) if pmax else None) + + def get_scale_points(self): + """Get the scale points (enumeration values) of a port. + + This returns a collection of 'interesting' named values of a port + (e.g. appropriate entries for a UI selector associated with this port). + Returned value may be None if `port` has no scale points. + """ + return ScalePoints(port_get_scale_points(self.plugin.plugin, self.port)) + +class ScalePoint(Structure): + """Scale point (detent).""" + def __init__(self, point): + self.point = point + + def get_label(self): + """Get the label of this scale point (enumeration value).""" + return Node.wrap(scale_point_get_label(self.point)) + + def get_value(self): + """Get the value of this scale point (enumeration value).""" + return Node.wrap(scale_point_get_value(self.point)) + +class UI(Structure): + """Plugin UI.""" + def __init__(self, ui): + self.ui = ui + + def __str__(self): + return str(self.get_uri()) + + def __eq__(self, other): + return self.get_uri() == _as_uri(other) + + def get_uri(self): + """Get the URI of a Plugin UI.""" + return Node.wrap(node_duplicate(ui_get_uri(self.ui))) + + def get_classes(self): + """Get the types (URIs of RDF classes) of a Plugin UI. + + Note that in most cases is_supported() should be used, which avoids + the need to use this function (and type specific logic). + """ + return Nodes(ui_get_classes(self.ui)) + + def is_a(self, class_uri): + """Check whether a plugin UI has a given type.""" + return ui_is_a(self.ui, class_uri.node) + + def get_bundle_uri(self): + """Get the URI of the UI's bundle.""" + return Node.wrap(node_duplicate(ui_get_bundle_uri(self.ui))) + + def get_binary_uri(self): + """Get the URI for the UI's shared library.""" + return Node.wrap(node_duplicate(ui_get_binary_uri(self.ui))) + +class Node(Structure): + """Data node (URI, string, integer, etc.). + + A Node can be converted to the corresponding Python datatype, and all nodes + can be converted to strings, for example:: + + >>> world = lilv.World() + >>> i = world.new_int(42) + >>> print(i) + 42 + >>> int(i) * 2 + 84 + """ + @classmethod + def wrap(cls, node): + return Node(node) if node else None + + def __init__(self, node): + self.node = node + + def __del__(self): + if hasattr(self, 'node'): + node_free(self.node) + + def __eq__(self, other): + otype = type(other) + if otype in [str, int, float]: + return otype(self) == other + return node_equals(self.node, other.node) + + def __ne__(self, other): + return not node_equals(self.node, other.node) + + def __str__(self): + return node_as_string(self.node).decode('utf-8') + + def __int__(self): + if not self.is_int(): + raise ValueError('node %s is not an integer' % str(self)) + return node_as_int(self.node) + + def __float__(self): + if not self.is_float(): + raise ValueError('node %s is not a float' % str(self)) + return node_as_float(self.node) + + def __bool__(self): + if not self.is_bool(): + raise ValueError('node %s is not a bool' % str(self)) + return node_as_bool(self.node) + __nonzero__ = __bool__ + + def get_turtle_token(self): + """Return this value as a Turtle/SPARQL token.""" + return node_get_turtle_token(self.node).decode('utf-8') + + def is_uri(self): + """Return whether the value is a URI (resource).""" + return node_is_uri(self.node) + + def is_blank(self): + """Return whether the value is a blank node (resource with no URI).""" + return node_is_blank(self.node) + + def is_literal(self): + """Return whether this value is a literal (i.e. not a URI).""" + return node_is_literal(self.node) + + def is_string(self): + """Return whether this value is a string literal. + + Returns true if value is a string value (and not numeric). + """ + return node_is_string(self.node) + + def get_path(self, hostname=None): + """Return the path of a file URI node. + + Returns None if value is not a file URI.""" + return node_get_path(self.node, hostname).decode('utf-8') + + def is_float(self): + """Return whether this value is a decimal literal.""" + return node_is_float(self.node) + + def is_int(self): + """Return whether this value is an integer literal.""" + return node_is_int(self.node) + + def is_bool(self): + """Return whether this value is a boolean.""" + return node_is_bool(self.node) + +class Iter(Structure): + """Collection iterator.""" + def __init__(self, collection, iterator, constructor, iter_get, iter_next, iter_is_end): + self.collection = collection + self.iterator = iterator + self.constructor = constructor + self.iter_get = iter_get + self.iter_next = iter_next + self.iter_is_end = iter_is_end + + def get(self): + """Get the current item.""" + return self.constructor(self.iter_get(self.collection, self.iterator)) + + def next(self): + """Move to and return the next item.""" + if self.is_end(): + raise StopIteration + elem = self.get() + self.iterator = self.iter_next(self.collection, self.iterator) + return elem + + def is_end(self): + """Return true if the end of the collection has been reached.""" + return self.iter_is_end(self.collection, self.iterator) + + __next__ = next + +class Collection(Structure): + # Base class for all lilv collection wrappers. + def __init__(self, collection, iter_begin, constructor, iter_get, iter_next, is_end): + self.collection = collection + self.constructor = constructor + self.iter_begin = iter_begin + self.iter_get = iter_get + self.iter_next = iter_next + self.is_end = is_end + + def __iter__(self): + return Iter(self.collection, self.iter_begin(self.collection), self.constructor, + self.iter_get, self.iter_next, self.is_end) + + def __getitem__(self, index): + if index >= len(self): + raise IndexError + pos = 0 + for i in self: + if pos == index: + return i + pos += 1 + + def begin(self): + return self.__iter__() + + def get(self, iterator): + return iterator.get() + +class Plugins(Collection): + """Collection of plugins.""" + def __init__(self, world, collection): + def constructor(plugin): + return Plugin(world, plugin) + + super(Plugins, self).__init__(collection, plugins_begin, constructor, plugins_get, plugins_next, plugins_is_end) + self.world = world + + def __contains__(self, key): + return bool(self.get_by_uri(_as_uri(key))) + + def __len__(self): + return plugins_size(self.collection) + + def __getitem__(self, key): + if type(key) == int: + return super(Plugins, self).__getitem__(key) + return self.get_by_uri(key) + + def get_by_uri(self, uri): + plugin = plugins_get_by_uri(self.collection, uri.node) + return Plugin(self.world, plugin) if plugin else None + +class PluginClasses(Collection): + """Collection of plugin classes.""" + def __init__(self, collection): + super(PluginClasses, self).__init__( + collection, plugin_classes_begin, PluginClass, + plugin_classes_get, plugin_classes_next, plugin_classes_is_end) + + def __contains__(self, key): + return bool(self.get_by_uri(_as_uri(key))) + + def __len__(self): + return plugin_classes_size(self.collection) + + def __getitem__(self, key): + if type(key) == int: + return super(PluginClasses, self).__getitem__(key) + return self.get_by_uri(key) + + def get_by_uri(self, uri): + plugin_class = plugin_classes_get_by_uri(self.collection, uri.node) + return PluginClass(plugin_class) if plugin_class else None + +class ScalePoints(Collection): + """Collection of scale points.""" + def __init__(self, collection): + super(ScalePoints, self).__init__( + collection, scale_points_begin, ScalePoint, + scale_points_get, scale_points_next, scale_points_is_end) + + def __len__(self): + return scale_points_size(self.collection) + +class UIs(Collection): + """Collection of plugin UIs.""" + def __init__(self, collection): + super(UIs, self).__init__(collection, uis_begin, UI, + uis_get, uis_next, uis_is_end) + + def __contains__(self, uri): + return bool(self.get_by_uri(_as_uri(uri))) + + def __len__(self): + return uis_size(self.collection) + + def __getitem__(self, key): + if type(key) == int: + return super(UIs, self).__getitem__(key) + return self.get_by_uri(key) + + def get_by_uri(self, uri): + ui = uis_get_by_uri(self.collection, uri.node) + return UI(ui) if ui else None + +class Nodes(Collection): + """Collection of data nodes.""" + @classmethod + def constructor(ignore, node): + return Node(node_duplicate(node)) + + def __init__(self, collection): + super(Nodes, self).__init__(collection, nodes_begin, Nodes.constructor, + nodes_get, nodes_next, nodes_is_end) + + def __contains__(self, value): + return nodes_contains(self.collection, value.node) + + def __len__(self): + return nodes_size(self.collection) + + def merge(self, b): + return Nodes(nodes_merge(self.collection, b.collection)) + +class Namespace(): + """Namespace prefix. + + Use attribute syntax to easily create URIs within this namespace, for + example:: + + >>> world = lilv.World() + >>> ns = Namespace(world, "http://example.org/") + >>> print(ns.foo) + http://example.org/foo + """ + def __init__(self, world, prefix): + self.world = world + self.prefix = prefix + + def __eq__(self, other): + return str(self) == str(other) + + def __str__(self): + return self.prefix + + def __getattr__(self, suffix): + return self.world.new_uri(self.prefix + suffix) + +class World(Structure): + """Library context. + + Includes a set of namespaces as the instance variable `ns`, so URIs can be constructed like:: + + uri = world.ns.lv2.Plugin + + :ivar ns: Common LV2 namespace prefixes: atom, doap, foaf, lilv, lv2, midi, owl, rdf, rdfs, ui, xsd. + """ + def __init__(self): + world = self + + # Define Namespaces class locally so available prefixes are documented + class Namespaces(): + """Set of namespaces. + + Use to easily construct uris, like: ns.lv2.InputPort""" + + atom = Namespace(world, 'http://lv2plug.in/ns/ext/atom#') + doap = Namespace(world, 'http://usefulinc.com/ns/doap#') + foaf = Namespace(world, 'http://xmlns.com/foaf/0.1/') + lilv = Namespace(world, 'http://drobilla.net/ns/lilv#') + lv2 = Namespace(world, 'http://lv2plug.in/ns/lv2core#') + midi = Namespace(world, 'http://lv2plug.in/ns/ext/midi#') + owl = Namespace(world, 'http://www.w3.org/2002/07/owl#') + rdf = Namespace(world, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#') + rdfs = Namespace(world, 'http://www.w3.org/2000/01/rdf-schema#') + ui = Namespace(world, 'http://lv2plug.in/ns/extensions/ui#') + xsd = Namespace(world, 'http://www.w3.org/2001/XMLSchema#') + + self.world = _lib.lilv_world_new() + self.ns = Namespaces() + + def __del__(self): + world_free(self.world) + + def set_option(self, uri, value): + """Set a world option. + + Currently recognized options: + lilv.OPTION_FILTER_LANG + lilv.OPTION_DYN_MANIFEST + """ + return world_set_option(self, uri, value.node) + + def load_all(self): + """Load all installed LV2 bundles on the system. + + This is the recommended way for hosts to load LV2 data. It implements the + established/standard best practice for discovering all LV2 data on the + system. The environment variable LV2_PATH may be used to control where + this function will look for bundles. + + Hosts should use this function rather than explicitly load bundles, except + in special circumstances (e.g. development utilities, or hosts that ship + with special plugin bundles which are installed to a known location). + """ + world_load_all(self.world) + + def load_bundle(self, bundle_uri): + """Load a specific bundle. + + `bundle_uri` must be a fully qualified URI to the bundle directory, + with the trailing slash, eg. file:///usr/lib/lv2/foo.lv2/ + + Normal hosts should not need this function (use load_all()). + + Hosts MUST NOT attach any long-term significance to bundle paths + (e.g. in save files), since there are no guarantees they will remain + unchanged between (or even during) program invocations. Plugins (among + other things) MUST be identified by URIs (not paths) in save files. + """ + world_load_bundle(self.world, bundle_uri.node) + + def load_specifications(self): + """Load all specifications from currently loaded bundles. + + This is for hosts that explicitly load specific bundles, its use is not + necessary when using load_all(). This function parses the + specifications and adds them to the model. + """ + world_load_specifications(self.world) + + def load_plugin_classes(self): + """Load all plugin classes from currently loaded specifications. + + Must be called after load_specifications(). This is for hosts + that explicitly load specific bundles, its use is not necessary when using + load_all(). + """ + world_load_plugin_classes(self.world) + + def unload_bundle(self, bundle_uri): + """Unload a specific bundle. + + This unloads statements loaded by load_bundle(). Note that this + is not necessarily all information loaded from the bundle. If any resources + have been separately loaded with load_resource(), they must be + separately unloaded with unload_resource(). + """ + return world_unload_bundle(self.world, bundle_uri.node) + + def load_resource(self, resource): + """Load all the data associated with the given `resource`. + + The resource must be a subject (i.e. a URI or a blank node). + Returns the number of files parsed, or -1 on error. + + All accessible data files linked to `resource` with rdfs:seeAlso will be + loaded into the world model. + """ + return world_load_resource(self.world, _as_uri(resource).node) + + def unload_resource(self, resource): + """Unload all the data associated with the given `resource`. + + The resource must be a subject (i.e. a URI or a blank node). + + This unloads all data loaded by a previous call to + load_resource() with the given `resource`. + """ + return world_unload_resource(self.world, _as_uri(resource).node) + + def get_plugin_class(self): + """Get the parent of all other plugin classes, lv2:Plugin.""" + return PluginClass(world_get_plugin_class(self.world)) + + def get_plugin_classes(self): + """Return a list of all found plugin classes.""" + return PluginClasses(world_get_plugin_classes(self.world)) + + def get_all_plugins(self): + """Return a list of all found plugins. + + The returned list contains just enough references to query + or instantiate plugins. The data for a particular plugin will not be + loaded into memory until a call to an lilv_plugin_* function results in + a query (at which time the data is cached with the LilvPlugin so future + queries are very fast). + + The returned list and the plugins it contains are owned by `world` + and must not be freed by caller. + """ + return Plugins(self, _lib.lilv_world_get_all_plugins(self.world)) + + def find_nodes(self, subject, predicate, obj): + """Find nodes matching a triple pattern. + + Either `subject` or `object` may be None (i.e. a wildcard), but not both. + Returns all matches for the wildcard field, or None. + """ + return Nodes(world_find_nodes(self.world, + subject.node if subject is not None else None, + predicate.node if predicate is not None else None, + obj.node if obj is not None else None)) + + def get(self, subject, predicate, obj): + """Find a single node that matches a pattern. + + Exactly one of `subject`, `predicate`, `object` must be None. + + Returns the first matching node, or None if no matches are found. + """ + return Node.wrap(world_get(self.world, + subject.node if subject is not None else None, + predicate.node if predicate is not None else None, + obj.node if obj is not None else None)) + + def ask(self, subject, predicate, obj): + """Return true iff a statement matching a certain pattern exists. + + This is useful for checking if particular statement exists without having to + bother with collections and memory management. + """ + return world_ask(self.world, + subject.node if subject is not None else None, + predicate.node if predicate is not None else None, + obj.node if obj is not None else None) + + def new_uri(self, uri): + """Create a new URI node.""" + return Node.wrap(_lib.lilv_new_uri(self.world, uri)) + + def new_file_uri(self, host, path): + """Create a new file URI node. The host may be None.""" + return Node.wrap(_lib.lilv_new_file_uri(self.world, host, path)) + + def new_string(self, string): + """Create a new string node.""" + return Node.wrap(_lib.lilv_new_string(self.world, string)) + + def new_int(self, val): + """Create a new int node.""" + return Node.wrap(_lib.lilv_new_int(self.world, val)) + + def new_float(self, val): + """Create a new float node.""" + return Node.wrap(_lib.lilv_new_float(self.world, val)) + + def new_bool(self, val): + """Create a new bool node.""" + return Node.wrap(_lib.lilv_new_bool(self.world, val)) + +class Instance(Structure): + """Plugin instance.""" + __slots__ = [ 'lv2_descriptor', 'lv2_handle', 'pimpl', 'plugin', 'rate', 'instance' ] + _fields_ = [ + ('lv2_descriptor', POINTER(LV2_Descriptor)), + ('lv2_handle', LV2_Handle), + ('pimpl', POINTER(None)), + ] + + def __init__(self, plugin, rate, features=None): + self.plugin = plugin + self.rate = rate + self.instance = plugin_instantiate(plugin.plugin, rate, features) + + def get_uri(self): + """Get the URI of the plugin which `instance` is an instance of. + + Returned string is shared and must not be modified or deleted. + """ + return self.get_descriptor().URI + + def connect_port(self, port_index, data): + """Connect a port to a data location. + + This may be called regardless of whether the plugin is activated, + activation and deactivation does not destroy port connections. + """ + import numpy + if data is None: + self.get_descriptor().connect_port( + self.get_handle(), + port_index, + data) + elif type(data) == numpy.ndarray: + self.get_descriptor().connect_port( + self.get_handle(), + port_index, + data.ctypes.data_as(POINTER(c_float))) + else: + raise Exception("Unsupported data type") + + def activate(self): + """Activate a plugin instance. + + This resets all state information in the plugin, except for port data + locations (as set by connect_port()). This MUST be called + before calling run(). + """ + if self.get_descriptor().activate: + self.get_descriptor().activate(self.get_handle()) + + def run(self, sample_count): + """Run `instance` for `sample_count` frames. + + If the hint lv2:hardRTCapable is set for this plugin, this function is + guaranteed not to block. + """ + self.get_descriptor().run(self.get_handle(), sample_count) + + def deactivate(self): + """Deactivate a plugin instance. + + Note that to run the plugin after this you must activate it, which will + reset all state information (except port connections). + """ + if self.get_descriptor().deactivate: + self.get_descriptor().deactivate(self.get_handle()) + + def get_extension_data(self, uri): + """Get extension data from the plugin instance. + + The type and semantics of the data returned is specific to the particular + extension, though in all cases it is shared and must not be deleted. + """ + if self.get_descriptor().extension_data: + return self.get_descriptor().extension_data(str(uri)) + + def get_descriptor(self): + """Get the LV2_Descriptor of the plugin instance. + + Normally hosts should not need to access the LV2_Descriptor directly, + use the lilv_instance_* functions. + """ + return self.instance[0].lv2_descriptor[0] + + def get_handle(self): + """Get the LV2_Handle of the plugin instance. + + Normally hosts should not need to access the LV2_Handle directly, + use the lilv_instance_* functions. + """ + return self.instance[0].lv2_handle + +class State(Structure): + """Plugin state (TODO).""" + pass + +class VariadicFunction(object): + # Wrapper for calling C variadic functions + def __init__(self, function, restype, argtypes): + self.function = function + self.function.restype = restype + self.argtypes = argtypes + + def __call__(self, *args): + fixed_args = [] + i = 0 + for argtype in self.argtypes: + fixed_args.append(argtype.from_param(args[i])) + i += 1 + return self.function(*fixed_args + list(args[i:])) + +# Set return and argument types for lilv C functions + +free.argtypes = [POINTER(None)] +free.restype = None + +# uri_to_path.argtypes = [String] +# uri_to_path.restype = c_char_p + +file_uri_parse.argtypes = [String, POINTER(POINTER(c_char))] +file_uri_parse.restype = c_char_p + +new_uri.argtypes = [POINTER(World), String] +new_uri.restype = POINTER(Node) + +new_file_uri.argtypes = [POINTER(World), c_char_p, String] +new_file_uri.restype = POINTER(Node) + +new_string.argtypes = [POINTER(World), String] +new_string.restype = POINTER(Node) + +new_int.argtypes = [POINTER(World), c_int] +new_int.restype = POINTER(Node) + +new_float.argtypes = [POINTER(World), c_float] +new_float.restype = POINTER(Node) + +new_bool.argtypes = [POINTER(World), c_bool] +new_bool.restype = POINTER(Node) + +node_free.argtypes = [POINTER(Node)] +node_free.restype = None + +node_duplicate.argtypes = [POINTER(Node)] +node_duplicate.restype = POINTER(Node) + +node_equals.argtypes = [POINTER(Node), POINTER(Node)] +node_equals.restype = c_bool + +node_get_turtle_token.argtypes = [POINTER(Node)] +node_get_turtle_token.restype = c_char_p + +node_is_uri.argtypes = [POINTER(Node)] +node_is_uri.restype = c_bool + +node_as_uri.argtypes = [POINTER(Node)] +node_as_uri.restype = c_char_p + +node_is_blank.argtypes = [POINTER(Node)] +node_is_blank.restype = c_bool + +node_as_blank.argtypes = [POINTER(Node)] +node_as_blank.restype = c_char_p + +node_is_literal.argtypes = [POINTER(Node)] +node_is_literal.restype = c_bool + +node_is_string.argtypes = [POINTER(Node)] +node_is_string.restype = c_bool + +node_as_string.argtypes = [POINTER(Node)] +node_as_string.restype = c_char_p + +node_get_path.argtypes = [POINTER(Node), POINTER(POINTER(c_char))] +node_get_path.restype = c_char_p + +node_is_float.argtypes = [POINTER(Node)] +node_is_float.restype = c_bool + +node_as_float.argtypes = [POINTER(Node)] +node_as_float.restype = c_float + +node_is_int.argtypes = [POINTER(Node)] +node_is_int.restype = c_bool + +node_as_int.argtypes = [POINTER(Node)] +node_as_int.restype = c_int + +node_is_bool.argtypes = [POINTER(Node)] +node_is_bool.restype = c_bool + +node_as_bool.argtypes = [POINTER(Node)] +node_as_bool.restype = c_bool + +plugin_classes_free.argtypes = [POINTER(PluginClasses)] +plugin_classes_free.restype = None + +plugin_classes_size.argtypes = [POINTER(PluginClasses)] +plugin_classes_size.restype = c_uint + +plugin_classes_begin.argtypes = [POINTER(PluginClasses)] +plugin_classes_begin.restype = POINTER(Iter) + +plugin_classes_get.argtypes = [POINTER(PluginClasses), POINTER(Iter)] +plugin_classes_get.restype = POINTER(PluginClass) + +plugin_classes_next.argtypes = [POINTER(PluginClasses), POINTER(Iter)] +plugin_classes_next.restype = POINTER(Iter) + +plugin_classes_is_end.argtypes = [POINTER(PluginClasses), POINTER(Iter)] +plugin_classes_is_end.restype = c_bool + +plugin_classes_get_by_uri.argtypes = [POINTER(PluginClasses), POINTER(Node)] +plugin_classes_get_by_uri.restype = POINTER(PluginClass) + +scale_points_free.argtypes = [POINTER(ScalePoints)] +scale_points_free.restype = None + +scale_points_size.argtypes = [POINTER(ScalePoints)] +scale_points_size.restype = c_uint + +scale_points_begin.argtypes = [POINTER(ScalePoints)] +scale_points_begin.restype = POINTER(Iter) + +scale_points_get.argtypes = [POINTER(ScalePoints), POINTER(Iter)] +scale_points_get.restype = POINTER(ScalePoint) + +scale_points_next.argtypes = [POINTER(ScalePoints), POINTER(Iter)] +scale_points_next.restype = POINTER(Iter) + +scale_points_is_end.argtypes = [POINTER(ScalePoints), POINTER(Iter)] +scale_points_is_end.restype = c_bool + +uis_free.argtypes = [POINTER(UIs)] +uis_free.restype = None + +uis_size.argtypes = [POINTER(UIs)] +uis_size.restype = c_uint + +uis_begin.argtypes = [POINTER(UIs)] +uis_begin.restype = POINTER(Iter) + +uis_get.argtypes = [POINTER(UIs), POINTER(Iter)] +uis_get.restype = POINTER(UI) + +uis_next.argtypes = [POINTER(UIs), POINTER(Iter)] +uis_next.restype = POINTER(Iter) + +uis_is_end.argtypes = [POINTER(UIs), POINTER(Iter)] +uis_is_end.restype = c_bool + +uis_get_by_uri.argtypes = [POINTER(UIs), POINTER(Node)] +uis_get_by_uri.restype = POINTER(UI) + +nodes_free.argtypes = [POINTER(Nodes)] +nodes_free.restype = None + +nodes_size.argtypes = [POINTER(Nodes)] +nodes_size.restype = c_uint + +nodes_begin.argtypes = [POINTER(Nodes)] +nodes_begin.restype = POINTER(Iter) + +nodes_get.argtypes = [POINTER(Nodes), POINTER(Iter)] +nodes_get.restype = POINTER(Node) + +nodes_next.argtypes = [POINTER(Nodes), POINTER(Iter)] +nodes_next.restype = POINTER(Iter) + +nodes_is_end.argtypes = [POINTER(Nodes), POINTER(Iter)] +nodes_is_end.restype = c_bool + +nodes_get_first.argtypes = [POINTER(Nodes)] +nodes_get_first.restype = POINTER(Node) + +nodes_contains.argtypes = [POINTER(Nodes), POINTER(Node)] +nodes_contains.restype = c_bool + +nodes_merge.argtypes = [POINTER(Nodes), POINTER(Nodes)] +nodes_merge.restype = POINTER(Nodes) + +plugins_size.argtypes = [POINTER(Plugins)] +plugins_size.restype = c_uint + +plugins_begin.argtypes = [POINTER(Plugins)] +plugins_begin.restype = POINTER(Iter) + +plugins_get.argtypes = [POINTER(Plugins), POINTER(Iter)] +plugins_get.restype = POINTER(Plugin) + +plugins_next.argtypes = [POINTER(Plugins), POINTER(Iter)] +plugins_next.restype = POINTER(Iter) + +plugins_is_end.argtypes = [POINTER(Plugins), POINTER(Iter)] +plugins_is_end.restype = c_bool + +plugins_get_by_uri.argtypes = [POINTER(Plugins), POINTER(Node)] +plugins_get_by_uri.restype = POINTER(Plugin) + +world_new.argtypes = [] +world_new.restype = POINTER(World) + +world_set_option.argtypes = [POINTER(World), String, POINTER(Node)] +world_set_option.restype = None + +world_free.argtypes = [POINTER(World)] +world_free.restype = None + +world_load_all.argtypes = [POINTER(World)] +world_load_all.restype = None + +world_load_bundle.argtypes = [POINTER(World), POINTER(Node)] +world_load_bundle.restype = None + +world_load_specifications.argtypes = [POINTER(World)] +world_load_specifications.restype = None + +world_load_plugin_classes.argtypes = [POINTER(World)] +world_load_plugin_classes.restype = None + +world_unload_bundle.argtypes = [POINTER(World), POINTER(Node)] +world_unload_bundle.restype = c_int + +world_load_resource.argtypes = [POINTER(World), POINTER(Node)] +world_load_resource.restype = c_int + +world_unload_resource.argtypes = [POINTER(World), POINTER(Node)] +world_unload_resource.restype = c_int + +world_get_plugin_class.argtypes = [POINTER(World)] +world_get_plugin_class.restype = POINTER(PluginClass) + +world_get_plugin_classes.argtypes = [POINTER(World)] +world_get_plugin_classes.restype = POINTER(PluginClasses) + +world_get_all_plugins.argtypes = [POINTER(World)] +world_get_all_plugins.restype = POINTER(Plugins) + +world_find_nodes.argtypes = [POINTER(World), POINTER(Node), POINTER(Node), POINTER(Node)] +world_find_nodes.restype = POINTER(Nodes) + +world_get.argtypes = [POINTER(World), POINTER(Node), POINTER(Node), POINTER(Node)] +world_get.restype = POINTER(Node) + +world_ask.argtypes = [POINTER(World), POINTER(Node), POINTER(Node), POINTER(Node)] +world_ask.restype = c_bool + +plugin_verify.argtypes = [POINTER(Plugin)] +plugin_verify.restype = c_bool + +plugin_get_uri.argtypes = [POINTER(Plugin)] +plugin_get_uri.restype = POINTER(Node) + +plugin_get_bundle_uri.argtypes = [POINTER(Plugin)] +plugin_get_bundle_uri.restype = POINTER(Node) + +plugin_get_data_uris.argtypes = [POINTER(Plugin)] +plugin_get_data_uris.restype = POINTER(Nodes) + +plugin_get_library_uri.argtypes = [POINTER(Plugin)] +plugin_get_library_uri.restype = POINTER(Node) + +plugin_get_name.argtypes = [POINTER(Plugin)] +plugin_get_name.restype = POINTER(Node) + +plugin_get_class.argtypes = [POINTER(Plugin)] +plugin_get_class.restype = POINTER(PluginClass) + +plugin_get_value.argtypes = [POINTER(Plugin), POINTER(Node)] +plugin_get_value.restype = POINTER(Nodes) + +plugin_has_feature.argtypes = [POINTER(Plugin), POINTER(Node)] +plugin_has_feature.restype = c_bool + +plugin_get_supported_features.argtypes = [POINTER(Plugin)] +plugin_get_supported_features.restype = POINTER(Nodes) + +plugin_get_required_features.argtypes = [POINTER(Plugin)] +plugin_get_required_features.restype = POINTER(Nodes) + +plugin_get_optional_features.argtypes = [POINTER(Plugin)] +plugin_get_optional_features.restype = POINTER(Nodes) + +plugin_has_extension_data.argtypes = [POINTER(Plugin), POINTER(Node)] +plugin_has_extension_data.restype = c_bool + +plugin_get_extension_data.argtypes = [POINTER(Plugin)] +plugin_get_extension_data.restype = POINTER(Nodes) + +plugin_get_num_ports.argtypes = [POINTER(Plugin)] +plugin_get_num_ports.restype = c_uint32 + +plugin_get_port_ranges_float.argtypes = [POINTER(Plugin), POINTER(c_float), POINTER(c_float), POINTER(c_float)] +plugin_get_port_ranges_float.restype = None + +plugin_get_num_ports_of_class = VariadicFunction(_lib.lilv_plugin_get_num_ports_of_class, + c_uint32, + [POINTER(Plugin), POINTER(Node)]) + +plugin_has_latency.argtypes = [POINTER(Plugin)] +plugin_has_latency.restype = c_bool + +plugin_get_latency_port_index.argtypes = [POINTER(Plugin)] +plugin_get_latency_port_index.restype = c_uint32 + +plugin_get_port_by_index.argtypes = [POINTER(Plugin), c_uint32] +plugin_get_port_by_index.restype = POINTER(Port) + +plugin_get_port_by_symbol.argtypes = [POINTER(Plugin), POINTER(Node)] +plugin_get_port_by_symbol.restype = POINTER(Port) + +plugin_get_port_by_designation.argtypes = [POINTER(Plugin), POINTER(Node), POINTER(Node)] +plugin_get_port_by_designation.restype = POINTER(Port) + +plugin_get_project.argtypes = [POINTER(Plugin)] +plugin_get_project.restype = POINTER(Node) + +plugin_get_author_name.argtypes = [POINTER(Plugin)] +plugin_get_author_name.restype = POINTER(Node) + +plugin_get_author_email.argtypes = [POINTER(Plugin)] +plugin_get_author_email.restype = POINTER(Node) + +plugin_get_author_homepage.argtypes = [POINTER(Plugin)] +plugin_get_author_homepage.restype = POINTER(Node) + +plugin_is_replaced.argtypes = [POINTER(Plugin)] +plugin_is_replaced.restype = c_bool + +plugin_get_related.argtypes = [POINTER(Plugin), POINTER(Node)] +plugin_get_related.restype = POINTER(Nodes) + +port_get_node.argtypes = [POINTER(Plugin), POINTER(Port)] +port_get_node.restype = POINTER(Node) + +port_get_value.argtypes = [POINTER(Plugin), POINTER(Port), POINTER(Node)] +port_get_value.restype = POINTER(Nodes) + +port_get.argtypes = [POINTER(Plugin), POINTER(Port), POINTER(Node)] +port_get.restype = POINTER(Node) + +port_get_properties.argtypes = [POINTER(Plugin), POINTER(Port)] +port_get_properties.restype = POINTER(Nodes) + +port_has_property.argtypes = [POINTER(Plugin), POINTER(Port), POINTER(Node)] +port_has_property.restype = c_bool + +port_supports_event.argtypes = [POINTER(Plugin), POINTER(Port), POINTER(Node)] +port_supports_event.restype = c_bool + +port_get_index.argtypes = [POINTER(Plugin), POINTER(Port)] +port_get_index.restype = c_uint32 + +port_get_symbol.argtypes = [POINTER(Plugin), POINTER(Port)] +port_get_symbol.restype = POINTER(Node) + +port_get_name.argtypes = [POINTER(Plugin), POINTER(Port)] +port_get_name.restype = POINTER(Node) + +port_get_classes.argtypes = [POINTER(Plugin), POINTER(Port)] +port_get_classes.restype = POINTER(Nodes) + +port_is_a.argtypes = [POINTER(Plugin), POINTER(Port), POINTER(Node)] +port_is_a.restype = c_bool + +port_get_range.argtypes = [POINTER(Plugin), POINTER(Port), POINTER(POINTER(Node)), POINTER(POINTER(Node)), POINTER(POINTER(Node))] +port_get_range.restype = None + +port_get_scale_points.argtypes = [POINTER(Plugin), POINTER(Port)] +port_get_scale_points.restype = POINTER(ScalePoints) + +state_new_from_world.argtypes = [POINTER(World), POINTER(LV2_URID_Map), POINTER(Node)] +state_new_from_world.restype = POINTER(State) + +state_new_from_file.argtypes = [POINTER(World), POINTER(LV2_URID_Map), POINTER(Node), String] +state_new_from_file.restype = POINTER(State) + +state_new_from_string.argtypes = [POINTER(World), POINTER(LV2_URID_Map), String] +state_new_from_string.restype = POINTER(State) + +LilvGetPortValueFunc = CFUNCTYPE(c_void_p, c_char_p, POINTER(None), POINTER(c_uint32), POINTER(c_uint32)) + +state_new_from_instance.argtypes = [POINTER(Plugin), POINTER(Instance), POINTER(LV2_URID_Map), c_char_p, c_char_p, c_char_p, String, LilvGetPortValueFunc, POINTER(None), c_uint32, POINTER(POINTER(LV2_Feature))] +state_new_from_instance.restype = POINTER(State) + +state_free.argtypes = [POINTER(State)] +state_free.restype = None + +state_equals.argtypes = [POINTER(State), POINTER(State)] +state_equals.restype = c_bool + +state_get_num_properties.argtypes = [POINTER(State)] +state_get_num_properties.restype = c_uint + +state_get_plugin_uri.argtypes = [POINTER(State)] +state_get_plugin_uri.restype = POINTER(Node) + +state_get_uri.argtypes = [POINTER(State)] +state_get_uri.restype = POINTER(Node) + +state_get_label.argtypes = [POINTER(State)] +state_get_label.restype = c_char_p + +state_set_label.argtypes = [POINTER(State), String] +state_set_label.restype = None + +state_set_metadata.argtypes = [POINTER(State), c_uint32, POINTER(None), c_size_t, c_uint32, c_uint32] +state_set_metadata.restype = c_int + +LilvSetPortValueFunc = CFUNCTYPE(None, c_char_p, POINTER(None), POINTER(None), c_uint32, c_uint32) +state_emit_port_values.argtypes = [POINTER(State), LilvSetPortValueFunc, POINTER(None)] +state_emit_port_values.restype = None + +state_restore.argtypes = [POINTER(State), POINTER(Instance), LilvSetPortValueFunc, POINTER(None), c_uint32, POINTER(POINTER(LV2_Feature))] +state_restore.restype = None + +state_save.argtypes = [POINTER(World), POINTER(LV2_URID_Map), POINTER(LV2_URID_Unmap), POINTER(State), c_char_p, c_char_p, String] +state_save.restype = c_int + +state_to_string.argtypes = [POINTER(World), POINTER(LV2_URID_Map), POINTER(LV2_URID_Unmap), POINTER(State), c_char_p, String] +state_to_string.restype = c_char_p + +state_delete.argtypes = [POINTER(World), POINTER(State)] +state_delete.restype = c_int + +scale_point_get_label.argtypes = [POINTER(ScalePoint)] +scale_point_get_label.restype = POINTER(Node) + +scale_point_get_value.argtypes = [POINTER(ScalePoint)] +scale_point_get_value.restype = POINTER(Node) + +plugin_class_get_parent_uri.argtypes = [POINTER(PluginClass)] +plugin_class_get_parent_uri.restype = POINTER(Node) + +plugin_class_get_uri.argtypes = [POINTER(PluginClass)] +plugin_class_get_uri.restype = POINTER(Node) + +plugin_class_get_label.argtypes = [POINTER(PluginClass)] +plugin_class_get_label.restype = POINTER(Node) + +plugin_class_get_children.argtypes = [POINTER(PluginClass)] +plugin_class_get_children.restype = POINTER(PluginClasses) + +plugin_instantiate.argtypes = [POINTER(Plugin), c_double, POINTER(POINTER(LV2_Feature))] +plugin_instantiate.restype = POINTER(Instance) + +instance_free.argtypes = [POINTER(Instance)] +instance_free.restype = None + +plugin_get_uis.argtypes = [POINTER(Plugin)] +plugin_get_uis.restype = POINTER(UIs) + +ui_get_uri.argtypes = [POINTER(UI)] +ui_get_uri.restype = POINTER(Node) + +ui_get_classes.argtypes = [POINTER(UI)] +ui_get_classes.restype = POINTER(Nodes) + +ui_is_a.argtypes = [POINTER(UI), POINTER(Node)] +ui_is_a.restype = c_bool + +LilvUISupportedFunc = CFUNCTYPE(c_uint, c_char_p, c_char_p) + +ui_is_supported.argtypes = [POINTER(UI), LilvUISupportedFunc, POINTER(Node), POINTER(POINTER(Node))] +ui_is_supported.restype = c_uint + +ui_get_bundle_uri.argtypes = [POINTER(UI)] +ui_get_bundle_uri.restype = POINTER(Node) + +ui_get_binary_uri.argtypes = [POINTER(UI)] +ui_get_binary_uri.restype = POINTER(Node) + +OPTION_FILTER_LANG = 'http://drobilla.net/ns/lilv#filter-lang' +OPTION_DYN_MANIFEST = 'http://drobilla.net/ns/lilv#dyn-manifest' + +# Define URI constants for compatibility with old Python bindings + +LILV_NS_DOAP = 'http://usefulinc.com/ns/doap#' +LILV_NS_FOAF = 'http://xmlns.com/foaf/0.1/' +LILV_NS_LILV = 'http://drobilla.net/ns/lilv#' +LILV_NS_LV2 = 'http://lv2plug.in/ns/lv2core#' +LILV_NS_OWL = 'http://www.w3.org/2002/07/owl#' +LILV_NS_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' +LILV_NS_RDFS = 'http://www.w3.org/2000/01/rdf-schema#' +LILV_NS_XSD = 'http://www.w3.org/2001/XMLSchema#' +LILV_URI_ATOM_PORT = 'http://lv2plug.in/ns/ext/atom#AtomPort' +LILV_URI_AUDIO_PORT = 'http://lv2plug.in/ns/lv2core#AudioPort' +LILV_URI_CONTROL_PORT = 'http://lv2plug.in/ns/lv2core#ControlPort' +LILV_URI_CV_PORT = 'http://lv2plug.in/ns/lv2core#CVPort' +LILV_URI_EVENT_PORT = 'http://lv2plug.in/ns/ext/event#EventPort' +LILV_URI_INPUT_PORT = 'http://lv2plug.in/ns/lv2core#InputPort' +LILV_URI_MIDI_EVENT = 'http://lv2plug.in/ns/ext/midi#MidiEvent' +LILV_URI_OUTPUT_PORT = 'http://lv2plug.in/ns/lv2core#OutputPort' +LILV_URI_PORT = 'http://lv2plug.in/ns/lv2core#Port' +LILV_OPTION_FILTER_LANG = 'http://drobilla.net/ns/lilv#filter-lang' +LILV_OPTION_DYN_MANIFEST = 'http://drobilla.net/ns/lilv#dyn-manifest' diff --git a/bindings/python/lv2_apply.py b/bindings/python/lv2_apply.py new file mode 100755 index 0000000..4c7d9b4 --- /dev/null +++ b/bindings/python/lv2_apply.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import math +import lilv +import sys +import wave +import numpy + +class WavFile(object): + """Helper class for accessing wav file data. Should work on the most common + formats (8 bit unsigned, 16 bit signed, 32 bit signed). Audio data is + converted to float32.""" + + # (struct format code, is_signedtype) for each sample width: + WAV_SPECS = { + 1: ("B", False), + 2: ("h", True), + 4: ("l", True), + } + + def __init__(self, wav_in_path): + self.wav_in = wave.open(wav_in_path, 'r') + self.framerate = self.wav_in.getframerate() + self.nframes = self.wav_in.getnframes() + self.nchannels = self.wav_in.getnchannels() + self.sampwidth = self.wav_in.getsampwidth() + wav_spec = self.WAV_SPECS[self.sampwidth] + self.struct_fmt_code, self.signed = wav_spec + self.range = 2 ** (8*self.sampwidth) + + def read(self): + """Read data from an open wav file. Return a list of channels, where each + channel is a list of floats.""" + raw_bytes = self.wav_in.readframes(self.nframes) + struct_fmt = "%u%s" % (len(raw_bytes) / self.sampwidth, self.struct_fmt_code) + data = wave.struct.unpack(struct_fmt, raw_bytes) + if self.signed: + data = [i / float(self.range/2) for i in data] + else: + data = [(i - float(range/2)) / float(range/2) for i in data] + + channels = [] + for i in range(self.nchannels): + channels.append([data[j] for j in range(0, len(data), self.nchannels) ]) + + return channels + + def close(self): + self.wav_in.close() + +def main(): + # Read command line arguments + if len(sys.argv) != 4: + print('USAGE: lv2_apply.py PLUGIN_URI INPUT_WAV OUTPUT_WAV') + sys.exit(1) + + # Initialise Lilv + world = lilv.World() + ns = world.ns + world.load_all() + + plugin_uri = sys.argv[1] + wav_in_path = sys.argv[2] + wav_out_path = sys.argv[3] + + # Find plugin + plugin_uri_node = world.new_uri(plugin_uri) + plugins = world.get_all_plugins() + if plugin_uri_node not in plugins: + print("Unknown plugin `%s'" % plugin_uri) + sys.exit(1) + + plugin = plugins[plugin_uri_node] + n_audio_in = plugin.get_num_ports_of_class(ns.lv2.InputPort, ns.lv2.AudioPort) + n_audio_out = plugin.get_num_ports_of_class(ns.lv2.OutputPort, ns.lv2.AudioPort) + if n_audio_out == 0: + print("Plugin has no audio outputs\n") + sys.exit(1) + + # Open input file + try: + wav_in = WavFile(wav_in_path) + except: + print("Failed to open input `%s'\n" % wav_in_path) + sys.exit(1) + + if wav_in.nchannels != n_audio_in: + print("Input has %d channels, but plugin has %d audio inputs\n" % ( + wav_in.nchannels, n_audio_in)) + sys.exit(1) + + # Open output file + wav_out = wave.open(wav_out_path, 'w') + if not wav_out: + print("Failed to open output `%s'\n" % wav_out_path) + sys.exit(1) + + # Set output file to same format as input (except possibly nchannels) + wav_out.setparams(wav_in.wav_in.getparams()) + wav_out.setnchannels(n_audio_out) + + print('%s => %s => %s @ %d Hz' + % (wav_in_path, plugin.get_name(), wav_out_path, wav_in.framerate)) + + instance = lilv.Instance(plugin, wav_in.framerate) + + channels = wav_in.read() + wav_in.close() + + # Connect all ports to buffers. NB if we fail to connect any buffer, lilv + # will segfault. + audio_input_buffers = [] + audio_output_buffers = [] + control_input_buffers = [] + control_output_buffers = [] + for index in range(plugin.get_num_ports()): + port = plugin.get_port_by_index(index) + if port.is_a(ns.lv2.InputPort): + if port.is_a(ns.lv2.AudioPort): + audio_input_buffers.append(numpy.array(channels[len(audio_input_buffers)], numpy.float32)) + instance.connect_port(index, audio_input_buffers[-1]) + elif port.is_a(ns.lv2.ControlPort): + default = float(port.get(ns.lv2.default)) + control_input_buffers.append(numpy.array([default], numpy.float32)) + instance.connect_port(index, control_input_buffers[-1]) + else: + raise ValueError("Unhandled port type") + elif port.is_a(ns.lv2.OutputPort): + if port.is_a(ns.lv2.AudioPort): + audio_output_buffers.append(numpy.array([0] * wav_in.nframes, numpy.float32)) + instance.connect_port(index, audio_output_buffers[-1]) + elif port.is_a(ns.lv2.ControlPort): + control_output_buffers.append(numpy.array([0], numpy.float32)) + instance.connect_port(index, control_output_buffers[-1]) + else: + raise ValueError("Unhandled port type") + + # Run the plugin: + instance.run(wav_in.nframes) + + # Interleave output buffers: + data = numpy.dstack(audio_output_buffers).flatten() + + # Return to original int range: + if wav_in.signed: + data = data * float(wav_in.range / 2) + else: + data = (data + 1) * float(wav_in.range/2) + + # Write output file in chunks to stop memory usage getting out of hand: + CHUNK_SIZE = 8192 + for chunk in numpy.array_split(data, CHUNK_SIZE): + wav_out.writeframes(wave.struct.pack("%u%s" % (len(chunk), wav_in.struct_fmt_code), *chunk.astype(int))) + wav_out.close() + + +if __name__ == "__main__": + main() diff --git a/bindings/python/lv2_list.py b/bindings/python/lv2_list.py new file mode 100755 index 0000000..babe1b4 --- /dev/null +++ b/bindings/python/lv2_list.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +import lilv + +world = lilv.World() +world.load_all() + +for i in world.get_all_plugins(): + print(i.get_uri()) diff --git a/bindings/test/bindings_test_plugin.c b/bindings/test/bindings_test_plugin.c new file mode 100644 index 0000000..3d6c763 --- /dev/null +++ b/bindings/test/bindings_test_plugin.c @@ -0,0 +1,196 @@ +/* + Copyright 2006-2011 David Robillard <d@drobilla.net> + Copyright 2006 Steve Harris <steve@plugin.org.uk> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** Include standard C headers */ +#include <math.h> +#include <stdlib.h> + +/** + LV2 headers are based on the URI of the specification they come from, so a + consistent convention can be used even for unofficial extensions. The URI + of the core LV2 specification is <http://lv2plug.in/ns/lv2core>, by + replacing `http:/` with `lv2` any header in the specification bundle can be + included, in this case `lv2.h`. +*/ +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +/** + The URI is the identifier for a plugin, and how the host associates this + implementation in code with its description in data. In this plugin it is + only used once in the code, but defining the plugin URI at the top of the + file is a good convention to follow. If this URI does not match that used + in the data files, the host will fail to load the plugin. +*/ +#define TEST_URI "http://example.org/lilv-bindings-test-plugin" + +/** + In code, ports are referred to by index. An enumeration of port indices + should be defined for readability. +*/ +typedef enum { + TEST_CONTROL_IN = 0, + TEST_CONTROL_OUT = 1, + TEST_AUDIO_IN = 2, + TEST_AUDIO_OUT = 3 +} PortIndex; + +/** + Every plugin defines a private structure for the plugin instance. All data + associated with a plugin instance is stored here, and is available to + every instance method. In this simple plugin, only port buffers need to be + stored, since there is no additional instance data. */ +typedef struct { + float* buf; +} Test; + +/** + The instantiate() function is called by the host to create a new plugin + instance. The host passes the plugin descriptor, sample rate, and bundle + path for plugins that need to load additional resources (e.g. waveforms). + The features parameter contains host-provided features defined in LV2 + extensions, but this simple plugin does not use any. + + This function is in the ``instantiation'' threading class, so no other + methods on this instance will be called concurrently with it. +*/ +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* bundle_path, + const LV2_Feature* const* features) +{ + Test* test = (Test*)malloc(sizeof(Test)); + + return (LV2_Handle)test; +} + +/** + The connect_port() method is called by the host to connect a particular port + to a buffer. The plugin must store the data location, but data may not be + accessed except in run(). + + This method is in the ``audio'' threading class, and is called in the same + context as run(). +*/ +static void +connect_port(LV2_Handle instance, + uint32_t port, + void* data) +{ +} + +/** + The activate() method is called by the host to initialise and prepare the + plugin instance for running. The plugin must reset all internal state + except for buffer locations set by connect_port(). Since this plugin has + no other internal state, this method does nothing. + + This method is in the ``instantiation'' threading class, so no other + methods on this instance will be called concurrently with it. +*/ +static void +activate(LV2_Handle instance) +{ +} + +/** Process a block of audio (audio thread, must be RT safe). */ +static void +run(LV2_Handle instance, uint32_t n_samples) +{ +} + +/** + The deactivate() method is the counterpart to activate() called by the host + after running the plugin. It indicates that the host will not call run() + again until another call to activate() and is mainly useful for more + advanced plugins with ``live'' characteristics such as those with auxiliary + processing threads. As with activate(), this plugin has no use for this + information so this method does nothing. + + This method is in the ``instantiation'' threading class, so no other + methods on this instance will be called concurrently with it. +*/ +static void +deactivate(LV2_Handle instance) +{ +} + +/** + Destroy a plugin instance (counterpart to instantiate()). + + This method is in the ``instantiation'' threading class, so no other + methods on this instance will be called concurrently with it. +*/ +static void +cleanup(LV2_Handle instance) +{ + free(instance); +} + +/** + The extension_data function returns any extension data supported by the + plugin. Note that this is not an instance method, but a function on the + plugin descriptor. It is usually used by plugins to implement additional + interfaces. This plugin does not have any extension data, so this function + returns NULL. + + This method is in the ``discovery'' threading class, so no other functions + or methods in this plugin library will be called concurrently with it. +*/ +static const void* +extension_data(const char* uri) +{ + return NULL; +} + +/** + Define the LV2_Descriptor for this plugin. It is best to define descriptors + statically to avoid leaking memory and non-portable shared library + constructors and destructors to clean up properly. +*/ +static const LV2_Descriptor descriptor = { + TEST_URI, + instantiate, + connect_port, + activate, + run, + deactivate, + cleanup, + extension_data +}; + +/** + The lv2_descriptor() function is the entry point to the plugin library. The + host will load the library and call this function repeatedly with increasing + indices to find all the plugins defined in the library. The index is not an + indentifier, the URI of the returned descriptor is used to determine the + identify of the plugin. + + This method is in the ``discovery'' threading class, so no other functions + or methods in this plugin library will be called concurrently with it. +*/ +LV2_SYMBOL_EXPORT +const LV2_Descriptor* +lv2_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} diff --git a/bindings/test/bindings_test_plugin.ttl.in b/bindings/test/bindings_test_plugin.ttl.in new file mode 100644 index 0000000..e8323d5 --- /dev/null +++ b/bindings/test/bindings_test_plugin.ttl.in @@ -0,0 +1,62 @@ +# Lilv Bindings Test Plugin +# Copyright 2011 David Robillard <d@drobilla.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . + +<http://example.org/lilv-bindings-test-plugin> + a lv2:Plugin ; + doap:name "Lilv Bindings Test" ; + doap:license <http://opensource.org/licenses/isc> ; + lv2:optionalFeature lv2:hardRTCapable ; + ui:ui <http://example.org/lilv-bindings-test-plugin-ui> ; + lv2:port [ + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "input" ; + lv2:name "Input" ; + lv2:default 0.5 ; + lv2:minimum 0.0 ; + lv2:maximum 1.0 ; + lv2:scalePoint [ rdfs:label "off" ; rdf:value 0.0 ] ; + lv2:scalePoint [ rdfs:label "on" ; rdf:value 1.0 ] ; + ] , [ + a lv2:OutputPort , + lv2:ControlPort ; + lv2:index 1 ; + lv2:symbol "output" ; + lv2:name "Output" + ] , [ + a lv2:AudioPort , + lv2:InputPort ; + lv2:index 2 ; + lv2:symbol "audio_input" ; + lv2:name "Audio Input" ; + ] , [ + a lv2:AudioPort , + lv2:OutputPort ; + lv2:index 3 ; + lv2:symbol "audio_output" ; + lv2:name "Audio Output" ; + ] . + +<http://example.org/lilv-bindings-test-plugin-ui> + a ui:GtkUI ; + ui:binary <TODO> . diff --git a/bindings/test/manifest.ttl.in b/bindings/test/manifest.ttl.in new file mode 100644 index 0000000..9cc7fa8 --- /dev/null +++ b/bindings/test/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://example.org/lilv-bindings-test-plugin> + a lv2:Plugin ; + lv2:binary <bindings_test_plugin@SHLIB_EXT@> ; + rdfs:seeAlso <bindings_test_plugin.ttl> . diff --git a/bindings/test/python/test_api.py b/bindings/test/python/test_api.py new file mode 100644 index 0000000..f594013 --- /dev/null +++ b/bindings/test/python/test_api.py @@ -0,0 +1,290 @@ +# Copyright 2016 David Robillard <d@drobilla.net> +# Copyright 2013 Kaspar Emanuel <kaspar.emanuel@gmail.com> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import lilv +import unittest +import os + +location = "file://" + os.getcwd() + "/bindings/bindings_test_plugin.lv2/" + +class NodeTests(unittest.TestCase): + def setUp(self): + self.world = lilv.World() + def testNodes(self): + aint = self.world.new_int(1) + aint2 = self.world.new_int(1) + aint3 = self.world.new_int(3) + afloat = self.world.new_float(2.0) + atrue = self.world.new_bool(True) + afalse = self.world.new_bool(False) + auri = self.world.new_uri("http://example.org") + afile = self.world.new_file_uri(None, "/foo/bar") + astring = self.world.new_string("hello") + self.assertEqual(auri.get_turtle_token(), '<http://example.org>') + self.assertTrue(aint.is_int()) + self.assertTrue(afloat.is_float()) + self.assertTrue(auri.is_uri()) + self.assertTrue(astring.is_string()) + self.assertTrue(astring.is_literal()) + self.assertFalse(auri.is_blank()) + self.assertTrue(int(aint) == 1) + self.assertTrue(float(afloat) == 2.0) + self.assertTrue(bool(atrue)) + self.assertFalse(bool(afalse)) + self.assertEqual(afile.get_path(), "/foo/bar") + self.assertTrue(aint == aint2) + self.assertTrue(aint != aint3) + self.assertTrue(aint != afloat) + with self.assertRaises(ValueError): + int(atrue) + with self.assertRaises(ValueError): + float(aint) + with self.assertRaises(ValueError): + bool(astring) + +class UriTests(unittest.TestCase): + def setUp(self): + self.world = lilv.World() + self.world.load_all(); + def testInvalidURI(self): + self.plugin_uri = self.world.new_uri("invalid_uri") + self.assertIsNone(self.plugin_uri) + def testNonExistentURI(self): + self.plugin_uri = self.world.new_uri("exist:does_not") + self.plugin = self.world.get_all_plugins().get_by_uri(self.plugin_uri) + self.assertEqual(self.plugin, None) + def testPortTypes(self): + self.assertIsNotNone(self.world.new_uri(lilv.LILV_URI_INPUT_PORT)) + def testPortTypes2(self): + self.assertIsNotNone(self.world.new_uri(lilv.LILV_URI_OUTPUT_PORT)) + def testPortTypes3(self): + self.assertIsNotNone(self.world.new_uri(lilv.LILV_URI_AUDIO_PORT)) + def testPortTypes4(self): + self.assertIsNotNone(self.world.new_uri(lilv.LILV_URI_CONTROL_PORT)) + +class PluginClassTests(unittest.TestCase): + def setUp(self): + self.world = lilv.World() + def testPluginClasses(self): + pclass = self.world.get_plugin_class() + self.assertIsNotNone(pclass) + self.assertIsNone(pclass.get_parent_uri()) + self.assertIsNotNone(pclass.get_uri()) + self.assertIsNotNone(pclass.get_label()) + self.assertEqual(str(pclass.get_uri()), str(pclass)) + for i in pclass.get_children(): + self.assertIsNotNone(i) + self.assertIsNotNone(i.get_uri()) + self.assertIsNotNone(i.get_label()) + +class PluginClassesTests(unittest.TestCase): + def setUp(self): + self.world = lilv.World() + self.world.load_all() + def testPluginClasses(self): + classes = self.world.get_plugin_classes() + pclass = self.world.get_plugin_class() + self.assertIsNotNone(classes) + self.assertIsNotNone(pclass) + self.assertTrue(pclass in classes) + self.assertTrue(pclass.get_uri() in classes) + self.assertGreater(len(classes), 1) + self.assertIsNotNone(classes[0]) + self.assertIsNotNone(classes[pclass.get_uri()]) + +class LoadTests(unittest.TestCase): + def setUp(self): + self.world = lilv.World() + self.bundle_uri = self.world.new_uri(location) + self.world.load_specifications() + self.world.load_plugin_classes() + def tearDown(self): + del self.world + def testLoadUnload(self): + self.world.load_bundle(self.bundle_uri) + plugins = self.world.get_all_plugins() + plugin = plugins.get(plugins.begin()) + self.world.load_resource(plugin) + self.world.unload_resource(plugin) + self.world.unload_bundle(self.bundle_uri) + +class PluginTests(unittest.TestCase): + def setUp(self): + self.world = lilv.World() + self.world.set_option(lilv.OPTION_FILTER_LANG, self.world.new_bool(True)) + self.bundle_uri = self.world.new_uri(location) + self.assertIsNotNone(self.bundle_uri, "Invalid URI: '" + location + "'") + self.world.load_bundle(self.bundle_uri) + self.plugins = self.world.get_all_plugins() + self.plugin = self.plugins.get(self.plugins.begin()) + self.assertTrue(self.plugin.verify()) + self.assertTrue(self.plugin in self.plugins) + self.assertTrue(self.plugin.get_uri() in self.plugins) + self.assertEqual(self.plugins[self.plugin.get_uri()], self.plugin) + self.assertIsNotNone(self.plugin, msg="Test plugin not found at location: '" + location + "'") + self.assertEqual(location, str(self.plugin.get_bundle_uri())) + self.plugin_uri = self.plugin.get_uri() + self.assertEqual(self.plugin.get_uri(), self.plugin_uri, "URI equality broken") + self.instance = lilv.Instance(self.plugin, 48000, None) + self.assertIsNotNone(self.instance) + self.lv2_InputPort = self.world.new_uri(lilv.LILV_URI_INPUT_PORT) + self.lv2_OutputPort = self.world.new_uri(lilv.LILV_URI_OUTPUT_PORT) + self.lv2_AudioPort = self.world.new_uri(lilv.LILV_URI_AUDIO_PORT) + self.lv2_ControlPort = self.world.new_uri(lilv.LILV_URI_CONTROL_PORT) + def testGetters(self): + self.assertIsNotNone(self.plugin.get_bundle_uri()) + self.assertGreater(len(self.plugin.get_data_uris()), 0) + self.assertIsNotNone(self.plugin.get_library_uri()) + self.assertTrue(self.plugin.get_name().is_string()) + self.assertTrue(self.plugin.get_class().get_uri().is_uri()) + self.assertEqual(len(self.plugin.get_value(self.world.ns.doap.license)), 1) + licenses = self.plugin.get_value(self.world.ns.doap.license) + features = self.plugin.get_value(self.world.ns.lv2.optionalFeature) + self.assertEqual(len(licenses), 1) + self.assertTrue(licenses[0] in licenses) + with self.assertRaises(IndexError): + self.assertIsNone(licenses[len(licenses)]) + self.assertEqual(len(licenses) + len(features), + len(licenses.merge(features))) + self.assertEqual(licenses.get(licenses.begin()), self.world.new_uri('http://opensource.org/licenses/isc')) + self.assertEqual(licenses[0], licenses.get(licenses.begin())) + self.assertTrue(self.plugin.has_feature(self.world.ns.lv2.hardRTCapable)) + self.assertEqual(len(self.plugin.get_supported_features()), 1) + self.assertEqual(len(self.plugin.get_optional_features()), 1) + self.assertEqual(len(self.plugin.get_required_features()), 0) + self.assertFalse(self.plugin.has_extension_data(self.world.new_uri('http://example.org/nope'))) + self.assertEqual(len(self.plugin.get_extension_data()), 0) + self.assertEqual(len(self.plugin.get_extension_data()), 0) + self.assertFalse(self.plugin.has_latency()) + self.assertIsNone(self.plugin.get_latency_port_index()) + def testPorts(self): + self.assertEqual(self.plugin.get_num_ports(), 4) + self.assertIsNotNone(self.plugin.get_port(0)) + self.assertIsNotNone(self.plugin.get_port(1)) + self.assertIsNotNone(self.plugin.get_port(2)) + self.assertIsNotNone(self.plugin.get_port(3)) + self.assertIsNone(self.plugin.get_port_by_index(4)) + self.assertIsNotNone(self.plugin.get_port("input")) + self.assertIsNotNone(self.plugin.get_port("output")) + self.assertIsNotNone(self.plugin.get_port("audio_input")) + self.assertIsNotNone(self.plugin.get_port("audio_output")) + self.assertIsNone(self.plugin.get_port_by_symbol("nonexistent")) + self.assertIsNone(self.plugin.get_port_by_designation(self.world.ns.lv2.InputPort, self.world.ns.lv2.control)) + self.assertIsNone(self.plugin.get_project()) + self.assertIsNone(self.plugin.get_author_name()) + self.assertIsNone(self.plugin.get_author_email()) + self.assertIsNone(self.plugin.get_author_homepage()) + self.assertFalse(self.plugin.is_replaced()) + self.assertEqual(0, len(self.plugin.get_related(self.world.new_uri("http://example.org/Type")))) + self.assertEqual(1, self.plugin.get_num_ports_of_class(self.lv2_InputPort, self.lv2_AudioPort)) + port = self.plugin.get_port("input") + self.assertTrue(port.get_node().is_blank()) + self.assertEqual(0, port.get(self.world.ns.lv2.index)) + self.assertEqual(1, len(port.get_value(self.world.ns.lv2.symbol))) + self.assertEqual(port.get_value(self.world.ns.lv2.symbol)[0], "input") + self.assertFalse(port.has_property(self.world.ns.lv2.latency)) + self.assertFalse(port.supports_event(self.world.ns.midi.MidiEvent)) + self.assertEqual(0, port.get_index()) + self.assertEqual("input", port.get_symbol()) + self.assertEqual("Input", port.get_name()) + self.assertEqual([self.world.ns.lv2.ControlPort, self.world.ns.lv2.InputPort], + list(port.get_classes())) + self.assertTrue(port.is_a(self.world.ns.lv2.ControlPort)) + self.assertFalse(port.is_a(self.world.ns.lv2.AudioPort)) + self.assertEquals((0.5, 0.0, 1.0), port.get_range()) + self.assertEquals(0, len(port.get_properties())) + def testScalePoints(self): + port = self.plugin.get_port("input") + points = port.get_scale_points() + self.assertEqual(points[0].get_label(), "off") + self.assertEqual(points[0].get_value(), 0.0) + self.assertEqual(points[1].get_label(), "on") + self.assertEqual(points[1].get_value(), 1.0) + def testPortCount(self): + self.assertEqual(1, self.plugin.get_num_ports_of_class(self.lv2_OutputPort, self.lv2_AudioPort)) + self.assertEqual(1, self.plugin.get_num_ports_of_class(self.lv2_OutputPort, self.lv2_ControlPort)) + self.assertEqual(1, self.plugin.get_num_ports_of_class(self.lv2_InputPort, self.lv2_AudioPort)) + self.assertEqual(1, self.plugin.get_num_ports_of_class(self.lv2_InputPort, self.lv2_ControlPort)) + +class QueryTests(unittest.TestCase): + def setUp(self): + self.world = lilv.World() + self.world.load_all() + self.bundle_uri = self.world.new_uri(location) + self.world.load_bundle(self.bundle_uri) + self.plugins = self.world.get_all_plugins() + self.plugin = self.plugins.get(self.plugins.begin()) + def testNamespaces(self): + self.assertEqual(self.world.ns.lv2, "http://lv2plug.in/ns/lv2core#") + self.assertEqual(self.world.ns.lv2.Plugin, "http://lv2plug.in/ns/lv2core#Plugin") + def testQuery(self): + self.assertTrue(self.world.ask(None, + self.world.ns.rdf.type, + self.world.ns.lv2.Plugin)) + self.assertLess(0, len(self.world.find_nodes(None, + self.world.ns.rdf.type, + self.world.ns.lv2.Plugin))) + self.assertEqual(self.plugin.get_uri(), self.world.get(None, + self.world.ns.rdf.type, + self.world.ns.lv2.Plugin)) + +class InstanceTests(unittest.TestCase): + def setUp(self): + self.world = lilv.World() + self.bundle_uri = self.world.new_uri(location) + self.world.load_bundle(self.bundle_uri) + self.plugins = self.world.get_all_plugins() + self.plugin = self.plugins[0] + self.instance = lilv.Instance(self.plugin, 48000) + self.assertEqual(self.plugin.get_uri(), self.instance.get_uri()) + self.assertIsNone(self.instance.get_extension_data(self.world.new_uri("http://example.org/ext"))) + self.assertIsNone(self.instance.get_extension_data("http://example.org/ext")) + def testRun(self): + import numpy + n_samples = 100 + buf = numpy.zeros(n_samples) + with self.assertRaises(Exception): + self.instance.connect_port(0, "hello") + self.instance.connect_port(0, None) + self.instance.connect_port(0, None) + self.instance.connect_port(2, buf) + self.instance.connect_port(3, buf) + self.instance.activate() + self.instance.run(n_samples) + self.instance.deactivate() + +class UITests(unittest.TestCase): + def setUp(self): + self.world = lilv.World() + self.bundle_uri = self.world.new_uri(location) + self.world.load_bundle(self.bundle_uri) + self.plugins = self.world.get_all_plugins() + self.plugin = self.plugins[0] + def testUI(self): + uis = self.plugin.get_uis() + ui_uri = self.world.new_uri('http://example.org/lilv-bindings-test-plugin-ui') + self.assertEqual(1, len(uis)) + self.assertEqual(str(uis[0]), str(ui_uri)) + self.assertEqual(uis[0], str(ui_uri)) + self.assertEqual(uis[0].get_uri(), ui_uri) + self.assertEqual(uis[0].get_bundle_uri(), self.bundle_uri) + self.assertEqual(uis[0].get_binary_uri(), str(self.bundle_uri) + "TODO") + self.assertEqual(uis[uis[0].get_uri()], uis[0]) + self.assertTrue(uis[0].is_a(self.world.ns.ui.GtkUI)) + self.assertTrue(uis[0] in uis) + self.assertTrue(uis[0].get_uri() in uis) + self.assertEqual([self.world.ns.ui.GtkUI], list(uis[0].get_classes())) + for ui in uis: + print(ui) diff --git a/doc/layout.xml b/doc/layout.xml new file mode 100644 index 0000000..74a109f --- /dev/null +++ b/doc/layout.xml @@ -0,0 +1,187 @@ +<doxygenlayout version="1.0"> + <!-- Navigation index tabs for HTML output --> + <navindex> + <tab type="mainpage" visible="yes" title=""/> + <tab type="pages" visible="yes" title="" intro=""/> + <tab type="modules" visible="yes" title="" intro=""/> + <tab type="namespaces" visible="yes" title=""> + <tab type="namespacelist" visible="yes" title="" intro=""/> + <tab type="namespacemembers" visible="yes" title="" intro=""/> + </tab> + <tab type="classes" visible="yes" title=""> + <tab type="classlist" visible="yes" title="" intro=""/> + <tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/> + <tab type="hierarchy" visible="yes" title="" intro=""/> + <tab type="classmembers" visible="yes" title="" intro=""/> + </tab> + <tab type="files" visible="yes" title=""> + <tab type="filelist" visible="yes" title="" intro=""/> + <tab type="globals" visible="yes" title="" intro=""/> + </tab> + <tab type="examples" visible="yes" title="" intro=""/> + </navindex> + + <!-- Layout definition for a class page --> + <class> + <briefdescription visible="yes"/> + <includes visible="$SHOW_INCLUDE_FILES"/> + <inheritancegraph visible="$CLASS_GRAPH"/> + <collaborationgraph visible="$COLLABORATION_GRAPH"/> + <allmemberslink visible="yes"/> + <memberdecl> + <nestedclasses visible="yes" title=""/> + <publictypes title=""/> + <publicslots title=""/> + <signals title=""/> + <publicmethods title=""/> + <publicstaticmethods title=""/> + <publicattributes title=""/> + <publicstaticattributes title=""/> + <protectedtypes title=""/> + <protectedslots title=""/> + <protectedmethods title=""/> + <protectedstaticmethods title=""/> + <protectedattributes title=""/> + <protectedstaticattributes title=""/> + <packagetypes title=""/> + <packagemethods title=""/> + <packagestaticmethods title=""/> + <packageattributes title=""/> + <packagestaticattributes title=""/> + <properties title=""/> + <events title=""/> + <privatetypes title=""/> + <privateslots title=""/> + <privatemethods title=""/> + <privatestaticmethods title=""/> + <privateattributes title=""/> + <privatestaticattributes title=""/> + <friends title=""/> + <related title="" subtitle=""/> + <membergroups visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + <memberdef> + <inlineclasses title=""/> + <typedefs title=""/> + <enums title=""/> + <constructors title=""/> + <functions title=""/> + <related title=""/> + <variables title=""/> + <properties title=""/> + <events title=""/> + </memberdef> + <usedfiles visible="$SHOW_USED_FILES"/> + <authorsection visible="yes"/> + </class> + + <!-- Layout definition for a namespace page --> + <namespace> + <briefdescription visible="yes"/> + <memberdecl> + <nestednamespaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <typedefs title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <membergroups visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + <memberdef> + <inlineclasses title=""/> + <typedefs title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + </memberdef> + <authorsection visible="yes"/> + </namespace> + + <!-- Layout definition for a file page --> + <file> + <briefdescription visible="yes"/> + <includes visible="$SHOW_INCLUDE_FILES"/> + <includegraph visible="$INCLUDE_GRAPH"/> + <includedbygraph visible="$INCLUDED_BY_GRAPH"/> + <sourcelink visible="yes"/> + <memberdecl> + <classes visible="yes" title=""/> + <namespaces visible="yes" title=""/> + <defines title=""/> + <typedefs title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <membergroups visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + <memberdef> + <inlineclasses title=""/> + <defines title=""/> + <typedefs title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + </memberdef> + <authorsection/> + </file> + + <!-- Layout definition for a group page --> + <group> + <briefdescription visible="no"/> + <groupgraph visible="$GROUP_GRAPHS"/> + <detaileddescription title=""/> + <memberdecl> + <nestedgroups visible="yes" title=""/> + <dirs visible="yes" title=""/> + <files visible="yes" title=""/> + <namespaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <defines title=""/> + <typedefs title=""/> + <enums title=""/> + <enumvalues title=""/> + <functions title=""/> + <variables title=""/> + <signals title=""/> + <publicslots title=""/> + <protectedslots title=""/> + <privateslots title=""/> + <events title=""/> + <properties title=""/> + <friends title=""/> + <membergroups visible="yes"/> + </memberdecl> + <memberdef> + <pagedocs/> + <inlineclasses title=""/> + <defines title=""/> + <typedefs title=""/> + <enums title=""/> + <enumvalues title=""/> + <functions title=""/> + <variables title=""/> + <signals title=""/> + <publicslots title=""/> + <protectedslots title=""/> + <privateslots title=""/> + <events title=""/> + <properties title=""/> + <friends title=""/> + </memberdef> + <authorsection visible="yes"/> + </group> + + <!-- Layout definition for a directory page --> + <directory> + <briefdescription visible="yes"/> + <directorygraph visible="yes"/> + <memberdecl> + <dirs visible="yes"/> + <files visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + </directory> +</doxygenlayout> diff --git a/doc/lv2apply.1 b/doc/lv2apply.1 new file mode 100644 index 0000000..4877aba --- /dev/null +++ b/doc/lv2apply.1 @@ -0,0 +1,34 @@ +.TH LV2APPLY 1 "05 Sep 2016" + +.SH NAME +.B lv2apply \- apply an LV2 plugin to an audio file +.SH SYNOPSIS +.B lv2apply [OPTION]... PLUGIN_URI + +.SH OPTIONS +.TP +\fB\-i IN_FILE\fR +Input file + +.TP +\fB\-o OUT_FILE\fR +Output file + +.TP +\fB\-c SYM VAL\fR +Set control port SYM to VAL + +.TP +\fB\-\-help\fR +Display help and exit + +.TP +\fB\-\-version\fR +Display version information and exit + +.SH "SEE ALSO" +.BR lv2ls(1) +.BR lv2info(1) + +.SH AUTHOR +lv2apply was written by David Robillard <d@drobilla.net> diff --git a/doc/lv2info.1 b/doc/lv2info.1 new file mode 100644 index 0000000..32a3bec --- /dev/null +++ b/doc/lv2info.1 @@ -0,0 +1,33 @@ +.TH LV2INFO 1 "05 Sep 2016" + +.SH NAME +.B lv2info \- print information about an installed LV2 plugin +.SH SYNOPSIS +.B lv2info PLUGIN_URI + +.SH OPTIONS +.TP +\fB\-p FILE\fR +Write Turtle description of plugin to FILE + +.TP +\fB\-m FILE\fR +Add record of plugin to manifest FILE + +.TP +\fB\-\-help\fR +Display help and exit + +.TP +\fB\-\-version\fR +Display version information and exit + +.SH "SEE ALSO" +.BR lilv(3), +.BR lv2ls(1) + +.SH AUTHOR +lv2info was written by David Robillard <d@drobilla.net> +.PP +This manual page was written by JaromÃr Mikes <mira.mikes@seznam.cz> +and David Robillard <d@drobilla.net> diff --git a/doc/lv2ls.1 b/doc/lv2ls.1 new file mode 100644 index 0000000..320b71c --- /dev/null +++ b/doc/lv2ls.1 @@ -0,0 +1,30 @@ +.TH LV2LS 1 "26 Aug 2016" + +.SH NAME +.B lv2ls \- list all installed LV2 plugins + +.SH SYNOPSIS +.B lv2ls [OPTION]... + +.SH OPTIONS +.TP +\fB\-n\fR, \fB\-\-names\fR +Show names instead of URIs + +.TP +\fB\-\-help\fR +Display help and exit + +.TP +\fB\-\-version\fR +Display version information and exit + +.SH "SEE ALSO" +.BR lilv(3), +.BR lv2info(1) + +.SH AUTHOR +lv2ls was written by David Robillard <d@drobilla.net> +.PP +This manual page was written by JaromÃr Mikes <mira.mikes@seznam.cz> +and David Robillard <d@drobilla.net> diff --git a/doc/reference.doxygen.in b/doc/reference.doxygen.in new file mode 100644 index 0000000..e7a45d0 --- /dev/null +++ b/doc/reference.doxygen.in @@ -0,0 +1,2415 @@ +# Doxyfile 1.8.12 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = Lilv + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = @LILV_VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = . + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = NO + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = NO + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = YES + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = YES + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = YES + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = NO + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = YES + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = YES + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = NO + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = YES + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = NO + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = NO + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = NO + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = NO + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = NO + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = @LILV_SRCDIR@/doc/layout.xml + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = YES + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @LILV_SRCDIR@/lilv/lilv.h + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = *.c + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = NO + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = @LILV_SRCDIR@/doc/style.css + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 160 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = YES + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 1 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using Javascript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: http://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: http://xapian.org/). See the section "External Indexing and +# Searching" for details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when enabling USE_PDFLATEX this option is only used for generating +# bitmaps for formulas in the HTML output, but not in the Makefile that is +# written to the output directory. +# The default file is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the +# generated LaTeX document. The header should contain everything until the first +# chapter. If it is left blank doxygen will generate a standard header. See +# section "Doxygen usage" for information on how to let doxygen write the +# default header to a separate file. +# +# Note: Only use a user-defined header if you know what you are doing! The +# following commands have a special meaning inside the header: $title, +# $datetime, $date, $doxygenversion, $projectname, $projectnumber, +# $projectbrief, $projectlogo. Doxygen will replace $title with the empty +# string, for the replacement values of the other commands the user is referred +# to HTML_HEADER. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the +# generated LaTeX document. The footer should contain everything after the last +# chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. +# +# Note: Only use a user-defined footer if you know what you are doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate +# the PDF file directly from the LaTeX files. Set this option to YES, to get a +# higher quality PDF documentation. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. This option is also used +# when generating formulas in HTML. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BATCHMODE = NO + +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HIDE_INDICES = NO + +# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source +# code with syntax highlighting in the LaTeX output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's config +# file, i.e. a series of assignments. You only have to provide replacements, +# missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's config file. A template extensions file can be generated +# using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTENSIONS_FILE = + +# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code +# with syntax highlighting in the RTF output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# classes and files. +# The default value is: NO. + +GENERATE_MAN = YES + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_EXTENSION = .3 + +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the +# program listings (including syntax highlighting and cross-referencing +# information) to the DOCBOOK output. Note that enabling this will significantly +# increase the size of the DOCBOOK output. +# The default value is: NO. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see http://autogen.sf.net) file that captures the +# structure of the code including all documentation. Note that this feature is +# still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO, the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES, the include files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES, all external class will be listed in +# the class index. If set to NO, only the inherited external classes will be +# listed. +# The default value is: NO. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of 'which perl'). +# The default file (with absolute path) is: /usr/bin/perl. + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram +# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to +# NO turns the diagrams off. Note that this option also works with HAVE_DOT +# disabled, but it is recommended to install and use dot, since it yields more +# powerful graphs. +# The default value is: YES. + +CLASS_DIAGRAMS = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see: +# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: NO. + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# When you want a differently looking font in the dot files that doxygen +# generates you can specify the font name using DOT_FONTNAME. You need to make +# sure dot is able to find the font, which can be done by putting it in a +# standard location or by setting the DOTFONTPATH environment variable or by +# setting DOT_FONTPATH to the directory containing the font. +# The default value is: Helvetica. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTNAME = + +# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of +# dot graphs. +# Minimum value: 4, maximum value: 24, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the default font as specified with +# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set +# the path where dot can find it using this tag. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for +# each documented class showing the direct and indirect inheritance relations. +# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +CLASS_GRAPH = NO + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = NO + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = NO + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = NO + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = NO + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = NO + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# http://www.graphviz.org/)). +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = NO + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file. If left blank, it is assumed +# PlantUML is not used or called during a preprocessing step. Doxygen will +# generate a warning when it encounters a \startuml command in this case and +# will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not seem +# to support this out of the box. +# +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = NO + +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# files that are used to generate the various graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_CLEANUP = YES diff --git a/doc/style.css b/doc/style.css new file mode 100644 index 0000000..f6ff8bb --- /dev/null +++ b/doc/style.css @@ -0,0 +1,691 @@ +body { + max-width: 80em; + margin: 0; + margin-left: auto; + margin-right: auto; + background: #FFF; + color: #000; +} + +#titlearea { + display: none; +} + +h1 { + font-size: 180%; + font-weight: 900; +} + +h2 { + font-size: 140%; + font-weight: 700; +} + +h3 { + font-size: 120%; + font-weight: 700; +} + +h4 { + font-size: 110%; + font-weight: 700; +} + +h5 { + font-size: 100%; + font-weight: 700; +} + +h6 { + font-size: 100%; + font-weight: 600; +} + +p { + margin: 0 0 1em 0; +} + +dt { + font-weight: 700; +} + +p.startli,p.startdd,p.starttd { + margin-top: 2px; +} + +p.endli { + margin-bottom: 0; +} + +p.enddd { + margin-bottom: 4px; +} + +p.endtd { + margin-bottom: 2px; +} + +caption { + font-weight: 700; +} + +span.legend { + font-size: small; + text-align: center; +} + +h3.version { + font-size: small; + text-align: center; +} + +div.qindex,div.navtab { + background-color: #EBEFF6; + border: 1px solid #A3B4D7; + text-align: center; + margin: 2px; + padding: 2px; +} + +div.navtab { + margin-right: 15px; +} + +/* @group Link Styling */ +a { + color: #546E00; + text-decoration: none; +} + +.contents a:visited { + color: #344E00; +} + +a:hover { + text-decoration: underline; +} + +a.qindexHL { + background-color: #9CAFD4; + color: #FFF; + border: 1px double #869DCA; +} + +code { + color: #444; +} + +a.code { + color: #4665A2; +} + +a.codeRef { + color: #4665A2; +} + +/* @end */ +dl.el { + margin-left: -1cm; +} + +.fragment { + font-family: monospace, fixed; +} + +pre.fragment { + border: 1px solid #C4C4C4; + background-color: #F9F9F9; + padding: 4px 6px; + margin: 4px 8px 4px 2px; + overflow: auto; + line-height: 125%; +} + +div.ah { + background-color: #000; + font-weight: 700; + color: #FFF; + margin-bottom: 3px; + margin-top: 3px; + padding: .2em; + border: thin solid #333; +} + +div.groupHeader { + margin-left: 16px; + margin-top: 12px; + margin-bottom: 6px; + font-weight: 700; +} + +a + h2.groupheader { + display: none; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +div.contents { + margin-top: 10px; + margin-left: 10px; + margin-right: 10px; +} + +td.indexkey { + background-color: #EBEFF6; + font-weight: 700; + border: 1px solid #C4CFE5; + margin: 2px 0; + padding: 2px 10px; +} + +td.indexvalue { + background-color: #EBEFF6; + border: 1px solid #C4CFE5; + padding: 2px 10px; + margin: 2px 0; +} + +tr.memlist { + background-color: #EEF1F7; +} + +p.formulaDsp { + text-align: center; +} + +img.formulaInl { + vertical-align: middle; +} + +div.center { + text-align: center; + margin-top: 0; + margin-bottom: 0; + padding: 0; +} + +div.center img { + border: 0; +} + +address.footer { + text-align: right; + padding-right: 12px; +} + +img.footer { + border: 0; + vertical-align: middle; +} + +/* @group Code Colorization */ +span.keyword { + color: green; +} + +span.keywordtype { + color: #3E873E; +} + +span.keywordflow { + color: #e08000; +} + +span.comment { + color: maroon; +} + +span.preprocessor { + color: #806020; +} + +span.stringliteral { + color: #002080; +} + +span.charliteral { + color: teal; +} + +span.vhdldigit { + color: #F0F; +} + +span.vhdlkeyword { + color: #700070; +} + +span.vhdllogic { + color: red; +} + +/* @end */ +td.tiny { + font-size: x-small; +} + +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid #A3B4D7; +} + +th.dirtab { + background: #EBEFF6; + font-weight: 700; +} + +hr { + height: 0; + border: none; + border-top: 1px solid #DDD; + margin: 2em 0 1em; +} + +hr.footer { + height: 1px; +} + +/* @group Member Descriptions */ +table.memberdecls { + border-spacing: 0.125em; +} + +h2.groupheader { + margin: 0.5em 0 0.25em 0; +} + +.mdescLeft,.mdescRight,.memItemLeft,.memItemRight,.memTemplItemLeft,.memTemplItemRight,.memTemplParams { + margin: 0; + padding: 0; +} + +.mdescLeft,.mdescRight { + color: #555; +} + +.memItemLeft,.memItemRight,.memTemplParams { + border: 0; + font-family: monospace, fixed; +} + +.memItemLeft,.memTemplItemLeft { + white-space: nowrap; + padding-left: 2em; + padding-right: 1em; +} + +.memItemLeft a.el { + font-weight: bold; +} + +.memTemplParams { + color: #464646; + white-space: nowrap; +} + +td.memSeparator { + display: none; +} + +td.mlabels-right { + vertical-align: top; + padding-top: 4px; + color: #AA6; +} + +.memtitle { + display: none; +} + +/* @end */ +/* @group Member Details */ +/* Styles for detailed member documentation */ +.memtemplate { + color: #4665A2; + font-weight: bold; +} + +.memnav { + background-color: #EBEFF6; + border: 1px solid #A3B4D7; + text-align: center; + margin: 2px; + margin-right: 15px; + padding: 2px; +} + +.memitem { + padding: 0; + margin: 1em 0 1em 0; +} + +.memproto { + padding: 0; + font-weight: bold; + color: #000; +} + +.memproto .paramname { + color: #444; + font-style: normal; +} + +.memdoc { + padding: 0 0 0.5em 2em; +} + +.paramkey { + text-align: right; +} + +.paramtype { + color: #3E873E; + white-space: nowrap; +} + +.paramname { + color: #444; + white-space: nowrap; + font-weight: bold; +} + +td.paramname { + vertical-align: top; +} + +.fieldname { + color: #000; +} + +td.fieldname { + padding-right: 1em; + vertical-align: top; +} + +td.fieldtype { + vertical-align: top; + color: #444; + padding-right: 0.5em; +} + +td.fielddoc p { + margin: 0; +} + +/* @end */ +/* @group Directory (tree) */ +/* for the tree view */ +.ftvtree { + font-family: sans-serif; + margin: 0; +} + +/* these are for tree view when used as main index */ +.directory { + font-size: small; + margin: 0.5em; +} + +.directory h3 { + margin: 0; + margin-top: 1em; + font-size: 11pt; +} + +.directory > h3 { + margin-top: 0; +} + +.directory p { + margin: 0; + white-space: nowrap; +} + +.directory div { + display: none; + margin: 0; +} + +.directory img { + vertical-align: -30%; +} + +/* these are for tree view when not used as main index */ +.directory-alt { + font-size: 100%; + font-weight: bold; +} + +.directory-alt h3 { + margin: 0; + margin-top: 1em; + font-size: 11pt; +} + +.directory-alt > h3 { + margin-top: 0; +} + +.directory-alt p { + margin: 0; + white-space: nowrap; +} + +.directory-alt div { + display: none; + margin: 0; +} + +.directory-alt img { + vertical-align: -30%; +} + +/* @end */ +div.dynheader { + margin-top: 8px; +} + +address { + font-style: normal; + color: #2A3D61; +} + +table.doxtable { + border-collapse: collapse; + margin: 0.5em; +} + +table.doxtable td,table.doxtable th { + border: 1px solid #DDD; + padding: 3px 7px 2px; +} + +table.doxtable th { + background-color: #F3F3F3; + color: #000; + padding-bottom: 4px; + padding-top: 5px; + text-align: left; + font-weight: bold; +} + +.tabsearch { + top: 0; + left: 10px; + height: 36px; + z-index: 101; + overflow: hidden; + font-size: 13px; +} + +div.navpath { + padding: 0.25em; +} + +.navpath ul { + font-size: x-small; + color: #8AA0CC; + overflow: hidden; + margin: 0; + padding: 0; +} + +.navpath li { + list-style-type: none; + float: left; + padding-left: 10px; + padding-right: 15px; + color: #364D7C; +} + +.navpath a { + display: block; + text-decoration: none; + outline: none; +} + +.navpath a:hover { + color: #6884BD; +} + +div.summary { + float: right; + font-size: x-small; + padding: 0.25em 0.5em 0 0; + width: 50%; + text-align: right; +} + +div.summary a { + white-space: nowrap; +} + +div.header { + background-color: #F3F3F3; + margin: 0; + border: 0; +} + +div.headertitle { + font-size: 180%; + font-weight: bold; + color: #FFF; + padding: 0.125em 0.25em 0.125em 0.25em; + background-color: #333; + background: linear-gradient(to bottom, #333 0%, #111 100%); + border: solid 1px #444; + border-top: 0; + border-radius: 0 0 6px 6px; +} + +div.line { + font-family: monospace, fixed; + font-size: 13px; + min-height: 13px; + line-height: 1.0; + text-wrap: avoid; + white-space: pre-wrap; + text-indent: -53px; + padding-left: 53px; + padding-bottom: 0; + margin: 0; +} + +.glow { + background-color: cyan; + box-shadow: 0 0 10px cyan; +} + +span.lineno { + padding-right: 4px; + text-align: right; + border-right: 2px solid #0F0; + background-color: #E8E8E8; + white-space: pre; +} +span.lineno a { + background-color: #D8D8D8; +} + +span.lineno a:hover { + background-color: #C8C8C8; +} + +.tabs, .tabs2, .navpath { + background-image: none; + background-color: #333; + background: linear-gradient(to bottom, #333 0%, #111 100%); + border: 0; + border-bottom: solid 2px #000; + padding: 0; + padding-top: 2px; + font-size: small; +} + +#navrow1 { + border: 0; +} + +th { + text-align: left; +} + +.mlabel { + padding: 0.125em; +} + +/* tabs*/ + +.tablist { + margin: 0; + padding: 0; + display: table; +} + +.tablist li { + display: table-cell; + line-height: 2em; + list-style: none; + background-color: #333; + background: linear-gradient(to bottom, #444 0%, #222 100%); + border: 1px solid #222; + border-bottom: 0; + border-radius: 6px 6px 0 0; + color: #DDD; +} + +.tablist a { + display: block; + padding: 0 20px; + font-weight: bold; + color: #859900; + text-decoration: none; + outline: none; +} + +.header a { + color: #859900; +} + +.tabs3 .tablist a { + padding: 0 10px; +} + +.tablist a:hover { + color: #fff; + text-shadow: 0 1px 1px rgba(0, 0, 0, 1.0); + text-decoration: none; +} + +.tablist li.current a { + color: #fff; + text-shadow: 0 1px 1px rgba(0, 0, 0, 1.0); +} + +span.icon { + display: none; +} diff --git a/lilv.pc.in b/lilv.pc.in new file mode 100644 index 0000000..1f3b57d --- /dev/null +++ b/lilv.pc.in @@ -0,0 +1,11 @@ +prefix=@PREFIX@ +exec_prefix=@EXEC_PREFIX@ +libdir=@LIBDIR@ +includedir=@INCLUDEDIR@ + +Name: Lilv +Version: @LILV_VERSION@ +Description: Simple C library for hosting LV2 plugins +Requires: lv2 @PKG_serd_0@ @PKG_sord_0@ @PKG_sratom_0@ +Libs: -L${libdir} -l@LIB_LILV@ @LILV_PKG_LIBS@ +Cflags: -I${includedir}/lilv-@LILV_MAJOR_VERSION@ diff --git a/lilv.ttl b/lilv.ttl new file mode 100644 index 0000000..edfb0e5 --- /dev/null +++ b/lilv.ttl @@ -0,0 +1,29 @@ +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix : <http://usefulinc.com/ns/doap#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . + +<http://drobilla.net/software/lilv> + a :Project ; + :bug-database <http://dev.drobilla.net/query?status=new&status=assigned&status=reopened&component=LILV&order=priority> ; + :developer [ + a foaf:Person ; + rdfs:seeAlso <http://drobilla.net/drobilla.rdf> ; + foaf:homepage <http://drobilla.net> ; + foaf:mbox_sha1sum "253b3c58086250260bac1232d744d150274ad308" ; + foaf:name "David Robillard" + ] ; + :download-page <http://download.drobilla.net> ; + :homepage <http://drobilla.net/software/lilv> ; + :license <http://usefulinc.com/doap/licenses/gpl> ; + :name "LILV" ; + :programming-language "C", "Turtle" ; + :repository [ + :browse <http://dev.drobilla.net/browser/trunk/lilv> ; + :location <http://svn.drobilla.net/lad/trunk/lilv> ; + a :SVNRepository + ] ; + :shortdesc "Library for simple use of LV2 plugins" ; + :shortname "LILV" . + + diff --git a/lilv/lilv.h b/lilv/lilv.h new file mode 100644 index 0000000..f709cf1 --- /dev/null +++ b/lilv/lilv.h @@ -0,0 +1,1844 @@ +/* + Copyright 2007-2016 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file lilv.h API for Lilv, a lightweight LV2 host library. +*/ + +#ifndef LILV_LILV_H +#define LILV_LILV_H + +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" + +#ifdef LILV_SHARED +# ifdef _WIN32 +# define LILV_LIB_IMPORT __declspec(dllimport) +# define LILV_LIB_EXPORT __declspec(dllexport) +# else +# define LILV_LIB_IMPORT __attribute__((visibility("default"))) +# define LILV_LIB_EXPORT __attribute__((visibility("default"))) +# endif +# ifdef LILV_INTERNAL +# define LILV_API LILV_LIB_EXPORT +# else +# define LILV_API LILV_LIB_IMPORT +# endif +#else +# define LILV_API +#endif +#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) +# define LILV_DEPRECATED __attribute__((__deprecated__)) +#else +# define LILV_DEPRECATED +#endif + +#ifdef __cplusplus +extern "C" { +#else +# include <stdbool.h> +#endif + +#define LILV_NS_DOAP "http://usefulinc.com/ns/doap#" +#define LILV_NS_FOAF "http://xmlns.com/foaf/0.1/" +#define LILV_NS_LILV "http://drobilla.net/ns/lilv#" +#define LILV_NS_LV2 "http://lv2plug.in/ns/lv2core#" +#define LILV_NS_OWL "http://www.w3.org/2002/07/owl#" +#define LILV_NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#" +#define LILV_NS_RDFS "http://www.w3.org/2000/01/rdf-schema#" +#define LILV_NS_XSD "http://www.w3.org/2001/XMLSchema#" + +#define LILV_URI_ATOM_PORT "http://lv2plug.in/ns/ext/atom#AtomPort" +#define LILV_URI_AUDIO_PORT "http://lv2plug.in/ns/lv2core#AudioPort" +#define LILV_URI_CONTROL_PORT "http://lv2plug.in/ns/lv2core#ControlPort" +#define LILV_URI_CV_PORT "http://lv2plug.in/ns/lv2core#CVPort" +#define LILV_URI_EVENT_PORT "http://lv2plug.in/ns/ext/event#EventPort" +#define LILV_URI_INPUT_PORT "http://lv2plug.in/ns/lv2core#InputPort" +#define LILV_URI_MIDI_EVENT "http://lv2plug.in/ns/ext/midi#MidiEvent" +#define LILV_URI_OUTPUT_PORT "http://lv2plug.in/ns/lv2core#OutputPort" +#define LILV_URI_PORT "http://lv2plug.in/ns/lv2core#Port" + +typedef struct LilvPluginImpl LilvPlugin; /**< LV2 Plugin. */ +typedef struct LilvPluginClassImpl LilvPluginClass; /**< Plugin Class. */ +typedef struct LilvPortImpl LilvPort; /**< Port. */ +typedef struct LilvScalePointImpl LilvScalePoint; /**< Scale Point. */ +typedef struct LilvUIImpl LilvUI; /**< Plugin UI. */ +typedef struct LilvNodeImpl LilvNode; /**< Typed Value. */ +typedef struct LilvWorldImpl LilvWorld; /**< Lilv World. */ +typedef struct LilvInstanceImpl LilvInstance; /**< Plugin instance. */ +typedef struct LilvStateImpl LilvState; /**< Plugin state. */ + +typedef void LilvIter; /**< Collection iterator */ +typedef void LilvPluginClasses; /**< set<PluginClass>. */ +typedef void LilvPlugins; /**< set<Plugin>. */ +typedef void LilvScalePoints; /**< set<ScalePoint>. */ +typedef void LilvUIs; /**< set<UI>. */ +typedef void LilvNodes; /**< set<Node>. */ + +/** + @defgroup lilv Lilv + Lilv is a simple yet powerful C API for using LV2 plugins. + + For more information about LV2, see <http://lv2plug.in>. + For more information about Lilv, see <http://drobilla.net/software/lilv>. + @{ +*/ + +/** + Free memory allocated by Lilv. + + This function exists because some systems require memory allocated by a + library to be freed by code in the same library. It is otherwise equivalent + to the standard C free() function. +*/ +LILV_API void +lilv_free(void* ptr); + +/** + @name Node + @{ +*/ + +/** + Convert a file URI string to a local path string. + For example, "file://foo/bar/baz.ttl" returns "/foo/bar/baz.ttl". + Return value is shared and must not be deleted by caller. + This function does not handle escaping correctly and should not be used for + general file URIs. Use lilv_file_uri_parse() instead. + @return `uri` converted to a path, or NULL on failure (URI is not local). +*/ +LILV_API LILV_DEPRECATED const char* +lilv_uri_to_path(const char* uri); + +/** + Convert a file URI string to a local path string. + For example, "file://foo/bar%20one/baz.ttl" returns "/foo/bar one/baz.ttl". + Return value must be freed by caller with lilv_free(). + @param uri The file URI to parse. + @param hostname If non-NULL, set to the hostname in the URI, if any. + @return `uri` converted to a path, or NULL on failure (URI is not local). +*/ +LILV_API char* +lilv_file_uri_parse(const char* uri, char** hostname); + +/** + Create a new URI value. + Returned value must be freed by caller with lilv_node_free(). +*/ +LILV_API LilvNode* +lilv_new_uri(LilvWorld* world, const char* uri); + +/** + Create a new file URI value. + @param world The world. + @param host Host name, or NULL. + @param path Path on host. + @return A new node that must be freed by caller. + + Relative paths are resolved against the current working directory. Note + that this may yield unexpected results if `host` is another machine. +*/ +LILV_API LilvNode* +lilv_new_file_uri(LilvWorld* world, const char* host, const char* path); + +/** + Create a new string value (with no language). + Returned value must be freed by caller with lilv_node_free(). +*/ +LILV_API LilvNode* +lilv_new_string(LilvWorld* world, const char* str); + +/** + Create a new integer value. + Returned value must be freed by caller with lilv_node_free(). +*/ +LILV_API LilvNode* +lilv_new_int(LilvWorld* world, int val); + +/** + Create a new floating point value. + Returned value must be freed by caller with lilv_node_free(). +*/ +LILV_API LilvNode* +lilv_new_float(LilvWorld* world, float val); + +/** + Create a new boolean value. + Returned value must be freed by caller with lilv_node_free(). +*/ +LILV_API LilvNode* +lilv_new_bool(LilvWorld* world, bool val); + +/** + Free a LilvNode. + It is safe to call this function on NULL. +*/ +LILV_API void +lilv_node_free(LilvNode* val); + +/** + Duplicate a LilvNode. +*/ +LILV_API LilvNode* +lilv_node_duplicate(const LilvNode* val); + +/** + Return whether two values are equivalent. +*/ +LILV_API bool +lilv_node_equals(const LilvNode* value, const LilvNode* other); + +/** + Return this value as a Turtle/SPARQL token. + Returned value must be freed by caller with lilv_free(). + <table> + <caption>Example Turtle Tokens</caption> + <tr><th>URI</th><td><http://example.org/foo ></td></tr> + <tr><th>QName</th><td>doap:name</td></tr> + <tr><th>String</th><td>"this is a string"</td></tr> + <tr><th>Float</th><td>1.0</td></tr> + <tr><th>Integer</th><td>1</td></tr> + <tr><th>Boolean</th><td>true</td></tr> + </table> +*/ +LILV_API char* +lilv_node_get_turtle_token(const LilvNode* value); + +/** + Return whether the value is a URI (resource). +*/ +LILV_API bool +lilv_node_is_uri(const LilvNode* value); + +/** + Return this value as a URI string, e.g. "http://example.org/foo". + Valid to call only if `lilv_node_is_uri(value)` returns true. + Returned value is owned by `value` and must not be freed by caller. +*/ +LILV_API const char* +lilv_node_as_uri(const LilvNode* value); + +/** + Return whether the value is a blank node (resource with no URI). +*/ +LILV_API bool +lilv_node_is_blank(const LilvNode* value); + +/** + Return this value as a blank node identifier, e.g. "genid03". + Valid to call only if `lilv_node_is_blank(value)` returns true. + Returned value is owned by `value` and must not be freed by caller. +*/ +LILV_API const char* +lilv_node_as_blank(const LilvNode* value); + +/** + Return whether this value is a literal (i.e. not a URI). + Returns true if `value` is a string or numeric value. +*/ +LILV_API bool +lilv_node_is_literal(const LilvNode* value); + +/** + Return whether this value is a string literal. + Returns true if `value` is a string value (and not numeric). +*/ +LILV_API bool +lilv_node_is_string(const LilvNode* value); + +/** + Return `value` as a string. +*/ +LILV_API const char* +lilv_node_as_string(const LilvNode* value); + +/** + Return the path of a file URI node. + Returns NULL if `value` is not a file URI. + Returned value must be freed by caller with lilv_free(). +*/ +LILV_API char* +lilv_node_get_path(const LilvNode* value, char** hostname); + +/** + Return whether this value is a decimal literal. +*/ +LILV_API bool +lilv_node_is_float(const LilvNode* value); + +/** + Return `value` as a float. + Valid to call only if `lilv_node_is_float(value)` or + `lilv_node_is_int(value)` returns true. +*/ +LILV_API float +lilv_node_as_float(const LilvNode* value); + +/** + Return whether this value is an integer literal. +*/ +LILV_API bool +lilv_node_is_int(const LilvNode* value); + +/** + Return `value` as an integer. + Valid to call only if `lilv_node_is_int(value)` returns true. +*/ +LILV_API int +lilv_node_as_int(const LilvNode* value); + +/** + Return whether this value is a boolean. +*/ +LILV_API bool +lilv_node_is_bool(const LilvNode* value); + +/** + Return `value` as a bool. + Valid to call only if `lilv_node_is_bool(value)` returns true. +*/ +LILV_API bool +lilv_node_as_bool(const LilvNode* value); + +/** + @} + @name Collections + Lilv has several collection types for holding various types of value: + <ul> + <li>LilvPlugins (function prefix "lilv_plugins_")</li> + <li>LilvPluginClasses (function prefix "lilv_plugin_classes_")</li> + <li>LilvScalePoints (function prefix "lilv_scale_points_")</li> + <li>LilvNodes (function prefix "lilv_nodes_")</li> + <li>LilvUIs (function prefix "lilv_uis_")</li> + </ul> + + Each collection type supports a similar basic API (except LilvPlugins which + is internal and thus lacks a free function): + <ul> + <li>void PREFIX_free (coll)</li> + <li>unsigned PREFIX_size (coll)</li> + <li>LilvIter* PREFIX_begin (coll)</li> + </ul> + @{ +*/ + +/* Collections */ + +/** + Iterate over each element of a collection. + @code + LILV_FOREACH(plugin_classes, i, classes) { + LilvPluginClass c = lilv_plugin_classes_get(classes, i); + // ... + } + @endcode +*/ +#define LILV_FOREACH(colltype, iter, collection) \ + for (LilvIter* iter = lilv_ ## colltype ## _begin(collection); \ + !lilv_ ## colltype ## _is_end(collection, iter); \ + (iter) = lilv_ ## colltype ## _next(collection, iter)) + +/* LilvPluginClasses */ + +LILV_API void +lilv_plugin_classes_free(LilvPluginClasses* collection); + +LILV_API unsigned +lilv_plugin_classes_size(const LilvPluginClasses* collection); + +LILV_API LilvIter* +lilv_plugin_classes_begin(const LilvPluginClasses* collection); + +LILV_API const LilvPluginClass* +lilv_plugin_classes_get(const LilvPluginClasses* collection, LilvIter* i); + +LILV_API LilvIter* +lilv_plugin_classes_next(const LilvPluginClasses* collection, LilvIter* i); + +LILV_API bool +lilv_plugin_classes_is_end(const LilvPluginClasses* collection, LilvIter* i); + +/** + Get a plugin class from `classes` by URI. + Return value is shared (stored in `classes`) and must not be freed or + modified by the caller in any way. + @return NULL if no plugin class with `uri` is found in `classes`. +*/ +LILV_API const LilvPluginClass* +lilv_plugin_classes_get_by_uri(const LilvPluginClasses* classes, + const LilvNode* uri); + +/* ScalePoints */ + +LILV_API void +lilv_scale_points_free(LilvScalePoints* collection); + +LILV_API unsigned +lilv_scale_points_size(const LilvScalePoints* collection); + +LILV_API LilvIter* +lilv_scale_points_begin(const LilvScalePoints* collection); + +LILV_API const LilvScalePoint* +lilv_scale_points_get(const LilvScalePoints* collection, LilvIter* i); + +LILV_API LilvIter* +lilv_scale_points_next(const LilvScalePoints* collection, LilvIter* i); + +LILV_API bool +lilv_scale_points_is_end(const LilvScalePoints* collection, LilvIter* i); + +/* UIs */ + +LILV_API void +lilv_uis_free(LilvUIs* collection); + +LILV_API unsigned +lilv_uis_size(const LilvUIs* collection); + +LILV_API LilvIter* +lilv_uis_begin(const LilvUIs* collection); + +LILV_API const LilvUI* +lilv_uis_get(const LilvUIs* collection, LilvIter* i); + +LILV_API LilvIter* +lilv_uis_next(const LilvUIs* collection, LilvIter* i); + +LILV_API bool +lilv_uis_is_end(const LilvUIs* collection, LilvIter* i); + +/** + Get a UI from `uis` by URI. + Return value is shared (stored in `uis`) and must not be freed or + modified by the caller in any way. + @return NULL if no UI with `uri` is found in `list`. +*/ +LILV_API const LilvUI* +lilv_uis_get_by_uri(const LilvUIs* uis, + const LilvNode* uri); + +/* Nodes */ + +LILV_API void +lilv_nodes_free(LilvNodes* collection); + +LILV_API unsigned +lilv_nodes_size(const LilvNodes* collection); + +LILV_API LilvIter* +lilv_nodes_begin(const LilvNodes* collection); + +LILV_API const LilvNode* +lilv_nodes_get(const LilvNodes* collection, LilvIter* i); + +LILV_API LilvIter* +lilv_nodes_next(const LilvNodes* collection, LilvIter* i); + +LILV_API bool +lilv_nodes_is_end(const LilvNodes* collection, LilvIter* i); + +LILV_API LilvNode* +lilv_nodes_get_first(const LilvNodes* collection); + +/** + Return whether `values` contains `value`. +*/ +LILV_API bool +lilv_nodes_contains(const LilvNodes* nodes, const LilvNode* value); + +/** + Return a new LilvNodes that contains all nodes from both `a` and `b`. +*/ +LILV_API LilvNodes* +lilv_nodes_merge(const LilvNodes* a, const LilvNodes* b); + +/* Plugins */ + +LILV_API unsigned +lilv_plugins_size(const LilvPlugins* collection); + +LILV_API LilvIter* +lilv_plugins_begin(const LilvPlugins* collection); + +LILV_API const LilvPlugin* +lilv_plugins_get(const LilvPlugins* collection, LilvIter* i); + +LILV_API LilvIter* +lilv_plugins_next(const LilvPlugins* collection, LilvIter* i); + +LILV_API bool +lilv_plugins_is_end(const LilvPlugins* collection, LilvIter* i); + +/** + Get a plugin from `plugins` by URI. + Return value is shared (stored in `plugins`) and must not be freed or + modified by the caller in any way. + @return NULL if no plugin with `uri` is found in `plugins`. +*/ +LILV_API const LilvPlugin* +lilv_plugins_get_by_uri(const LilvPlugins* plugins, + const LilvNode* uri); + +/** + @} + @name World + The "world" represents all Lilv state, and is used to discover/load/cache + LV2 data (plugins, UIs, and extensions). + Normal hosts which just need to load plugins by URI should simply use + lilv_world_load_all() to discover/load the system's LV2 resources. + @{ +*/ + +/** + Initialize a new, empty world. + If initialization fails, NULL is returned. +*/ +LILV_API LilvWorld* +lilv_world_new(void); + +/** + Enable/disable language filtering. + Language filtering applies to any functions that return (a) value(s). + With filtering enabled, Lilv will automatically return the best value(s) + for the current LANG. With filtering disabled, all matching values will + be returned regardless of language tag. Filtering is enabled by default. +*/ +#define LILV_OPTION_FILTER_LANG "http://drobilla.net/ns/lilv#filter-lang" + +/** + Enable/disable dynamic manifest support. + Dynamic manifest data will only be loaded if this option is true. +*/ +#define LILV_OPTION_DYN_MANIFEST "http://drobilla.net/ns/lilv#dyn-manifest" + +/** + Set an option option for `world`. + + Currently recognized options: + @ref LILV_OPTION_FILTER_LANG + @ref LILV_OPTION_DYN_MANIFEST +*/ +LILV_API void +lilv_world_set_option(LilvWorld* world, + const char* uri, + const LilvNode* value); + +/** + Destroy the world, mwahaha. + It is safe to call this function on NULL. + Note that destroying `world` will destroy all the objects it contains + (e.g. instances of LilvPlugin). Do not destroy the world until you are + finished with all objects that came from it. +*/ +LILV_API void +lilv_world_free(LilvWorld* world); + +/** + Load all installed LV2 bundles on the system. + This is the recommended way for hosts to load LV2 data. It implements the + established/standard best practice for discovering all LV2 data on the + system. The environment variable LV2_PATH may be used to control where + this function will look for bundles. + + Hosts should use this function rather than explicitly load bundles, except + in special circumstances (e.g. development utilities, or hosts that ship + with special plugin bundles which are installed to a known location). +*/ +LILV_API void +lilv_world_load_all(LilvWorld* world); + +/** + Load a specific bundle. + `bundle_uri` must be a fully qualified URI to the bundle directory, + with the trailing slash, eg. file:///usr/lib/lv2/foo.lv2/ + + Normal hosts should not need this function (use lilv_world_load_all()). + + Hosts MUST NOT attach any long-term significance to bundle paths + (e.g. in save files), since there are no guarantees they will remain + unchanged between (or even during) program invocations. Plugins (among + other things) MUST be identified by URIs (not paths) in save files. +*/ +LILV_API void +lilv_world_load_bundle(LilvWorld* world, + const LilvNode* bundle_uri); + +/** + Load all specifications from currently loaded bundles. + + This is for hosts that explicitly load specific bundles, its use is not + necessary when using lilv_world_load_all(). This function parses the + specifications and adds them to the model. +*/ +LILV_API void +lilv_world_load_specifications(LilvWorld* world); + +/** + Load all plugin classes from currently loaded specifications. + + Must be called after lilv_world_load_specifications(). This is for hosts + that explicitly load specific bundles, its use is not necessary when using + lilv_world_load_all(). +*/ +LILV_API void +lilv_world_load_plugin_classes(LilvWorld* world); + +/** + Unload a specific bundle. + + This unloads statements loaded by lilv_world_load_bundle(). Note that this + is not necessarily all information loaded from the bundle. If any resources + have been separately loaded with lilv_world_load_resource(), they must be + separately unloaded with lilv_world_unload_resource(). +*/ +LILV_API int +lilv_world_unload_bundle(LilvWorld* world, const LilvNode* bundle_uri); + +/** + Load all the data associated with the given `resource`. + @param world The world. + @param resource Must be a subject (i.e. a URI or a blank node). + @return The number of files parsed, or -1 on error + + All accessible data files linked to `resource` with rdfs:seeAlso will be + loaded into the world model. +*/ +LILV_API int +lilv_world_load_resource(LilvWorld* world, + const LilvNode* resource); + +/** + Unload all the data associated with the given `resource`. + @param world The world. + @param resource Must be a subject (i.e. a URI or a blank node). + + This unloads all data loaded by a previous call to + lilv_world_load_resource() with the given `resource`. +*/ +LILV_API int +lilv_world_unload_resource(LilvWorld* world, + const LilvNode* resource); + +/** + Get the parent of all other plugin classes, lv2:Plugin. +*/ +LILV_API const LilvPluginClass* +lilv_world_get_plugin_class(const LilvWorld* world); + +/** + Return a list of all found plugin classes. + Returned list is owned by world and must not be freed by the caller. +*/ +LILV_API const LilvPluginClasses* +lilv_world_get_plugin_classes(const LilvWorld* world); + +/** + Return a list of all found plugins. + The returned list contains just enough references to query + or instantiate plugins. The data for a particular plugin will not be + loaded into memory until a call to an lilv_plugin_* function results in + a query (at which time the data is cached with the LilvPlugin so future + queries are very fast). + + The returned list and the plugins it contains are owned by `world` + and must not be freed by caller. +*/ +LILV_API const LilvPlugins* +lilv_world_get_all_plugins(const LilvWorld* world); + +/** + Find nodes matching a triple pattern. + Either `subject` or `object` may be NULL (i.e. a wildcard), but not both. + @return All matches for the wildcard field, or NULL. +*/ +LILV_API LilvNodes* +lilv_world_find_nodes(LilvWorld* world, + const LilvNode* subject, + const LilvNode* predicate, + const LilvNode* object); + +/** + Find a single node that matches a pattern. + Exactly one of `subject`, `predicate`, `object` must be NULL. + This function is equivalent to + lilv_nodes_get_first(lilv_world_find_nodes(...)) but simplifies the common + case of only wanting a single value. + @return the first matching node, or NULL if no matches are found. +*/ +LILV_API LilvNode* +lilv_world_get(LilvWorld* world, + const LilvNode* subject, + const LilvNode* predicate, + const LilvNode* object); + +/** + Return true iff a statement matching a certain pattern exists. + + This is useful for checking if particular statement exists without having to + bother with collections and memory management. + + @param world The world. + @param subject Subject of statement, or NULL for anything. + @param predicate Predicate (key) of statement, or NULL for anything. + @param object Object (value) of statement, or NULL for anything. +*/ +LILV_API bool +lilv_world_ask(LilvWorld* world, + const LilvNode* subject, + const LilvNode* predicate, + const LilvNode* object); + +/** + Get an LV2 symbol for some subject. + + This will return the lv2:symbol property of the subject if it is given + explicitly, and otherwise will attempt to derive a symbol from the URI. + @return A string node that is a valid LV2 symbol, or NULL on error. +*/ +LILV_API LilvNode* +lilv_world_get_symbol(LilvWorld* world, const LilvNode* subject); + +/** + @} + @name Plugin + @{ +*/ + +/** + Check if `plugin` is valid. + This is not a rigorous validator, but can be used to reject some malformed + plugins that could cause bugs (e.g. plugins with missing required fields). + + Note that normal hosts do NOT need to use this - lilv does not + load invalid plugins into plugin lists. This is included for plugin + testing utilities, etc. + @return true iff `plugin` is valid. +*/ +LILV_API bool +lilv_plugin_verify(const LilvPlugin* plugin); + +/** + Get the URI of `plugin`. + Any serialization that refers to plugins should refer to them by this. + Hosts SHOULD NOT save any filesystem paths, plugin indexes, etc. in saved + files; save only the URI. + + The URI is a globally unique identifier for one specific plugin. Two + plugins with the same URI are compatible in port signature, and should + be guaranteed to work in a compatible and consistent way. If a plugin + is upgraded in an incompatible way (eg if it has different ports), it + MUST have a different URI than it's predecessor. + + @return A shared URI value which must not be modified or freed. +*/ +LILV_API const LilvNode* +lilv_plugin_get_uri(const LilvPlugin* plugin); + +/** + Get the (resolvable) URI of the plugin's "main" bundle. + This returns the URI of the bundle where the plugin itself was found. Note + that the data for a plugin may be spread over many bundles, that is, + lilv_plugin_get_data_uris() may return URIs which are not within this + bundle. + + Typical hosts should not need to use this function. + Note this always returns a fully qualified URI. If you want a local + filesystem path, use lilv_file_uri_parse(). + @return a shared string which must not be modified or freed. +*/ +LILV_API const LilvNode* +lilv_plugin_get_bundle_uri(const LilvPlugin* plugin); + +/** + Get the (resolvable) URIs of the RDF data files that define a plugin. + Typical hosts should not need to use this function. + Note this always returns fully qualified URIs. If you want local + filesystem paths, use lilv_file_uri_parse(). + @return a list of complete URLs eg. "file:///foo/ABundle.lv2/aplug.ttl", + which is shared and must not be modified or freed. +*/ +LILV_API const LilvNodes* +lilv_plugin_get_data_uris(const LilvPlugin* plugin); + +/** + Get the (resolvable) URI of the shared library for `plugin`. + Note this always returns a fully qualified URI. If you want a local + filesystem path, use lilv_file_uri_parse(). + @return a shared string which must not be modified or freed. +*/ +LILV_API const LilvNode* +lilv_plugin_get_library_uri(const LilvPlugin* plugin); + +/** + Get the name of `plugin`. + This returns the name (doap:name) of the plugin. The name may be + translated according to the current locale, this value MUST NOT be used + as a plugin identifier (use the URI for that). + Returned value must be freed by the caller. +*/ +LILV_API LilvNode* +lilv_plugin_get_name(const LilvPlugin* plugin); + +/** + Get the class this plugin belongs to (e.g. Filters). +*/ +LILV_API const LilvPluginClass* +lilv_plugin_get_class(const LilvPlugin* plugin); + +/** + Get a value associated with the plugin in a plugin's data files. + `predicate` must be either a URI or a QName. + + Returns the ?object of all triples found of the form: + + <code><plugin-uri> predicate ?object</code> + + May return NULL if the property was not found, or if object(s) is not + sensibly represented as a LilvNodes (e.g. blank nodes). + Return value must be freed by caller with lilv_nodes_free(). +*/ +LILV_API LilvNodes* +lilv_plugin_get_value(const LilvPlugin* plugin, + const LilvNode* predicate); + +/** + Return whether a feature is supported by a plugin. + This will return true if the feature is an optional or required feature + of the plugin. +*/ +LILV_API bool +lilv_plugin_has_feature(const LilvPlugin* plugin, + const LilvNode* feature); + +/** + Get the LV2 Features supported (required or optionally) by a plugin. + A feature is "supported" by a plugin if it is required OR optional. + + Since required features have special rules the host must obey, this function + probably shouldn't be used by normal hosts. Using lilv_plugin_get_optional_features() + and lilv_plugin_get_required_features() separately is best in most cases. + + Returned value must be freed by caller with lilv_nodes_free(). +*/ +LILV_API LilvNodes* +lilv_plugin_get_supported_features(const LilvPlugin* plugin); + +/** + Get the LV2 Features required by a plugin. + If a feature is required by a plugin, hosts MUST NOT use the plugin if they do not + understand (or are unable to support) that feature. + + All values returned here MUST be passed to the plugin's instantiate method + (along with data, if necessary, as defined by the feature specification) + or plugin instantiation will fail. + + Return value must be freed by caller with lilv_nodes_free(). +*/ +LILV_API LilvNodes* +lilv_plugin_get_required_features(const LilvPlugin* plugin); + +/** + Get the LV2 Features optionally supported by a plugin. + Hosts MAY ignore optional plugin features for whatever reasons. Plugins + MUST operate (at least somewhat) if they are instantiated without being + passed optional features. + + Return value must be freed by caller with lilv_nodes_free(). +*/ +LILV_API LilvNodes* +lilv_plugin_get_optional_features(const LilvPlugin* plugin); + +/** + Return whether or not a plugin provides a specific extension data. +*/ +LILV_API bool +lilv_plugin_has_extension_data(const LilvPlugin* plugin, + const LilvNode* uri); + +/** + Get a sequence of all extension data provided by a plugin. + This can be used to find which URIs lilv_instance_get_extension_data() + will return a value for without instantiating the plugin. +*/ +LILV_API LilvNodes* +lilv_plugin_get_extension_data(const LilvPlugin* plugin); + +/** + Get the number of ports on this plugin. +*/ +LILV_API uint32_t +lilv_plugin_get_num_ports(const LilvPlugin* plugin); + +/** + Get the port ranges (minimum, maximum and default values) for all ports. + `min_values`, `max_values` and `def_values` must either point to an array + of N floats, where N is the value returned by lilv_plugin_get_num_ports() + for this plugin, or NULL. The elements of the array will be set to the + the minimum, maximum and default values of the ports on this plugin, + with array index corresponding to port index. If a port doesn't have a + minimum, maximum or default value, or the port's type is not float, the + corresponding array element will be set to NAN. + + This is a convenience method for the common case of getting the range of + all float ports on a plugin, and may be significantly faster than + repeated calls to lilv_port_get_range(). +*/ +LILV_API void +lilv_plugin_get_port_ranges_float(const LilvPlugin* plugin, + float* min_values, + float* max_values, + float* def_values); + +/** + Get the number of ports on this plugin that are members of some class(es). + Note that this is a varargs function so ports fitting any type 'profile' + desired can be found quickly. REMEMBER TO TERMINATE THE PARAMETER LIST + OF THIS FUNCTION WITH NULL OR VERY NASTY THINGS WILL HAPPEN. +*/ +LILV_API uint32_t +lilv_plugin_get_num_ports_of_class(const LilvPlugin* plugin, + const LilvNode* class_1, ...); + +#ifndef SWIG +/** + Variant of lilv_plugin_get_num_ports_of_class() that takes a va_list. + + This function calls va_arg() on `args` but does not call va_end(). +*/ +LILV_API uint32_t +lilv_plugin_get_num_ports_of_class_va(const LilvPlugin* plugin, + const LilvNode* class_1, + va_list args); +#endif + +/** + Return whether or not the plugin introduces (and reports) latency. + The index of the latency port can be found with + lilv_plugin_get_latency_port() ONLY if this function returns true. +*/ +LILV_API bool +lilv_plugin_has_latency(const LilvPlugin* plugin); + +/** + Return the index of the plugin's latency port. + It is a fatal error to call this on a plugin without checking if the port + exists by first calling lilv_plugin_has_latency(). + + Any plugin that introduces unwanted latency that should be compensated for + (by hosts with the ability/need) MUST provide this port, which is a control + rate output port that reports the latency for each cycle in frames. +*/ +LILV_API uint32_t +lilv_plugin_get_latency_port_index(const LilvPlugin* plugin); + +/** + Get a port on `plugin` by `index`. +*/ +LILV_API const LilvPort* +lilv_plugin_get_port_by_index(const LilvPlugin* plugin, + uint32_t index); + +/** + Get a port on `plugin` by `symbol`. + Note this function is slower than lilv_plugin_get_port_by_index(), + especially on plugins with a very large number of ports. +*/ +LILV_API const LilvPort* +lilv_plugin_get_port_by_symbol(const LilvPlugin* plugin, + const LilvNode* symbol); + +/** + Get a port on `plugin` by its lv2:designation. + + The designation of a port describes the meaning, assignment, allocation or + role of the port, e.g. "left channel" or "gain". If found, the port with + matching `port_class` and `designation` is be returned, otherwise NULL is + returned. The `port_class` can be used to distinguish the input and output + ports for a particular designation. If `port_class` is NULL, any port with + the given designation will be returned. +*/ +LILV_API const LilvPort* +lilv_plugin_get_port_by_designation(const LilvPlugin* plugin, + const LilvNode* port_class, + const LilvNode* designation); + +/** + Get the project the plugin is a part of. + + More information about the project can be read via lilv_world_find_nodes(), + typically using properties from DOAP (e.g. doap:name). +*/ +LILV_API LilvNode* +lilv_plugin_get_project(const LilvPlugin* plugin); + +/** + Get the full name of the plugin's author. + Returns NULL if author name is not present. + Returned value must be freed by caller. +*/ +LILV_API LilvNode* +lilv_plugin_get_author_name(const LilvPlugin* plugin); + +/** + Get the email address of the plugin's author. + Returns NULL if author email address is not present. + Returned value must be freed by caller. +*/ +LILV_API LilvNode* +lilv_plugin_get_author_email(const LilvPlugin* plugin); + +/** + Get the address of the plugin author's home page. + Returns NULL if author homepage is not present. + Returned value must be freed by caller. +*/ +LILV_API LilvNode* +lilv_plugin_get_author_homepage(const LilvPlugin* plugin); + +/** + Return true iff `plugin` has been replaced by another plugin. + + The plugin will still be usable, but hosts should hide them from their + user interfaces to prevent users from using deprecated plugins. +*/ +LILV_API bool +lilv_plugin_is_replaced(const LilvPlugin* plugin); + +/** + Write the Turtle description of `plugin` to `plugin_file`. + + This function is particularly useful for porting plugins in conjunction with + an LV2 bridge such as NASPRO. +*/ +LILV_API void +lilv_plugin_write_description(LilvWorld* world, + const LilvPlugin* plugin, + const LilvNode* base_uri, + FILE* plugin_file); + +/** + Write a manifest entry for `plugin` to `manifest_file`. + + This function is intended for use with lilv_plugin_write_description() to + write a complete description of a plugin to a bundle. +*/ +LILV_API void +lilv_plugin_write_manifest_entry(LilvWorld* world, + const LilvPlugin* plugin, + const LilvNode* base_uri, + FILE* manifest_file, + const char* plugin_file_path); + +/** + Get the resources related to `plugin` with lv2:appliesTo. + + Some plugin-related resources are not linked directly to the plugin with + rdfs:seeAlso and thus will not be automatically loaded along with the plugin + data (usually for performance reasons). All such resources of the given @c + type related to `plugin` can be accessed with this function. + + If `type` is NULL, all such resources will be returned, regardless of type. + + To actually load the data for each returned resource, use + lilv_world_load_resource(). +*/ +LILV_API LilvNodes* +lilv_plugin_get_related(const LilvPlugin* plugin, const LilvNode* type); + +/** + @} + @name Port + @{ +*/ + +/** + Get the RDF node of `port`. + + Ports nodes may be may be URIs or blank nodes. + + @return A shared node which must not be modified or freed. +*/ +LILV_API const LilvNode* +lilv_port_get_node(const LilvPlugin* plugin, + const LilvPort* port); + +/** + Port analog of lilv_plugin_get_value(). +*/ +LILV_API LilvNodes* +lilv_port_get_value(const LilvPlugin* plugin, + const LilvPort* port, + const LilvNode* predicate); + +/** + Get a single property value of a port. + + This is equivalent to lilv_nodes_get_first(lilv_port_get_value(...)) but is + simpler to use in the common case of only caring about one value. The + caller is responsible for freeing the returned node. +*/ +LILV_API LilvNode* +lilv_port_get(const LilvPlugin* plugin, + const LilvPort* port, + const LilvNode* predicate); + +/** + Return the LV2 port properties of a port. +*/ +LILV_API LilvNodes* +lilv_port_get_properties(const LilvPlugin* plugin, + const LilvPort* port); + +/** + Return whether a port has a certain property. +*/ +LILV_API bool +lilv_port_has_property(const LilvPlugin* plugin, + const LilvPort* port, + const LilvNode* property); + +/** + Return whether a port supports a certain event type. + + More precisely, this returns true iff the port has an atom:supports or an + ev:supportsEvent property with `event_type` as the value. +*/ +LILV_API bool +lilv_port_supports_event(const LilvPlugin* plugin, + const LilvPort* port, + const LilvNode* event_type); + +/** + Get the index of a port. + The index is only valid for the life of the plugin and may change between + versions. For a stable identifier, use the symbol. +*/ +LILV_API uint32_t +lilv_port_get_index(const LilvPlugin* plugin, + const LilvPort* port); + +/** + Get the symbol of a port. + The 'symbol' is a short string, a valid C identifier. + Returned value is owned by `port` and must not be freed. +*/ +LILV_API const LilvNode* +lilv_port_get_symbol(const LilvPlugin* plugin, + const LilvPort* port); + +/** + Get the name of a port. + This is guaranteed to return the untranslated name (the doap:name in the + data file without a language tag). Returned value must be freed by + the caller. +*/ +LILV_API LilvNode* +lilv_port_get_name(const LilvPlugin* plugin, + const LilvPort* port); + +/** + Get all the classes of a port. + This can be used to determine if a port is an input, output, audio, + control, midi, etc, etc, though it's simpler to use lilv_port_is_a(). + The returned list does not include lv2:Port, which is implied. + Returned value is shared and must not be destroyed by caller. +*/ +LILV_API const LilvNodes* +lilv_port_get_classes(const LilvPlugin* plugin, + const LilvPort* port); + +/** + Determine if a port is of a given class (input, output, audio, etc). + For convenience/performance/extensibility reasons, hosts are expected to + create a LilvNode for each port class they "care about". Well-known type + URI strings are defined (e.g. LILV_URI_INPUT_PORT) for convenience, but + this function is designed so that Lilv is usable with any port types + without requiring explicit support in Lilv. +*/ +LILV_API bool +lilv_port_is_a(const LilvPlugin* plugin, + const LilvPort* port, + const LilvNode* port_class); + +/** + Get the default, minimum, and maximum values of a port. + + `def`, `min`, and `max` are outputs, pass pointers to uninitialized + LilvNode* variables. These will be set to point at new values (which must + be freed by the caller using lilv_node_free()), or NULL if the value does + not exist. +*/ +LILV_API void +lilv_port_get_range(const LilvPlugin* plugin, + const LilvPort* port, + LilvNode** def, + LilvNode** min, + LilvNode** max); + +/** + Get the scale points (enumeration values) of a port. + This returns a collection of 'interesting' named values of a port + (e.g. appropriate entries for a UI selector associated with this port). + Returned value may be NULL if `port` has no scale points, otherwise it + must be freed by caller with lilv_scale_points_free(). +*/ +LILV_API LilvScalePoints* +lilv_port_get_scale_points(const LilvPlugin* plugin, + const LilvPort* port); + +/** + @} + @name Plugin State + @{ +*/ + +/** + Load a state snapshot from the world RDF model. + This function can be used to load the default state of a plugin by passing + the plugin URI as the `subject` parameter. + @param world The world. + @param map URID mapper. + @param node The subject of the state description (e.g. a preset URI). + @return A new LilvState which must be freed with lilv_state_free(), or NULL. +*/ +LILV_API LilvState* +lilv_state_new_from_world(LilvWorld* world, + LV2_URID_Map* map, + const LilvNode* node); + +/** + Load a state snapshot from a file. + @param world The world. + @param map URID mapper. + @param subject The subject of the state description (e.g. a preset URI). + @param path The path of the file containing the state description. + @return A new LilvState which must be freed with lilv_state_free(). + + If `subject` is NULL, it is taken to be the URI of the file (i.e. + "<>" in Turtle). + + This function parses the file separately to create the state, it does not + parse the file into the world model, i.e. the returned state is the only + new memory consumed once this function returns. +*/ +LILV_API LilvState* +lilv_state_new_from_file(LilvWorld* world, + LV2_URID_Map* map, + const LilvNode* subject, + const char* path); + +/** + Load a state snapshot from a string made by lilv_state_to_string(). +*/ +LILV_API LilvState* +lilv_state_new_from_string(LilvWorld* world, + LV2_URID_Map* map, + const char* str); + +/** + Function to get a port value. + @param port_symbol The symbol of the port. + @param user_data The user_data passed to lilv_state_new_from_instance(). + @param size (Output) The size of the returned value. + @param type (Output) The URID of the type of the returned value. + @return A pointer to the port value. + + This function MUST set `size` and `type` appropriately. +*/ +typedef const void* (*LilvGetPortValueFunc)(const char* port_symbol, + void* user_data, + uint32_t* size, + uint32_t* type); + +/** + Create a new state snapshot from a plugin instance. + + @param plugin The plugin this state applies to. + + @param instance An instance of `plugin`. + + @param map The map to use for mapping URIs in state. + + @param file_dir Directory of files created by the plugin earlier (or NULL). + This is for hosts that support file creation at any time with state + state:makePath. These files will be copied as necessary to `copy_dir` and + not be referred to directly in state (a temporary directory is appropriate). + + @param copy_dir Directory of copies of files in `file_dir` (or NULL). This + directory will have the same structure as `file_dir` but with possibly + modified file names to distinguish different revisions. If you only care + about saving one state snapshot, it can be the same as `save_dir`. Plugin + state will refer to files in this directory. + + @param save_dir Directory of files created by plugin during save (or NULL). + If the state will be saved, this should be the bundle directory later passed + to lilv_state_save(). + + @param get_value Function to get port values (or NULL). If NULL, the + returned state will not represent port values. This should only be NULL in + hosts that save and restore port values via some other mechanism. + + @param user_data User data to pass to `get_value`. + + @param link_dir Directory of links to external files (or NULL). A link will + be made in this directory to any external files referred to in plugin state. + In turn, links will be created in the save directory to these links (e.g. + save_dir/file => link_dir/file => /foo/bar/file). This allows many state + snapshots to share a single link to an external file, so archival + (e.g. with tar -h) will not create several copies of the file. If this is + not required, it can be the same as save_dir. + + @param flags Bitwise OR of LV2_State_Flags values. + + @param features Features to pass LV2_State_Interface.save(). + + @return A new LilvState which must be freed with lilv_state_free(). + + This function may be called simultaneously with any instance function + (except discovery functions) unless the threading class of that function + explicitly disallows this. + + To support advanced file functionality, there are several directory + parameters. Simple hosts that only wish to save a single plugins state once + may simply use the same directory for all of them (or pass NULL to not + support files at all). The multiple parameters are necessary to support + saving an instances state many times while avoiding any duplication of data. + + If supported (via state:makePath passed to LV2_Descriptor::instantiate()), + `file_dir` should be the directory where any files created by the plugin + (not during save time, e.g. during instantiation) are stored. These files + will be copied to preserve their state at this time.plugin-created files are stored. + Lilv will assume any files within this directory (recursively) are created + by the plugin and all other files are immutable. Note that this function + does not save the state, use lilv_state_save() for that. + + See <a href="http://lv2plug.in/ns/ext/state/state.h">state.h</a> from the + LV2 State extension for details on the `flags` and `features` parameters. +*/ +LILV_API LilvState* +lilv_state_new_from_instance(const LilvPlugin* plugin, + LilvInstance* instance, + LV2_URID_Map* map, + const char* file_dir, + const char* copy_dir, + const char* link_dir, + const char* save_dir, + LilvGetPortValueFunc get_value, + void* user_data, + uint32_t flags, + const LV2_Feature *const * features); + +/** + Free `state`. +*/ +LILV_API void +lilv_state_free(LilvState* state); + +/** + Return true iff `a` is equivalent to `b`. +*/ +LILV_API bool +lilv_state_equals(const LilvState* a, const LilvState* b); + +/** + Return the number of properties in `state`. +*/ +LILV_API unsigned +lilv_state_get_num_properties(const LilvState* state); + +/** + Get the URI of the plugin `state` applies to. +*/ +LILV_API const LilvNode* +lilv_state_get_plugin_uri(const LilvState* state); + +/** + Get the URI of `state`. + + This may return NULL if the state has not been saved and has no URI. +*/ +LILV_API const LilvNode* +lilv_state_get_uri(const LilvState* state); + +/** + Get the label of `state`. +*/ +LILV_API const char* +lilv_state_get_label(const LilvState* state); + +/** + Set the label of `state`. +*/ +LILV_API void +lilv_state_set_label(LilvState* state, + const char* label); + +/** + Set a metadata property on `state`. + @param state The state to set the metadata for. + @param key The key to store `value` under (URID). + @param value Pointer to the value to be stored. + @param size The size of `value` in bytes. + @param type The type of `value` (URID). + @param flags LV2_State_Flags for `value`. + @return 0 on success. + + This is a generic version of lilv_state_set_label(), which sets metadata + properties visible to hosts, but not plugins. This allows storing useful + information such as comments or preset banks. +*/ +LILV_API int +lilv_state_set_metadata(LilvState* state, + uint32_t key, + const void* value, + size_t size, + uint32_t type, + uint32_t flags); + +/** + Function to set a port value. + @param port_symbol The symbol of the port. + @param user_data The user_data passed to lilv_state_restore(). + @param size The size of `value`. + @param type The URID of the type of `value`. + @param value A pointer to the port value. +*/ +typedef void (*LilvSetPortValueFunc)(const char* port_symbol, + void* user_data, + const void* value, + uint32_t size, + uint32_t type); + +/** + Enumerate the port values in a state snapshot. + @param state The state to retrieve port values from. + @param set_value A function to receive port values. + @param user_data User data to pass to `set_value`. + + This function is a subset of lilv_state_restore() that only fires the + `set_value` callback and does not directly affect a plugin instance. This + is useful in hosts that need to retrieve the port values in a state snapshot + for special handling. +*/ +LILV_API void +lilv_state_emit_port_values(const LilvState* state, + LilvSetPortValueFunc set_value, + void* user_data); + +/** + Restore a plugin instance from a state snapshot. + @param state The state to restore, which must apply to the correct plugin. + @param instance An instance of the plugin `state` applies to, or NULL. + @param set_value A function to set a port value (may be NULL). + @param user_data User data to pass to `set_value`. + @param flags Bitwise OR of LV2_State_Flags values. + @param features Features to pass LV2_State_Interface.restore(). + + This will set all the properties of `instance`, if given, to the values + stored in `state`. If `set_value` is provided, it will be called (with the + given `user_data`) to restore each port value, otherwise the host must + restore the port values itself (using lilv_state_get_port_value()) in order + to completely restore `state`. + + If the state has properties and `instance` is given, this function is in + the "instantiation" threading class, i.e. it MUST NOT be called + simultaneously with any function on the same plugin instance. If the state + has no properties, only port values are set via `set_value`. + + See <a href="http://lv2plug.in/ns/ext/state/state.h">state.h</a> from the + LV2 State extension for details on the `flags` and `features` parameters. +*/ +LILV_API void +lilv_state_restore(const LilvState* state, + LilvInstance* instance, + LilvSetPortValueFunc set_value, + void* user_data, + uint32_t flags, + const LV2_Feature *const * features); + +/** + Save state to a file. + @param world The world. + @param map URID mapper. + @param unmap URID unmapper. + @param state State to save. + @param uri URI of state, may be NULL. + @param dir Path of the bundle directory to save into. + @param filename Path of the state file relative to `dir`. + + The format of state on disk is compatible with that defined in the LV2 + preset extension, i.e. this function may be used to save presets which can + be loaded by any host. + + If `uri` is NULL, the preset URI will be a file URI, but the bundle + can safely be moved (i.e. the state file will use "<>" as the subject). +*/ +LILV_API int +lilv_state_save(LilvWorld* world, + LV2_URID_Map* map, + LV2_URID_Unmap* unmap, + const LilvState* state, + const char* uri, + const char* dir, + const char* filename); + +/** + Save state to a string. This function does not use the filesystem. + + @param world The world. + @param map URID mapper. + @param unmap URID unmapper. + @param state The state to serialize. + @param uri URI for the state description (mandatory). + @param base_uri Base URI for serialisation. Unless you know what you are + doing, pass NULL for this, otherwise the state may not be restorable via + lilv_state_new_from_string(). +*/ +LILV_API char* +lilv_state_to_string(LilvWorld* world, + LV2_URID_Map* map, + LV2_URID_Unmap* unmap, + const LilvState* state, + const char* uri, + const char* base_uri); + +/** + Unload a state from the world and delete all associated files. + @param world The world. + @param state State to remove from the system. + + This function DELETES FILES/DIRECTORIES FROM THE FILESYSTEM! It is intended + for removing user-saved presets, but can delete any state the user has + permission to delete, including presets shipped with plugins. + + The rdfs:seeAlso file for the state will be removed. The entry in the + bundle's manifest.ttl is removed, and if this results in an empty manifest, + then the manifest file is removed. If this results in an empty bundle, then + the bundle directory is removed as well. +*/ +LILV_API int +lilv_state_delete(LilvWorld* world, + const LilvState* state); + +/** + @} + @name Scale Point + @{ +*/ + +/** + Get the label of this scale point (enumeration value) + Returned value is owned by `point` and must not be freed. +*/ +LILV_API const LilvNode* +lilv_scale_point_get_label(const LilvScalePoint* point); + +/** + Get the value of this scale point (enumeration value) + Returned value is owned by `point` and must not be freed. +*/ +LILV_API const LilvNode* +lilv_scale_point_get_value(const LilvScalePoint* point); + +/** + @} + @name Plugin Class + @{ +*/ + +/** + Get the URI of this class' superclass. + Returned value is owned by `plugin_class` and must not be freed by caller. + Returned value may be NULL, if class has no parent. +*/ +LILV_API const LilvNode* +lilv_plugin_class_get_parent_uri(const LilvPluginClass* plugin_class); + +/** + Get the URI of this plugin class. + Returned value is owned by `plugin_class` and must not be freed by caller. +*/ +LILV_API const LilvNode* +lilv_plugin_class_get_uri(const LilvPluginClass* plugin_class); + +/** + Get the label of this plugin class, ie "Oscillators". + Returned value is owned by `plugin_class` and must not be freed by caller. +*/ +LILV_API const LilvNode* +lilv_plugin_class_get_label(const LilvPluginClass* plugin_class); + +/** + Get the subclasses of this plugin class. + Returned value must be freed by caller with lilv_plugin_classes_free(). +*/ +LILV_API LilvPluginClasses* +lilv_plugin_class_get_children(const LilvPluginClass* plugin_class); + +/** + @} + @name Plugin Instance + @{ +*/ + +/** + @cond LILV_DOCUMENT_INSTANCE_IMPL +*/ + +/* Instance of a plugin. + This is exposed in the ABI to allow inlining of performance critical + functions like lilv_instance_run() (simple wrappers of functions in lv2.h). + This is for performance reasons, user code should not use this definition + in any way (which is why it is not machine documented). + Truly private implementation details are hidden via `pimpl`. +*/ +struct LilvInstanceImpl { + const LV2_Descriptor* lv2_descriptor; + LV2_Handle lv2_handle; + void* pimpl; +}; + +/** + @endcond +*/ + +/** + Instantiate a plugin. + The returned value is a lightweight handle for an LV2 plugin instance, + it does not refer to `plugin`, or any other Lilv state. The caller must + eventually free it with lilv_instance_free(). + `features` is a NULL-terminated array of features the host supports. + NULL may be passed if the host supports no additional features. + @return NULL if instantiation failed. +*/ +LILV_API LilvInstance* +lilv_plugin_instantiate(const LilvPlugin* plugin, + double sample_rate, + const LV2_Feature*const* features); + +/** + Free a plugin instance. + It is safe to call this function on NULL. + `instance` is invalid after this call. +*/ +LILV_API void +lilv_instance_free(LilvInstance* instance); + +#ifndef LILV_INTERNAL + +/** + Get the URI of the plugin which `instance` is an instance of. + Returned string is shared and must not be modified or deleted. +*/ +static inline const char* +lilv_instance_get_uri(const LilvInstance* instance) +{ + return instance->lv2_descriptor->URI; +} + +/** + Connect a port to a data location. + This may be called regardless of whether the plugin is activated, + activation and deactivation does not destroy port connections. +*/ +static inline void +lilv_instance_connect_port(LilvInstance* instance, + uint32_t port_index, + void* data_location) +{ + instance->lv2_descriptor->connect_port + (instance->lv2_handle, port_index, data_location); +} + +/** + Activate a plugin instance. + This resets all state information in the plugin, except for port data + locations (as set by lilv_instance_connect_port()). This MUST be called + before calling lilv_instance_run(). +*/ +static inline void +lilv_instance_activate(LilvInstance* instance) +{ + if (instance->lv2_descriptor->activate) { + instance->lv2_descriptor->activate(instance->lv2_handle); + } +} + +/** + Run `instance` for `sample_count` frames. + If the hint lv2:hardRTCapable is set for this plugin, this function is + guaranteed not to block. +*/ +static inline void +lilv_instance_run(LilvInstance* instance, + uint32_t sample_count) +{ + instance->lv2_descriptor->run(instance->lv2_handle, sample_count); +} + +/** + Deactivate a plugin instance. + Note that to run the plugin after this you must activate it, which will + reset all state information (except port connections). +*/ +static inline void +lilv_instance_deactivate(LilvInstance* instance) +{ + if (instance->lv2_descriptor->deactivate) { + instance->lv2_descriptor->deactivate(instance->lv2_handle); + } +} + +/** + Get extension data from the plugin instance. + The type and semantics of the data returned is specific to the particular + extension, though in all cases it is shared and must not be deleted. +*/ +static inline const void* +lilv_instance_get_extension_data(const LilvInstance* instance, + const char* uri) +{ + if (instance->lv2_descriptor->extension_data) { + return instance->lv2_descriptor->extension_data(uri); + } else { + return NULL; + } +} + +/** + Get the LV2_Descriptor of the plugin instance. + Normally hosts should not need to access the LV2_Descriptor directly, + use the lilv_instance_* functions. + + The returned descriptor is shared and must not be deleted. +*/ +static inline const LV2_Descriptor* +lilv_instance_get_descriptor(const LilvInstance* instance) +{ + return instance->lv2_descriptor; +} + +/** + Get the LV2_Handle of the plugin instance. + Normally hosts should not need to access the LV2_Handle directly, + use the lilv_instance_* functions. + + The returned handle is shared and must not be deleted. +*/ +static inline LV2_Handle +lilv_instance_get_handle(const LilvInstance* instance) +{ + return instance->lv2_handle; +} + +#endif /* LILV_INTERNAL */ + +/** + @} + @name Plugin UI + @{ +*/ + +/** + Get all UIs for `plugin`. + Returned value must be freed by caller using lilv_uis_free(). +*/ +LILV_API LilvUIs* +lilv_plugin_get_uis(const LilvPlugin* plugin); + +/** + Get the URI of a Plugin UI. + @param ui The Plugin UI + @return a shared value which must not be modified or freed. +*/ +LILV_API const LilvNode* +lilv_ui_get_uri(const LilvUI* ui); + +/** + Get the types (URIs of RDF classes) of a Plugin UI. + @param ui The Plugin UI + @return a shared value which must not be modified or freed. + + Note that in most cases lilv_ui_is_supported() should be used, which avoids + the need to use this function (and type specific logic). +*/ +LILV_API const LilvNodes* +lilv_ui_get_classes(const LilvUI* ui); + +/** + Check whether a plugin UI has a given type. + @param ui The Plugin UI + @param class_uri The URI of the LV2 UI type to check this UI against +*/ +LILV_API bool +lilv_ui_is_a(const LilvUI* ui, const LilvNode* class_uri); + +/** + Function to determine whether a UI type is supported. + + This is provided by the user and must return non-zero iff using a UI of type + `ui_type_uri` in a container of type `container_type_uri` is supported. +*/ +typedef unsigned (*LilvUISupportedFunc)(const char* container_type_uri, + const char* ui_type_uri); + +/** + Return true iff a Plugin UI is supported as a given widget type. + @param ui The Plugin UI + @param supported_func User provided supported predicate. + @param container_type The widget type to host the UI within. + @param ui_type (Output) If non-NULL, set to the native type of the UI + which is owned by `ui` and must not be freed by the caller. + @return The embedding quality level returned by `supported_func`. +*/ +LILV_API unsigned +lilv_ui_is_supported(const LilvUI* ui, + LilvUISupportedFunc supported_func, + const LilvNode* container_type, + const LilvNode** ui_type); + +/** + Get the URI for a Plugin UI's bundle. + @param ui The Plugin UI + @return a shared value which must not be modified or freed. +*/ +LILV_API const LilvNode* +lilv_ui_get_bundle_uri(const LilvUI* ui); + +/** + Get the URI for a Plugin UI's shared library. + @param ui The Plugin UI + @return a shared value which must not be modified or freed. +*/ +LILV_API const LilvNode* +lilv_ui_get_binary_uri(const LilvUI* ui); + +/** + @} + @} +*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LILV_LILV_H */ diff --git a/lilv/lilvmm.hpp b/lilv/lilvmm.hpp new file mode 100644 index 0000000..30ec274 --- /dev/null +++ b/lilv/lilvmm.hpp @@ -0,0 +1,345 @@ +/* + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef LILV_LILVMM_HPP +#define LILV_LILVMM_HPP + +#include "lilv/lilv.h" + +#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) +# define LILV_DEPRECATED __attribute__((__deprecated__)) +#else +# define LILV_DEPRECATED +#endif + +namespace Lilv { + +LILV_DEPRECATED +static inline const char* +uri_to_path(const char* uri) { + return lilv_uri_to_path(uri); +} + +#define LILV_WRAP0(RT, prefix, name) \ + inline RT name() { return lilv_ ## prefix ## _ ## name (me); } + +#define LILV_WRAP0_VOID(prefix, name) \ + inline void name() { lilv_ ## prefix ## _ ## name(me); } + +#define LILV_WRAP1(RT, prefix, name, T1, a1) \ + inline RT name(T1 a1) { return lilv_ ## prefix ## _ ## name (me, a1); } + +#define LILV_WRAP1_VOID(prefix, name, T1, a1) \ + inline void name(T1 a1) { lilv_ ## prefix ## _ ## name(me, a1); } + +#define LILV_WRAP2(RT, prefix, name, T1, a1, T2, a2) \ + inline RT name(T1 a1, T2 a2) { \ + return lilv_ ## prefix ## _ ## name(me, a1, a2); \ + } + +#define LILV_WRAP3(RT, prefix, name, T1, a1, T2, a2, T3, a3) \ + inline RT name(T1 a1, T2 a2, T3 a3) { \ + return lilv_ ## prefix ## _ ## name(me, a1, a2, a3); \ + } + +#define LILV_WRAP2_VOID(prefix, name, T1, a1, T2, a2) \ + inline void name(T1 a1, T2 a2) { lilv_ ## prefix ## _ ## name(me, a1, a2); } + +#ifndef SWIG +#define LILV_WRAP_CONVERSION(CT) \ + inline operator CT*() const { return me; } +#else +#define LILV_WRAP_CONVERSION(CT) +#endif + +struct Node { + inline Node(const LilvNode* node) : me(lilv_node_duplicate(node)) {} + inline Node(const Node& copy) : me(lilv_node_duplicate(copy.me)) {} + + inline ~Node() { lilv_node_free(me); } + + inline bool equals(const Node& other) const { + return lilv_node_equals(me, other.me); + } + + inline bool operator==(const Node& other) const { return equals(other); } + + LILV_WRAP_CONVERSION(LilvNode); + + LILV_WRAP0(char*, node, get_turtle_token); + LILV_WRAP0(bool, node, is_uri); + LILV_WRAP0(const char*, node, as_uri); + LILV_WRAP0(bool, node, is_blank); + LILV_WRAP0(const char*, node, as_blank); + LILV_WRAP0(bool, node, is_literal); + LILV_WRAP0(bool, node, is_string); + LILV_WRAP0(const char*, node, as_string); + LILV_WRAP0(bool, node, is_float); + LILV_WRAP0(float, node, as_float); + LILV_WRAP0(bool, node, is_int); + LILV_WRAP0(int, node, as_int); + LILV_WRAP0(bool, node, is_bool); + LILV_WRAP0(bool, node, as_bool); + + LilvNode* me; +}; + +struct ScalePoint { + inline ScalePoint(const LilvScalePoint* c_obj) : me(c_obj) {} + LILV_WRAP_CONVERSION(const LilvScalePoint); + + LILV_WRAP0(const LilvNode*, scale_point, get_label); + LILV_WRAP0(const LilvNode*, scale_point, get_value); + + const LilvScalePoint* me; +}; + +struct PluginClass { + inline PluginClass(const LilvPluginClass* c_obj) : me(c_obj) {} + LILV_WRAP_CONVERSION(const LilvPluginClass); + + LILV_WRAP0(Node, plugin_class, get_parent_uri); + LILV_WRAP0(Node, plugin_class, get_uri); + LILV_WRAP0(Node, plugin_class, get_label); + LILV_WRAP0(LilvPluginClasses*, plugin_class, get_children); + + const LilvPluginClass* me; +}; + +#define LILV_WRAP_COLL(CT, ET, prefix) \ + inline CT(const Lilv ## CT* c_obj) : me(c_obj) {} \ + LILV_WRAP_CONVERSION(const Lilv ## CT); \ + LILV_WRAP0(unsigned, prefix, size); \ + LILV_WRAP1(const ET, prefix, get, LilvIter*, i); \ + LILV_WRAP0(LilvIter*, prefix, begin); \ + LILV_WRAP1(LilvIter*, prefix, next, LilvIter*, i); \ + LILV_WRAP1(bool, prefix, is_end, LilvIter*, i); \ + const Lilv ## CT* me; \ + +struct PluginClasses { + LILV_WRAP_COLL(PluginClasses, PluginClass, plugin_classes); + LILV_WRAP1(const PluginClass, plugin_classes, + get_by_uri, const LilvNode*, uri); +}; + +struct ScalePoints { + LILV_WRAP_COLL(ScalePoints, ScalePoint, scale_points); +}; + +struct Nodes { + LILV_WRAP_COLL(Nodes, Node, nodes); + LILV_WRAP1(bool, nodes, contains, const Node, node); + LILV_WRAP0(Node, nodes, get_first); +}; + +struct UI { + inline UI(const LilvUI* c_obj) : me(c_obj) {} + LILV_WRAP_CONVERSION(const LilvUI); + + LILV_WRAP0(const LilvNode*, ui, get_uri); + LILV_WRAP0(const LilvNode*, ui, get_bundle_uri); + LILV_WRAP0(const LilvNode*, ui, get_binary_uri); + LILV_WRAP0(const LilvNodes*, ui, get_classes); + /*LILV_WRAP3(bool, ui, is_supported, + LilvUISupportedFunc, supported_func, + const LilvNode*, container_type, + const LilvNode**, ui_type);*/ + LILV_WRAP1(bool, ui, is_a, const LilvNode*, class_uri); + + const LilvUI* me; +}; + +struct UIs { + LILV_WRAP_COLL(UIs, UI, uis); +}; + +struct Port { + inline Port(const LilvPlugin* p, const LilvPort* c_obj) + : parent(p), me(c_obj) + {} + + LILV_WRAP_CONVERSION(const LilvPort); + +#define LILV_PORT_WRAP0(RT, name) \ + inline RT name () { return lilv_port_ ## name (parent, me); } + +#define LILV_PORT_WRAP1(RT, name, T1, a1) \ + inline RT name (T1 a1) { return lilv_port_ ## name (parent, me, a1); } + + LILV_PORT_WRAP1(LilvNodes*, get_value, LilvNode*, predicate); + LILV_PORT_WRAP0(LilvNodes*, get_properties) + LILV_PORT_WRAP1(bool, has_property, LilvNode*, property_uri); + LILV_PORT_WRAP1(bool, supports_event, LilvNode*, event_uri); + LILV_PORT_WRAP0(const LilvNode*, get_symbol); + LILV_PORT_WRAP0(LilvNode*, get_name); + LILV_PORT_WRAP0(const LilvNodes*, get_classes); + LILV_PORT_WRAP1(bool, is_a, LilvNode*, port_class); + LILV_PORT_WRAP0(LilvScalePoints*, get_scale_points); + + // TODO: get_range (output parameters) + + const LilvPlugin* parent; + const LilvPort* me; +}; + +struct Plugin { + inline Plugin(const LilvPlugin* c_obj) : me(c_obj) {} + LILV_WRAP_CONVERSION(const LilvPlugin); + + LILV_WRAP0(bool, plugin, verify); + LILV_WRAP0(Node, plugin, get_uri); + LILV_WRAP0(Node, plugin, get_bundle_uri); + LILV_WRAP0(Nodes, plugin, get_data_uris); + LILV_WRAP0(Node, plugin, get_library_uri); + LILV_WRAP0(Node, plugin, get_name); + LILV_WRAP0(PluginClass, plugin, get_class); + LILV_WRAP1(Nodes, plugin, get_value, Node, pred); + LILV_WRAP1(bool, plugin, has_feature, Node, feature_uri); + LILV_WRAP0(Nodes, plugin, get_supported_features); + LILV_WRAP0(Nodes, plugin, get_required_features); + LILV_WRAP0(Nodes, plugin, get_optional_features); + LILV_WRAP0(unsigned, plugin, get_num_ports); + LILV_WRAP0(bool, plugin, has_latency); + LILV_WRAP0(unsigned, plugin, get_latency_port_index); + LILV_WRAP0(Node, plugin, get_author_name); + LILV_WRAP0(Node, plugin, get_author_email); + LILV_WRAP0(Node, plugin, get_author_homepage); + LILV_WRAP0(bool, plugin, is_replaced); + LILV_WRAP0(Nodes, plugin, get_extension_data); + LILV_WRAP0(UIs, plugin, get_uis); + LILV_WRAP1(Nodes, plugin, get_related, Node, type); + + inline Port get_port_by_index(unsigned index) { + return Port(me, lilv_plugin_get_port_by_index(me, index)); + } + + inline Port get_port_by_symbol(LilvNode* symbol) { + return Port(me, lilv_plugin_get_port_by_symbol(me, symbol)); + } + + inline void get_port_ranges_float(float* min_values, + float* max_values, + float* def_values) { + return lilv_plugin_get_port_ranges_float( + me, min_values, max_values, def_values); + } + + inline unsigned get_num_ports_of_class(LilvNode* class_1, ...) { + va_list args; + va_start(args, class_1); + + const uint32_t count = lilv_plugin_get_num_ports_of_class_va( + me, class_1, args); + + va_end(args); + return count; + } + + const LilvPlugin* me; +}; + +struct Plugins { + LILV_WRAP_COLL(Plugins, Plugin, plugins); + LILV_WRAP1(const Plugin, plugins, get_by_uri, const LilvNode*, uri); +}; + +struct Instance { + inline Instance(LilvInstance* instance) : me(instance) {} + + LILV_DEPRECATED + inline Instance(Plugin plugin, double sample_rate) { + me = lilv_plugin_instantiate(plugin, sample_rate, NULL); + } + + LILV_DEPRECATED inline Instance(Plugin plugin, + double sample_rate, + LV2_Feature* const* features) { + me = lilv_plugin_instantiate(plugin, sample_rate, features); + } + + static inline Instance* create(Plugin plugin, + double sample_rate, + LV2_Feature* const* features) { + LilvInstance* me = lilv_plugin_instantiate( + plugin, sample_rate, features); + + return me ? new Instance(me) : NULL; + } + + LILV_WRAP_CONVERSION(LilvInstance); + + LILV_WRAP2_VOID(instance, connect_port, + unsigned, port_index, + void*, data_location); + + LILV_WRAP0_VOID(instance, activate); + LILV_WRAP1_VOID(instance, run, unsigned, sample_count); + LILV_WRAP0_VOID(instance, deactivate); + + inline const void* get_extension_data(const char* uri) { + return lilv_instance_get_extension_data(me, uri); + } + + inline const LV2_Descriptor* get_descriptor() { + return lilv_instance_get_descriptor(me); + } + + inline LV2_Handle get_handle() { + return lilv_instance_get_handle(me); + } + + LilvInstance* me; +}; + +struct World { + inline World() : me(lilv_world_new()) {} + inline ~World() { lilv_world_free(me); } + + inline LilvNode* new_uri(const char* uri) { + return lilv_new_uri(me, uri); + } + inline LilvNode* new_string(const char* str) { + return lilv_new_string(me, str); + } + inline LilvNode* new_int(int val) { + return lilv_new_int(me, val); + } + inline LilvNode* new_float(float val) { + return lilv_new_float(me, val); + } + inline LilvNode* new_bool(bool val) { + return lilv_new_bool(me, val); + } + inline Nodes find_nodes(const LilvNode* subject, + const LilvNode* predicate, + const LilvNode* object) { + return lilv_world_find_nodes(me, subject, predicate, object); + } + + LILV_WRAP2_VOID(world, set_option, const char*, uri, LilvNode*, value); + LILV_WRAP0_VOID(world, load_all); + LILV_WRAP1_VOID(world, load_bundle, LilvNode*, bundle_uri); + LILV_WRAP0(const LilvPluginClass*, world, get_plugin_class); + LILV_WRAP0(const LilvPluginClasses*, world, get_plugin_classes); + LILV_WRAP0(const Plugins, world, get_all_plugins); + LILV_WRAP1(int, world, load_resource, const LilvNode*, resource); + + LilvWorld* me; +}; + +} /* namespace Lilv */ + +#endif /* LILV_LILVMM_HPP */ diff --git a/src/collections.c b/src/collections.c new file mode 100644 index 0000000..9ded845 --- /dev/null +++ b/src/collections.c @@ -0,0 +1,224 @@ +/* + Copyright 2008-2014 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "lilv_internal.h" + +int +lilv_ptr_cmp(const void* a, const void* b, void* user_data) +{ + return (intptr_t)a - (intptr_t)b; +} + +int +lilv_resource_node_cmp(const void* a, const void* b, void* user_data) +{ + const SordNode* an = ((const LilvNode*)a)->node; + const SordNode* bn = ((const LilvNode*)b)->node; + return (intptr_t)an - (intptr_t)bn; +} + +/* Generic collection functions */ + +static inline LilvCollection* +lilv_collection_new(ZixComparator cmp, ZixDestroyFunc destructor) +{ + return zix_tree_new(false, cmp, NULL, destructor); +} + +void +lilv_collection_free(LilvCollection* collection) +{ + if (collection) { + zix_tree_free((ZixTree*)collection); + } +} + +unsigned +lilv_collection_size(const LilvCollection* collection) +{ + return (collection ? zix_tree_size((const ZixTree*)collection) : 0); +} + +LilvIter* +lilv_collection_begin(const LilvCollection* collection) +{ + return collection ? (LilvIter*)zix_tree_begin((ZixTree*)collection) : NULL; +} + +void* +lilv_collection_get(const LilvCollection* collection, + const LilvIter* i) +{ + return zix_tree_get((const ZixTreeIter*)i); +} + +/* Constructors */ + +LilvScalePoints* +lilv_scale_points_new(void) +{ + return lilv_collection_new(lilv_ptr_cmp, + (ZixDestroyFunc)lilv_scale_point_free); +} + +LilvNodes* +lilv_nodes_new(void) +{ + return lilv_collection_new(lilv_ptr_cmp, + (ZixDestroyFunc)lilv_node_free); +} + +LilvUIs* +lilv_uis_new(void) +{ + return lilv_collection_new(lilv_header_compare_by_uri, + (ZixDestroyFunc)lilv_ui_free); +} + +LilvPluginClasses* +lilv_plugin_classes_new(void) +{ + return lilv_collection_new(lilv_header_compare_by_uri, + (ZixDestroyFunc)lilv_plugin_class_free); +} + +/* URI based accessors (for collections of things with URIs) */ + +LILV_API const LilvPluginClass* +lilv_plugin_classes_get_by_uri(const LilvPluginClasses* classes, + const LilvNode* uri) +{ + return (LilvPluginClass*)lilv_collection_get_by_uri( + (const ZixTree*)classes, uri); +} + +LILV_API const LilvUI* +lilv_uis_get_by_uri(const LilvUIs* uis, const LilvNode* uri) +{ + return (LilvUI*)lilv_collection_get_by_uri((const ZixTree*)uis, uri); +} + +/* Plugins */ + +LilvPlugins* +lilv_plugins_new(void) +{ + return lilv_collection_new(lilv_header_compare_by_uri, NULL); +} + +LILV_API const LilvPlugin* +lilv_plugins_get_by_uri(const LilvPlugins* plugins, const LilvNode* uri) +{ + return (LilvPlugin*)lilv_collection_get_by_uri( + (const ZixTree*)plugins, uri); +} + +/* Nodes */ + +LILV_API bool +lilv_nodes_contains(const LilvNodes* nodes, const LilvNode* value) +{ + LILV_FOREACH(nodes, i, nodes) { + if (lilv_node_equals(lilv_nodes_get(nodes, i), value)) { + return true; + } + } + + return false; +} + +LILV_API LilvNodes* +lilv_nodes_merge(const LilvNodes* a, const LilvNodes* b) +{ + LilvNodes* result = lilv_nodes_new(); + + LILV_FOREACH(nodes, i, a) + zix_tree_insert((ZixTree*)result, + lilv_node_duplicate(lilv_nodes_get(a, i)), + NULL); + + LILV_FOREACH(nodes, i, b) + zix_tree_insert((ZixTree*)result, + lilv_node_duplicate(lilv_nodes_get(b, i)), + NULL); + + return result; +} + +/* Iterator */ + +#define LILV_COLLECTION_IMPL(prefix, CT, ET) \ +LILV_API \ +unsigned \ +prefix##_size(const CT* collection) { \ + return lilv_collection_size(collection); \ +} \ +\ +LILV_API \ +LilvIter* \ +prefix##_begin(const CT* collection) { \ + return lilv_collection_begin(collection); \ +} \ +\ +LILV_API \ +const ET* \ +prefix##_get(const CT* collection, LilvIter* i) { \ + return (ET*)lilv_collection_get(collection, i); \ +} \ +\ +LILV_API \ +LilvIter* \ +prefix##_next(const CT* collection, LilvIter* i) { \ + return zix_tree_iter_next((ZixTreeIter*)i); \ +} \ +\ +LILV_API \ +bool \ +prefix##_is_end(const CT* collection, LilvIter* i) { \ + return zix_tree_iter_is_end((ZixTreeIter*)i); \ +} + +LILV_COLLECTION_IMPL(lilv_plugin_classes, LilvPluginClasses, LilvPluginClass) +LILV_COLLECTION_IMPL(lilv_scale_points, LilvScalePoints, LilvScalePoint) +LILV_COLLECTION_IMPL(lilv_uis, LilvUIs, LilvUI) +LILV_COLLECTION_IMPL(lilv_nodes, LilvNodes, LilvNode) +LILV_COLLECTION_IMPL(lilv_plugins, LilvPlugins, LilvPlugin) + +LILV_API void +lilv_plugin_classes_free(LilvPluginClasses* collection) { + lilv_collection_free(collection); +} + +LILV_API void +lilv_scale_points_free(LilvScalePoints* collection) { + lilv_collection_free(collection); +} + +LILV_API void +lilv_uis_free(LilvUIs* collection) { + lilv_collection_free(collection); +} + +LILV_API void +lilv_nodes_free(LilvNodes* collection) { + lilv_collection_free(collection); +} + +LILV_API LilvNode* +lilv_nodes_get_first(const LilvNodes* collection) { + return (LilvNode*)lilv_collection_get(collection, + lilv_collection_begin(collection)); +} diff --git a/src/instance.c b/src/instance.c new file mode 100644 index 0000000..030c6ff --- /dev/null +++ b/src/instance.c @@ -0,0 +1,110 @@ +/* + Copyright 2007-2016 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "lilv_internal.h" + +LILV_API LilvInstance* +lilv_plugin_instantiate(const LilvPlugin* plugin, + double sample_rate, + const LV2_Feature*const* features) +{ + lilv_plugin_load_if_necessary(plugin); + if (plugin->parse_errors) { + return NULL; + } + + LilvInstance* result = NULL; + const LilvNode* const lib_uri = lilv_plugin_get_library_uri(plugin); + const LilvNode* const bundle_uri = lilv_plugin_get_bundle_uri(plugin); + if (!lib_uri || !bundle_uri) { + return NULL; + } + + char* const bundle_path = lilv_file_uri_parse( + lilv_node_as_uri(bundle_uri), NULL); + + LilvLib* lib = lilv_lib_open(plugin->world, lib_uri, bundle_path, features); + if (!lib) { + lilv_free(bundle_path); + return NULL; + } + + const LV2_Feature** local_features = NULL; + if (features == NULL) { + local_features = (const LV2_Feature**)malloc(sizeof(LV2_Feature*)); + local_features[0] = NULL; + } + + // Search for plugin by URI + for (uint32_t i = 0; true; ++i) { + const LV2_Descriptor* ld = lilv_lib_get_plugin(lib, i); + if (!ld) { + LILV_ERRORF("No plugin <%s> in <%s>\n", + lilv_node_as_uri(lilv_plugin_get_uri(plugin)), + lilv_node_as_uri(lib_uri)); + lilv_lib_close(lib); + break; // return NULL + } + + if (!strcmp(ld->URI, lilv_node_as_uri(lilv_plugin_get_uri(plugin)))) { + // Create LilvInstance to return + result = (LilvInstance*)malloc(sizeof(LilvInstance)); + result->lv2_descriptor = ld; + result->lv2_handle = ld->instantiate( + ld, sample_rate, bundle_path, + (features) ? features : local_features); + result->pimpl = lib; + break; + } + } + + free(local_features); + lilv_free(bundle_path); + + if (result) { + if (result->lv2_handle == NULL) { + // Failed to instantiate + free(result); + lilv_lib_close(lib); + return NULL; + } + + // "Connect" all ports to NULL (catches bugs) + for (uint32_t i = 0; i < lilv_plugin_get_num_ports(plugin); ++i) { + result->lv2_descriptor->connect_port(result->lv2_handle, i, NULL); + } + } + + return result; +} + +LILV_API void +lilv_instance_free(LilvInstance* instance) +{ + if (!instance) { + return; + } + + instance->lv2_descriptor->cleanup(instance->lv2_handle); + instance->lv2_descriptor = NULL; + lilv_lib_close((LilvLib*)instance->pimpl); + instance->pimpl = NULL; + free(instance); +} diff --git a/src/lib.c b/src/lib.c new file mode 100644 index 0000000..73e57bb --- /dev/null +++ b/src/lib.c @@ -0,0 +1,112 @@ +/* + Copyright 2012-2016 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "lilv_internal.h" + +LilvLib* +lilv_lib_open(LilvWorld* world, + const LilvNode* uri, + const char* bundle_path, + const LV2_Feature*const* features) +{ + ZixTreeIter* i = NULL; + const LilvLib key = { + world, (LilvNode*)uri, (char*)bundle_path, NULL, NULL, NULL, 0 + }; + if (!zix_tree_find(world->libs, &key, &i)) { + LilvLib* llib = (LilvLib*)zix_tree_get(i); + ++llib->refs; + return llib; + } + + const char* const lib_uri = lilv_node_as_uri(uri); + char* const lib_path = lilv_file_uri_parse(lib_uri, NULL); + if (!lib_path) { + return NULL; + } + + dlerror(); + void* lib = dlopen(lib_path, RTLD_NOW); + if (!lib) { + LILV_ERRORF("Failed to open library %s (%s)\n", lib_path, dlerror()); + lilv_free(lib_path); + return NULL; + } + + LV2_Descriptor_Function df = (LV2_Descriptor_Function) + lilv_dlfunc(lib, "lv2_descriptor"); + + LV2_Lib_Descriptor_Function ldf = (LV2_Lib_Descriptor_Function) + lilv_dlfunc(lib, "lv2_lib_descriptor"); + + const LV2_Lib_Descriptor* desc = NULL; + if (ldf) { + desc = ldf(bundle_path, features); + if (!desc) { + LILV_ERRORF("Call to %s:lv2_lib_descriptor failed\n", lib_path); + dlclose(lib); + lilv_free(lib_path); + return NULL; + } + } else if (!df) { + LILV_ERRORF("No `lv2_descriptor' or `lv2_lib_descriptor' in %s\n", + lib_path); + dlclose(lib); + lilv_free(lib_path); + return NULL; + } + lilv_free(lib_path); + + LilvLib* llib = (LilvLib*)malloc(sizeof(LilvLib)); + llib->world = world; + llib->uri = lilv_node_duplicate(uri); + llib->bundle_path = lilv_strdup(bundle_path); + llib->lib = lib; + llib->lv2_descriptor = df; + llib->desc = desc; + llib->refs = 1; + + zix_tree_insert(world->libs, llib, NULL); + return llib; +} + +const LV2_Descriptor* +lilv_lib_get_plugin(LilvLib* lib, uint32_t index) +{ + if (lib->lv2_descriptor) { + return lib->lv2_descriptor(index); + } else if (lib->desc) { + return lib->desc->get_plugin(lib->desc->handle, index); + } + return NULL; +} + +void +lilv_lib_close(LilvLib* lib) +{ + if (--lib->refs == 0) { + dlclose(lib->lib); + + ZixTreeIter* i = NULL; + if (lib->world->libs && !zix_tree_find(lib->world->libs, lib, &i)) { + zix_tree_remove(lib->world->libs, i); + } + + lilv_node_free(lib->uri); + free(lib->bundle_path); + free(lib); + } +} diff --git a/src/lilv_internal.h b/src/lilv_internal.h new file mode 100644 index 0000000..15d2716 --- /dev/null +++ b/src/lilv_internal.h @@ -0,0 +1,444 @@ +/* + Copyright 2007-2016 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef LILV_INTERNAL_H +#define LILV_INTERNAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <float.h> + +#ifdef _WIN32 +# include <windows.h> +# include <direct.h> +# include <stdio.h> +# define dlopen(path, flags) LoadLibrary(path) +# define dlclose(lib) FreeLibrary((HMODULE)lib) +# define unlink(path) _unlink(path) +# define rmdir(path) _rmdir(path) +# ifdef _MSC_VER +# define __func__ __FUNCTION__ +# ifndef snprintf +# define snprintf _snprintf +# endif +# endif +#ifndef INFINITY +# define INFINITY DBL_MAX + DBL_MAX +#endif +#ifndef NAN +# define NAN INFINITY - INFINITY +#endif +static inline const char* dlerror(void) { return "Unknown error"; } +#else +# include <dlfcn.h> +# include <unistd.h> +#endif + +#include "serd/serd.h" +#include "sord/sord.h" + +#include "zix/tree.h" + +#include "lilv_config.h" +#include "lilv/lilv.h" + +#ifdef LILV_DYN_MANIFEST +# include "lv2/lv2plug.in/ns/ext/dynmanifest/dynmanifest.h" +#endif + +/* + * + * Types + * + */ + +typedef struct LilvSpecImpl LilvSpec; + +typedef void LilvCollection; + +struct LilvPortImpl { + LilvNode* node; ///< RDF node + uint32_t index; ///< lv2:index + LilvNode* symbol; ///< lv2:symbol + LilvNodes* classes; ///< rdf:type +}; + +struct LilvSpecImpl { + SordNode* spec; + SordNode* bundle; + LilvNodes* data_uris; + struct LilvSpecImpl* next; +}; + +/** + Header of an LilvPlugin, LilvPluginClass, or LilvUI. + Any of these structs may be safely casted to LilvHeader, which is used to + implement collections using the same comparator. +*/ +struct LilvHeader { + LilvWorld* world; + LilvNode* uri; +}; + +#ifdef LILV_DYN_MANIFEST +typedef struct { + LilvNode* bundle; + void* lib; + LV2_Dyn_Manifest_Handle handle; + uint32_t refs; +} LilvDynManifest; +#endif + +typedef struct { + LilvWorld* world; + LilvNode* uri; + char* bundle_path; + void* lib; + LV2_Descriptor_Function lv2_descriptor; + const LV2_Lib_Descriptor* desc; + uint32_t refs; +} LilvLib; + +struct LilvPluginImpl { + LilvWorld* world; + LilvNode* plugin_uri; + LilvNode* bundle_uri; ///< Bundle plugin was loaded from + LilvNode* binary_uri; ///< lv2:binary +#ifdef LILV_DYN_MANIFEST + LilvDynManifest* dynmanifest; +#endif + const LilvPluginClass* plugin_class; + LilvNodes* data_uris; ///< rdfs::seeAlso + LilvPort** ports; + uint32_t num_ports; + bool loaded; + bool parse_errors; + bool replaced; +}; + +struct LilvPluginClassImpl { + LilvWorld* world; + LilvNode* uri; + LilvNode* parent_uri; + LilvNode* label; +}; + +struct LilvInstancePimpl { + LilvWorld* world; + LilvLib* lib; +}; + +typedef struct { + bool dyn_manifest; + bool filter_language; +} LilvOptions; + +struct LilvWorldImpl { + SordWorld* world; + SordModel* model; + SerdReader* reader; + unsigned n_read_files; + LilvPluginClass* lv2_plugin_class; + LilvPluginClasses* plugin_classes; + LilvSpec* specs; + LilvPlugins* plugins; + LilvPlugins* zombies; + LilvNodes* loaded_files; + ZixTree* libs; + struct { + SordNode* dc_replaces; + SordNode* dman_DynManifest; + SordNode* doap_name; + SordNode* lv2_Plugin; + SordNode* lv2_Specification; + SordNode* lv2_appliesTo; + SordNode* lv2_binary; + SordNode* lv2_default; + SordNode* lv2_designation; + SordNode* lv2_extensionData; + SordNode* lv2_index; + SordNode* lv2_latency; + SordNode* lv2_maximum; + SordNode* lv2_microVersion; + SordNode* lv2_minimum; + SordNode* lv2_minorVersion; + SordNode* lv2_name; + SordNode* lv2_optionalFeature; + SordNode* lv2_port; + SordNode* lv2_portProperty; + SordNode* lv2_reportsLatency; + SordNode* lv2_requiredFeature; + SordNode* lv2_symbol; + SordNode* lv2_prototype; + SordNode* owl_Ontology; + SordNode* pset_value; + SordNode* rdf_a; + SordNode* rdf_value; + SordNode* rdfs_Class; + SordNode* rdfs_label; + SordNode* rdfs_seeAlso; + SordNode* rdfs_subClassOf; + SordNode* xsd_base64Binary; + SordNode* xsd_boolean; + SordNode* xsd_decimal; + SordNode* xsd_double; + SordNode* xsd_integer; + SordNode* null_uri; + } uris; + LilvOptions opt; +}; + +typedef enum { + LILV_VALUE_URI, + LILV_VALUE_STRING, + LILV_VALUE_INT, + LILV_VALUE_FLOAT, + LILV_VALUE_BOOL, + LILV_VALUE_BLANK, + LILV_VALUE_BLOB +} LilvNodeType; + +struct LilvNodeImpl { + LilvWorld* world; + SordNode* node; + LilvNodeType type; + union { + int int_val; + float float_val; + bool bool_val; + } val; +}; + +struct LilvScalePointImpl { + LilvNode* value; + LilvNode* label; +}; + +struct LilvUIImpl { + LilvWorld* world; + LilvNode* uri; + LilvNode* bundle_uri; + LilvNode* binary_uri; + LilvNodes* classes; +}; + +typedef struct LilvVersion { + int minor; + int micro; +} LilvVersion; + +/* + * + * Functions + * + */ + +LilvPort* lilv_port_new(LilvWorld* world, + const SordNode* node, + uint32_t index, + const char* symbol); +void lilv_port_free(const LilvPlugin* plugin, LilvPort* port); + +LilvPlugin* lilv_plugin_new(LilvWorld* world, + LilvNode* uri, + LilvNode* bundle_uri); +void lilv_plugin_clear(LilvPlugin* plugin, LilvNode* bundle_uri); +void lilv_plugin_load_if_necessary(const LilvPlugin* plugin); +void lilv_plugin_free(LilvPlugin* plugin); +LilvNode* lilv_plugin_get_unique(const LilvPlugin* plugin, + const SordNode* subject, + const SordNode* predicate); + +void lilv_collection_free(LilvCollection* collection); +unsigned lilv_collection_size(const LilvCollection* collection); +LilvIter* lilv_collection_begin(const LilvCollection* collection); +void* lilv_collection_get(const LilvCollection* collection, + const LilvIter* i); + +LilvPluginClass* lilv_plugin_class_new(LilvWorld* world, + const SordNode* parent_node, + const SordNode* uri, + const char* label); + +void lilv_plugin_class_free(LilvPluginClass* plugin_class); + +LilvLib* +lilv_lib_open(LilvWorld* world, + const LilvNode* uri, + const char* bundle_path, + const LV2_Feature*const* features); + +const LV2_Descriptor* lilv_lib_get_plugin(LilvLib* lib, uint32_t index); +void lilv_lib_close(LilvLib* lib); + +LilvNodes* lilv_nodes_new(void); +LilvPlugins* lilv_plugins_new(void); +LilvScalePoints* lilv_scale_points_new(void); +LilvPluginClasses* lilv_plugin_classes_new(void); +LilvUIs* lilv_uis_new(void); + +LilvNode* lilv_world_get_manifest_uri(LilvWorld* world, + const LilvNode* bundle_uri); + +const uint8_t* lilv_world_blank_node_prefix(LilvWorld* world); + +SerdStatus lilv_world_load_file(LilvWorld* world, + SerdReader* reader, + const LilvNode* uri); + +SerdStatus +lilv_world_load_graph(LilvWorld* world, + SordNode* graph, + const LilvNode* uri); + +LilvUI* lilv_ui_new(LilvWorld* world, + LilvNode* uri, + LilvNode* type_uri, + LilvNode* binary_uri); + +void lilv_ui_free(LilvUI* ui); + +LilvNode* lilv_node_new(LilvWorld* world, LilvNodeType type, const char* str); +LilvNode* lilv_node_new_from_node(LilvWorld* world, const SordNode* node); + +int lilv_header_compare_by_uri(const void* a, const void* b, void* user_data); +int lilv_lib_compare(const void* a, const void* b, void* user_data); + +int lilv_ptr_cmp(const void* a, const void* b, void* user_data); +int lilv_resource_node_cmp(const void* a, const void* b, void* user_data); + +static inline int +lilv_version_cmp(const LilvVersion* a, const LilvVersion* b) +{ + if (a->minor == b->minor && a->micro == b->micro) { + return 0; + } else if ((a->minor < b->minor) + || (a->minor == b->minor && a->micro < b->micro)) { + return -1; + } else { + return 1; + } +} + +struct LilvHeader* +lilv_collection_get_by_uri(const ZixTree* seq, const LilvNode* uri); + +LilvScalePoint* lilv_scale_point_new(LilvNode* value, LilvNode* label); +void lilv_scale_point_free(LilvScalePoint* point); + +SordIter* +lilv_world_query_internal(LilvWorld* world, + const SordNode* subject, + const SordNode* predicate, + const SordNode* object); + +bool +lilv_world_ask_internal(LilvWorld* world, + const SordNode* subject, + const SordNode* predicate, + const SordNode* object); + +LilvNodes* +lilv_world_find_nodes_internal(LilvWorld* world, + const SordNode* subject, + const SordNode* predicate, + const SordNode* object); + +SordModel* +lilv_world_filter_model(LilvWorld* world, + SordModel* model, + const SordNode* subject, + const SordNode* predicate, + const SordNode* object, + const SordNode* graph); + +#define FOREACH_MATCH(iter) \ + for (; !sord_iter_end(iter); sord_iter_next(iter)) + +LilvNodes* lilv_nodes_from_stream_objects(LilvWorld* world, + SordIter* stream, + SordQuadIndex field); + +char* lilv_strjoin(const char* first, ...); +char* lilv_strdup(const char* str); +char* lilv_get_lang(void); +char* lilv_expand(const char* path); +char* lilv_dirname(const char* path); +int lilv_copy_file(const char* src, const char* dst); +bool lilv_path_exists(const char* path, void* ignored); +char* lilv_path_absolute(const char* path); +bool lilv_path_is_absolute(const char* path); +char* lilv_get_latest_copy(const char* path, const char* copy_path); +char* lilv_path_relative_to(const char* path, const char* base); +bool lilv_path_is_child(const char* path, const char* dir); +int lilv_flock(FILE* file, bool lock); +char* lilv_realpath(const char* path); +int lilv_symlink(const char* oldpath, const char* newpath); +int lilv_mkdir_p(const char* dir_path); +char* lilv_path_join(const char* a, const char* b); +bool lilv_file_equals(const char* a_path, const char* b_path); + +char* +lilv_find_free_path(const char* in_path, + bool (*exists)(const char*, void*), void* user_data); + +void +lilv_dir_for_each(const char* path, + void* data, + void (*f)(const char* path, const char* name, void* data)); + +typedef void (*LilvVoidFunc)(void); + +/** dlsym wrapper to return a function pointer (without annoying warning) */ +static inline LilvVoidFunc +lilv_dlfunc(void* handle, const char* symbol) +{ +#ifdef _WIN32 + return (LilvVoidFunc)GetProcAddress((HMODULE)handle, symbol); +#else + typedef LilvVoidFunc (*VoidFuncGetter)(void*, const char*); + VoidFuncGetter dlfunc = (VoidFuncGetter)dlsym; + return dlfunc(handle, symbol); +#endif +} + +#ifdef LILV_DYN_MANIFEST +static const LV2_Feature* const dman_features = { NULL }; +#endif + +#define LILV_ERROR(str) fprintf(stderr, "%s(): error: " str, \ + __func__) +#define LILV_ERRORF(fmt, ...) fprintf(stderr, "%s(): error: " fmt, \ + __func__, __VA_ARGS__) +#define LILV_WARN(str) fprintf(stderr, "%s(): warning: " str, \ + __func__) +#define LILV_WARNF(fmt, ...) fprintf(stderr, "%s(): warning: " fmt, \ + __func__, __VA_ARGS__) +#define LILV_NOTE(str) fprintf(stderr, "%s(): note: " str, \ + __func__) +#define LILV_NOTEF(fmt, ...) fprintf(stderr, "%s(): note: " fmt, \ + __func__, __VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif /* LILV_INTERNAL_H */ diff --git a/src/node.c b/src/node.c new file mode 100644 index 0000000..5605177 --- /dev/null +++ b/src/node.c @@ -0,0 +1,397 @@ +/* + Copyright 2007-2016 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "lilv_internal.h" + +static void +lilv_node_set_numerics_from_string(LilvNode* val) +{ + const char* str = (const char*)sord_node_get_string(val->node); + + switch (val->type) { + case LILV_VALUE_URI: + case LILV_VALUE_BLANK: + case LILV_VALUE_STRING: + case LILV_VALUE_BLOB: + break; + case LILV_VALUE_INT: + val->val.int_val = strtol(str, NULL, 10); + break; + case LILV_VALUE_FLOAT: + val->val.float_val = serd_strtod(str, NULL); + break; + case LILV_VALUE_BOOL: + val->val.bool_val = !strcmp(str, "true"); + break; + } +} + +/** Note that if `type` is numeric or boolean, the returned value is corrupt + * until lilv_node_set_numerics_from_string is called. It is not + * automatically called from here to avoid overhead and imprecision when the + * exact string value is known. + */ +LilvNode* +lilv_node_new(LilvWorld* world, LilvNodeType type, const char* str) +{ + LilvNode* val = (LilvNode*)malloc(sizeof(LilvNode)); + val->world = world; + val->type = type; + + const uint8_t* ustr = (const uint8_t*)str; + switch (type) { + case LILV_VALUE_URI: + val->node = sord_new_uri(world->world, ustr); + break; + case LILV_VALUE_BLANK: + val->node = sord_new_blank(world->world, ustr); + break; + case LILV_VALUE_STRING: + val->node = sord_new_literal(world->world, NULL, ustr, NULL); + break; + case LILV_VALUE_INT: + val->node = sord_new_literal( + world->world, world->uris.xsd_integer, ustr, NULL); + break; + case LILV_VALUE_FLOAT: + val->node = sord_new_literal( + world->world, world->uris.xsd_decimal, ustr, NULL); + break; + case LILV_VALUE_BOOL: + val->node = sord_new_literal( + world->world, world->uris.xsd_boolean, ustr, NULL); + break; + case LILV_VALUE_BLOB: + val->node = sord_new_literal( + world->world, world->uris.xsd_base64Binary, ustr, NULL); + break; + } + + if (!val->node) { + free(val); + return NULL; + } + + return val; +} + +/** Create a new LilvNode from `node`, or return NULL if impossible */ +LilvNode* +lilv_node_new_from_node(LilvWorld* world, const SordNode* node) +{ + if (!node) { + return NULL; + } + + LilvNode* result = NULL; + SordNode* datatype_uri = NULL; + LilvNodeType type = LILV_VALUE_STRING; + + switch (sord_node_get_type(node)) { + case SORD_URI: + result = (LilvNode*)malloc(sizeof(LilvNode)); + result->world = world; + result->type = LILV_VALUE_URI; + result->node = sord_node_copy(node); + break; + case SORD_BLANK: + result = (LilvNode*)malloc(sizeof(LilvNode)); + result->world = world; + result->type = LILV_VALUE_BLANK; + result->node = sord_node_copy(node); + break; + case SORD_LITERAL: + datatype_uri = sord_node_get_datatype(node); + if (datatype_uri) { + if (sord_node_equals(datatype_uri, world->uris.xsd_boolean)) { + type = LILV_VALUE_BOOL; + } else if (sord_node_equals(datatype_uri, world->uris.xsd_decimal) || + sord_node_equals(datatype_uri, world->uris.xsd_double)) { + type = LILV_VALUE_FLOAT; + } else if (sord_node_equals(datatype_uri, world->uris.xsd_integer)) { + type = LILV_VALUE_INT; + } else if (sord_node_equals(datatype_uri, + world->uris.xsd_base64Binary)) { + type = LILV_VALUE_BLOB; + } else { + LILV_ERRORF("Unknown datatype `%s'\n", + sord_node_get_string(datatype_uri)); + } + } + result = lilv_node_new( + world, type, (const char*)sord_node_get_string(node)); + lilv_node_set_numerics_from_string(result); + break; + } + + return result; +} + +LILV_API LilvNode* +lilv_new_uri(LilvWorld* world, const char* uri) +{ + return lilv_node_new(world, LILV_VALUE_URI, uri); +} + +LILV_API LilvNode* +lilv_new_file_uri(LilvWorld* world, const char* host, const char* path) +{ + char* abs_path = lilv_path_absolute(path); + SerdNode s = serd_node_new_file_uri( + (const uint8_t*)abs_path, (const uint8_t*)host, NULL, true); + + LilvNode* ret = lilv_node_new(world, LILV_VALUE_URI, (const char*)s.buf); + serd_node_free(&s); + free(abs_path); + return ret; +} + +LILV_API LilvNode* +lilv_new_string(LilvWorld* world, const char* str) +{ + return lilv_node_new(world, LILV_VALUE_STRING, str); +} + +LILV_API LilvNode* +lilv_new_int(LilvWorld* world, int val) +{ + char str[32]; + snprintf(str, sizeof(str), "%d", val); + LilvNode* ret = lilv_node_new(world, LILV_VALUE_INT, str); + ret->val.int_val = val; + return ret; +} + +LILV_API LilvNode* +lilv_new_float(LilvWorld* world, float val) +{ + char str[32]; + snprintf(str, sizeof(str), "%f", val); + LilvNode* ret = lilv_node_new(world, LILV_VALUE_FLOAT, str); + ret->val.float_val = val; + return ret; +} + +LILV_API LilvNode* +lilv_new_bool(LilvWorld* world, bool val) +{ + LilvNode* ret = lilv_node_new(world, LILV_VALUE_BOOL, + val ? "true" : "false"); + ret->val.bool_val = val; + return ret; +} + +LILV_API LilvNode* +lilv_node_duplicate(const LilvNode* val) +{ + if (!val) { + return NULL; + } + + LilvNode* result = (LilvNode*)malloc(sizeof(LilvNode)); + result->world = val->world; + result->node = sord_node_copy(val->node); + result->val = val->val; + result->type = val->type; + return result; +} + +LILV_API void +lilv_node_free(LilvNode* val) +{ + if (val) { + sord_node_free(val->world->world, val->node); + free(val); + } +} + +LILV_API bool +lilv_node_equals(const LilvNode* value, const LilvNode* other) +{ + if (value == NULL && other == NULL) { + return true; + } else if (value == NULL || other == NULL) { + return false; + } else if (value->type != other->type) { + return false; + } + + switch (value->type) { + case LILV_VALUE_URI: + case LILV_VALUE_BLANK: + case LILV_VALUE_STRING: + case LILV_VALUE_BLOB: + return sord_node_equals(value->node, other->node); + case LILV_VALUE_INT: + return (value->val.int_val == other->val.int_val); + case LILV_VALUE_FLOAT: + return (value->val.float_val == other->val.float_val); + case LILV_VALUE_BOOL: + return (value->val.bool_val == other->val.bool_val); + } + + return false; /* shouldn't get here */ +} + +LILV_API char* +lilv_node_get_turtle_token(const LilvNode* value) +{ + const char* str = (const char*)sord_node_get_string(value->node); + size_t len = 0; + char* result = NULL; + SerdNode node; + + switch (value->type) { + case LILV_VALUE_URI: + len = strlen(str) + 3; + result = (char*)calloc(len, 1); + snprintf(result, len, "<%s>", str); + break; + case LILV_VALUE_BLANK: + len = strlen(str) + 3; + result = (char*)calloc(len, 1); + snprintf(result, len, "_:%s", str); + break; + case LILV_VALUE_STRING: + case LILV_VALUE_BOOL: + case LILV_VALUE_BLOB: + result = lilv_strdup(str); + break; + case LILV_VALUE_INT: + node = serd_node_new_integer(value->val.int_val); + result = (char*)node.buf; + break; + case LILV_VALUE_FLOAT: + node = serd_node_new_decimal(value->val.float_val, 8); + result = (char*)node.buf; + break; + } + + return result; +} + +LILV_API bool +lilv_node_is_uri(const LilvNode* value) +{ + return (value && value->type == LILV_VALUE_URI); +} + +LILV_API const char* +lilv_node_as_uri(const LilvNode* value) +{ + return (lilv_node_is_uri(value) + ? (const char*)sord_node_get_string(value->node) + : NULL); +} + +LILV_API bool +lilv_node_is_blank(const LilvNode* value) +{ + return (value && value->type == LILV_VALUE_BLANK); +} + +LILV_API const char* +lilv_node_as_blank(const LilvNode* value) +{ + return (lilv_node_is_blank(value) + ? (const char*)sord_node_get_string(value->node) + : NULL); +} + +LILV_API bool +lilv_node_is_literal(const LilvNode* value) +{ + if (!value) { + return false; + } + + switch (value->type) { + case LILV_VALUE_STRING: + case LILV_VALUE_INT: + case LILV_VALUE_FLOAT: + case LILV_VALUE_BLOB: + return true; + default: + return false; + } +} + +LILV_API bool +lilv_node_is_string(const LilvNode* value) +{ + return (value && value->type == LILV_VALUE_STRING); +} + +LILV_API const char* +lilv_node_as_string(const LilvNode* value) +{ + return value ? (const char*)sord_node_get_string(value->node) : NULL; +} + +LILV_API bool +lilv_node_is_int(const LilvNode* value) +{ + return (value && value->type == LILV_VALUE_INT); +} + +LILV_API int +lilv_node_as_int(const LilvNode* value) +{ + return lilv_node_is_int(value) ? value->val.int_val : 0; +} + +LILV_API bool +lilv_node_is_float(const LilvNode* value) +{ + return (value && value->type == LILV_VALUE_FLOAT); +} + +LILV_API float +lilv_node_as_float(const LilvNode* value) +{ + if (lilv_node_is_float(value)) { + return value->val.float_val; + } else if (lilv_node_is_int(value)) { + return (float)value->val.int_val; + } + return NAN; +} + +LILV_API bool +lilv_node_is_bool(const LilvNode* value) +{ + return (value && value->type == LILV_VALUE_BOOL); +} + +LILV_API bool +lilv_node_as_bool(const LilvNode* value) +{ + return lilv_node_is_bool(value) ? value->val.bool_val : false; +} + +LILV_API char* +lilv_node_get_path(const LilvNode* value, char** hostname) +{ + if (lilv_node_is_uri(value)) { + return lilv_file_uri_parse(lilv_node_as_uri(value), hostname); + } + return NULL; +} diff --git a/src/plugin.c b/src/plugin.c new file mode 100644 index 0000000..0575b32 --- /dev/null +++ b/src/plugin.c @@ -0,0 +1,1138 @@ +/* + Copyright 2007-2016 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#define __STDC_LIMIT_MACROS + +#include <assert.h> +#include <math.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "lilv_config.h" +#include "lilv_internal.h" + +#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" + +#define NS_DOAP (const uint8_t*)"http://usefulinc.com/ns/doap#" +#define NS_FOAF (const uint8_t*)"http://xmlns.com/foaf/0.1/" + +static void +lilv_plugin_init(LilvPlugin* plugin, LilvNode* bundle_uri) +{ + plugin->bundle_uri = bundle_uri; + plugin->binary_uri = NULL; +#ifdef LILV_DYN_MANIFEST + plugin->dynmanifest = NULL; +#endif + plugin->plugin_class = NULL; + plugin->data_uris = lilv_nodes_new(); + plugin->ports = NULL; + plugin->num_ports = 0; + plugin->loaded = false; + plugin->parse_errors = false; + plugin->replaced = false; +} + +/** Ownership of `uri` and `bundle` is taken */ +LilvPlugin* +lilv_plugin_new(LilvWorld* world, LilvNode* uri, LilvNode* bundle_uri) +{ + LilvPlugin* plugin = (LilvPlugin*)malloc(sizeof(LilvPlugin)); + + plugin->world = world; + plugin->plugin_uri = uri; + + lilv_plugin_init(plugin, bundle_uri); + return plugin; +} + +void +lilv_plugin_clear(LilvPlugin* plugin, LilvNode* bundle_uri) +{ + lilv_node_free(plugin->bundle_uri); + lilv_node_free(plugin->binary_uri); + lilv_nodes_free(plugin->data_uris); + lilv_plugin_init(plugin, bundle_uri); +} + +static void +lilv_plugin_free_ports(LilvPlugin* plugin) +{ + if (plugin->ports) { + for (uint32_t i = 0; i < plugin->num_ports; ++i) { + lilv_port_free(plugin, plugin->ports[i]); + } + free(plugin->ports); + plugin->num_ports = 0; + plugin->ports = NULL; + } +} + +void +lilv_plugin_free(LilvPlugin* plugin) +{ +#ifdef LILV_DYN_MANIFEST + if (plugin->dynmanifest && --plugin->dynmanifest->refs == 0) { + typedef int (*CloseFunc)(LV2_Dyn_Manifest_Handle); + CloseFunc close_func = (CloseFunc)lilv_dlfunc(plugin->dynmanifest->lib, + "lv2_dyn_manifest_close"); + if (close_func) { + close_func(plugin->dynmanifest->handle); + } + + dlclose(plugin->dynmanifest->lib); + lilv_node_free(plugin->dynmanifest->bundle); + free(plugin->dynmanifest); + } +#endif + + lilv_node_free(plugin->plugin_uri); + plugin->plugin_uri = NULL; + + lilv_node_free(plugin->bundle_uri); + plugin->bundle_uri = NULL; + + lilv_node_free(plugin->binary_uri); + plugin->binary_uri = NULL; + + lilv_plugin_free_ports(plugin); + + lilv_nodes_free(plugin->data_uris); + plugin->data_uris = NULL; + + free(plugin); +} + +static LilvNode* +lilv_plugin_get_one(const LilvPlugin* plugin, + const SordNode* subject, + const SordNode* predicate) +{ + LilvNode* ret = NULL; + SordIter* stream = lilv_world_query_internal( + plugin->world, subject, predicate, NULL); + if (!sord_iter_end(stream)) { + ret = lilv_node_new_from_node(plugin->world, + sord_iter_get_node(stream, SORD_OBJECT)); + } + sord_iter_free(stream); + return ret; +} + +LilvNode* +lilv_plugin_get_unique(const LilvPlugin* plugin, + const SordNode* subject, + const SordNode* predicate) +{ + LilvNode* ret = lilv_plugin_get_one(plugin, subject, predicate); + if (!ret) { + LILV_ERRORF("No value found for (%s %s ...) property\n", + sord_node_get_string(subject), + sord_node_get_string(predicate)); + } + return ret; +} + +static void +lilv_plugin_load(LilvPlugin* plugin) +{ + SordNode* bundle_uri_node = plugin->bundle_uri->node; + const SerdNode* bundle_uri_snode = sord_node_to_serd_node(bundle_uri_node); + + SerdEnv* env = serd_env_new(bundle_uri_snode); + SerdReader* reader = sord_new_reader(plugin->world->model, env, SERD_TURTLE, + bundle_uri_node); + + SordModel* prots = lilv_world_filter_model( + plugin->world, + plugin->world->model, + plugin->plugin_uri->node, + plugin->world->uris.lv2_prototype, + NULL, NULL); + SordModel* skel = sord_new(plugin->world->world, SORD_SPO, false); + SordIter* iter = sord_begin(prots); + for (; !sord_iter_end(iter); sord_iter_next(iter)) { + const SordNode* t = sord_iter_get_node(iter, SORD_OBJECT); + LilvNode* prototype = lilv_node_new_from_node(plugin->world, t); + + lilv_world_load_resource(plugin->world, prototype); + + SordIter* statements = sord_search( + plugin->world->model, prototype->node, NULL, NULL, NULL); + FOREACH_MATCH(statements) { + SordQuad quad; + sord_iter_get(statements, quad); + quad[0] = plugin->plugin_uri->node; + sord_add(skel, quad); + } + + sord_iter_free(statements); + lilv_node_free(prototype); + } + sord_iter_free(iter); + + for (iter = sord_begin(skel); !sord_iter_end(iter); sord_iter_next(iter)) { + SordQuad quad; + sord_iter_get(iter, quad); + sord_add(plugin->world->model, quad); + } + sord_iter_free(iter); + sord_free(skel); + sord_free(prots); + + // Parse all the plugin's data files into RDF model + SerdStatus st = SERD_SUCCESS; + LILV_FOREACH(nodes, i, plugin->data_uris) { + const LilvNode* data_uri = lilv_nodes_get(plugin->data_uris, i); + + serd_env_set_base_uri(env, sord_node_to_serd_node(data_uri->node)); + st = lilv_world_load_file(plugin->world, reader, data_uri); + if (st > SERD_FAILURE) { + break; + } + } + + if (st > SERD_FAILURE) { + plugin->loaded = true; + plugin->parse_errors = true; + serd_reader_free(reader); + serd_env_free(env); + return; + } + +#ifdef LILV_DYN_MANIFEST + // Load and parse dynamic manifest data, if this is a library + if (plugin->dynmanifest) { + typedef int (*GetDataFunc)(LV2_Dyn_Manifest_Handle handle, + FILE* fp, + const char* uri); + GetDataFunc get_data_func = (GetDataFunc)lilv_dlfunc( + plugin->dynmanifest->lib, "lv2_dyn_manifest_get_data"); + if (get_data_func) { + const SordNode* bundle = plugin->dynmanifest->bundle->node; + serd_env_set_base_uri(env, sord_node_to_serd_node(bundle)); + FILE* fd = tmpfile(); + get_data_func(plugin->dynmanifest->handle, fd, + lilv_node_as_string(plugin->plugin_uri)); + rewind(fd); + serd_reader_add_blank_prefix( + reader, lilv_world_blank_node_prefix(plugin->world)); + serd_reader_read_file_handle( + reader, fd, (const uint8_t*)"(dyn-manifest)"); + fclose(fd); + } + } +#endif + serd_reader_free(reader); + serd_env_free(env); + + plugin->loaded = true; +} + +static bool +is_symbol(const char* str) +{ + for (const char* s = str; *s; ++s) { + if (!((*s >= 'a' && *s <= 'z') || + (*s >= 'A' && *s <= 'Z') || + (s > str && *s >= '0' && *s <= '9') || + *s == '_')) { + return false; + } + } + return true; +} + +static void +lilv_plugin_load_ports_if_necessary(const LilvPlugin* const_plugin) +{ + LilvPlugin* plugin = (LilvPlugin*)const_plugin; + + lilv_plugin_load_if_necessary(plugin); + + if (!plugin->ports) { + plugin->ports = (LilvPort**)malloc(sizeof(LilvPort*)); + plugin->ports[0] = NULL; + + SordIter* ports = lilv_world_query_internal( + plugin->world, + plugin->plugin_uri->node, + plugin->world->uris.lv2_port, + NULL); + + FOREACH_MATCH(ports) { + const SordNode* port = sord_iter_get_node(ports, SORD_OBJECT); + LilvNode* index = lilv_plugin_get_unique( + plugin, port, plugin->world->uris.lv2_index); + LilvNode* symbol = lilv_plugin_get_unique( + plugin, port, plugin->world->uris.lv2_symbol); + + if (!lilv_node_is_string(symbol) || + !is_symbol((const char*)sord_node_get_string(symbol->node))) { + LILV_ERRORF("Plugin <%s> port symbol `%s' is invalid\n", + lilv_node_as_uri(plugin->plugin_uri), + lilv_node_as_string(symbol)); + lilv_node_free(symbol); + lilv_node_free(index); + lilv_plugin_free_ports(plugin); + break; + } + + if (!lilv_node_is_int(index)) { + LILV_ERRORF("Plugin <%s> port index is not an integer\n", + lilv_node_as_uri(plugin->plugin_uri)); + lilv_node_free(symbol); + lilv_node_free(index); + lilv_plugin_free_ports(plugin); + break; + } + + uint32_t this_index = lilv_node_as_int(index); + LilvPort* this_port = NULL; + if (plugin->num_ports > this_index) { + this_port = plugin->ports[this_index]; + } else { + plugin->ports = (LilvPort**)realloc( + plugin->ports, (this_index + 1) * sizeof(LilvPort*)); + memset(plugin->ports + plugin->num_ports, '\0', + (this_index - plugin->num_ports) * sizeof(LilvPort*)); + plugin->num_ports = this_index + 1; + } + + // Havn't seen this port yet, add it to array + if (!this_port) { + this_port = lilv_port_new(plugin->world, + port, + this_index, + lilv_node_as_string(symbol)); + plugin->ports[this_index] = this_port; + } + + SordIter* types = lilv_world_query_internal( + plugin->world, port, plugin->world->uris.rdf_a, NULL); + FOREACH_MATCH(types) { + const SordNode* type = sord_iter_get_node(types, SORD_OBJECT); + if (sord_node_get_type(type) == SORD_URI) { + zix_tree_insert( + (ZixTree*)this_port->classes, + lilv_node_new_from_node(plugin->world, type), NULL); + } else { + LILV_WARNF("Plugin <%s> port type is not a URI\n", + lilv_node_as_uri(plugin->plugin_uri)); + } + } + sord_iter_free(types); + + lilv_node_free(symbol); + lilv_node_free(index); + } + sord_iter_free(ports); + + // Check sanity + for (uint32_t i = 0; i < plugin->num_ports; ++i) { + if (!plugin->ports[i]) { + LILV_ERRORF("Plugin <%s> is missing port %d/%d\n", + lilv_node_as_uri(plugin->plugin_uri), i, plugin->num_ports); + lilv_plugin_free_ports(plugin); + break; + } + } + } +} + +void +lilv_plugin_load_if_necessary(const LilvPlugin* plugin) +{ + if (!plugin->loaded) { + lilv_plugin_load((LilvPlugin*)plugin); + } +} + +LILV_API const LilvNode* +lilv_plugin_get_uri(const LilvPlugin* plugin) +{ + return plugin->plugin_uri; +} + +LILV_API const LilvNode* +lilv_plugin_get_bundle_uri(const LilvPlugin* plugin) +{ + return plugin->bundle_uri; +} + +LILV_API const LilvNode* +lilv_plugin_get_library_uri(const LilvPlugin* plugin) +{ + lilv_plugin_load_if_necessary((LilvPlugin*)plugin); + if (!plugin->binary_uri) { + // <plugin> lv2:binary ?binary + SordIter* i = lilv_world_query_internal(plugin->world, + plugin->plugin_uri->node, + plugin->world->uris.lv2_binary, + NULL); + FOREACH_MATCH(i) { + const SordNode* binary_node = sord_iter_get_node(i, SORD_OBJECT); + if (sord_node_get_type(binary_node) == SORD_URI) { + ((LilvPlugin*)plugin)->binary_uri = + lilv_node_new_from_node(plugin->world, binary_node); + break; + } + } + sord_iter_free(i); + } + if (!plugin->binary_uri) { + LILV_WARNF("Plugin <%s> has no lv2:binary\n", + lilv_node_as_uri(lilv_plugin_get_uri(plugin))); + } + return plugin->binary_uri; +} + +LILV_API const LilvNodes* +lilv_plugin_get_data_uris(const LilvPlugin* plugin) +{ + return plugin->data_uris; +} + +LILV_API const LilvPluginClass* +lilv_plugin_get_class(const LilvPlugin* plugin) +{ + lilv_plugin_load_if_necessary((LilvPlugin*)plugin); + if (!plugin->plugin_class) { + // <plugin> a ?class + SordIter* c = lilv_world_query_internal(plugin->world, + plugin->plugin_uri->node, + plugin->world->uris.rdf_a, + NULL); + FOREACH_MATCH(c) { + const SordNode* class_node = sord_iter_get_node(c, SORD_OBJECT); + if (sord_node_get_type(class_node) != SORD_URI) { + continue; + } + + LilvNode* klass = lilv_node_new_from_node(plugin->world, class_node); + if (!lilv_node_equals(klass, plugin->world->lv2_plugin_class->uri)) { + const LilvPluginClass* pclass = lilv_plugin_classes_get_by_uri( + plugin->world->plugin_classes, klass); + + if (pclass) { + ((LilvPlugin*)plugin)->plugin_class = pclass; + lilv_node_free(klass); + break; + } + } + + lilv_node_free(klass); + } + sord_iter_free(c); + + if (plugin->plugin_class == NULL) { + ((LilvPlugin*)plugin)->plugin_class = + plugin->world->lv2_plugin_class; + } + } + return plugin->plugin_class; +} + +static LilvNodes* +lilv_plugin_get_value_internal(const LilvPlugin* plugin, + const SordNode* predicate) +{ + lilv_plugin_load_if_necessary(plugin); + return lilv_world_find_nodes_internal( + plugin->world, plugin->plugin_uri->node, predicate, NULL); +} + +LILV_API bool +lilv_plugin_verify(const LilvPlugin* plugin) +{ + lilv_plugin_load_if_necessary(plugin); + if (plugin->parse_errors) { + return false; + } + + LilvNode* rdf_type = lilv_new_uri(plugin->world, LILV_NS_RDF "type"); + LilvNodes* results = lilv_plugin_get_value(plugin, rdf_type); + lilv_node_free(rdf_type); + if (!results) { + return false; + } + + lilv_nodes_free(results); + results = lilv_plugin_get_value_internal(plugin, + plugin->world->uris.doap_name); + if (!results) { + return false; + } + + lilv_nodes_free(results); + LilvNode* lv2_port = lilv_new_uri(plugin->world, LV2_CORE__port); + results = lilv_plugin_get_value(plugin, lv2_port); + lilv_node_free(lv2_port); + if (!results) { + return false; + } + + lilv_nodes_free(results); + return true; +} + +LILV_API LilvNode* +lilv_plugin_get_name(const LilvPlugin* plugin) +{ + LilvNodes* results = lilv_plugin_get_value_internal( + plugin, plugin->world->uris.doap_name); + + LilvNode* ret = NULL; + if (results) { + LilvNode* val = lilv_nodes_get_first(results); + if (lilv_node_is_string(val)) { + ret = lilv_node_duplicate(val); + } + lilv_nodes_free(results); + } + + if (!ret) { + LILV_WARNF("Plugin <%s> has no (mandatory) doap:name\n", + lilv_node_as_string(lilv_plugin_get_uri(plugin))); + } + + return ret; +} + +LILV_API LilvNodes* +lilv_plugin_get_value(const LilvPlugin* plugin, + const LilvNode* predicate) +{ + lilv_plugin_load_if_necessary(plugin); + return lilv_world_find_nodes(plugin->world, plugin->plugin_uri, predicate, NULL); +} + +LILV_API uint32_t +lilv_plugin_get_num_ports(const LilvPlugin* plugin) +{ + lilv_plugin_load_ports_if_necessary(plugin); + return plugin->num_ports; +} + +LILV_API void +lilv_plugin_get_port_ranges_float(const LilvPlugin* plugin, + float* min_values, + float* max_values, + float* def_values) +{ + lilv_plugin_load_ports_if_necessary(plugin); + LilvNode* min = NULL; + LilvNode* max = NULL; + LilvNode* def = NULL; + LilvNode** minptr = min_values ? &min : NULL; + LilvNode** maxptr = max_values ? &max : NULL; + LilvNode** defptr = def_values ? &def : NULL; + + for (uint32_t i = 0; i < plugin->num_ports; ++i) { + lilv_port_get_range(plugin, plugin->ports[i], defptr, minptr, maxptr); + + if (min_values) { + if (lilv_node_is_float(min) || lilv_node_is_int(min)) { + min_values[i] = lilv_node_as_float(min); + } else { + min_values[i] = NAN; + } + } + + if (max_values) { + if (lilv_node_is_float(max) || lilv_node_is_int(max)) { + max_values[i] = lilv_node_as_float(max); + } else { + max_values[i] = NAN; + } + } + + if (def_values) { + if (lilv_node_is_float(def) || lilv_node_is_int(def)) { + def_values[i] = lilv_node_as_float(def); + } else { + def_values[i] = NAN; + } + } + + lilv_node_free(def); + lilv_node_free(min); + lilv_node_free(max); + } +} + +LILV_API uint32_t +lilv_plugin_get_num_ports_of_class_va(const LilvPlugin* plugin, + const LilvNode* class_1, + va_list args) +{ + lilv_plugin_load_ports_if_necessary(plugin); + + uint32_t count = 0; + + // Build array of classes from args so we can walk it several times + size_t n_classes = 0; + const LilvNode** classes = NULL; + for (LilvNode* c = NULL; (c = va_arg(args, LilvNode*)); ) { + classes = (const LilvNode**)realloc( + classes, ++n_classes * sizeof(LilvNode*)); + classes[n_classes - 1] = c; + } + + // Check each port against every type + for (unsigned i = 0; i < plugin->num_ports; ++i) { + LilvPort* port = plugin->ports[i]; + if (port && lilv_port_is_a(plugin, port, class_1)) { + bool matches = true; + for (size_t j = 0; j < n_classes; ++j) { + if (!lilv_port_is_a(plugin, port, classes[j])) { + matches = false; + break; + } + } + + if (matches) { + ++count; + } + } + } + + free(classes); + return count; +} + +LILV_API uint32_t +lilv_plugin_get_num_ports_of_class(const LilvPlugin* plugin, + const LilvNode* class_1, ...) +{ + va_list args; + va_start(args, class_1); + + uint32_t count = lilv_plugin_get_num_ports_of_class_va(plugin, class_1, args); + + va_end(args); + return count; +} + +LILV_API bool +lilv_plugin_has_latency(const LilvPlugin* plugin) +{ + lilv_plugin_load_if_necessary(plugin); + SordIter* ports = lilv_world_query_internal( + plugin->world, + plugin->plugin_uri->node, + plugin->world->uris.lv2_port, + NULL); + + bool ret = false; + FOREACH_MATCH(ports) { + const SordNode* port = sord_iter_get_node(ports, SORD_OBJECT); + SordIter* prop = lilv_world_query_internal( + plugin->world, + port, + plugin->world->uris.lv2_portProperty, + plugin->world->uris.lv2_reportsLatency); + SordIter* des = lilv_world_query_internal( + plugin->world, + port, + plugin->world->uris.lv2_designation, + plugin->world->uris.lv2_latency); + const bool latent = !sord_iter_end(prop) || !sord_iter_end(des); + sord_iter_free(prop); + sord_iter_free(des); + if (latent) { + ret = true; + break; + } + } + sord_iter_free(ports); + + return ret; +} + +static const LilvPort* +lilv_plugin_get_port_by_property(const LilvPlugin* plugin, + const SordNode* port_property) +{ + lilv_plugin_load_ports_if_necessary(plugin); + for (uint32_t i = 0; i < plugin->num_ports; ++i) { + LilvPort* port = plugin->ports[i]; + SordIter* iter = lilv_world_query_internal( + plugin->world, + port->node->node, + plugin->world->uris.lv2_portProperty, + port_property); + + const bool found = !sord_iter_end(iter); + sord_iter_free(iter); + + if (found) { + return port; + } + } + + return NULL; +} + +LILV_API const LilvPort* +lilv_plugin_get_port_by_designation(const LilvPlugin* plugin, + const LilvNode* port_class, + const LilvNode* designation) +{ + LilvWorld* world = plugin->world; + lilv_plugin_load_ports_if_necessary(plugin); + for (uint32_t i = 0; i < plugin->num_ports; ++i) { + LilvPort* port = plugin->ports[i]; + SordIter* iter = lilv_world_query_internal( + world, + port->node->node, + world->uris.lv2_designation, + designation->node); + + const bool found = !sord_iter_end(iter) && + (!port_class || lilv_port_is_a(plugin, port, port_class)); + sord_iter_free(iter); + + if (found) { + return port; + } + } + + return NULL; +} + +LILV_API uint32_t +lilv_plugin_get_latency_port_index(const LilvPlugin* plugin) +{ + const LilvPort* prop_port = lilv_plugin_get_port_by_property( + plugin, plugin->world->uris.lv2_reportsLatency); + const LilvPort* des_port = lilv_plugin_get_port_by_property( + plugin, plugin->world->uris.lv2_latency); + if (prop_port) { + return prop_port->index; + } else if (des_port) { + return des_port->index; + } else { + return (uint32_t)-1; + } +} + +LILV_API bool +lilv_plugin_has_feature(const LilvPlugin* plugin, + const LilvNode* feature) +{ + lilv_plugin_load_if_necessary(plugin); + const SordNode* predicates[] = { plugin->world->uris.lv2_requiredFeature, + plugin->world->uris.lv2_optionalFeature, + NULL }; + + for (const SordNode** pred = predicates; *pred; ++pred) { + if (lilv_world_ask_internal( + plugin->world, plugin->plugin_uri->node, *pred, feature->node)) { + return true; + } + } + return false; +} + +LILV_API LilvNodes* +lilv_plugin_get_supported_features(const LilvPlugin* plugin) +{ + LilvNodes* optional = lilv_plugin_get_optional_features(plugin); + LilvNodes* required = lilv_plugin_get_required_features(plugin); + LilvNodes* result = lilv_nodes_merge(optional, required); + lilv_nodes_free(optional); + lilv_nodes_free(required); + return result; +} + +LILV_API LilvNodes* +lilv_plugin_get_optional_features(const LilvPlugin* plugin) +{ + lilv_plugin_load_if_necessary(plugin); + return lilv_world_find_nodes_internal(plugin->world, + plugin->plugin_uri->node, + plugin->world->uris.lv2_optionalFeature, + NULL); +} + +LILV_API LilvNodes* +lilv_plugin_get_required_features(const LilvPlugin* plugin) +{ + lilv_plugin_load_if_necessary(plugin); + return lilv_world_find_nodes_internal(plugin->world, + plugin->plugin_uri->node, + plugin->world->uris.lv2_requiredFeature, + NULL); +} + +LILV_API bool +lilv_plugin_has_extension_data(const LilvPlugin* plugin, + const LilvNode* uri) +{ + if (!lilv_node_is_uri(uri)) { + LILV_ERRORF("Extension data `%s' is not a URI\n", + sord_node_get_string(uri->node)); + return false; + } + + lilv_plugin_load_if_necessary(plugin); + return lilv_world_ask_internal( + plugin->world, + plugin->plugin_uri->node, + plugin->world->uris.lv2_extensionData, + uri->node); +} + +LILV_API LilvNodes* +lilv_plugin_get_extension_data(const LilvPlugin* plugin) +{ + return lilv_plugin_get_value_internal(plugin, plugin->world->uris.lv2_extensionData); +} + +LILV_API const LilvPort* +lilv_plugin_get_port_by_index(const LilvPlugin* plugin, + uint32_t index) +{ + lilv_plugin_load_ports_if_necessary(plugin); + if (index < plugin->num_ports) { + return plugin->ports[index]; + } else { + return NULL; + } +} + +LILV_API const LilvPort* +lilv_plugin_get_port_by_symbol(const LilvPlugin* plugin, + const LilvNode* symbol) +{ + lilv_plugin_load_ports_if_necessary(plugin); + for (uint32_t i = 0; i < plugin->num_ports; ++i) { + LilvPort* port = plugin->ports[i]; + if (lilv_node_equals(port->symbol, symbol)) { + return port; + } + } + + return NULL; +} + +LILV_API LilvNode* +lilv_plugin_get_project(const LilvPlugin* plugin) +{ + lilv_plugin_load_if_necessary(plugin); + + SordNode* lv2_project = sord_new_uri(plugin->world->world, + (const uint8_t*)LV2_CORE__project); + + SordIter* projects = lilv_world_query_internal(plugin->world, + plugin->plugin_uri->node, + lv2_project, + NULL); + + sord_node_free(plugin->world->world, lv2_project); + + if (sord_iter_end(projects)) { + sord_iter_free(projects); + return NULL; + } + + const SordNode* project = sord_iter_get_node(projects, SORD_OBJECT); + + sord_iter_free(projects); + return lilv_node_new_from_node(plugin->world, project); +} + +static const SordNode* +lilv_plugin_get_author(const LilvPlugin* plugin) +{ + lilv_plugin_load_if_necessary(plugin); + + SordNode* doap_maintainer = sord_new_uri( + plugin->world->world, NS_DOAP "maintainer"); + + SordIter* maintainers = lilv_world_query_internal( + plugin->world, + plugin->plugin_uri->node, + doap_maintainer, + NULL); + + if (sord_iter_end(maintainers)) { + sord_iter_free(maintainers); + + LilvNode* project = lilv_plugin_get_project(plugin); + if (!project) { + sord_node_free(plugin->world->world, doap_maintainer); + return NULL; + } + + maintainers = lilv_world_query_internal( + plugin->world, + project->node, + doap_maintainer, + NULL); + + lilv_node_free(project); + } + + sord_node_free(plugin->world->world, doap_maintainer); + + if (sord_iter_end(maintainers)) { + sord_iter_free(maintainers); + return NULL; + } + + const SordNode* author = sord_iter_get_node(maintainers, SORD_OBJECT); + + sord_iter_free(maintainers); + return author; +} + +static LilvNode* +lilv_plugin_get_author_property(const LilvPlugin* plugin, const uint8_t* uri) +{ + const SordNode* author = lilv_plugin_get_author(plugin); + if (author) { + SordWorld* sworld = plugin->world->world; + SordNode* pred = sord_new_uri(sworld, uri); + LilvNode* ret = lilv_plugin_get_one(plugin, author, pred); + sord_node_free(sworld, pred); + return ret; + } + return NULL; +} + +LILV_API LilvNode* +lilv_plugin_get_author_name(const LilvPlugin* plugin) +{ + return lilv_plugin_get_author_property(plugin, NS_FOAF "name"); +} + +LILV_API LilvNode* +lilv_plugin_get_author_email(const LilvPlugin* plugin) +{ + return lilv_plugin_get_author_property(plugin, NS_FOAF "mbox"); +} + +LILV_API LilvNode* +lilv_plugin_get_author_homepage(const LilvPlugin* plugin) +{ + return lilv_plugin_get_author_property(plugin, NS_FOAF "homepage"); +} + +LILV_API bool +lilv_plugin_is_replaced(const LilvPlugin* plugin) +{ + return plugin->replaced; +} + +LILV_API LilvUIs* +lilv_plugin_get_uis(const LilvPlugin* plugin) +{ + lilv_plugin_load_if_necessary(plugin); + + SordNode* ui_ui_node = sord_new_uri(plugin->world->world, + (const uint8_t*)LV2_UI__ui); + SordNode* ui_binary_node = sord_new_uri(plugin->world->world, + (const uint8_t*)LV2_UI__binary); + + LilvUIs* result = lilv_uis_new(); + SordIter* uis = lilv_world_query_internal(plugin->world, + plugin->plugin_uri->node, + ui_ui_node, + NULL); + + FOREACH_MATCH(uis) { + const SordNode* ui = sord_iter_get_node(uis, SORD_OBJECT); + + LilvNode* type = lilv_plugin_get_unique(plugin, ui, plugin->world->uris.rdf_a); + LilvNode* binary = lilv_plugin_get_one(plugin, ui, plugin->world->uris.lv2_binary); + if (!binary) { + binary = lilv_plugin_get_unique(plugin, ui, ui_binary_node); + } + + if (sord_node_get_type(ui) != SORD_URI + || !lilv_node_is_uri(type) + || !lilv_node_is_uri(binary)) { + lilv_node_free(binary); + lilv_node_free(type); + LILV_ERRORF("Corrupt UI <%s>\n", sord_node_get_string(ui)); + continue; + } + + LilvUI* lilv_ui = lilv_ui_new( + plugin->world, + lilv_node_new_from_node(plugin->world, ui), + type, + binary); + + zix_tree_insert((ZixTree*)result, lilv_ui, NULL); + } + sord_iter_free(uis); + + sord_node_free(plugin->world->world, ui_binary_node); + sord_node_free(plugin->world->world, ui_ui_node); + + if (lilv_uis_size(result) > 0) { + return result; + } else { + lilv_uis_free(result); + return NULL; + } +} + +LILV_API LilvNodes* +lilv_plugin_get_related(const LilvPlugin* plugin, const LilvNode* type) +{ + lilv_plugin_load_if_necessary(plugin); + + LilvWorld* const world = plugin->world; + LilvNodes* const related = lilv_world_find_nodes_internal( + world, + NULL, + world->uris.lv2_appliesTo, + lilv_plugin_get_uri(plugin)->node); + + if (!type) { + return related; + } + + LilvNodes* matches = lilv_nodes_new(); + LILV_FOREACH(nodes, i, related) { + LilvNode* node = (LilvNode*)lilv_collection_get((ZixTree*)related, i); + if (lilv_world_ask_internal( + world, node->node, world->uris.rdf_a, type->node)) { + zix_tree_insert((ZixTree*)matches, + lilv_node_new_from_node(world, node->node), + NULL); + } + } + + lilv_nodes_free(related); + return matches; +} + +static SerdEnv* +new_lv2_env(const SerdNode* base) +{ + SerdEnv* env = serd_env_new(base); + +#define USTR(s) ((const uint8_t*)(s)) + serd_env_set_prefix_from_strings(env, USTR("doap"), USTR(LILV_NS_DOAP)); + serd_env_set_prefix_from_strings(env, USTR("foaf"), USTR(LILV_NS_FOAF)); + serd_env_set_prefix_from_strings(env, USTR("lv2"), USTR(LILV_NS_LV2)); + serd_env_set_prefix_from_strings(env, USTR("owl"), USTR(LILV_NS_OWL)); + serd_env_set_prefix_from_strings(env, USTR("rdf"), USTR(LILV_NS_RDF)); + serd_env_set_prefix_from_strings(env, USTR("rdfs"), USTR(LILV_NS_RDFS)); + serd_env_set_prefix_from_strings(env, USTR("xsd"), USTR(LILV_NS_XSD)); + + return env; +} + +static void +maybe_write_prefixes(SerdWriter* writer, SerdEnv* env, FILE* file) +{ + fseek(file, 0, SEEK_END); + if (ftell(file) == 0) { + serd_env_foreach( + env, (SerdPrefixSink)serd_writer_set_prefix, writer); + } else { + fprintf(file, "\n"); + } +} + +LILV_API void +lilv_plugin_write_description(LilvWorld* world, + const LilvPlugin* plugin, + const LilvNode* base_uri, + FILE* plugin_file) +{ + const LilvNode* subject = lilv_plugin_get_uri(plugin); + const uint32_t num_ports = lilv_plugin_get_num_ports(plugin); + const SerdNode* base = sord_node_to_serd_node(base_uri->node); + SerdEnv* env = new_lv2_env(base); + + SerdWriter* writer = serd_writer_new( + SERD_TURTLE, + (SerdStyle)(SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED), + env, + NULL, + serd_file_sink, + plugin_file); + + // Write prefixes if this is a new file + maybe_write_prefixes(writer, env, plugin_file); + + // Write plugin description + SordIter* plug_iter = lilv_world_query_internal( + world, subject->node, NULL, NULL); + sord_write_iter(plug_iter, writer); + + // Write port descriptions + for (uint32_t i = 0; i < num_ports; ++i) { + const LilvPort* port = plugin->ports[i]; + SordIter* port_iter = lilv_world_query_internal( + world, port->node->node, NULL, NULL); + sord_write_iter(port_iter, writer); + } + + serd_writer_free(writer); + serd_env_free(env); +} + +LILV_API void +lilv_plugin_write_manifest_entry(LilvWorld* world, + const LilvPlugin* plugin, + const LilvNode* base_uri, + FILE* manifest_file, + const char* plugin_file_path) +{ + const LilvNode* subject = lilv_plugin_get_uri(plugin); + const SerdNode* base = sord_node_to_serd_node(base_uri->node); + SerdEnv* env = new_lv2_env(base); + + SerdWriter* writer = serd_writer_new( + SERD_TURTLE, + (SerdStyle)(SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED), + env, + NULL, + serd_file_sink, + manifest_file); + + // Write prefixes if this is a new file + maybe_write_prefixes(writer, env, manifest_file); + + // Write manifest entry + serd_writer_write_statement( + writer, 0, NULL, + sord_node_to_serd_node(subject->node), + sord_node_to_serd_node(plugin->world->uris.rdf_a), + sord_node_to_serd_node(plugin->world->uris.lv2_Plugin), 0, 0); + + const SerdNode file_node = serd_node_from_string( + SERD_URI, (const uint8_t*)plugin_file_path); + serd_writer_write_statement( + writer, 0, NULL, + sord_node_to_serd_node(subject->node), + sord_node_to_serd_node(plugin->world->uris.rdfs_seeAlso), + &file_node, 0, 0); + + serd_writer_free(writer); + serd_env_free(env); +} diff --git a/src/pluginclass.c b/src/pluginclass.c new file mode 100644 index 0000000..622ff8f --- /dev/null +++ b/src/pluginclass.c @@ -0,0 +1,88 @@ +/* + Copyright 2007-2015 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <stdlib.h> +#include <string.h> + +#include "lilv_internal.h" + +LilvPluginClass* +lilv_plugin_class_new(LilvWorld* world, + const SordNode* parent_node, + const SordNode* uri, + const char* label) +{ + LilvPluginClass* pc = (LilvPluginClass*)malloc(sizeof(LilvPluginClass)); + pc->world = world; + pc->uri = lilv_node_new_from_node(world, uri); + pc->label = lilv_node_new(world, LILV_VALUE_STRING, label); + pc->parent_uri = (parent_node + ? lilv_node_new_from_node(world, parent_node) + : NULL); + return pc; +} + +void +lilv_plugin_class_free(LilvPluginClass* plugin_class) +{ + if (!plugin_class) { + return; + } + + lilv_node_free(plugin_class->uri); + lilv_node_free(plugin_class->parent_uri); + lilv_node_free(plugin_class->label); + free(plugin_class); +} + +LILV_API const LilvNode* +lilv_plugin_class_get_parent_uri(const LilvPluginClass* plugin_class) +{ + return plugin_class->parent_uri ? plugin_class->parent_uri : NULL; +} + +LILV_API const LilvNode* +lilv_plugin_class_get_uri(const LilvPluginClass* plugin_class) +{ + return plugin_class->uri; +} + +LILV_API const LilvNode* +lilv_plugin_class_get_label(const LilvPluginClass* plugin_class) +{ + return plugin_class->label; +} + +LILV_API LilvPluginClasses* +lilv_plugin_class_get_children(const LilvPluginClass* plugin_class) +{ + // Returned list doesn't own categories + LilvPluginClasses* all = plugin_class->world->plugin_classes; + LilvPluginClasses* result = zix_tree_new(false, lilv_ptr_cmp, NULL, NULL); + + for (ZixTreeIter* i = zix_tree_begin((ZixTree*)all); + i != zix_tree_end((ZixTree*)all); + i = zix_tree_iter_next(i)) { + const LilvPluginClass* c = (LilvPluginClass*)zix_tree_get(i); + const LilvNode* parent = lilv_plugin_class_get_parent_uri(c); + if (parent && lilv_node_equals(lilv_plugin_class_get_uri(plugin_class), + parent)) { + zix_tree_insert((ZixTree*)result, (LilvPluginClass*)c, NULL); + } + } + + return result; +} diff --git a/src/port.c b/src/port.c new file mode 100644 index 0000000..fcc32fc --- /dev/null +++ b/src/port.c @@ -0,0 +1,268 @@ +/* + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <assert.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/event/event.h" + +#include "lilv_internal.h" + +LilvPort* +lilv_port_new(LilvWorld* world, + const SordNode* node, + uint32_t index, + const char* symbol) +{ + LilvPort* port = (LilvPort*)malloc(sizeof(LilvPort)); + port->node = lilv_node_new_from_node(world, node); + port->index = index; + port->symbol = lilv_node_new(world, LILV_VALUE_STRING, symbol); + port->classes = lilv_nodes_new(); + return port; +} + +void +lilv_port_free(const LilvPlugin* plugin, LilvPort* port) +{ + if (port) { + lilv_node_free(port->node); + lilv_nodes_free(port->classes); + lilv_node_free(port->symbol); + free(port); + } +} + +LILV_API bool +lilv_port_is_a(const LilvPlugin* plugin, + const LilvPort* port, + const LilvNode* port_class) +{ + LILV_FOREACH(nodes, i, port->classes) { + if (lilv_node_equals(lilv_nodes_get(port->classes, i), port_class)) { + return true; + } + } + + return false; +} + +LILV_API bool +lilv_port_has_property(const LilvPlugin* plugin, + const LilvPort* port, + const LilvNode* property) +{ + return lilv_world_ask_internal(plugin->world, + port->node->node, + plugin->world->uris.lv2_portProperty, + property->node); +} + +LILV_API bool +lilv_port_supports_event(const LilvPlugin* plugin, + const LilvPort* port, + const LilvNode* event_type) +{ + const uint8_t* predicates[] = { (const uint8_t*)LV2_EVENT__supportsEvent, + (const uint8_t*)LV2_ATOM__supports, + NULL }; + + for (const uint8_t** pred = predicates; *pred; ++pred) { + if (lilv_world_ask_internal(plugin->world, + port->node->node, + sord_new_uri(plugin->world->world, *pred), + event_type->node)) { + return true; + } + } + return false; +} + +static LilvNodes* +lilv_port_get_value_by_node(const LilvPlugin* plugin, + const LilvPort* port, + const SordNode* predicate) +{ + return lilv_world_find_nodes_internal(plugin->world, + port->node->node, + predicate, + NULL); +} + +LILV_API const LilvNode* +lilv_port_get_node(const LilvPlugin* plugin, + const LilvPort* port) +{ + return port->node; +} + +LILV_API LilvNodes* +lilv_port_get_value(const LilvPlugin* plugin, + const LilvPort* port, + const LilvNode* predicate) +{ + if (!lilv_node_is_uri(predicate)) { + LILV_ERRORF("Predicate `%s' is not a URI\n", + sord_node_get_string(predicate->node)); + return NULL; + } + + return lilv_port_get_value_by_node(plugin, port, predicate->node); +} + +LILV_API LilvNode* +lilv_port_get(const LilvPlugin* plugin, + const LilvPort* port, + const LilvNode* predicate) +{ + LilvNodes* values = lilv_port_get_value(plugin, port, predicate); + + LilvNode* value = lilv_node_duplicate( + values ? lilv_nodes_get_first(values) : NULL); + + lilv_nodes_free(values); + return value; +} + +LILV_API uint32_t +lilv_port_get_index(const LilvPlugin* plugin, + const LilvPort* port) +{ + return port->index; +} + +LILV_API const LilvNode* +lilv_port_get_symbol(const LilvPlugin* plugin, + const LilvPort* port) +{ + return port->symbol; +} + +LILV_API LilvNode* +lilv_port_get_name(const LilvPlugin* plugin, + const LilvPort* port) +{ + LilvNodes* results = lilv_port_get_value_by_node( + plugin, port, plugin->world->uris.lv2_name); + + LilvNode* ret = NULL; + if (results) { + LilvNode* val = lilv_nodes_get_first(results); + if (lilv_node_is_string(val)) { + ret = lilv_node_duplicate(val); + } + lilv_nodes_free(results); + } + + if (!ret) { + LILV_WARNF("Plugin <%s> port has no (mandatory) doap:name\n", + lilv_node_as_string(lilv_plugin_get_uri(plugin))); + } + + return ret; +} + +LILV_API const LilvNodes* +lilv_port_get_classes(const LilvPlugin* plugin, + const LilvPort* port) +{ + return port->classes; +} + +LILV_API void +lilv_port_get_range(const LilvPlugin* plugin, + const LilvPort* port, + LilvNode** def, + LilvNode** min, + LilvNode** max) +{ + if (def) { + LilvNodes* defaults = lilv_port_get_value_by_node( + plugin, port, plugin->world->uris.lv2_default); + *def = defaults + ? lilv_node_duplicate(lilv_nodes_get_first(defaults)) + : NULL; + lilv_nodes_free(defaults); + } + if (min) { + LilvNodes* minimums = lilv_port_get_value_by_node( + plugin, port, plugin->world->uris.lv2_minimum); + *min = minimums + ? lilv_node_duplicate(lilv_nodes_get_first(minimums)) + : NULL; + lilv_nodes_free(minimums); + } + if (max) { + LilvNodes* maximums = lilv_port_get_value_by_node( + plugin, port, plugin->world->uris.lv2_maximum); + *max = maximums + ? lilv_node_duplicate(lilv_nodes_get_first(maximums)) + : NULL; + lilv_nodes_free(maximums); + } +} + +LILV_API LilvScalePoints* +lilv_port_get_scale_points(const LilvPlugin* plugin, + const LilvPort* port) +{ + SordIter* points = lilv_world_query_internal( + plugin->world, + port->node->node, + sord_new_uri(plugin->world->world, (const uint8_t*)LV2_CORE__scalePoint), + NULL); + + LilvScalePoints* ret = NULL; + if (!sord_iter_end(points)) { + ret = lilv_scale_points_new(); + } + + FOREACH_MATCH(points) { + const SordNode* point = sord_iter_get_node(points, SORD_OBJECT); + + LilvNode* value = lilv_plugin_get_unique(plugin, + point, + plugin->world->uris.rdf_value); + + LilvNode* label = lilv_plugin_get_unique(plugin, + point, + plugin->world->uris.rdfs_label); + + if (value && label) { + zix_tree_insert( + (ZixTree*)ret, lilv_scale_point_new(value, label), NULL); + } + } + sord_iter_free(points); + + assert(!ret || lilv_nodes_size(ret) > 0); + return ret; +} + +LILV_API LilvNodes* +lilv_port_get_properties(const LilvPlugin* plugin, + const LilvPort* port) +{ + LilvNode* pred = lilv_node_new_from_node( + plugin->world, plugin->world->uris.lv2_portProperty); + LilvNodes* ret = lilv_port_get_value(plugin, port, pred); + lilv_node_free(pred); + return ret; +} diff --git a/src/query.c b/src/query.c new file mode 100644 index 0000000..fe2988f --- /dev/null +++ b/src/query.c @@ -0,0 +1,139 @@ +/* + Copyright 2007-2015 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#include "lilv_internal.h" + +typedef enum { + LILV_LANG_MATCH_NONE, ///< Language does not match at all + LILV_LANG_MATCH_PARTIAL, ///< Partial (language, but not country) match + LILV_LANG_MATCH_EXACT ///< Exact (language and country) match +} LilvLangMatch; + +static LilvLangMatch +lilv_lang_matches(const char* a, const char* b) +{ + if (!strcmp(a, b)) { + return LILV_LANG_MATCH_EXACT; + } + + const char* a_dash = strchr(a, '-'); + const size_t a_lang_len = a_dash ? (size_t)(a_dash - a) : strlen(a); + const char* b_dash = strchr(b, '-'); + const size_t b_lang_len = b_dash ? (size_t)(b_dash - b) : strlen(b); + + if (a_lang_len == b_lang_len && !strncmp(a, b, a_lang_len)) { + return LILV_LANG_MATCH_PARTIAL; + } + + return LILV_LANG_MATCH_NONE; +} + +static LilvNodes* +lilv_nodes_from_stream_objects_i18n(LilvWorld* world, + SordIter* stream, + SordQuadIndex field) +{ + LilvNodes* values = lilv_nodes_new(); + const SordNode* nolang = NULL; // Untranslated value + const SordNode* partial = NULL; // Partial language match + char* syslang = lilv_get_lang(); + FOREACH_MATCH(stream) { + const SordNode* value = sord_iter_get_node(stream, field); + if (sord_node_get_type(value) == SORD_LITERAL) { + const char* lang = sord_node_get_language(value); + LilvLangMatch lm = LILV_LANG_MATCH_NONE; + if (lang) { + lm = (syslang) + ? lilv_lang_matches(lang, syslang) + : LILV_LANG_MATCH_PARTIAL; + } else { + nolang = value; + if (!syslang) { + lm = LILV_LANG_MATCH_EXACT; + } + } + + if (lm == LILV_LANG_MATCH_EXACT) { + // Exact language match, add to results + zix_tree_insert((ZixTree*)values, + lilv_node_new_from_node(world, value), + NULL); + } else if (lm == LILV_LANG_MATCH_PARTIAL) { + // Partial language match, save in case we find no exact + partial = value; + } + } else { + zix_tree_insert((ZixTree*)values, + lilv_node_new_from_node(world, value), + NULL); + } + } + sord_iter_free(stream); + free(syslang); + + if (lilv_nodes_size(values) > 0) { + return values; + } + + const SordNode* best = nolang; + if (syslang && partial) { + // Partial language match for system language + best = partial; + } else if (!best) { + // No languages matches at all, and no untranslated value + // Use any value, if possible + best = partial; + } + + if (best) { + zix_tree_insert( + (ZixTree*)values, lilv_node_new_from_node(world, best), NULL); + } else { + // No matches whatsoever + lilv_nodes_free(values); + values = NULL; + } + + return values; +} + +LilvNodes* +lilv_nodes_from_stream_objects(LilvWorld* world, + SordIter* stream, + SordQuadIndex field) +{ + if (sord_iter_end(stream)) { + sord_iter_free(stream); + return NULL; + } else if (world->opt.filter_language) { + return lilv_nodes_from_stream_objects_i18n(world, stream, field); + } else { + LilvNodes* values = lilv_nodes_new(); + FOREACH_MATCH(stream) { + const SordNode* value = sord_iter_get_node(stream, field); + LilvNode* node = lilv_node_new_from_node(world, value); + if (node) { + zix_tree_insert((ZixTree*)values, node, NULL); + } + } + sord_iter_free(stream); + return values; + } +} diff --git a/src/scalepoint.c b/src/scalepoint.c new file mode 100644 index 0000000..349b2b3 --- /dev/null +++ b/src/scalepoint.c @@ -0,0 +1,49 @@ +/* + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "lilv_internal.h" + +/** Ownership of value and label is taken */ +LilvScalePoint* +lilv_scale_point_new(LilvNode* value, LilvNode* label) +{ + LilvScalePoint* point = (LilvScalePoint*)malloc(sizeof(LilvScalePoint)); + point->value = value; + point->label = label; + return point; +} + +void +lilv_scale_point_free(LilvScalePoint* point) +{ + if (point) { + lilv_node_free(point->value); + lilv_node_free(point->label); + free(point); + } +} + +LILV_API const LilvNode* +lilv_scale_point_get_value(const LilvScalePoint* point) +{ + return point->value; +} + +LILV_API const LilvNode* +lilv_scale_point_get_label(const LilvScalePoint* point) +{ + return point->label; +} diff --git a/src/state.c b/src/state.c new file mode 100644 index 0000000..2d1b349 --- /dev/null +++ b/src/state.c @@ -0,0 +1,1329 @@ +/* + Copyright 2007-2017 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/atom/forge.h" +#include "lv2/lv2plug.in/ns/ext/presets/presets.h" +#include "lv2/lv2plug.in/ns/ext/state/state.h" + +#include "lilv_config.h" +#include "lilv_internal.h" +#include "sratom/sratom.h" + +#define USTR(s) ((const uint8_t*)(s)) + +typedef struct { + void* value; ///< Value/Object + size_t size; ///< Size of value + uint32_t key; ///< Key/Predicate (URID) + uint32_t type; ///< Type of value (URID) + uint32_t flags; ///< State flags (POD, etc) +} Property; + +typedef struct { + char* symbol; ///< Symbol of port + void* value; ///< Value of port + uint32_t size; ///< Size of value + uint32_t type; ///< Type of value (URID) +} PortValue; + +typedef struct { + char* abs; ///< Absolute path of actual file + char* rel; ///< Abstract path (relative path in state dir) +} PathMap; + +typedef struct { + size_t n; + Property* props; +} PropertyArray; + +struct LilvStateImpl { + LilvNode* plugin_uri; ///< Plugin URI + LilvNode* uri; ///< State/preset URI + char* dir; ///< Save directory (if saved) + char* file_dir; ///< Directory for files created by plugin + char* copy_dir; ///< Directory for snapshots of external files + char* link_dir; ///< Directory for links to external files + char* label; ///< State/Preset label + ZixTree* abs2rel; ///< PathMap sorted by abs + ZixTree* rel2abs; ///< PathMap sorted by rel + PropertyArray props; ///< State properties + PropertyArray metadata; ///< State metadata + PortValue* values; ///< Port values + uint32_t atom_Path; ///< atom:Path URID + uint32_t n_values; ///< Number of port values +}; + +static int +abs_cmp(const void* a, const void* b, void* user_data) +{ + return strcmp(((const PathMap*)a)->abs, ((const PathMap*)b)->abs); +} + +static int +rel_cmp(const void* a, const void* b, void* user_data) +{ + return strcmp(((const PathMap*)a)->rel, ((const PathMap*)b)->rel); +} + +static int +property_cmp(const void* a, const void* b) +{ + return ((const Property*)a)->key - ((const Property*)b)->key; +} + +static int +value_cmp(const void* a, const void* b) +{ + return strcmp(((const PortValue*)a)->symbol, + ((const PortValue*)b)->symbol); +} + +static void +path_rel_free(void* ptr) +{ + free(((PathMap*)ptr)->abs); + free(((PathMap*)ptr)->rel); + free(ptr); +} + +static PortValue* +append_port_value(LilvState* state, + const char* port_symbol, + const void* value, + uint32_t size, + uint32_t type) +{ + PortValue* pv = NULL; + if (value) { + state->values = (PortValue*)realloc( + state->values, (++state->n_values) * sizeof(PortValue)); + pv = &state->values[state->n_values - 1]; + pv->symbol = lilv_strdup(port_symbol); + pv->value = malloc(size); + pv->size = size; + pv->type = type; + memcpy(pv->value, value, size); + } + return pv; +} + +static const char* +lilv_state_rel2abs(const LilvState* state, const char* path) +{ + ZixTreeIter* iter = NULL; + const PathMap key = { NULL, (char*)path }; + if (state->rel2abs && !zix_tree_find(state->rel2abs, &key, &iter)) { + return ((const PathMap*)zix_tree_get(iter))->abs; + } + return path; +} + +static void +append_property(LilvState* state, + PropertyArray* array, + uint32_t key, + const void* value, + size_t size, + uint32_t type, + uint32_t flags) +{ + array->props = (Property*)realloc( + array->props, (++array->n) * sizeof(Property)); + + Property* const prop = &array->props[array->n - 1]; + if ((flags & LV2_STATE_IS_POD) || type == state->atom_Path) { + prop->value = malloc(size); + memcpy(prop->value, value, size); + } else { + prop->value = (void*)value; + } + + prop->size = size; + prop->key = key; + prop->type = type; + prop->flags = flags; +} + +static LV2_State_Status +store_callback(LV2_State_Handle handle, + uint32_t key, + const void* value, + size_t size, + uint32_t type, + uint32_t flags) +{ + LilvState* const state = (LilvState*)handle; + append_property(state, &state->props, key, value, size, type, flags); + return LV2_STATE_SUCCESS; +} + +static const void* +retrieve_callback(LV2_State_Handle handle, + uint32_t key, + size_t* size, + uint32_t* type, + uint32_t* flags) +{ + const LilvState* const state = (LilvState*)handle; + const Property search_key = { NULL, 0, key, 0, 0 }; + const Property* const prop = (Property*)bsearch( + &search_key, state->props.props, state->props.n, + sizeof(Property), property_cmp); + + if (prop) { + *size = prop->size; + *type = prop->type; + *flags = prop->flags; + return prop->value; + } + return NULL; +} + +static bool +lilv_state_has_path(const char* path, void* state) +{ + return lilv_state_rel2abs((LilvState*)state, path) != path; +} + +static char* +make_path(LV2_State_Make_Path_Handle handle, const char* path) +{ + LilvState* state = (LilvState*)handle; + lilv_mkdir_p(state->dir); + + return lilv_path_join(state->dir, path); +} + +static char* +abstract_path(LV2_State_Map_Path_Handle handle, + const char* abs_path) +{ + LilvState* state = (LilvState*)handle; + char* path = NULL; + char* real_path = lilv_realpath(abs_path); + const PathMap key = { real_path, NULL }; + ZixTreeIter* iter = NULL; + + if (abs_path[0] == '\0') { + return lilv_strdup(abs_path); + } else if (!zix_tree_find(state->abs2rel, &key, &iter)) { + // Already mapped path in a previous call + PathMap* pm = (PathMap*)zix_tree_get(iter); + free(real_path); + return lilv_strdup(pm->rel); + } else if (lilv_path_is_child(real_path, state->dir)) { + // File in state directory (loaded, or created by plugin during save) + path = lilv_path_relative_to(real_path, state->dir); + } else if (lilv_path_is_child(real_path, state->file_dir)) { + // File created by plugin earlier + path = lilv_path_relative_to(real_path, state->file_dir); + if (state->copy_dir) { + int st = lilv_mkdir_p(state->copy_dir); + if (st) { + LILV_ERRORF("Error creating directory %s (%s)\n", + state->copy_dir, strerror(st)); + } + + char* cpath = lilv_path_join(state->copy_dir, path); + char* copy = lilv_get_latest_copy(real_path, cpath); + if (!copy || !lilv_file_equals(real_path, copy)) { + // No recent enough copy, make a new one + free(copy); + copy = lilv_find_free_path(cpath, lilv_path_exists, NULL); + if ((st = lilv_copy_file(real_path, copy))) { + LILV_ERRORF("Error copying state file %s (%s)\n", + copy, strerror(st)); + } + } + free(real_path); + free(cpath); + + // Refer to the latest copy in plugin state + real_path = copy; + } + } else if (state->link_dir) { + // New path outside state directory, make a link + const char* slash = strrchr(real_path, '/'); + const char* name = slash ? (slash + 1) : real_path; + + // Find a free name in the (virtual) state directory + path = lilv_find_free_path(name, lilv_state_has_path, state); + } else { + // No link directory, preserve absolute path + path = lilv_strdup(abs_path); + } + + // Add record to path mapping + PathMap* pm = (PathMap*)malloc(sizeof(PathMap)); + pm->abs = real_path; + pm->rel = lilv_strdup(path); + zix_tree_insert(state->abs2rel, pm, NULL); + zix_tree_insert(state->rel2abs, pm, NULL); + + return path; +} + +static char* +absolute_path(LV2_State_Map_Path_Handle handle, + const char* state_path) +{ + LilvState* state = (LilvState*)handle; + char* path = NULL; + if (lilv_path_is_absolute(state_path)) { + // Absolute path, return identical path + path = lilv_strdup(state_path); + } else if (state->dir) { + // Relative path inside state directory + path = lilv_path_join(state->dir, state_path); + } else { + // State has not been saved, unmap + path = lilv_strdup(lilv_state_rel2abs(state, state_path)); + } + + return path; +} + +/** Return a new features array which is `feature` added to `features`. */ +static const LV2_Feature** +add_features(const LV2_Feature *const * features, + const LV2_Feature* map, const LV2_Feature* make) +{ + size_t n_features = 0; + for (; features && features[n_features]; ++n_features) {} + + const LV2_Feature** ret = (const LV2_Feature**)calloc( + n_features + 3, sizeof(LV2_Feature*)); + + if (features) { + memcpy(ret, features, n_features * sizeof(LV2_Feature*)); + } + + ret[n_features] = map; + ret[n_features + 1] = make; + return ret; +} + +static char* +absolute_dir(const char* path) +{ + char* abs_path = lilv_path_absolute(path); + char* base = lilv_path_join(abs_path, NULL); + free(abs_path); + return base; +} + +static const char* +state_strerror(LV2_State_Status st) +{ + switch (st) { + case LV2_STATE_SUCCESS: return "Completed successfully"; + case LV2_STATE_ERR_BAD_TYPE: return "Unsupported type"; + case LV2_STATE_ERR_BAD_FLAGS: return "Unsupported flags"; + case LV2_STATE_ERR_NO_FEATURE: return "Missing features"; + case LV2_STATE_ERR_NO_PROPERTY: return "Missing property"; + default: return "Unknown error"; + } +} + +LILV_API LilvState* +lilv_state_new_from_instance(const LilvPlugin* plugin, + LilvInstance* instance, + LV2_URID_Map* map, + const char* file_dir, + const char* copy_dir, + const char* link_dir, + const char* save_dir, + LilvGetPortValueFunc get_value, + void* user_data, + uint32_t flags, + const LV2_Feature *const * features) +{ + const LV2_Feature** sfeatures = NULL; + LilvWorld* const world = plugin->world; + LilvState* const state = (LilvState*)calloc(1, sizeof(LilvState)); + state->plugin_uri = lilv_node_duplicate(lilv_plugin_get_uri(plugin)); + state->abs2rel = zix_tree_new(false, abs_cmp, NULL, path_rel_free); + state->rel2abs = zix_tree_new(false, rel_cmp, NULL, NULL); + state->file_dir = file_dir ? absolute_dir(file_dir) : NULL; + state->copy_dir = copy_dir ? absolute_dir(copy_dir) : NULL; + state->link_dir = link_dir ? absolute_dir(link_dir) : NULL; + state->dir = save_dir ? absolute_dir(save_dir) : NULL; + state->atom_Path = map->map(map->handle, LV2_ATOM__Path); + + LV2_State_Map_Path pmap = { state, abstract_path, absolute_path }; + LV2_Feature pmap_feature = { LV2_STATE__mapPath, &pmap }; + LV2_State_Make_Path pmake = { state, make_path }; + LV2_Feature pmake_feature = { LV2_STATE__makePath, &pmake }; + features = sfeatures = add_features(features, &pmap_feature, + save_dir ? &pmake_feature : NULL); + + // Store port values + if (get_value) { + LilvNode* lv2_ControlPort = lilv_new_uri(world, LILV_URI_CONTROL_PORT); + LilvNode* lv2_InputPort = lilv_new_uri(world, LILV_URI_INPUT_PORT); + for (uint32_t i = 0; i < plugin->num_ports; ++i) { + const LilvPort* const port = plugin->ports[i]; + if (lilv_port_is_a(plugin, port, lv2_ControlPort) + && lilv_port_is_a(plugin, port, lv2_InputPort)) { + uint32_t size, type; + const char* sym = lilv_node_as_string(port->symbol); + const void* value = get_value(sym, user_data, &size, &type); + append_port_value(state, sym, value, size, type); + } + } + lilv_node_free(lv2_ControlPort); + lilv_node_free(lv2_InputPort); + } + + // Store properties + const LV2_Descriptor* desc = instance->lv2_descriptor; + const LV2_State_Interface* iface = (desc->extension_data) + ? (const LV2_State_Interface*)desc->extension_data(LV2_STATE__interface) + : NULL; + + if (iface) { + LV2_State_Status st = iface->save( + instance->lv2_handle, store_callback, state, flags, features); + if (st) { + LILV_ERRORF("Error saving plugin state: %s\n", state_strerror(st)); + free(state->props.props); + state->props.props = NULL; + state->props.n = 0; + } else { + qsort(state->props.props, state->props.n, sizeof(Property), property_cmp); + } + } + + qsort(state->values, state->n_values, sizeof(PortValue), value_cmp); + + free(sfeatures); + return state; +} + +LILV_API void +lilv_state_emit_port_values(const LilvState* state, + LilvSetPortValueFunc set_value, + void* user_data) +{ + for (uint32_t i = 0; i < state->n_values; ++i) { + const PortValue* val = &state->values[i]; + set_value(val->symbol, user_data, val->value, val->size, val->type); + } +} + +LILV_API void +lilv_state_restore(const LilvState* state, + LilvInstance* instance, + LilvSetPortValueFunc set_value, + void* user_data, + uint32_t flags, + const LV2_Feature *const * features) +{ + if (!state) { + LILV_ERROR("lilv_state_restore() called on NULL state\n"); + return; + } + + LV2_State_Map_Path map_path = { + (LilvState*)state, abstract_path, absolute_path }; + LV2_Feature map_feature = { LV2_STATE__mapPath, &map_path }; + + if (instance) { + const LV2_Descriptor* desc = instance->lv2_descriptor; + if (desc->extension_data) { + const LV2_State_Interface* iface = (const LV2_State_Interface*) + desc->extension_data(LV2_STATE__interface); + + if (iface && iface->restore) { + const LV2_Feature** sfeatures = add_features( + features, &map_feature, NULL); + + iface->restore(instance->lv2_handle, retrieve_callback, + (LV2_State_Handle)state, flags, sfeatures); + + free(sfeatures); + } + } + } + + + if (set_value) { + lilv_state_emit_port_values(state, set_value, user_data); + } +} + +static LilvState* +new_state_from_model(LilvWorld* world, + LV2_URID_Map* map, + SordModel* model, + const SordNode* node, + const char* dir) +{ + // Check that we know at least something about this state subject + if (!sord_ask(model, node, 0, 0, 0)) { + return NULL; + } + + // Allocate state + LilvState* const state = (LilvState*)calloc(1, sizeof(LilvState)); + state->dir = lilv_strdup(dir); + state->atom_Path = map->map(map->handle, LV2_ATOM__Path); + state->uri = lilv_node_new_from_node(world, node); + + // Get the plugin URI this state applies to + SordIter* i = sord_search(model, node, world->uris.lv2_appliesTo, 0, 0); + if (i) { + const SordNode* object = sord_iter_get_node(i, SORD_OBJECT); + const SordNode* graph = sord_iter_get_node(i, SORD_GRAPH); + state->plugin_uri = lilv_node_new_from_node(world, object); + if (!state->dir && graph) { + state->dir = lilv_strdup((const char*)sord_node_get_string(graph)); + } + sord_iter_free(i); + } else if (sord_ask(model, + node, + world->uris.rdf_a, + world->uris.lv2_Plugin, 0)) { + // Loading plugin description as state (default state) + state->plugin_uri = lilv_node_new_from_node(world, node); + } else { + LILV_ERRORF("State %s missing lv2:appliesTo property\n", + sord_node_get_string(node)); + } + + // Get the state label + i = sord_search(model, node, world->uris.rdfs_label, NULL, NULL); + if (i) { + const SordNode* object = sord_iter_get_node(i, SORD_OBJECT); + const SordNode* graph = sord_iter_get_node(i, SORD_GRAPH); + state->label = lilv_strdup((const char*)sord_node_get_string(object)); + if (!state->dir && graph) { + state->dir = lilv_strdup((const char*)sord_node_get_string(graph)); + } + sord_iter_free(i); + } + + Sratom* sratom = sratom_new(map); + SerdChunk chunk = { NULL, 0 }; + LV2_Atom_Forge forge; + lv2_atom_forge_init(&forge, map); + lv2_atom_forge_set_sink( + &forge, sratom_forge_sink, sratom_forge_deref, &chunk); + + // Get port values + SordIter* ports = sord_search(model, node, world->uris.lv2_port, 0, 0); + FOREACH_MATCH(ports) { + const SordNode* port = sord_iter_get_node(ports, SORD_OBJECT); + + SordNode* label = sord_get(model, port, world->uris.rdfs_label, 0, 0); + SordNode* symbol = sord_get(model, port, world->uris.lv2_symbol, 0, 0); + SordNode* value = sord_get(model, port, world->uris.pset_value, 0, 0); + if (!value) { + value = sord_get(model, port, world->uris.lv2_default, 0, 0); + } + if (!symbol) { + LILV_ERRORF("State `%s' port missing symbol.\n", + sord_node_get_string(node)); + } else if (value) { + chunk.len = 0; + sratom_read(sratom, &forge, world->world, model, value); + const LV2_Atom* atom = (const LV2_Atom*)chunk.buf; + + append_port_value(state, + (const char*)sord_node_get_string(symbol), + LV2_ATOM_BODY_CONST(atom), + atom->size, atom->type); + + if (label) { + lilv_state_set_label(state, + (const char*)sord_node_get_string(label)); + } + } + sord_node_free(world->world, value); + sord_node_free(world->world, symbol); + sord_node_free(world->world, label); + } + sord_iter_free(ports); + + // Get properties + SordNode* statep = sord_new_uri(world->world, USTR(LV2_STATE__state)); + SordNode* state_node = sord_get(model, node, statep, NULL, NULL); + if (state_node) { + SordIter* props = sord_search(model, state_node, 0, 0, 0); + FOREACH_MATCH(props) { + const SordNode* p = sord_iter_get_node(props, SORD_PREDICATE); + const SordNode* o = sord_iter_get_node(props, SORD_OBJECT); + const char* key = (const char*)sord_node_get_string(p); + + chunk.len = 0; + lv2_atom_forge_set_sink( + &forge, sratom_forge_sink, sratom_forge_deref, &chunk); + + sratom_read(sratom, &forge, world->world, model, o); + const LV2_Atom* atom = (const LV2_Atom*)chunk.buf; + uint32_t flags = LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE; + Property prop = { NULL, 0, 0, 0, flags }; + + prop.key = map->map(map->handle, key); + prop.type = atom->type; + prop.size = atom->size; + prop.value = malloc(atom->size); + memcpy(prop.value, LV2_ATOM_BODY_CONST(atom), atom->size); + if (atom->type == forge.Path) { + prop.flags = LV2_STATE_IS_POD; + } + + if (prop.value) { + state->props.props = (Property*)realloc( + state->props.props, (++state->props.n) * sizeof(Property)); + state->props.props[state->props.n - 1] = prop; + } + } + sord_iter_free(props); + } + sord_node_free(world->world, state_node); + sord_node_free(world->world, statep); + + free((void*)chunk.buf); + sratom_free(sratom); + + if (state->props.props) { + qsort(state->props.props, state->props.n, sizeof(Property), property_cmp); + } + if (state->values) { + qsort(state->values, state->n_values, sizeof(PortValue), value_cmp); + } + + return state; +} + +LILV_API LilvState* +lilv_state_new_from_world(LilvWorld* world, + LV2_URID_Map* map, + const LilvNode* node) +{ + if (!lilv_node_is_uri(node) && !lilv_node_is_blank(node)) { + LILV_ERRORF("Subject `%s' is not a URI or blank node.\n", + lilv_node_as_string(node)); + return NULL; + } + + return new_state_from_model(world, map, world->model, node->node, NULL); +} + +LILV_API LilvState* +lilv_state_new_from_file(LilvWorld* world, + LV2_URID_Map* map, + const LilvNode* subject, + const char* path) +{ + if (subject && !lilv_node_is_uri(subject) + && !lilv_node_is_blank(subject)) { + LILV_ERRORF("Subject `%s' is not a URI or blank node.\n", + lilv_node_as_string(subject)); + return NULL; + } + + uint8_t* abs_path = (uint8_t*)lilv_path_absolute(path); + SerdNode node = serd_node_new_file_uri(abs_path, NULL, NULL, true); + SerdEnv* env = serd_env_new(&node); + SordModel* model = sord_new(world->world, SORD_SPO, false); + SerdReader* reader = sord_new_reader(model, env, SERD_TURTLE, NULL); + + serd_reader_read_file(reader, node.buf); + + SordNode* subject_node = (subject) + ? subject->node + : sord_node_from_serd_node(world->world, env, &node, NULL, NULL); + + char* dirname = lilv_dirname(path); + char* real_path = lilv_realpath(dirname); + LilvState* state = new_state_from_model( + world, map, model, subject_node, real_path); + free(dirname); + free(real_path); + + serd_node_free(&node); + free(abs_path); + serd_reader_free(reader); + sord_free(model); + serd_env_free(env); + return state; +} + +static void +set_prefixes(SerdEnv* env) +{ +#define SET_PSET(e, p, u) serd_env_set_prefix_from_strings(e, p, u) + SET_PSET(env, USTR("atom"), USTR(LV2_ATOM_PREFIX)); + SET_PSET(env, USTR("lv2"), USTR(LV2_CORE_PREFIX)); + SET_PSET(env, USTR("pset"), USTR(LV2_PRESETS_PREFIX)); + SET_PSET(env, USTR("rdf"), USTR(LILV_NS_RDF)); + SET_PSET(env, USTR("rdfs"), USTR(LILV_NS_RDFS)); + SET_PSET(env, USTR("state"), USTR(LV2_STATE_PREFIX)); + SET_PSET(env, USTR("xsd"), USTR(LILV_NS_XSD)); +} + +LILV_API LilvState* +lilv_state_new_from_string(LilvWorld* world, + LV2_URID_Map* map, + const char* str) +{ + if (!str) { + return NULL; + } + + SerdNode base = SERD_NODE_NULL; + SerdEnv* env = serd_env_new(&base); + SordModel* model = sord_new(world->world, SORD_SPO|SORD_OPS, false); + SerdReader* reader = sord_new_reader(model, env, SERD_TURTLE, NULL); + + set_prefixes(env); + serd_reader_read_string(reader, USTR(str)); + + SordNode* o = sord_new_uri(world->world, USTR(LV2_PRESETS__Preset)); + SordNode* s = sord_get(model, NULL, world->uris.rdf_a, o, NULL); + + LilvState* state = new_state_from_model(world, map, model, s, NULL); + + sord_node_free(world->world, s); + sord_node_free(world->world, o); + serd_reader_free(reader); + sord_free(model); + serd_env_free(env); + + return state; +} + +static SerdWriter* +ttl_writer(SerdSink sink, void* stream, const SerdNode* base, SerdEnv** new_env) +{ + SerdURI base_uri = SERD_URI_NULL; + if (base && base->buf) { + serd_uri_parse(base->buf, &base_uri); + } + + SerdEnv* env = *new_env ? *new_env : serd_env_new(base); + set_prefixes(env); + + SerdWriter* writer = serd_writer_new( + SERD_TURTLE, + (SerdStyle)(SERD_STYLE_RESOLVED | + SERD_STYLE_ABBREVIATED| + SERD_STYLE_CURIED), + env, + &base_uri, + sink, + stream); + + if (!*new_env) { + *new_env = env; + } + + return writer; +} + +static SerdWriter* +ttl_file_writer(FILE* fd, const SerdNode* node, SerdEnv** env) +{ + SerdWriter* writer = ttl_writer(serd_file_sink, fd, node, env); + + fseek(fd, 0, SEEK_END); + if (ftell(fd) == 0) { + serd_env_foreach(*env, (SerdPrefixSink)serd_writer_set_prefix, writer); + } else { + fprintf(fd, "\n"); + } + + return writer; +} + +static void +add_to_model(SordWorld* world, + SerdEnv* env, + SordModel* model, + const SerdNode s, + const SerdNode p, + const SerdNode o) +{ + SordNode* ss = sord_node_from_serd_node(world, env, &s, NULL, NULL); + SordNode* sp = sord_node_from_serd_node(world, env, &p, NULL, NULL); + SordNode* so = sord_node_from_serd_node(world, env, &o, NULL, NULL); + + SordQuad quad = { ss, sp, so, NULL }; + sord_add(model, quad); + + sord_node_free(world, ss); + sord_node_free(world, sp); + sord_node_free(world, so); +} + +static void +remove_manifest_entry(SordWorld* world, SordModel* model, const char* subject) +{ + SordNode* s = sord_new_uri(world, USTR(subject)); + SordIter* i = sord_search(model, s, NULL, NULL, NULL); + while (!sord_iter_end(i)) { + sord_erase(model, i); + } + sord_iter_free(i); + sord_node_free(world, s); +} + +static int +add_state_to_manifest(LilvWorld* lworld, + const LilvNode* plugin_uri, + const char* manifest_path, + const char* state_uri, + const char* state_path) +{ + SordWorld* world = lworld->world; + SerdNode manifest = serd_node_new_file_uri(USTR(manifest_path), 0, 0, 1); + SerdNode file = serd_node_new_file_uri(USTR(state_path), 0, 0, 1); + SerdEnv* env = serd_env_new(&manifest); + SordModel* model = sord_new(world, SORD_SPO, false); + + FILE* rfd = fopen(manifest_path, "r"); + if (rfd) { + // Read manifest into model + SerdReader* reader = sord_new_reader(model, env, SERD_TURTLE, NULL); + lilv_flock(rfd, true); + serd_reader_read_file_handle(reader, rfd, manifest.buf); + serd_reader_free(reader); + } + + // Choose state URI (use file URI if not given) + if (!state_uri) { + state_uri = (const char*)file.buf; + } + + // Remove any existing manifest entries for this state + remove_manifest_entry(world, model, state_uri); + + // Add manifest entry for this state to model + SerdNode s = serd_node_from_string(SERD_URI, USTR(state_uri)); + + // <state> a pset:Preset + add_to_model(world, env, model, + s, + serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "type")), + serd_node_from_string(SERD_URI, USTR(LV2_PRESETS__Preset))); + + // <state> a pset:Preset + add_to_model(world, env, model, + s, + serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "type")), + serd_node_from_string(SERD_URI, USTR(LV2_PRESETS__Preset))); + + // <state> rdfs:seeAlso <file> + add_to_model(world, env, model, + s, + serd_node_from_string(SERD_URI, USTR(LILV_NS_RDFS "seeAlso")), + file); + + // <state> lv2:appliesTo <plugin> + add_to_model(world, env, model, + s, + serd_node_from_string(SERD_URI, USTR(LV2_CORE__appliesTo)), + serd_node_from_string(SERD_URI, + USTR(lilv_node_as_string(plugin_uri)))); + + // Write manifest model to file + FILE* wfd = fopen(manifest_path, "w"); + if (wfd) { + SerdWriter* writer = ttl_file_writer(wfd, &manifest, &env); + sord_write(model, writer, NULL); + serd_writer_free(writer); + fclose(wfd); + } else { + LILV_ERRORF("Failed to open %s for writing (%s)\n", + manifest_path, strerror(errno)); + } + + sord_free(model); + serd_node_free(&file); + serd_node_free(&manifest); + serd_env_free(env); + + if (rfd) { + lilv_flock(rfd, false); + fclose(rfd); + } + + return 0; +} + +static bool +link_exists(const char* path, void* data) +{ + const char* target = (const char*)data; + if (!lilv_path_exists(path, NULL)) { + return false; + } + char* real_path = lilv_realpath(path); + bool matches = !strcmp(real_path, target); + free(real_path); + return !matches; +} + +static void +write_property_array(const LilvState* state, + const PropertyArray* array, + Sratom* sratom, + uint32_t flags, + const SerdNode* subject, + LV2_URID_Unmap* unmap, + const char* dir) +{ + for (uint32_t i = 0; i < array->n; ++i) { + Property* prop = &array->props[i]; + const char* key = unmap->unmap(unmap->handle, prop->key); + + const SerdNode p = serd_node_from_string(SERD_URI, USTR(key)); + if (prop->type == state->atom_Path && !dir) { + const char* path = (const char*)prop->value; + const char* abs_path = lilv_state_rel2abs(state, path); + LILV_WARNF("Writing absolute path %s\n", abs_path); + sratom_write(sratom, unmap, flags, + subject, &p, prop->type, + strlen(abs_path) + 1, abs_path); + } else if (prop->flags & LV2_STATE_IS_POD || + prop->type == state->atom_Path) { + sratom_write(sratom, unmap, flags, + subject, &p, prop->type, prop->size, prop->value); + } else { + LILV_WARNF("Lost non-POD property <%s> on save\n", key); + } + } +} + +static int +lilv_state_write(LilvWorld* world, + LV2_URID_Map* map, + LV2_URID_Unmap* unmap, + const LilvState* state, + SerdWriter* writer, + const char* uri, + const char* dir) +{ + SerdNode lv2_appliesTo = serd_node_from_string( + SERD_CURIE, USTR("lv2:appliesTo")); + + const SerdNode* plugin_uri = sord_node_to_serd_node( + state->plugin_uri->node); + + SerdNode subject = serd_node_from_string(SERD_URI, USTR(uri ? uri : "")); + + // <subject> a pset:Preset + SerdNode p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDF "type")); + SerdNode o = serd_node_from_string(SERD_URI, USTR(LV2_PRESETS__Preset)); + serd_writer_write_statement(writer, 0, NULL, + &subject, &p, &o, NULL, NULL); + + // <subject> lv2:appliesTo <http://example.org/plugin> + serd_writer_write_statement(writer, 0, NULL, + &subject, + &lv2_appliesTo, + plugin_uri, NULL, NULL); + + // <subject> rdfs:label label + if (state->label) { + p = serd_node_from_string(SERD_URI, USTR(LILV_NS_RDFS "label")); + o = serd_node_from_string(SERD_LITERAL, USTR(state->label)); + serd_writer_write_statement(writer, 0, + NULL, &subject, &p, &o, NULL, NULL); + } + + SerdEnv* env = serd_writer_get_env(writer); + const SerdNode* base = serd_env_get_base_uri(env, NULL); + + Sratom* sratom = sratom_new(map); + sratom_set_sink(sratom, (const char*)base->buf, + (SerdStatementSink)serd_writer_write_statement, + (SerdEndSink)serd_writer_end_anon, + writer); + + // Write metadata + sratom_set_pretty_numbers(sratom, false); // Use precise types + write_property_array(state, &state->metadata, sratom, 0, + &subject, unmap, dir); + + // Write port values + sratom_set_pretty_numbers(sratom, true); // Use pretty numbers + for (uint32_t i = 0; i < state->n_values; ++i) { + PortValue* const value = &state->values[i]; + + const SerdNode port = serd_node_from_string( + SERD_BLANK, USTR(value->symbol)); + + // <> lv2:port _:symbol + p = serd_node_from_string(SERD_URI, USTR(LV2_CORE__port)); + serd_writer_write_statement(writer, SERD_ANON_O_BEGIN, + NULL, &subject, &p, &port, NULL, NULL); + + // _:symbol lv2:symbol "symbol" + p = serd_node_from_string(SERD_URI, USTR(LV2_CORE__symbol)); + o = serd_node_from_string(SERD_LITERAL, USTR(value->symbol)); + serd_writer_write_statement(writer, SERD_ANON_CONT, + NULL, &port, &p, &o, NULL, NULL); + + // _:symbol pset:value value + p = serd_node_from_string(SERD_URI, USTR(LV2_PRESETS__value)); + sratom_write(sratom, unmap, SERD_ANON_CONT, &port, &p, + value->type, value->size, value->value); + + serd_writer_end_anon(writer, &port); + } + + // Write properties + const SerdNode body = serd_node_from_string(SERD_BLANK, USTR("body")); + if (state->props.n > 0) { + p = serd_node_from_string(SERD_URI, USTR(LV2_STATE__state)); + serd_writer_write_statement(writer, SERD_ANON_O_BEGIN, NULL, + &subject, &p, &body, NULL, NULL); + } + sratom_set_pretty_numbers(sratom, false); // Use precise types + write_property_array(state, &state->props, sratom, SERD_ANON_CONT, + &body, unmap, dir); + + if (state->props.n > 0) { + serd_writer_end_anon(writer, &body); + } + + sratom_free(sratom); + return 0; +} + +static void +lilv_state_make_links(const LilvState* state, const char* dir) +{ + // Create symlinks to files + for (ZixTreeIter* i = zix_tree_begin(state->abs2rel); + i != zix_tree_end(state->abs2rel); + i = zix_tree_iter_next(i)) { + const PathMap* pm = (const PathMap*)zix_tree_get(i); + + char* path = lilv_path_join(dir, pm->rel); + if (lilv_path_is_child(pm->abs, state->copy_dir) + && strcmp(state->copy_dir, dir)) { + // Link directly to snapshot in the copy directory + char* target = lilv_path_relative_to(pm->abs, dir); + lilv_symlink(target, path); + free(target); + } else if (!lilv_path_is_child(pm->abs, dir)) { + const char* link_dir = state->link_dir ? state->link_dir : dir; + char* pat = lilv_path_join(link_dir, pm->rel); + if (!strcmp(dir, link_dir)) { + // Link directory is save directory, make link at exact path + remove(pat); + lilv_symlink(pm->abs, pat); + } else { + // Make a link in the link directory to external file + char* lpath = lilv_find_free_path(pat, link_exists, pm->abs); + if (!lilv_path_exists(lpath, NULL)) { + lilv_symlink(pm->abs, lpath); + } + + // Make a link in the save directory to the external link + char* target = lilv_path_relative_to(lpath, dir); + lilv_symlink(target, path); + free(target); + free(lpath); + } + free(pat); + } + free(path); + } +} + +LILV_API int +lilv_state_save(LilvWorld* world, + LV2_URID_Map* map, + LV2_URID_Unmap* unmap, + const LilvState* state, + const char* uri, + const char* dir, + const char* filename) +{ + if (!filename || !dir || lilv_mkdir_p(dir)) { + return 1; + } + + char* abs_dir = absolute_dir(dir); + char* const path = lilv_path_join(abs_dir, filename); + FILE* fd = fopen(path, "w"); + if (!fd) { + LILV_ERRORF("Failed to open %s (%s)\n", path, strerror(errno)); + free(abs_dir); + free(path); + return 4; + } + + // Create symlinks to files if necessary + lilv_state_make_links(state, abs_dir); + + // Write state to Turtle file + SerdNode file = serd_node_new_file_uri(USTR(path), NULL, NULL, true); + SerdNode node = uri ? serd_node_from_string(SERD_URI, USTR(uri)) : file; + SerdEnv* env = NULL; + SerdWriter* ttl = ttl_file_writer(fd, &file, &env); + int ret = lilv_state_write( + world, map, unmap, state, ttl, (const char*)node.buf, dir); + + // Set saved dir and uri (FIXME: const violation) + SerdNode dir_uri = serd_node_new_file_uri(USTR(abs_dir), NULL, NULL, true); + free(state->dir); + lilv_node_free(state->uri); + ((LilvState*)state)->dir = (char*)dir_uri.buf; + ((LilvState*)state)->uri = lilv_new_uri(world, (const char*)node.buf); + + serd_node_free(&file); + serd_writer_free(ttl); + serd_env_free(env); + fclose(fd); + + // Add entry to manifest + char* const manifest = lilv_path_join(abs_dir, "manifest.ttl"); + add_state_to_manifest(world, state->plugin_uri, manifest, uri, path); + + free(manifest); + free(abs_dir); + free(path); + return ret; +} + +LILV_API char* +lilv_state_to_string(LilvWorld* world, + LV2_URID_Map* map, + LV2_URID_Unmap* unmap, + const LilvState* state, + const char* uri, + const char* base_uri) +{ + if (!uri) { + LILV_ERROR("Attempt to serialise state with no URI\n"); + return NULL; + } + + SerdChunk chunk = { NULL, 0 }; + SerdEnv* env = NULL; + SerdNode base = serd_node_from_string(SERD_URI, USTR(base_uri)); + SerdWriter* writer = ttl_writer(serd_chunk_sink, &chunk, &base, &env); + + lilv_state_write(world, map, unmap, state, writer, uri, NULL); + + serd_writer_free(writer); + serd_env_free(env); + return (char*)serd_chunk_sink_finish(&chunk); +} + +LILV_API int +lilv_state_delete(LilvWorld* world, + const LilvState* state) +{ + if (!state->dir || !state->uri) { + LILV_ERROR("Attempt to delete unsaved state\n"); + return -1; + } + + LilvNode* bundle = lilv_new_uri(world, state->dir); + LilvNode* manifest = lilv_world_get_manifest_uri(world, bundle); + char* manifest_path = lilv_node_get_path(manifest, NULL); + SordModel* model = sord_new(world->world, SORD_SPO, false); + + { + // Read manifest into temporary local model + SerdEnv* env = serd_env_new(sord_node_to_serd_node(manifest->node)); + SerdReader* ttl = sord_new_reader(model, env, SERD_TURTLE, NULL); + serd_reader_read_file(ttl, USTR(manifest_path)); + serd_reader_free(ttl); + serd_env_free(env); + } + + SordNode* file = sord_get( + model, state->uri->node, world->uris.rdfs_seeAlso, NULL, NULL); + if (file) { + // Remove state file + char* path = lilv_file_uri_parse( + (const char*)sord_node_get_string(file), NULL); + if (unlink(path)) { + LILV_ERRORF("Failed to remove %s (%s)\n", path, strerror(errno)); + } + lilv_free(path); + } + + // Remove any existing manifest entries for this state + remove_manifest_entry( + world->world, model, lilv_node_as_string(state->uri)); + remove_manifest_entry( + world->world, world->model, lilv_node_as_string(state->uri)); + + // Drop bundle from model + lilv_world_unload_bundle(world, bundle); + + if (sord_num_quads(model) == 0) { + // Manifest is empty, attempt to remove bundle entirely + if (unlink(manifest_path)) { + LILV_ERRORF("Failed to remove %s (%s)\n", + manifest_path, strerror(errno)); + } + char* dir_path = lilv_file_uri_parse(state->dir, NULL); + if (rmdir(dir_path)) { + LILV_ERRORF("Failed to remove %s (%s)\n", + dir_path, strerror(errno)); + } + lilv_free(dir_path); + } else { + // Still something in the manifest, reload bundle + lilv_world_load_bundle(world, bundle); + } + + sord_free(model); + lilv_free(manifest_path); + lilv_node_free(manifest); + lilv_node_free(bundle); + + return 0; +} + +static void +free_property_array(LilvState* state, PropertyArray* array) +{ + for (uint32_t i = 0; i < array->n; ++i) { + Property* prop = &array->props[i]; + if ((prop->flags & LV2_STATE_IS_POD) || + prop->type == state->atom_Path) { + free(prop->value); + } + } + free(array->props); +} + +LILV_API void +lilv_state_free(LilvState* state) +{ + if (state) { + free_property_array(state, &state->props); + free_property_array(state, &state->metadata); + for (uint32_t i = 0; i < state->n_values; ++i) { + free(state->values[i].value); + free(state->values[i].symbol); + } + lilv_node_free(state->plugin_uri); + lilv_node_free(state->uri); + zix_tree_free(state->abs2rel); + zix_tree_free(state->rel2abs); + free(state->values); + free(state->label); + free(state->dir); + free(state->file_dir); + free(state->copy_dir); + free(state->link_dir); + free(state); + } +} + +LILV_API bool +lilv_state_equals(const LilvState* a, const LilvState* b) +{ + if (!lilv_node_equals(a->plugin_uri, b->plugin_uri) + || (a->label && !b->label) + || (b->label && !a->label) + || (a->label && b->label && strcmp(a->label, b->label)) + || a->props.n != b->props.n + || a->n_values != b->n_values) { + return false; + } + + for (uint32_t i = 0; i < a->n_values; ++i) { + PortValue* const av = &a->values[i]; + PortValue* const bv = &b->values[i]; + if (av->size != bv->size || av->type != bv->type + || strcmp(av->symbol, bv->symbol) + || memcmp(av->value, bv->value, av->size)) { + return false; + } + } + + for (uint32_t i = 0; i < a->props.n; ++i) { + Property* const ap = &a->props.props[i]; + Property* const bp = &b->props.props[i]; + if (ap->key != bp->key + || ap->type != bp->type + || ap->flags != bp->flags) { + return false; + } else if (ap->type == a->atom_Path) { + if (!lilv_file_equals(lilv_state_rel2abs(a, (char*)ap->value), + lilv_state_rel2abs(b, (char*)bp->value))) { + return false; + } + } else if (ap->size != bp->size + || memcmp(ap->value, bp->value, ap->size)) { + return false; + } + } + + return true; +} + +LILV_API unsigned +lilv_state_get_num_properties(const LilvState* state) +{ + return state->props.n; +} + +LILV_API const LilvNode* +lilv_state_get_plugin_uri(const LilvState* state) +{ + return state->plugin_uri; +} + +LILV_API const LilvNode* +lilv_state_get_uri(const LilvState* state) +{ + return state->uri; +} + +LILV_API const char* +lilv_state_get_label(const LilvState* state) +{ + return state->label; +} + +LILV_API void +lilv_state_set_label(LilvState* state, const char* label) +{ + const size_t len = strlen(label); + state->label = (char*)realloc(state->label, len + 1); + memcpy(state->label, label, len + 1); +} + +LILV_API int +lilv_state_set_metadata(LilvState* state, + uint32_t key, + const void* value, + size_t size, + uint32_t type, + uint32_t flags) +{ + append_property(state, &state->metadata, key, value, size, type, flags); + return LV2_STATE_SUCCESS; +} diff --git a/src/ui.c b/src/ui.c new file mode 100644 index 0000000..e3a9b2b --- /dev/null +++ b/src/ui.c @@ -0,0 +1,111 @@ +/* + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "lilv_internal.h" + +LilvUI* +lilv_ui_new(LilvWorld* world, + LilvNode* uri, + LilvNode* type_uri, + LilvNode* binary_uri) +{ + assert(uri); + assert(type_uri); + assert(binary_uri); + + LilvUI* ui = (LilvUI*)malloc(sizeof(LilvUI)); + ui->world = world; + ui->uri = uri; + ui->binary_uri = binary_uri; + + // FIXME: kludge + char* bundle = lilv_strdup(lilv_node_as_string(ui->binary_uri)); + char* last_slash = strrchr(bundle, '/') + 1; + *last_slash = '\0'; + ui->bundle_uri = lilv_new_uri(world, bundle); + free(bundle); + + ui->classes = lilv_nodes_new(); + zix_tree_insert((ZixTree*)ui->classes, type_uri, NULL); + + return ui; +} + +void +lilv_ui_free(LilvUI* ui) +{ + lilv_node_free(ui->uri); + lilv_node_free(ui->bundle_uri); + lilv_node_free(ui->binary_uri); + lilv_nodes_free(ui->classes); + free(ui); +} + +LILV_API const LilvNode* +lilv_ui_get_uri(const LilvUI* ui) +{ + return ui->uri; +} + +LILV_API unsigned +lilv_ui_is_supported(const LilvUI* ui, + LilvUISupportedFunc supported_func, + const LilvNode* container_type, + const LilvNode** ui_type) +{ + const LilvNodes* classes = lilv_ui_get_classes(ui); + LILV_FOREACH(nodes, c, classes) { + const LilvNode* type = lilv_nodes_get(classes, c); + const unsigned q = supported_func(lilv_node_as_uri(container_type), + lilv_node_as_uri(type)); + if (q) { + if (ui_type) { + *ui_type = type; + } + return q; + } + } + + return 0; +} + +LILV_API const LilvNodes* +lilv_ui_get_classes(const LilvUI* ui) +{ + return ui->classes; +} + +LILV_API bool +lilv_ui_is_a(const LilvUI* ui, const LilvNode* class_uri) +{ + return lilv_nodes_contains(ui->classes, class_uri); +} + +LILV_API const LilvNode* +lilv_ui_get_bundle_uri(const LilvUI* ui) +{ + return ui->bundle_uri; +} + +LILV_API const LilvNode* +lilv_ui_get_binary_uri(const LilvUI* ui) +{ + return ui->binary_uri; +} diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..4973181 --- /dev/null +++ b/src/util.c @@ -0,0 +1,640 @@ +/* + Copyright 2007-2016 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#define _POSIX_C_SOURCE 200809L /* for fileno */ +#define _BSD_SOURCE 1 /* for realpath, symlink */ +#define _DEFAULT_SOURCE 1 /* for realpath, symlink */ + +#ifdef __APPLE__ +# define _DARWIN_C_SOURCE 1 /* for flock */ +#endif + +#include <ctype.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stddef.h> + +#ifdef _WIN32 +#ifndef _WIN32_WINNT +# define _WIN32_WINNT 0x0600 /* for CreateSymbolicLink */ +#endif +# include <windows.h> +# include <direct.h> +# include <io.h> +# define F_OK 0 +# define mkdir(path, flags) _mkdir(path) +# if defined(_MSC_VER) && _MSC_VER <= 1400 +/** Implement 'CreateSymbolicLink()' for MSVC 8 or earlier */ +extern "C" BOOLEAN WINAPI +CreateSymbolicLink(LPCTSTR linkpath, LPCTSTR targetpath, DWORD flags) +{ + typedef BOOLEAN (WINAPI* PFUNC)(LPCTSTR, LPCTSTR, DWORD); + + PFUNC pfn = (PFUNC)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), + "CreateSymbolicLinkA"); + return pfn ? pfn(linkpath, targetpath, flags) : 0; +} +# endif +#else +# include <dirent.h> +# include <limits.h> +# include <unistd.h> +#endif + +#include <sys/stat.h> +#include <sys/types.h> + +#include "lilv_internal.h" + +#if defined(HAVE_FLOCK) && defined(HAVE_FILENO) +# include <sys/file.h> +#endif + +#ifndef PAGE_SIZE +# define PAGE_SIZE 4096 +#endif + +void +lilv_free(void* ptr) +{ + free(ptr); +} + +char* +lilv_strjoin(const char* first, ...) +{ + size_t len = strlen(first); + char* result = (char*)malloc(len + 1); + + memcpy(result, first, len); + + va_list args; + va_start(args, first); + while (1) { + const char* const s = va_arg(args, const char *); + if (s == NULL) { + break; + } + + const size_t this_len = strlen(s); + char* new_result = (char*)realloc(result, len + this_len + 1); + if (!new_result) { + free(result); + return NULL; + } + + result = new_result; + memcpy(result + len, s, this_len); + len += this_len; + } + va_end(args); + + result[len] = '\0'; + + return result; +} + +char* +lilv_strdup(const char* str) +{ + if (!str) { + return NULL; + } + + const size_t len = strlen(str); + char* copy = (char*)malloc(len + 1); + memcpy(copy, str, len + 1); + return copy; +} + +const char* +lilv_uri_to_path(const char* uri) +{ + return (const char*)serd_uri_to_path((const uint8_t*)uri); +} + +char* +lilv_file_uri_parse(const char* uri, char** hostname) +{ + return (char*)serd_file_uri_parse((const uint8_t*)uri, (uint8_t**)hostname); +} + +/** Return the current LANG converted to Turtle (i.e. RFC3066) style. + * For example, if LANG is set to "en_CA.utf-8", this returns "en-ca". + */ +char* +lilv_get_lang(void) +{ + const char* const env_lang = getenv("LANG"); + if (!env_lang || !strcmp(env_lang, "") + || !strcmp(env_lang, "C") || !strcmp(env_lang, "POSIX")) { + return NULL; + } + + const size_t env_lang_len = strlen(env_lang); + char* const lang = (char*)malloc(env_lang_len + 1); + for (size_t i = 0; i < env_lang_len + 1; ++i) { + if (env_lang[i] == '_') { + lang[i] = '-'; // Convert _ to - + } else if (env_lang[i] >= 'A' && env_lang[i] <= 'Z') { + lang[i] = env_lang[i] + ('a' - 'A'); // Convert to lowercase + } else if (env_lang[i] >= 'a' && env_lang[i] <= 'z') { + lang[i] = env_lang[i]; // Lowercase letter, copy verbatim + } else if (env_lang[i] >= '0' && env_lang[i] <= '9') { + lang[i] = env_lang[i]; // Digit, copy verbatim + } else if (env_lang[i] == '\0' || env_lang[i] == '.') { + // End, or start of suffix (e.g. en_CA.utf-8), finished + lang[i] = '\0'; + break; + } else { + LILV_ERRORF("Illegal LANG `%s' ignored\n", env_lang); + free(lang); + return NULL; + } + } + + return lang; +} + +/** Append suffix to dst, update dst_len, and return the realloc'd result. */ +static char* +strappend(char* dst, size_t* dst_len, const char* suffix, size_t suffix_len) +{ + dst = (char*)realloc(dst, *dst_len + suffix_len + 1); + memcpy(dst + *dst_len, suffix, suffix_len); + dst[(*dst_len += suffix_len)] = '\0'; + return dst; +} + +/** Append the value of the environment variable var to dst. */ +static char* +append_var(char* dst, size_t* dst_len, const char* var) +{ + // Get value from environment + const char* val = getenv(var); + if (val) { // Value found, append it + return strappend(dst, dst_len, val, strlen(val)); + } else { // No value found, append variable reference as-is + return strappend(strappend(dst, dst_len, "$", 1), + dst_len, var, strlen(var)); + } +} + +/** Expand variables (e.g. POSIX ~ or $FOO, Windows %FOO%) in `path`. */ +char* +lilv_expand(const char* path) +{ +#ifdef _WIN32 + char* out = (char*)malloc(MAX_PATH); + ExpandEnvironmentStrings(path, out, MAX_PATH); +#else + char* out = NULL; + size_t len = 0; + + const char* start = path; // Start of current chunk to copy + for (const char* s = path; *s;) { + if (*s == '$') { + // Hit $ (variable reference, e.g. $VAR_NAME) + for (const char* t = s + 1; ; ++t) { + if (!*t || (!isupper(*t) && !isdigit(*t) && *t != '_')) { + // Append preceding chunk + out = strappend(out, &len, start, s - start); + + // Append variable value (or $VAR_NAME if not found) + char* var = (char*)calloc(t - s, 1); + memcpy(var, s + 1, t - s - 1); + out = append_var(out, &len, var); + free(var); + + // Continue after variable reference + start = s = t; + break; + } + } + } else if (*s == '~' && (*(s + 1) == '/' || !*(s + 1))) { + // Hit ~ before slash or end of string (home directory reference) + out = strappend(out, &len, start, s - start); + out = append_var(out, &len, "HOME"); + start = ++s; + } else { + ++s; + } + } + + if (*start) { + out = strappend(out, &len, start, strlen(start)); + } +#endif + + return out; +} + +static bool +lilv_is_dir_sep(const char c) +{ + return c == '/' || c == LILV_DIR_SEP[0]; +} + +char* +lilv_dirname(const char* path) +{ + const char* s = path + strlen(path) - 1; // Last character + for (; s > path && lilv_is_dir_sep(*s); --s) {} // Last non-slash + for (; s > path && !lilv_is_dir_sep(*s); --s) {} // Last internal slash + for (; s > path && lilv_is_dir_sep(*s); --s) {} // Skip duplicates + + if (s == path) { // Hit beginning + return lilv_is_dir_sep(*s) ? lilv_strdup("/") : lilv_strdup("."); + } else { // Pointing to the last character of the result (inclusive) + char* dirname = (char*)malloc(s - path + 2); + memcpy(dirname, path, s - path + 1); + dirname[s - path + 1] = '\0'; + return dirname; + } +} + +bool +lilv_path_exists(const char* path, void* ignored) +{ +#ifdef HAVE_LSTAT + struct stat st; + return !lstat(path, &st); +#else + return !access(path, F_OK); +#endif +} + +char* +lilv_find_free_path(const char* in_path, + bool (*exists)(const char*, void*), void* user_data) +{ + const size_t in_path_len = strlen(in_path); + char* path = (char*)malloc(in_path_len + 7); + memcpy(path, in_path, in_path_len + 1); + + for (int i = 2; i < 1000000; ++i) { + if (!exists(path, user_data)) { + return path; + } + snprintf(path, in_path_len + 7, "%s.%u", in_path, i); + } + + return NULL; +} + +int +lilv_copy_file(const char* src, const char* dst) +{ + FILE* in = fopen(src, "r"); + if (!in) { + return errno; + } + + FILE* out = fopen(dst, "w"); + if (!out) { + fclose(in); + return errno; + } + + char* page = (char*)malloc(PAGE_SIZE); + size_t n_read = 0; + int st = 0; + while ((n_read = fread(page, 1, PAGE_SIZE, in)) > 0) { + if (fwrite(page, 1, n_read, out) != n_read) { + st = errno; + break; + } + } + + if (!st && (ferror(in) || ferror(out))) { + st = EBADF; + } + + free(page); + fclose(in); + fclose(out); + + return st; +} + +bool +lilv_path_is_absolute(const char* path) +{ + if (lilv_is_dir_sep(path[0])) { + return true; + } + +#ifdef _WIN32 + if (isalpha(path[0]) && path[1] == ':' && lilv_is_dir_sep(path[2])) { + return true; + } +#endif + + return false; +} + +char* +lilv_path_absolute(const char* path) +{ + if (lilv_path_is_absolute(path)) { + return lilv_strdup(path); + } else { + char* cwd = getcwd(NULL, 0); + char* abs_path = lilv_path_join(cwd, path); + free(cwd); + return abs_path; + } +} + +char* +lilv_path_join(const char* a, const char* b) +{ + if (!a) { + return lilv_strdup(b); + } + + const size_t a_len = strlen(a); + const size_t b_len = b ? strlen(b) : 0; + const size_t pre_len = a_len - (lilv_is_dir_sep(a[a_len - 1]) ? 1 : 0); + char* path = (char*)calloc(1, a_len + b_len + 2); + memcpy(path, a, pre_len); + path[pre_len] = '/'; + if (b) { + memcpy(path + pre_len + 1, + b + (lilv_is_dir_sep(b[0]) ? 1 : 0), + lilv_is_dir_sep(b[0]) ? b_len - 1 : b_len); + } + return path; +} + +typedef struct { + char* pattern; + time_t time; + char* latest; +} Latest; + +static void +update_latest(const char* path, const char* name, void* data) +{ + Latest* latest = (Latest*)data; + char* entry_path = lilv_path_join(path, name); + unsigned num; + if (sscanf(entry_path, latest->pattern, &num) == 1) { + struct stat st; + if (!stat(entry_path, &st)) { + if (st.st_mtime >= latest->time) { + free(latest->latest); + latest->latest = entry_path; + } + } else { + LILV_ERRORF("stat(%s) (%s)\n", path, strerror(errno)); + } + } + if (entry_path != latest->latest) { + free(entry_path); + } +} + +/** Return the latest copy of the file at `path` that is newer. */ +char* +lilv_get_latest_copy(const char* path, const char* copy_path) +{ + char* copy_dir = lilv_dirname(copy_path); + Latest latest = { lilv_strjoin(copy_path, ".%u", NULL), 0, NULL }; + + struct stat st; + if (!stat(path, &st)) { + latest.time = st.st_mtime; + } else { + LILV_ERRORF("stat(%s) (%s)\n", path, strerror(errno)); + } + + lilv_dir_for_each(copy_dir, &latest, update_latest); + + free(latest.pattern); + free(copy_dir); + return latest.latest; +} + +char* +lilv_realpath(const char* path) +{ + if (!path) { + return NULL; + } + +#if defined(_WIN32) + char* out = (char*)malloc(MAX_PATH); + GetFullPathName(path, MAX_PATH, out, NULL); + return out; +#elif _POSIX_VERSION >= 200809L + char* real_path = realpath(path, NULL); + return real_path ? real_path : lilv_strdup(path); +#else + // OSX <= 10.5, if anyone cares. I sure don't. + char* out = (char*)malloc(PATH_MAX); + char* real_path = realpath(path, out); + if (!real_path) { + free(out); + return lilv_strdup(path); + } else { + return real_path; + } +#endif +} + +int +lilv_symlink(const char* oldpath, const char* newpath) +{ + int ret = 0; + if (strcmp(oldpath, newpath)) { +#ifdef _WIN32 + ret = !CreateSymbolicLink(newpath, oldpath, 0); + if (ret) { + ret = !CreateHardLink(newpath, oldpath, 0); + } +#else + ret = symlink(oldpath, newpath); +#endif + } + if (ret) { + LILV_ERRORF("Failed to link %s => %s (%s)\n", + newpath, oldpath, strerror(errno)); + } + return ret; +} + +char* +lilv_path_relative_to(const char* path, const char* base) +{ + const size_t path_len = strlen(path); + const size_t base_len = strlen(base); + const size_t min_len = (path_len < base_len) ? path_len : base_len; + + // Find the last separator common to both paths + size_t last_shared_sep = 0; + for (size_t i = 0; i < min_len && path[i] == base[i]; ++i) { + if (lilv_is_dir_sep(path[i])) { + last_shared_sep = i; + } + } + + if (last_shared_sep == 0) { + // No common components, return path + return lilv_strdup(path); + } + + // Find the number of up references ("..") required + size_t up = 0; + for (size_t i = last_shared_sep + 1; i < base_len; ++i) { + if (lilv_is_dir_sep(base[i])) { + ++up; + } + } + + // Write up references + const size_t suffix_len = path_len - last_shared_sep; + char* rel = (char*)calloc(1, suffix_len + (up * 3) + 1); + for (size_t i = 0; i < up; ++i) { + memcpy(rel + (i * 3), "../", 3); + } + + // Write suffix + memcpy(rel + (up * 3), path + last_shared_sep + 1, suffix_len); + return rel; +} + +bool +lilv_path_is_child(const char* path, const char* dir) +{ + if (path && dir) { + const size_t path_len = strlen(path); + const size_t dir_len = strlen(dir); + return dir && path_len >= dir_len && !strncmp(path, dir, dir_len); + } + return false; +} + +int +lilv_flock(FILE* file, bool lock) +{ +#if defined(HAVE_FLOCK) && defined(HAVE_FILENO) + return flock(fileno(file), lock ? LOCK_EX : LOCK_UN); +#else + return 0; +#endif +} + +void +lilv_dir_for_each(const char* path, + void* data, + void (*f)(const char* path, const char* name, void* data)) +{ +#ifdef _WIN32 + char* pat = lilv_path_join(path, "*"); + WIN32_FIND_DATA fd; + HANDLE fh = FindFirstFile(pat, &fd); + if (fh != INVALID_HANDLE_VALUE) { + do { + f(path, fd.cFileName, data); + } while (FindNextFile(fh, &fd)); + } + free(pat); +#else + DIR* dir = opendir(path); + if (dir) { + for (struct dirent* entry; (entry = readdir(dir));) { + f(path, entry->d_name, data); + } + closedir(dir); + } +#endif +} + +int +lilv_mkdir_p(const char* dir_path) +{ + char* path = lilv_strdup(dir_path); + const size_t path_len = strlen(path); + for (size_t i = 1; i <= path_len; ++i) { + if (path[i] == LILV_DIR_SEP[0] || path[i] == '\0') { + path[i] = '\0'; + if (mkdir(path, 0755) && errno != EEXIST) { + free(path); + return errno; + } + path[i] = LILV_DIR_SEP[0]; + } + } + + free(path); + return 0; +} + +static off_t +lilv_file_size(const char* path) +{ + struct stat buf; + if (stat(path, &buf)) { + LILV_ERRORF("stat(%s) (%s)\n", path, strerror(errno)); + return 0; + } + return buf.st_size; +} + +bool +lilv_file_equals(const char* a_path, const char* b_path) +{ + if (!strcmp(a_path, b_path)) { + return true; // Paths match + } + + bool match = false; + FILE* a_file = NULL; + FILE* b_file = NULL; + char* const a_real = lilv_realpath(a_path); + char* const b_real = lilv_realpath(b_path); + if (!strcmp(a_real, b_real)) { + match = true; // Real paths match + } else if (lilv_file_size(a_path) != lilv_file_size(b_path)) { + match = false; // Sizes differ + } else if (!(a_file = fopen(a_real, "rb")) || + !(b_file = fopen(b_real, "rb"))) { + match = false; // Missing file matches nothing + } else { + // TODO: Improve performance by reading chunks + match = true; + while (!feof(a_file) && !feof(b_file)) { + if (fgetc(a_file) != fgetc(b_file)) { + match = false; + break; + } + } + } + + if (a_file) { + fclose(a_file); + } + if (b_file) { + fclose(b_file); + } + free(a_real); + free(b_real); + return match; +} diff --git a/src/world.c b/src/world.c new file mode 100644 index 0000000..1858569 --- /dev/null +++ b/src/world.c @@ -0,0 +1,1212 @@ +/* + Copyright 2007-2016 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "lv2/lv2plug.in/ns/ext/presets/presets.h" + +#include "lilv_internal.h" + +static int +lilv_world_drop_graph(LilvWorld* world, const SordNode* graph); + +LILV_API LilvWorld* +lilv_world_new(void) +{ + LilvWorld* world = (LilvWorld*)malloc(sizeof(LilvWorld)); + + world->world = sord_world_new(); + if (!world->world) { + goto fail; + } + + world->model = sord_new(world->world, SORD_SPO|SORD_OPS, true); + if (!world->model) { + goto fail; + } + + world->specs = NULL; + world->plugin_classes = lilv_plugin_classes_new(); + world->plugins = lilv_plugins_new(); + world->zombies = lilv_plugins_new(); + world->loaded_files = zix_tree_new( + false, lilv_resource_node_cmp, NULL, (ZixDestroyFunc)lilv_node_free); + + world->libs = zix_tree_new(false, lilv_lib_compare, NULL, NULL); + +#define NS_DCTERMS "http://purl.org/dc/terms/" +#define NS_DYNMAN "http://lv2plug.in/ns/ext/dynmanifest#" +#define NS_OWL "http://www.w3.org/2002/07/owl#" + +#define NEW_URI(uri) sord_new_uri(world->world, (const uint8_t*)(uri)) + + world->uris.dc_replaces = NEW_URI(NS_DCTERMS "replaces"); + world->uris.dman_DynManifest = NEW_URI(NS_DYNMAN "DynManifest"); + world->uris.doap_name = NEW_URI(LILV_NS_DOAP "name"); + world->uris.lv2_Plugin = NEW_URI(LV2_CORE__Plugin); + world->uris.lv2_Specification = NEW_URI(LV2_CORE__Specification); + world->uris.lv2_appliesTo = NEW_URI(LV2_CORE__appliesTo); + world->uris.lv2_binary = NEW_URI(LV2_CORE__binary); + world->uris.lv2_default = NEW_URI(LV2_CORE__default); + world->uris.lv2_designation = NEW_URI(LV2_CORE__designation); + world->uris.lv2_extensionData = NEW_URI(LV2_CORE__extensionData); + world->uris.lv2_index = NEW_URI(LV2_CORE__index); + world->uris.lv2_latency = NEW_URI(LV2_CORE__latency); + world->uris.lv2_maximum = NEW_URI(LV2_CORE__maximum); + world->uris.lv2_microVersion = NEW_URI(LV2_CORE__microVersion); + world->uris.lv2_minimum = NEW_URI(LV2_CORE__minimum); + world->uris.lv2_minorVersion = NEW_URI(LV2_CORE__minorVersion); + world->uris.lv2_name = NEW_URI(LV2_CORE__name); + world->uris.lv2_optionalFeature = NEW_URI(LV2_CORE__optionalFeature); + world->uris.lv2_port = NEW_URI(LV2_CORE__port); + world->uris.lv2_portProperty = NEW_URI(LV2_CORE__portProperty); + world->uris.lv2_reportsLatency = NEW_URI(LV2_CORE__reportsLatency); + world->uris.lv2_requiredFeature = NEW_URI(LV2_CORE__requiredFeature); + world->uris.lv2_symbol = NEW_URI(LV2_CORE__symbol); + world->uris.lv2_prototype = NEW_URI(LV2_CORE__prototype); + world->uris.owl_Ontology = NEW_URI(NS_OWL "Ontology"); + world->uris.pset_value = NEW_URI(LV2_PRESETS__value); + world->uris.rdf_a = NEW_URI(LILV_NS_RDF "type"); + world->uris.rdf_value = NEW_URI(LILV_NS_RDF "value"); + world->uris.rdfs_Class = NEW_URI(LILV_NS_RDFS "Class"); + world->uris.rdfs_label = NEW_URI(LILV_NS_RDFS "label"); + world->uris.rdfs_seeAlso = NEW_URI(LILV_NS_RDFS "seeAlso"); + world->uris.rdfs_subClassOf = NEW_URI(LILV_NS_RDFS "subClassOf"); + world->uris.xsd_base64Binary = NEW_URI(LILV_NS_XSD "base64Binary"); + world->uris.xsd_boolean = NEW_URI(LILV_NS_XSD "boolean"); + world->uris.xsd_decimal = NEW_URI(LILV_NS_XSD "decimal"); + world->uris.xsd_double = NEW_URI(LILV_NS_XSD "double"); + world->uris.xsd_integer = NEW_URI(LILV_NS_XSD "integer"); + world->uris.null_uri = NULL; + + world->lv2_plugin_class = lilv_plugin_class_new( + world, NULL, world->uris.lv2_Plugin, "Plugin"); + assert(world->lv2_plugin_class); + + world->n_read_files = 0; + world->opt.filter_language = true; + world->opt.dyn_manifest = true; + + return world; + +fail: + /* keep on rockin' in the */ free(world); + return NULL; +} + +LILV_API void +lilv_world_free(LilvWorld* world) +{ + if (!world) { + return; + } + + lilv_plugin_class_free(world->lv2_plugin_class); + world->lv2_plugin_class = NULL; + + for (SordNode** n = (SordNode**)&world->uris; *n; ++n) { + sord_node_free(world->world, *n); + } + + for (LilvSpec* spec = world->specs; spec;) { + LilvSpec* next = spec->next; + sord_node_free(world->world, spec->spec); + sord_node_free(world->world, spec->bundle); + lilv_nodes_free(spec->data_uris); + free(spec); + spec = next; + } + world->specs = NULL; + + LILV_FOREACH(plugins, i, world->plugins) { + const LilvPlugin* p = lilv_plugins_get(world->plugins, i); + lilv_plugin_free((LilvPlugin*)p); + } + zix_tree_free((ZixTree*)world->plugins); + world->plugins = NULL; + + LILV_FOREACH(plugins, i, world->zombies) { + const LilvPlugin* p = lilv_plugins_get(world->zombies, i); + lilv_plugin_free((LilvPlugin*)p); + } + zix_tree_free((ZixTree*)world->zombies); + world->zombies = NULL; + + zix_tree_free((ZixTree*)world->loaded_files); + world->loaded_files = NULL; + + zix_tree_free(world->libs); + world->libs = NULL; + + zix_tree_free((ZixTree*)world->plugin_classes); + world->plugin_classes = NULL; + + sord_free(world->model); + world->model = NULL; + + sord_world_free(world->world); + world->world = NULL; + + free(world); +} + +LILV_API void +lilv_world_set_option(LilvWorld* world, + const char* uri, + const LilvNode* value) +{ + if (!strcmp(uri, LILV_OPTION_DYN_MANIFEST)) { + if (lilv_node_is_bool(value)) { + world->opt.dyn_manifest = lilv_node_as_bool(value); + return; + } + } else if (!strcmp(uri, LILV_OPTION_FILTER_LANG)) { + if (lilv_node_is_bool(value)) { + world->opt.filter_language = lilv_node_as_bool(value); + return; + } + } + LILV_WARNF("Unrecognized or invalid option `%s'\n", uri); +} + +LILV_API LilvNodes* +lilv_world_find_nodes(LilvWorld* world, + const LilvNode* subject, + const LilvNode* predicate, + const LilvNode* object) +{ + if (subject && !lilv_node_is_uri(subject) && !lilv_node_is_blank(subject)) { + LILV_ERRORF("Subject `%s' is not a resource\n", + sord_node_get_string(subject->node)); + return NULL; + } else if (!predicate) { + LILV_ERROR("Missing required predicate\n"); + return NULL; + } else if (!lilv_node_is_uri(predicate)) { + LILV_ERRORF("Predicate `%s' is not a URI\n", + sord_node_get_string(predicate->node)); + return NULL; + } else if (!subject && !object) { + LILV_ERROR("Both subject and object are NULL\n"); + return NULL; + } + + return lilv_world_find_nodes_internal(world, + subject ? subject->node : NULL, + predicate->node, + object ? object->node : NULL); +} + +LILV_API LilvNode* +lilv_world_get(LilvWorld* world, + const LilvNode* subject, + const LilvNode* predicate, + const LilvNode* object) +{ + SordNode* snode = sord_get(world->model, + subject ? subject->node : NULL, + predicate ? predicate->node : NULL, + object ? object->node : NULL, + NULL); + LilvNode* lnode = lilv_node_new_from_node(world, snode); + sord_node_free(world->world, snode); + return lnode; +} + +SordIter* +lilv_world_query_internal(LilvWorld* world, + const SordNode* subject, + const SordNode* predicate, + const SordNode* object) +{ + return sord_search(world->model, subject, predicate, object, NULL); +} + +bool +lilv_world_ask_internal(LilvWorld* world, + const SordNode* subject, + const SordNode* predicate, + const SordNode* object) +{ + return sord_ask(world->model, subject, predicate, object, NULL); +} + +LILV_API bool +lilv_world_ask(LilvWorld* world, + const LilvNode* subject, + const LilvNode* predicate, + const LilvNode* object) +{ + return sord_ask(world->model, + subject ? subject->node : NULL, + predicate ? predicate->node : NULL, + object ? object->node : NULL, + NULL); +} + +SordModel* +lilv_world_filter_model(LilvWorld* world, + SordModel* model, + const SordNode* subject, + const SordNode* predicate, + const SordNode* object, + const SordNode* graph) +{ + SordModel* results = sord_new(world->world, SORD_SPO, false); + SordIter* i = sord_search(model, subject, predicate, object, graph); + for (; !sord_iter_end(i); sord_iter_next(i)) { + SordQuad quad; + sord_iter_get(i, quad); + sord_add(results, quad); + } + sord_iter_free(i); + return results; +} + +LilvNodes* +lilv_world_find_nodes_internal(LilvWorld* world, + const SordNode* subject, + const SordNode* predicate, + const SordNode* object) +{ + return lilv_nodes_from_stream_objects( + world, + lilv_world_query_internal(world, subject, predicate, object), + (object == NULL) ? SORD_OBJECT : SORD_SUBJECT); +} + +static SerdNode +lilv_new_uri_relative_to_base(const uint8_t* uri_str, + const uint8_t* base_uri_str) +{ + SerdURI base_uri; + serd_uri_parse(base_uri_str, &base_uri); + return serd_node_new_uri_from_string(uri_str, &base_uri, NULL); +} + +const uint8_t* +lilv_world_blank_node_prefix(LilvWorld* world) +{ + static char str[32]; + snprintf(str, sizeof(str), "%d", world->n_read_files++); + return (const uint8_t*)str; +} + +/** Comparator for sequences (e.g. world->plugins). */ +int +lilv_header_compare_by_uri(const void* a, const void* b, void* user_data) +{ + const struct LilvHeader* const header_a = (const struct LilvHeader*)a; + const struct LilvHeader* const header_b = (const struct LilvHeader*)b; + return strcmp(lilv_node_as_uri(header_a->uri), + lilv_node_as_uri(header_b->uri)); +} + +/** + Comparator for libraries (world->libs). + + Libraries do have a LilvHeader, but we must also compare the bundle to + handle the case where the same library is loaded with different bundles, and + consequently different contents (mainly plugins). + */ +int +lilv_lib_compare(const void* a, const void* b, void* user_data) +{ + const LilvLib* const lib_a = (const LilvLib*)a; + const LilvLib* const lib_b = (const LilvLib*)b; + int cmp = strcmp(lilv_node_as_uri(lib_a->uri), + lilv_node_as_uri(lib_b->uri)); + return cmp ? cmp : strcmp(lib_a->bundle_path, lib_b->bundle_path); +} + +/** Get an element of a collection of any object with an LilvHeader by URI. */ +static ZixTreeIter* +lilv_collection_find_by_uri(const ZixTree* seq, const LilvNode* uri) +{ + ZixTreeIter* i = NULL; + if (lilv_node_is_uri(uri)) { + struct LilvHeader key = { NULL, (LilvNode*)uri }; + zix_tree_find(seq, &key, &i); + } + return i; +} + +/** Get an element of a collection of any object with an LilvHeader by URI. */ +struct LilvHeader* +lilv_collection_get_by_uri(const ZixTree* seq, const LilvNode* uri) +{ + ZixTreeIter* const i = lilv_collection_find_by_uri(seq, uri); + + return i ? (struct LilvHeader*)zix_tree_get(i) : NULL; +} + +static void +lilv_world_add_spec(LilvWorld* world, + const SordNode* specification_node, + const SordNode* bundle_node) +{ + LilvSpec* spec = (LilvSpec*)malloc(sizeof(LilvSpec)); + spec->spec = sord_node_copy(specification_node); + spec->bundle = sord_node_copy(bundle_node); + spec->data_uris = lilv_nodes_new(); + + // Add all data files (rdfs:seeAlso) + SordIter* files = sord_search(world->model, + specification_node, + world->uris.rdfs_seeAlso, + NULL, + NULL); + FOREACH_MATCH(files) { + const SordNode* file_node = sord_iter_get_node(files, SORD_OBJECT); + zix_tree_insert((ZixTree*)spec->data_uris, + lilv_node_new_from_node(world, file_node), + NULL); + } + sord_iter_free(files); + + // Add specification to world specification list + spec->next = world->specs; + world->specs = spec; +} + +static void +lilv_world_add_plugin(LilvWorld* world, + const SordNode* plugin_node, + const LilvNode* manifest_uri, + void* dynmanifest, + const SordNode* bundle) +{ + LilvNode* plugin_uri = lilv_node_new_from_node(world, plugin_node); + ZixTreeIter* z = NULL; + LilvPlugin* plugin = (LilvPlugin*)lilv_plugins_get_by_uri( + world->plugins, plugin_uri); + + if (plugin) { + // Existing plugin, if this is different bundle, ignore it + // (use the first plugin found in LV2_PATH) + const LilvNode* last_bundle = lilv_plugin_get_bundle_uri(plugin); + const char* plugin_uri_str = lilv_node_as_uri(plugin_uri); + if (sord_node_equals(bundle, last_bundle->node)) { + LILV_WARNF("Reloading plugin <%s>\n", plugin_uri_str); + plugin->loaded = false; + lilv_node_free(plugin_uri); + } else { + LILV_WARNF("Duplicate plugin <%s>\n", plugin_uri_str); + LILV_WARNF("... found in %s\n", lilv_node_as_string(last_bundle)); + LILV_WARNF("... and %s (ignored)\n", sord_node_get_string(bundle)); + lilv_node_free(plugin_uri); + return; + } + } else if ((z = lilv_collection_find_by_uri((const ZixTree*)world->zombies, + plugin_uri))) { + // Plugin bundle has been re-loaded, move from zombies to plugins + plugin = (LilvPlugin*)zix_tree_get(z); + zix_tree_remove((ZixTree*)world->zombies, z); + zix_tree_insert((ZixTree*)world->plugins, plugin, NULL); + lilv_node_free(plugin_uri); + lilv_plugin_clear(plugin, lilv_node_new_from_node(world, bundle)); + } else { + // Add new plugin to the world + plugin = lilv_plugin_new( + world, plugin_uri, lilv_node_new_from_node(world, bundle)); + + // Add manifest as plugin data file (as if it were rdfs:seeAlso) + zix_tree_insert((ZixTree*)plugin->data_uris, + lilv_node_duplicate(manifest_uri), + NULL); + + // Add plugin to world plugin sequence + zix_tree_insert((ZixTree*)world->plugins, plugin, NULL); + } + + +#ifdef LILV_DYN_MANIFEST + // Set dynamic manifest library URI, if applicable + if (dynmanifest) { + plugin->dynmanifest = (LilvDynManifest*)dynmanifest; + ++((LilvDynManifest*)dynmanifest)->refs; + } +#endif + + // Add all plugin data files (rdfs:seeAlso) + SordIter* files = sord_search(world->model, + plugin_node, + world->uris.rdfs_seeAlso, + NULL, + NULL); + FOREACH_MATCH(files) { + const SordNode* file_node = sord_iter_get_node(files, SORD_OBJECT); + zix_tree_insert((ZixTree*)plugin->data_uris, + lilv_node_new_from_node(world, file_node), + NULL); + } + sord_iter_free(files); +} + +SerdStatus +lilv_world_load_graph(LilvWorld* world, SordNode* graph, const LilvNode* uri) +{ + const SerdNode* base = sord_node_to_serd_node(uri->node); + SerdEnv* env = serd_env_new(base); + SerdReader* reader = sord_new_reader( + world->model, env, SERD_TURTLE, graph); + + const SerdStatus st = lilv_world_load_file(world, reader, uri); + + serd_env_free(env); + serd_reader_free(reader); + return st; +} + +static void +lilv_world_load_dyn_manifest(LilvWorld* world, + SordNode* bundle_node, + const LilvNode* manifest) +{ +#ifdef LILV_DYN_MANIFEST + if (!world->opt.dyn_manifest) { + return; + } + + LV2_Dyn_Manifest_Handle handle = NULL; + + // ?dman a dynman:DynManifest bundle_node + SordModel* model = lilv_world_filter_model(world, + world->model, + NULL, + world->uris.rdf_a, + world->uris.dman_DynManifest, + bundle_node); + SordIter* iter = sord_begin(model); + for (; !sord_iter_end(iter); sord_iter_next(iter)) { + const SordNode* dmanifest = sord_iter_get_node(iter, SORD_SUBJECT); + + // ?dman lv2:binary ?binary + SordIter* binaries = sord_search(world->model, + dmanifest, + world->uris.lv2_binary, + NULL, + bundle_node); + if (sord_iter_end(binaries)) { + sord_iter_free(binaries); + LILV_ERRORF("Dynamic manifest in <%s> has no binaries, ignored\n", + sord_node_get_string(bundle_node)); + continue; + } + + // Get binary path + const SordNode* binary = sord_iter_get_node(binaries, SORD_OBJECT); + const uint8_t* lib_uri = sord_node_get_string(binary); + char* lib_path = lilv_file_uri_parse((const char*)lib_uri, 0); + if (!lib_path) { + LILV_ERROR("No dynamic manifest library path\n"); + sord_iter_free(binaries); + continue; + } + + // Open library + dlerror(); + void* lib = dlopen(lib_path, RTLD_LAZY); + if (!lib) { + LILV_ERRORF("Failed to open dynmanifest library `%s' (%s)\n", + lib_path, dlerror()); + sord_iter_free(binaries); + lilv_free(lib_path); + continue; + } + + // Open dynamic manifest + typedef int (*OpenFunc)(LV2_Dyn_Manifest_Handle*, + const LV2_Feature *const *); + OpenFunc dmopen = (OpenFunc)lilv_dlfunc(lib, "lv2_dyn_manifest_open"); + if (!dmopen || dmopen(&handle, &dman_features)) { + LILV_ERRORF("No `lv2_dyn_manifest_open' in `%s'\n", lib_path); + sord_iter_free(binaries); + dlclose(lib); + lilv_free(lib_path); + continue; + } + + // Get subjects (the data that would be in manifest.ttl) + typedef int (*GetSubjectsFunc)(LV2_Dyn_Manifest_Handle, FILE*); + GetSubjectsFunc get_subjects_func = (GetSubjectsFunc)lilv_dlfunc( + lib, "lv2_dyn_manifest_get_subjects"); + if (!get_subjects_func) { + LILV_ERRORF("No `lv2_dyn_manifest_get_subjects' in `%s'\n", + lib_path); + sord_iter_free(binaries); + dlclose(lib); + lilv_free(lib_path); + continue; + } + + LilvDynManifest* desc = malloc(sizeof(LilvDynManifest)); + desc->bundle = lilv_node_new_from_node(world, bundle_node); + desc->lib = lib; + desc->handle = handle; + desc->refs = 0; + + sord_iter_free(binaries); + + // Generate data file + FILE* fd = tmpfile(); + get_subjects_func(handle, fd); + rewind(fd); + + // Parse generated data file into temporary model + // FIXME + const SerdNode* base = sord_node_to_serd_node(dmanifest); + SerdEnv* env = serd_env_new(base); + SerdReader* reader = sord_new_reader( + world->model, env, SERD_TURTLE, sord_node_copy(dmanifest)); + serd_reader_add_blank_prefix(reader, + lilv_world_blank_node_prefix(world)); + serd_reader_read_file_handle(reader, fd, + (const uint8_t*)"(dyn-manifest)"); + serd_reader_free(reader); + serd_env_free(env); + + // Close (and automatically delete) temporary data file + fclose(fd); + + // ?plugin a lv2:Plugin + SordModel* plugins = lilv_world_filter_model(world, + world->model, + NULL, + world->uris.rdf_a, + world->uris.lv2_Plugin, + dmanifest); + SordIter* p = sord_begin(plugins); + FOREACH_MATCH(p) { + const SordNode* plug = sord_iter_get_node(p, SORD_SUBJECT); + lilv_world_add_plugin(world, plug, manifest, desc, bundle_node); + } + if (desc->refs == 0) { + free(desc); + } + sord_iter_free(p); + sord_free(plugins); + lilv_free(lib_path); + } + sord_iter_free(iter); + sord_free(model); +#endif // LILV_DYN_MANIFEST +} + +LilvNode* +lilv_world_get_manifest_uri(LilvWorld* world, const LilvNode* bundle_uri) +{ + SerdNode manifest_uri = lilv_new_uri_relative_to_base( + (const uint8_t*)"manifest.ttl", + sord_node_get_string(bundle_uri->node)); + LilvNode* manifest = lilv_new_uri(world, (const char*)manifest_uri.buf); + serd_node_free(&manifest_uri); + return manifest; +} + +static SordModel* +load_plugin_model(LilvWorld* world, + const LilvNode* bundle_uri, + const LilvNode* plugin_uri) +{ + // Create model and reader for loading into it + SordNode* bundle_node = bundle_uri->node; + SordModel* model = sord_new(world->world, SORD_SPO|SORD_OPS, false); + SerdEnv* env = serd_env_new(sord_node_to_serd_node(bundle_node)); + SerdReader* reader = sord_new_reader(model, env, SERD_TURTLE, NULL); + + // Load manifest + LilvNode* manifest_uri = lilv_world_get_manifest_uri(world, bundle_uri); + serd_reader_add_blank_prefix(reader, lilv_world_blank_node_prefix(world)); + serd_reader_read_file( + reader, (const uint8_t*)lilv_node_as_string(manifest_uri)); + + // Load any seeAlso files + SordModel* files = lilv_world_filter_model( + world, model, plugin_uri->node, world->uris.rdfs_seeAlso, NULL, NULL); + + SordIter* f = sord_begin(files); + FOREACH_MATCH(f) { + const SordNode* file = sord_iter_get_node(f, SORD_OBJECT); + const uint8_t* file_str = sord_node_get_string(file); + if (sord_node_get_type(file) == SORD_URI) { + serd_reader_add_blank_prefix( + reader, lilv_world_blank_node_prefix(world)); + serd_reader_read_file(reader, file_str); + } + } + + sord_iter_free(f); + sord_free(files); + serd_reader_free(reader); + serd_env_free(env); + lilv_node_free(manifest_uri); + + return model; +} + +static LilvVersion +get_version(LilvWorld* world, SordModel* model, const LilvNode* subject) +{ + const SordNode* minor_node = sord_get( + model, subject->node, world->uris.lv2_minorVersion, NULL, NULL); + const SordNode* micro_node = sord_get( + model, subject->node, world->uris.lv2_microVersion, NULL, NULL); + + + LilvVersion version = { 0, 0 }; + if (minor_node && micro_node) { + version.minor = atoi((const char*)sord_node_get_string(minor_node)); + version.micro = atoi((const char*)sord_node_get_string(micro_node)); + } + + return version; +} + +LILV_API void +lilv_world_load_bundle(LilvWorld* world, const LilvNode* bundle_uri) +{ + if (!lilv_node_is_uri(bundle_uri)) { + LILV_ERRORF("Bundle URI `%s' is not a URI\n", + sord_node_get_string(bundle_uri->node)); + return; + } + + SordNode* bundle_node = bundle_uri->node; + LilvNode* manifest = lilv_world_get_manifest_uri(world, bundle_uri); + + // Read manifest into model with graph = bundle_node + SerdStatus st = lilv_world_load_graph(world, bundle_node, manifest); + if (st > SERD_FAILURE) { + LILV_ERRORF("Error reading %s\n", lilv_node_as_string(manifest)); + lilv_node_free(manifest); + return; + } + + // ?plugin a lv2:Plugin + SordIter* plug_results = sord_search(world->model, + NULL, + world->uris.rdf_a, + world->uris.lv2_Plugin, + bundle_node); + + // Find any loaded plugins that will be replaced with a newer version + LilvNodes* unload_uris = lilv_nodes_new(); + FOREACH_MATCH(plug_results) { + const SordNode* plug = sord_iter_get_node(plug_results, SORD_SUBJECT); + + LilvNode* plugin_uri = lilv_node_new_from_node(world, plug); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(world->plugins, plugin_uri); + const LilvNode* last_bundle = plugin ? lilv_plugin_get_bundle_uri(plugin) : NULL; + if (!plugin || sord_node_equals(bundle_node, last_bundle->node)) { + // No previously loaded version, or it's from the same bundle + lilv_node_free(plugin_uri); + continue; + } + + // Compare versions + SordModel* this_model = load_plugin_model(world, bundle_uri, plugin_uri); + LilvVersion this_version = get_version(world, this_model, plugin_uri); + SordModel* last_model = load_plugin_model(world, last_bundle, plugin_uri); + LilvVersion last_version = get_version(world, last_model, plugin_uri); + sord_free(this_model); + sord_free(last_model); + const int cmp = lilv_version_cmp(&this_version, &last_version); + if (cmp > 0) { + zix_tree_insert((ZixTree*)unload_uris, + lilv_node_duplicate(plugin_uri), + NULL); + LILV_WARNF("Replacing version %d.%d of <%s> from <%s>\n", + last_version.minor, last_version.micro, + sord_node_get_string(plug), + sord_node_get_string(last_bundle->node)); + LILV_NOTEF("New version %d.%d found in <%s>\n", + this_version.minor, this_version.micro, + sord_node_get_string(bundle_node)); + } else if (cmp < 0) { + LILV_WARNF("Ignoring bundle <%s>\n", + sord_node_get_string(bundle_node)); + LILV_NOTEF("Newer version of <%s> loaded from <%s>\n", + sord_node_get_string(plug), + sord_node_get_string(last_bundle->node)); + lilv_node_free(plugin_uri); + sord_iter_free(plug_results); + lilv_world_drop_graph(world, bundle_node); + lilv_node_free(manifest); + lilv_nodes_free(unload_uris); + return; + } + lilv_node_free(plugin_uri); + } + + sord_iter_free(plug_results); + + // Unload any old conflicting plugins + LilvNodes* unload_bundles = lilv_nodes_new(); + LILV_FOREACH(nodes, i, unload_uris) { + const LilvNode* uri = lilv_nodes_get(unload_uris, i); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(world->plugins, uri); + const LilvNode* bundle = lilv_plugin_get_bundle_uri(plugin); + + // Unload plugin and record bundle for later unloading + lilv_world_unload_resource(world, uri); + zix_tree_insert((ZixTree*)unload_bundles, + lilv_node_duplicate(bundle), + NULL); + + } + lilv_nodes_free(unload_uris); + + // Now unload the associated bundles + // This must be done last since several plugins could be in the same bundle + LILV_FOREACH(nodes, i, unload_bundles) { + lilv_world_unload_bundle(world, lilv_nodes_get(unload_bundles, i)); + } + lilv_nodes_free(unload_bundles); + + // Re-search for plugin results now that old plugins are gone + plug_results = sord_search(world->model, + NULL, + world->uris.rdf_a, + world->uris.lv2_Plugin, + bundle_node); + + FOREACH_MATCH(plug_results) { + const SordNode* plug = sord_iter_get_node(plug_results, SORD_SUBJECT); + lilv_world_add_plugin(world, plug, manifest, NULL, bundle_node); + } + sord_iter_free(plug_results); + + lilv_world_load_dyn_manifest(world, bundle_node, manifest); + + // ?spec a lv2:Specification + // ?spec a owl:Ontology + const SordNode* spec_preds[] = { world->uris.lv2_Specification, + world->uris.owl_Ontology, + NULL }; + for (const SordNode** p = spec_preds; *p; ++p) { + SordIter* i = sord_search( + world->model, NULL, world->uris.rdf_a, *p, bundle_node); + FOREACH_MATCH(i) { + const SordNode* spec = sord_iter_get_node(i, SORD_SUBJECT); + lilv_world_add_spec(world, spec, bundle_node); + } + sord_iter_free(i); + } + + lilv_node_free(manifest); +} + +static int +lilv_world_drop_graph(LilvWorld* world, const SordNode* graph) +{ + SordIter* i = sord_search(world->model, NULL, NULL, NULL, graph); + while (!sord_iter_end(i)) { + const SerdStatus st = sord_erase(world->model, i); + if (st) { + LILV_ERRORF("Error removing statement from <%s> (%s)\n", + sord_node_get_string(graph), serd_strerror(st)); + return st; + } + } + sord_iter_free(i); + + return 0; +} + +/** Remove loaded_files entry so file will be reloaded if requested. */ +static int +lilv_world_unload_file(LilvWorld* world, const LilvNode* file) +{ + ZixTreeIter* iter; + if (!zix_tree_find((ZixTree*)world->loaded_files, file, &iter)) { + zix_tree_remove((ZixTree*)world->loaded_files, iter); + return 0; + } + return 1; +} + +LILV_API int +lilv_world_unload_bundle(LilvWorld* world, const LilvNode* bundle_uri) +{ + if (!bundle_uri) { + return 0; + } + + // Find all loaded files that are inside the bundle + LilvNodes* files = lilv_nodes_new(); + LILV_FOREACH(nodes, i, world->loaded_files) { + const LilvNode* file = lilv_nodes_get(world->loaded_files, i); + if (!strncmp(lilv_node_as_string(file), + lilv_node_as_string(bundle_uri), + strlen(lilv_node_as_string(bundle_uri)))) { + zix_tree_insert((ZixTree*)files, + lilv_node_duplicate(file), + NULL); + } + } + + // Unload all loaded files in the bundle + LILV_FOREACH(nodes, i, files) { + const LilvNode* file = lilv_nodes_get(world->plugins, i); + lilv_world_unload_file(world, file); + } + + lilv_nodes_free(files); + + /* Remove any plugins in the bundle from the plugin list. Since the + application may still have a pointer to the LilvPlugin, it can not be + destroyed here. Instead, we move it to the zombie plugin list, so it + will not be in the list returned by lilv_world_get_all_plugins() but can + still be used. + */ + ZixTreeIter* i = zix_tree_begin((ZixTree*)world->plugins); + while (i != zix_tree_end((ZixTree*)world->plugins)) { + LilvPlugin* p = (LilvPlugin*)zix_tree_get(i); + ZixTreeIter* next = zix_tree_iter_next(i); + + if (lilv_node_equals(lilv_plugin_get_bundle_uri(p), bundle_uri)) { + zix_tree_remove((ZixTree*)world->plugins, i); + zix_tree_insert((ZixTree*)world->zombies, p, NULL); + } + + i = next; + } + + // Drop everything in bundle graph + return lilv_world_drop_graph(world, bundle_uri->node); +} + +static void +load_dir_entry(const char* dir, const char* name, void* data) +{ + LilvWorld* world = (LilvWorld*)data; + if (!strcmp(name, ".") || !strcmp(name, "..")) { + return; + } + + char* path = lilv_strjoin(dir, "/", name, "/", NULL); + SerdNode suri = serd_node_new_file_uri((const uint8_t*)path, 0, 0, true); + LilvNode* node = lilv_new_uri(world, (const char*)suri.buf); + + lilv_world_load_bundle(world, node); + lilv_node_free(node); + serd_node_free(&suri); + free(path); +} + +/** Load all bundles in the directory at `dir_path`. */ +static void +lilv_world_load_directory(LilvWorld* world, const char* dir_path) +{ + char* path = lilv_expand(dir_path); + if (path) { + lilv_dir_for_each(path, world, load_dir_entry); + free(path); + } +} + +static const char* +first_path_sep(const char* path) +{ + for (const char* p = path; *p != '\0'; ++p) { + if (*p == LILV_PATH_SEP[0]) { + return p; + } + } + return NULL; +} + +/** Load all bundles found in `lv2_path`. + * @param lv2_path A colon-delimited list of directories. These directories + * should contain LV2 bundle directories (ie the search path is a list of + * parent directories of bundles, not a list of bundle directories). + */ +static void +lilv_world_load_path(LilvWorld* world, + const char* lv2_path) +{ + while (lv2_path[0] != '\0') { + const char* const sep = first_path_sep(lv2_path); + if (sep) { + const size_t dir_len = sep - lv2_path; + char* const dir = (char*)malloc(dir_len + 1); + memcpy(dir, lv2_path, dir_len); + dir[dir_len] = '\0'; + lilv_world_load_directory(world, dir); + free(dir); + lv2_path += dir_len + 1; + } else { + lilv_world_load_directory(world, lv2_path); + lv2_path = "\0"; + } + } +} + +void +lilv_world_load_specifications(LilvWorld* world) +{ + for (LilvSpec* spec = world->specs; spec; spec = spec->next) { + LILV_FOREACH(nodes, f, spec->data_uris) { + LilvNode* file = (LilvNode*)lilv_collection_get(spec->data_uris, f); + lilv_world_load_graph(world, NULL, file); + } + } +} + +void +lilv_world_load_plugin_classes(LilvWorld* world) +{ + /* FIXME: This loads all classes, not just lv2:Plugin subclasses. + However, if the host gets all the classes via lilv_plugin_class_get_children + starting with lv2:Plugin as the root (which is e.g. how a host would build + a menu), they won't be seen anyway... + */ + + SordIter* classes = sord_search(world->model, + NULL, + world->uris.rdf_a, + world->uris.rdfs_Class, + NULL); + FOREACH_MATCH(classes) { + const SordNode* class_node = sord_iter_get_node(classes, SORD_SUBJECT); + + SordNode* parent = sord_get( + world->model, class_node, world->uris.rdfs_subClassOf, NULL, NULL); + if (!parent || sord_node_get_type(parent) != SORD_URI) { + continue; + } + + SordNode* label = sord_get( + world->model, class_node, world->uris.rdfs_label, NULL, NULL); + if (!label) { + sord_node_free(world->world, parent); + continue; + } + + LilvPluginClass* pclass = lilv_plugin_class_new( + world, parent, class_node, + (const char*)sord_node_get_string(label)); + if (pclass) { + zix_tree_insert((ZixTree*)world->plugin_classes, pclass, NULL); + } + + sord_node_free(world->world, label); + sord_node_free(world->world, parent); + } + sord_iter_free(classes); +} + +LILV_API void +lilv_world_load_all(LilvWorld* world) +{ + const char* lv2_path = getenv("LV2_PATH"); + if (!lv2_path) { + lv2_path = LILV_DEFAULT_LV2_PATH; + } + + // Discover bundles and read all manifest files into model + lilv_world_load_path(world, lv2_path); + + LILV_FOREACH(plugins, p, world->plugins) { + const LilvPlugin* plugin = (const LilvPlugin*)lilv_collection_get( + (ZixTree*)world->plugins, p); + + // ?new dc:replaces plugin + if (sord_ask(world->model, + NULL, + world->uris.dc_replaces, + lilv_plugin_get_uri(plugin)->node, + NULL)) { + // TODO: Check if replacement is a known plugin? (expensive) + ((LilvPlugin*)plugin)->replaced = true; + } + } + + // Query out things to cache + lilv_world_load_specifications(world); + lilv_world_load_plugin_classes(world); +} + +SerdStatus +lilv_world_load_file(LilvWorld* world, SerdReader* reader, const LilvNode* uri) +{ + ZixTreeIter* iter; + if (!zix_tree_find((ZixTree*)world->loaded_files, uri, &iter)) { + return SERD_FAILURE; // File has already been loaded + } + + size_t uri_len; + const uint8_t* const uri_str = sord_node_get_string_counted( + uri->node, &uri_len); + if (strncmp((const char*)uri_str, "file:", 5)) { + return SERD_FAILURE; // Not a local file + } else if (strcmp((const char*)uri_str + uri_len - 4, ".ttl")) { + return SERD_FAILURE; // Not a Turtle file + } + + serd_reader_add_blank_prefix(reader, lilv_world_blank_node_prefix(world)); + const SerdStatus st = serd_reader_read_file(reader, uri_str); + if (st) { + LILV_ERRORF("Error loading file `%s'\n", lilv_node_as_string(uri)); + return st; + } + + zix_tree_insert((ZixTree*)world->loaded_files, + lilv_node_duplicate(uri), + NULL); + return SERD_SUCCESS; +} + +LILV_API int +lilv_world_load_resource(LilvWorld* world, + const LilvNode* resource) +{ + if (!lilv_node_is_uri(resource) && !lilv_node_is_blank(resource)) { + LILV_ERRORF("Node `%s' is not a resource\n", + sord_node_get_string(resource->node)); + return -1; + } + + SordModel* files = lilv_world_filter_model(world, + world->model, + resource->node, + world->uris.rdfs_seeAlso, + NULL, NULL); + + SordIter* f = sord_begin(files); + int n_read = 0; + FOREACH_MATCH(f) { + const SordNode* file = sord_iter_get_node(f, SORD_OBJECT); + const uint8_t* file_str = sord_node_get_string(file); + LilvNode* file_node = lilv_node_new_from_node(world, file); + if (sord_node_get_type(file) != SORD_URI) { + LILV_ERRORF("rdfs:seeAlso node `%s' is not a URI\n", file_str); + } else if (!lilv_world_load_graph(world, (SordNode*)file, file_node)) { + ++n_read; + } + lilv_node_free(file_node); + } + sord_iter_free(f); + + sord_free(files); + return n_read; +} + +LILV_API int +lilv_world_unload_resource(LilvWorld* world, + const LilvNode* resource) +{ + if (!lilv_node_is_uri(resource) && !lilv_node_is_blank(resource)) { + LILV_ERRORF("Node `%s' is not a resource\n", + sord_node_get_string(resource->node)); + return -1; + } + + SordModel* files = lilv_world_filter_model(world, + world->model, + resource->node, + world->uris.rdfs_seeAlso, + NULL, NULL); + + SordIter* f = sord_begin(files); + int n_dropped = 0; + FOREACH_MATCH(f) { + const SordNode* file = sord_iter_get_node(f, SORD_OBJECT); + LilvNode* file_node = lilv_node_new_from_node(world, file); + if (sord_node_get_type(file) != SORD_URI) { + LILV_ERRORF("rdfs:seeAlso node `%s' is not a URI\n", + sord_node_get_string(file)); + } else if (!lilv_world_drop_graph(world, file_node->node)) { + lilv_world_unload_file(world, file_node); + ++n_dropped; + } + lilv_node_free(file_node); + } + sord_iter_free(f); + + sord_free(files); + return n_dropped; +} + +LILV_API const LilvPluginClass* +lilv_world_get_plugin_class(const LilvWorld* world) +{ + return world->lv2_plugin_class; +} + +LILV_API const LilvPluginClasses* +lilv_world_get_plugin_classes(const LilvWorld* world) +{ + return world->plugin_classes; +} + +LILV_API const LilvPlugins* +lilv_world_get_all_plugins(const LilvWorld* world) +{ + return world->plugins; +} + +LILV_API LilvNode* +lilv_world_get_symbol(LilvWorld* world, const LilvNode* subject) +{ + // Check for explicitly given symbol + SordNode* snode = sord_get( + world->model, subject->node, world->uris.lv2_symbol, NULL, NULL); + + if (snode) { + LilvNode* ret = lilv_node_new_from_node(world, snode); + sord_node_free(world->world, snode); + return ret; + } + + if (!lilv_node_is_uri(subject)) { + return NULL; + } + + // Find rightmost segment of URI + SerdURI uri; + serd_uri_parse((const uint8_t*)lilv_node_as_uri(subject), &uri); + const char* str = "_"; + if (uri.fragment.buf) { + str = (const char*)uri.fragment.buf + 1; + } else if (uri.query.buf) { + str = (const char*)uri.query.buf; + } else if (uri.path.buf) { + const char* last_slash = strrchr((const char*)uri.path.buf, '/'); + str = last_slash ? (last_slash + 1) : (const char*)uri.path.buf; + } + + // Replace invalid characters + const size_t len = strlen(str); + char* const sym = (char*)calloc(1, len + 1); + for (size_t i = 0; i < len; ++i) { + const char c = str[i]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c == '_') || (i > 0 && c >= '0' && c <= '9'))) { + sym[i] = '_'; + } else { + sym[i] = str[i]; + } + } + + LilvNode* ret = lilv_new_string(world, sym); + free(sym); + return ret; +} diff --git a/src/zix/common.h b/src/zix/common.h new file mode 100644 index 0000000..32019e9 --- /dev/null +++ b/src/zix/common.h @@ -0,0 +1,88 @@ +/* + Copyright 2016 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef ZIX_COMMON_H +#define ZIX_COMMON_H + +/** + @addtogroup zix + @{ +*/ + +/** @cond */ +#ifdef ZIX_SHARED +# ifdef _WIN32 +# define ZIX_LIB_IMPORT __declspec(dllimport) +# define ZIX_LIB_EXPORT __declspec(dllexport) +# else +# define ZIX_LIB_IMPORT __attribute__((visibility("default"))) +# define ZIX_LIB_EXPORT __attribute__((visibility("default"))) +# endif +# ifdef ZIX_INTERNAL +# define ZIX_API ZIX_LIB_EXPORT +# else +# define ZIX_API ZIX_LIB_IMPORT +# endif +# define ZIX_PRIVATE static +#elif defined(ZIX_INLINE) +# define ZIX_API static inline +# define ZIX_PRIVATE static inline +#else +# define ZIX_API +# define ZIX_PRIVATE static +#endif +/** @endcond */ + +#ifdef __cplusplus +extern "C" { +#else +# include <stdbool.h> +#endif + +typedef enum { + ZIX_STATUS_SUCCESS, + ZIX_STATUS_ERROR, + ZIX_STATUS_NO_MEM, + ZIX_STATUS_NOT_FOUND, + ZIX_STATUS_EXISTS, + ZIX_STATUS_BAD_ARG, + ZIX_STATUS_BAD_PERMS +} ZixStatus; + +/** + Function for comparing two elements. +*/ +typedef int (*ZixComparator)(const void* a, const void* b, void* user_data); + +/** + Function for testing equality of two elements. +*/ +typedef bool (*ZixEqualFunc)(const void* a, const void* b); + +/** + Function to destroy an element. +*/ +typedef void (*ZixDestroyFunc)(void* ptr); + +/** + @} +*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ZIX_COMMON_H */ diff --git a/src/zix/tree.c b/src/zix/tree.c new file mode 100644 index 0000000..1646709 --- /dev/null +++ b/src/zix/tree.c @@ -0,0 +1,716 @@ +/* + Copyright 2011-2014 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "zix/common.h" +#include "zix/tree.h" + +typedef struct ZixTreeNodeImpl ZixTreeNode; + +struct ZixTreeImpl { + ZixTreeNode* root; + ZixDestroyFunc destroy; + ZixComparator cmp; + void* cmp_data; + size_t size; + bool allow_duplicates; +}; + +struct ZixTreeNodeImpl { + void* data; + struct ZixTreeNodeImpl* left; + struct ZixTreeNodeImpl* right; + struct ZixTreeNodeImpl* parent; + int_fast8_t balance; +}; + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +// Uncomment these for debugging features +// #define ZIX_TREE_DUMP 1 +// #define ZIX_TREE_VERIFY 1 +// #define ZIX_TREE_HYPER_VERIFY 1 + +#if defined(ZIX_TREE_VERIFY) || defined(ZIX_TREE_HYPER_VERIFY) +# include "tree_debug.h" +# define ASSERT_BALANCE(n) assert(verify_balance(n)) +#else +# define ASSERT_BALANCE(n) +#endif + +#ifdef ZIX_TREE_DUMP +# include "tree_debug.h" +# define DUMP(t) zix_tree_print(t->root, 0) +# define DEBUG_PRINTF(fmt, ...) printf(fmt, __VA_ARGS__) +#else +# define DUMP(t) +# define DEBUG_PRINTF(fmt, ...) +#endif + +ZIX_API ZixTree* +zix_tree_new(bool allow_duplicates, + ZixComparator cmp, + void* cmp_data, + ZixDestroyFunc destroy) +{ + ZixTree* t = (ZixTree*)malloc(sizeof(ZixTree)); + t->root = NULL; + t->destroy = destroy; + t->cmp = cmp; + t->cmp_data = cmp_data; + t->size = 0; + t->allow_duplicates = allow_duplicates; + return t; +} + +ZIX_PRIVATE void +zix_tree_free_rec(ZixTree* t, ZixTreeNode* n) +{ + if (n) { + zix_tree_free_rec(t, n->left); + zix_tree_free_rec(t, n->right); + if (t->destroy) { + t->destroy(n->data); + } + free(n); + } +} + +ZIX_API void +zix_tree_free(ZixTree* t) +{ + if (t) { + zix_tree_free_rec(t, t->root); + free(t); + } +} + +ZIX_API size_t +zix_tree_size(const ZixTree* t) +{ + return t->size; +} + +ZIX_PRIVATE void +rotate(ZixTreeNode* p, ZixTreeNode* q) +{ + assert(q->parent == p); + assert(p->left == q || p->right == q); + + q->parent = p->parent; + if (q->parent) { + if (q->parent->left == p) { + q->parent->left = q; + } else { + q->parent->right = q; + } + } + + if (p->right == q) { + // Rotate left + p->right = q->left; + q->left = p; + if (p->right) { + p->right->parent = p; + } + } else { + // Rotate right + assert(p->left == q); + p->left = q->right; + q->right = p; + if (p->left) { + p->left->parent = p; + } + } + + p->parent = q; +} + +/** + * Rotate left about `p`. + * + * p q + * / \ / \ + * A q => p C + * / \ / \ + * B C A B + */ +ZIX_PRIVATE ZixTreeNode* +rotate_left(ZixTreeNode* p, int* height_change) +{ + ZixTreeNode* const q = p->right; + *height_change = (q->balance == 0) ? 0 : -1; + + DEBUG_PRINTF("LL %ld\n", (intptr_t)p->data); + + assert(p->balance == 2); + assert(q->balance == 0 || q->balance == 1); + + rotate(p, q); + + // p->balance -= 1 + MAX(0, q->balance); + // q->balance -= 1 - MIN(0, p->balance); + --q->balance; + p->balance = -(q->balance); + + ASSERT_BALANCE(p); + ASSERT_BALANCE(q); + return q; +} + +/** + * Rotate right about `p`. + * + * p q + * / \ / \ + * q C => A p + * / \ / \ + * A B B C + * + */ +ZIX_PRIVATE ZixTreeNode* +rotate_right(ZixTreeNode* p, int* height_change) +{ + ZixTreeNode* const q = p->left; + *height_change = (q->balance == 0) ? 0 : -1; + + DEBUG_PRINTF("RR %ld\n", (intptr_t)p->data); + + assert(p->balance == -2); + assert(q->balance == 0 || q->balance == -1); + + rotate(p, q); + + // p->balance += 1 - MIN(0, q->balance); + // q->balance += 1 + MAX(0, p->balance); + ++q->balance; + p->balance = -(q->balance); + + ASSERT_BALANCE(p); + ASSERT_BALANCE(q); + return q; +} + +/** + * Rotate left about `p->left` then right about `p`. + * + * p r + * / \ / \ + * q D => q p + * / \ / \ / \ + * A r A B C D + * / \ + * B C + * + */ +ZIX_PRIVATE ZixTreeNode* +rotate_left_right(ZixTreeNode* p, int* height_change) +{ + ZixTreeNode* const q = p->left; + ZixTreeNode* const r = q->right; + + assert(p->balance == -2); + assert(q->balance == 1); + assert(r->balance == -1 || r->balance == 0 || r->balance == 1); + + DEBUG_PRINTF("LR %ld P: %2d Q: %2d R: %2d\n", + (intptr_t)p->data, p->balance, q->balance, r->balance); + + rotate(q, r); + rotate(p, r); + + q->balance -= 1 + MAX(0, r->balance); + p->balance += 1 - MIN(MIN(0, r->balance) - 1, r->balance + q->balance); + // r->balance += MAX(0, p->balance) + MIN(0, q->balance); + + // p->balance = (p->left && p->right) ? -MIN(r->balance, 0) : 0; + // q->balance = - MAX(r->balance, 0); + r->balance = 0; + + *height_change = -1; + + ASSERT_BALANCE(p); + ASSERT_BALANCE(q); + ASSERT_BALANCE(r); + return r; +} + +/** + * Rotate right about `p->right` then right about `p`. + * + * p r + * / \ / \ + * A q => p q + * / \ / \ / \ + * r D A B C D + * / \ + * B C + * + */ +ZIX_PRIVATE ZixTreeNode* +rotate_right_left(ZixTreeNode* p, int* height_change) +{ + ZixTreeNode* const q = p->right; + ZixTreeNode* const r = q->left; + + assert(p->balance == 2); + assert(q->balance == -1); + assert(r->balance == -1 || r->balance == 0 || r->balance == 1); + + DEBUG_PRINTF("RL %ld P: %2d Q: %2d R: %2d\n", + (intptr_t)p->data, p->balance, q->balance, r->balance); + + rotate(q, r); + rotate(p, r); + + q->balance += 1 - MIN(0, r->balance); + p->balance -= 1 + MAX(MAX(0, r->balance) + 1, r->balance + q->balance); + // r->balance += MAX(0, q->balance) + MIN(0, p->balance); + + // p->balance = (p->left && p->right) ? -MAX(r->balance, 0) : 0; + // q->balance = - MIN(r->balance, 0); + r->balance = 0; + // assert(r->balance == 0); + + *height_change = -1; + + ASSERT_BALANCE(p); + ASSERT_BALANCE(q); + ASSERT_BALANCE(r); + return r; +} + +ZIX_PRIVATE ZixTreeNode* +zix_tree_rebalance(ZixTree* t, ZixTreeNode* node, int* height_change) +{ +#ifdef ZIX_TREE_HYPER_VERIFY + const size_t old_height = height(node); +#endif + DEBUG_PRINTF("REBALANCE %ld (%d)\n", (intptr_t)node->data, node->balance); + *height_change = 0; + const bool is_root = !node->parent; + assert((is_root && t->root == node) || (!is_root && t->root != node)); + ZixTreeNode* replacement = node; + if (node->balance == -2) { + assert(node->left); + if (node->left->balance == 1) { + replacement = rotate_left_right(node, height_change); + } else { + replacement = rotate_right(node, height_change); + } + } else if (node->balance == 2) { + assert(node->right); + if (node->right->balance == -1) { + replacement = rotate_right_left(node, height_change); + } else { + replacement = rotate_left(node, height_change); + } + } + if (is_root) { + assert(!replacement->parent); + t->root = replacement; + } + DUMP(t); +#ifdef ZIX_TREE_HYPER_VERIFY + assert(old_height + *height_change == height(replacement)); +#endif + return replacement; +} + +ZIX_API ZixStatus +zix_tree_insert(ZixTree* t, void* e, ZixTreeIter** ti) +{ + DEBUG_PRINTF("**** INSERT %ld\n", (intptr_t)e); + int cmp = 0; + ZixTreeNode* n = t->root; + ZixTreeNode* p = NULL; + + // Find the parent p of e + while (n) { + p = n; + cmp = t->cmp(e, n->data, t->cmp_data); + if (cmp < 0) { + n = n->left; + } else if (cmp > 0) { + n = n->right; + } else if (t->allow_duplicates) { + n = n->right; + } else { + if (ti) { + *ti = n; + } + DEBUG_PRINTF("%ld EXISTS!\n", (intptr_t)e); + return ZIX_STATUS_EXISTS; + } + } + + // Allocate a new node n + if (!(n = (ZixTreeNode*)malloc(sizeof(ZixTreeNode)))) { + return ZIX_STATUS_NO_MEM; + } + memset(n, '\0', sizeof(ZixTreeNode)); + n->data = e; + n->balance = 0; + if (ti) { + *ti = n; + } + + bool p_height_increased = false; + + // Make p the parent of n + n->parent = p; + if (!p) { + t->root = n; + } else { + if (cmp < 0) { + assert(!p->left); + assert(p->balance == 0 || p->balance == 1); + p->left = n; + --p->balance; + p_height_increased = !p->right; + } else { + assert(!p->right); + assert(p->balance == 0 || p->balance == -1); + p->right = n; + ++p->balance; + p_height_increased = !p->left; + } + } + + DUMP(t); + + // Rebalance if necessary (at most 1 rotation) + assert(!p || p->balance == -1 || p->balance == 0 || p->balance == 1); + if (p && p_height_increased) { + int height_change = 0; + for (ZixTreeNode* i = p; i && i->parent; i = i->parent) { + if (i == i->parent->left) { + if (--i->parent->balance == -2) { + zix_tree_rebalance(t, i->parent, &height_change); + break; + } + } else { + assert(i == i->parent->right); + if (++i->parent->balance == 2) { + zix_tree_rebalance(t, i->parent, &height_change); + break; + } + } + + if (i->parent->balance == 0) { + break; + } + } + } + + DUMP(t); + + ++t->size; + +#ifdef ZIX_TREE_VERIFY + if (!verify(t, t->root)) { + return ZIX_STATUS_ERROR; + } +#endif + + return ZIX_STATUS_SUCCESS; +} + +ZIX_API ZixStatus +zix_tree_remove(ZixTree* t, ZixTreeIter* ti) +{ + ZixTreeNode* const n = ti; + ZixTreeNode** pp = NULL; // parent pointer + ZixTreeNode* to_balance = n->parent; // lowest node to balance + int8_t d_balance = 0; // delta(balance) for n->parent + + DEBUG_PRINTF("*** REMOVE %ld\n", (intptr_t)n->data); + + if ((n == t->root) && !n->left && !n->right) { + t->root = NULL; + if (t->destroy) { + t->destroy(n->data); + } + free(n); + --t->size; + assert(t->size == 0); + return ZIX_STATUS_SUCCESS; + } + + // Set pp to the parent pointer to n, if applicable + if (n->parent) { + assert(n->parent->left == n || n->parent->right == n); + if (n->parent->left == n) { // n is left child + pp = &n->parent->left; + d_balance = 1; + } else { // n is right child + assert(n->parent->right == n); + pp = &n->parent->right; + d_balance = -1; + } + } + + assert(!pp || *pp == n); + + int height_change = 0; + if (!n->left && !n->right) { + // n is a leaf, just remove it + if (pp) { + *pp = NULL; + to_balance = n->parent; + height_change = (!n->parent->left && !n->parent->right) ? -1 : 0; + } + } else if (!n->left) { + // Replace n with right (only) child + if (pp) { + *pp = n->right; + to_balance = n->parent; + } else { + t->root = n->right; + } + n->right->parent = n->parent; + height_change = -1; + } else if (!n->right) { + // Replace n with left (only) child + if (pp) { + *pp = n->left; + to_balance = n->parent; + } else { + t->root = n->left; + } + n->left->parent = n->parent; + height_change = -1; + } else { + // Replace n with in-order successor (leftmost child of right subtree) + ZixTreeNode* replace = n->right; + while (replace->left) { + assert(replace->left->parent == replace); + replace = replace->left; + } + + // Remove replace from parent (replace_p) + if (replace->parent->left == replace) { + height_change = replace->parent->right ? 0 : -1; + d_balance = 1; + to_balance = replace->parent; + replace->parent->left = replace->right; + } else { + assert(replace->parent == n); + height_change = replace->parent->left ? 0 : -1; + d_balance = -1; + to_balance = replace->parent; + replace->parent->right = replace->right; + } + + if (to_balance == n) { + to_balance = replace; + } + + if (replace->right) { + replace->right->parent = replace->parent; + } + + replace->balance = n->balance; + + // Swap node to delete with replace + if (pp) { + *pp = replace; + } else { + assert(t->root == n); + t->root = replace; + } + replace->parent = n->parent; + replace->left = n->left; + n->left->parent = replace; + replace->right = n->right; + if (n->right) { + n->right->parent = replace; + } + + assert(!replace->parent + || replace->parent->left == replace + || replace->parent->right == replace); + } + + // Rebalance starting at to_balance upwards. + for (ZixTreeNode* i = to_balance; i; i = i->parent) { + i->balance += d_balance; + if (d_balance == 0 || i->balance == -1 || i->balance == 1) { + break; + } + + assert(i != n); + i = zix_tree_rebalance(t, i, &height_change); + if (i->balance == 0) { + height_change = -1; + } + + if (i->parent) { + if (i == i->parent->left) { + d_balance = height_change * -1; + } else { + assert(i == i->parent->right); + d_balance = height_change; + } + } + } + + DUMP(t); + + if (t->destroy) { + t->destroy(n->data); + } + free(n); + + --t->size; + +#ifdef ZIX_TREE_VERIFY + if (!verify(t, t->root)) { + return ZIX_STATUS_ERROR; + } +#endif + + return ZIX_STATUS_SUCCESS; +} + +ZIX_API ZixStatus +zix_tree_find(const ZixTree* t, const void* e, ZixTreeIter** ti) +{ + ZixTreeNode* n = t->root; + while (n) { + const int cmp = t->cmp(e, n->data, t->cmp_data); + if (cmp == 0) { + break; + } else if (cmp < 0) { + n = n->left; + } else { + n = n->right; + } + } + + *ti = n; + return (n) ? ZIX_STATUS_SUCCESS : ZIX_STATUS_NOT_FOUND; +} + +ZIX_API void* +zix_tree_get(const ZixTreeIter* ti) +{ + return ti ? ti->data : NULL; +} + +ZIX_API ZixTreeIter* +zix_tree_begin(ZixTree* t) +{ + if (!t->root) { + return NULL; + } + + ZixTreeNode* n = t->root; + while (n->left) { + n = n->left; + } + return n; +} + +ZIX_API ZixTreeIter* +zix_tree_end(ZixTree* t) +{ + return NULL; +} + +ZIX_API ZixTreeIter* +zix_tree_rbegin(ZixTree* t) +{ + if (!t->root) { + return NULL; + } + + ZixTreeNode* n = t->root; + while (n->right) { + n = n->right; + } + return n; +} + +ZIX_API ZixTreeIter* +zix_tree_rend(ZixTree* t) +{ + return NULL; +} + +ZIX_API bool +zix_tree_iter_is_end(const ZixTreeIter* i) +{ + return !i; +} + +ZIX_API bool +zix_tree_iter_is_rend(const ZixTreeIter* i) +{ + return !i; +} + +ZIX_API ZixTreeIter* +zix_tree_iter_next(ZixTreeIter* i) +{ + if (!i) { + return NULL; + } + + if (i->right) { + i = i->right; + while (i->left) { + i = i->left; + } + } else { + while (i->parent && i->parent->right == i) { // i is a right child + i = i->parent; + } + + i = i->parent; + } + + return i; +} + +ZIX_API ZixTreeIter* +zix_tree_iter_prev(ZixTreeIter* i) +{ + if (!i) { + return NULL; + } + + if (i->left) { + i = i->left; + while (i->right) { + i = i->right; + } + } else { + while (i->parent && i->parent->left == i) { // i is a left child + i = i->parent; + } + + i = i->parent; + } + + return i; +} diff --git a/src/zix/tree.h b/src/zix/tree.h new file mode 100644 index 0000000..e531a9b --- /dev/null +++ b/src/zix/tree.h @@ -0,0 +1,148 @@ +/* + Copyright 2011-2014 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef ZIX_TREE_H +#define ZIX_TREE_H + +#include <stddef.h> + +#include "zix/common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + @addtogroup zix + @{ + @name Tree + @{ +*/ + +/** + A balanced binary search tree. +*/ +typedef struct ZixTreeImpl ZixTree; + +/** + An iterator over a ZixTree. +*/ +typedef struct ZixTreeNodeImpl ZixTreeIter; + +/** + Create a new (empty) tree. +*/ +ZIX_API ZixTree* +zix_tree_new(bool allow_duplicates, + ZixComparator cmp, + void* cmp_data, + ZixDestroyFunc destroy); + +/** + Free `t`. +*/ +ZIX_API void +zix_tree_free(ZixTree* t); + +/** + Return the number of elements in `t`. +*/ +ZIX_API size_t +zix_tree_size(const ZixTree* t); + +/** + Insert the element `e` into `t` and point `ti` at the new element. +*/ +ZIX_API ZixStatus +zix_tree_insert(ZixTree* t, void* e, ZixTreeIter** ti); + +/** + Remove the item pointed at by `ti` from `t`. +*/ +ZIX_API ZixStatus +zix_tree_remove(ZixTree* t, ZixTreeIter* ti); + +/** + Set `ti` to an element equal to `e` in `t`. + If no such item exists, `ti` is set to NULL. +*/ +ZIX_API ZixStatus +zix_tree_find(const ZixTree* t, const void* e, ZixTreeIter** ti); + +/** + Return the data associated with the given tree item. +*/ +ZIX_API void* +zix_tree_get(const ZixTreeIter* ti); + +/** + Return an iterator to the first (smallest) element in `t`. +*/ +ZIX_API ZixTreeIter* +zix_tree_begin(ZixTree* t); + +/** + Return an iterator the the element one past the last element in `t`. +*/ +ZIX_API ZixTreeIter* +zix_tree_end(ZixTree* t); + +/** + Return true iff `i` is an iterator to the end of its tree. +*/ +ZIX_API bool +zix_tree_iter_is_end(const ZixTreeIter* i); + +/** + Return an iterator to the last (largest) element in `t`. +*/ +ZIX_API ZixTreeIter* +zix_tree_rbegin(ZixTree* t); + +/** + Return an iterator the the element one before the first element in `t`. +*/ +ZIX_API ZixTreeIter* +zix_tree_rend(ZixTree* t); + +/** + Return true iff `i` is an iterator to the reverse end of its tree. +*/ +ZIX_API bool +zix_tree_iter_is_rend(const ZixTreeIter* i); + +/** + Return an iterator that points to the element one past `i`. +*/ +ZIX_API ZixTreeIter* +zix_tree_iter_next(ZixTreeIter* i); + +/** + Return an iterator that points to the element one before `i`. +*/ +ZIX_API ZixTreeIter* +zix_tree_iter_prev(ZixTreeIter* i); + +/** + @} + @} +*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ZIX_TREE_H */ diff --git a/test/bad_syntax.lv2/bad_syntax.c b/test/bad_syntax.lv2/bad_syntax.c new file mode 100644 index 0000000..52620bd --- /dev/null +++ b/test/bad_syntax.lv2/bad_syntax.c @@ -0,0 +1,93 @@ +/* + Lilv Test Plugin - Bad syntax in plugin data file + Copyright 2011-2016 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <stdlib.h> + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define PLUGIN_URI "http://example.org/bad-syntax" + +enum { + TEST_INPUT = 0, + TEST_OUTPUT = 1 +}; + +typedef struct { + float* input; + float* output; +} Test; + +static void +cleanup(LV2_Handle instance) +{ + free((Test*)instance); +} + +static void +connect_port(LV2_Handle instance, uint32_t port, void* data) +{ + Test* test = (Test*)instance; + switch (port) { + case TEST_INPUT: + test->input = (float*)data; + break; + case TEST_OUTPUT: + test->output = (float*)data; + break; + default: + break; + } +} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* path, + const LV2_Feature* const* features) +{ + Test* test = (Test*)calloc(1, sizeof(Test)); + if (!test) { + return NULL; + } + + return (LV2_Handle)test; +} + +static void +run(LV2_Handle instance, uint32_t sample_count) +{ + Test* test = (Test*)instance; + + *test->output = *test->input; +} + +static const LV2_Descriptor descriptor = { + PLUGIN_URI, + instantiate, + connect_port, + NULL, // activate, + run, + NULL, // deactivate, + cleanup, + NULL // extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* lv2_descriptor(uint32_t index) +{ + return (index == 0) ? &descriptor : NULL; +} diff --git a/test/bad_syntax.lv2/bad_syntax.ttl.in b/test/bad_syntax.lv2/bad_syntax.ttl.in new file mode 100644 index 0000000..a96d1fc --- /dev/null +++ b/test/bad_syntax.lv2/bad_syntax.ttl.in @@ -0,0 +1,22 @@ +# Lilv Test Plugin - Bad syntax in plugin data file +# Copyright 2011-2016 David Robillard <d@drobilla.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . + +<http://example.org/bad-syntax> + a plugin with a clearly broken data file
\ No newline at end of file diff --git a/test/bad_syntax.lv2/manifest.ttl.in b/test/bad_syntax.lv2/manifest.ttl.in new file mode 100644 index 0000000..6350d8d --- /dev/null +++ b/test/bad_syntax.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://example.org/bad-syntax> + a lv2:Plugin ; + lv2:binary <bad_syntax@SHLIB_EXT@> ; + rdfs:seeAlso <bad_syntax.ttl> . diff --git a/test/bad_syntax.lv2/test_bad_syntax.c b/test/bad_syntax.lv2/test_bad_syntax.c new file mode 100644 index 0000000..d20b8b7 --- /dev/null +++ b/test/bad_syntax.lv2/test_bad_syntax.c @@ -0,0 +1,45 @@ +#include "lilv/lilv.h" +#include "../src/lilv_internal.h" + +#define PLUGIN_URI "http://example.org/bad-syntax" + +#define TEST_ASSERT(check) do {\ + if (!(check)) {\ + fprintf(stderr, "%s:%d: failed test: %s\n", __FILE__, __LINE__, #check);\ + return 1;\ + }\ +} while (0) + +int +main(int argc, char** argv) +{ + if (argc != 2) { + fprintf(stderr, "USAGE: %s BUNDLE\n", argv[0]); + return 1; + } + + const char* bundle_path = argv[1]; + LilvWorld* world = lilv_world_new(); + + // Load test plugin bundle + uint8_t* abs_bundle = (uint8_t*)lilv_path_absolute(bundle_path); + SerdNode bundle = serd_node_new_file_uri(abs_bundle, 0, 0, true); + LilvNode* bundle_uri = lilv_new_uri(world, (const char*)bundle.buf); + lilv_world_load_bundle(world, bundle_uri); + free(abs_bundle); + serd_node_free(&bundle); + lilv_node_free(bundle_uri); + + LilvNode* plugin_uri = lilv_new_uri(world, PLUGIN_URI); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + + TEST_ASSERT(!lilv_plugin_get_name(plugin)); + TEST_ASSERT(!lilv_plugin_instantiate(plugin, 48000, NULL)); + + lilv_node_free(plugin_uri); + lilv_world_free(world); + + return 0; +} + diff --git a/test/failed_instantiation.lv2/failed_instantiation.c b/test/failed_instantiation.lv2/failed_instantiation.c new file mode 100644 index 0000000..aa90532 --- /dev/null +++ b/test/failed_instantiation.lv2/failed_instantiation.c @@ -0,0 +1,70 @@ +/* + Lilv Test Plugin - Failed instantiation + Copyright 2011-2016 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <stdlib.h> + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define PLUGIN_URI "http://example.org/failed-instantiation" + +enum { + TEST_INPUT = 0, + TEST_OUTPUT = 1 +}; + +typedef struct { + float* input; + float* output; +} Test; + +static void +cleanup(LV2_Handle instance) +{} + +static void +connect_port(LV2_Handle instance, uint32_t port, void* data) +{} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* path, + const LV2_Feature* const* features) +{ + return NULL; +} + +static void +run(LV2_Handle instance, uint32_t sample_count) +{} + +static const LV2_Descriptor descriptor = { + PLUGIN_URI, + instantiate, + connect_port, + NULL, // activate, + run, + NULL, // deactivate, + cleanup, + NULL // extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* lv2_descriptor(uint32_t index) +{ + return (index == 0) ? &descriptor : NULL; +} diff --git a/test/failed_instantiation.lv2/failed_instantiation.ttl.in b/test/failed_instantiation.lv2/failed_instantiation.ttl.in new file mode 100644 index 0000000..8c7f678 --- /dev/null +++ b/test/failed_instantiation.lv2/failed_instantiation.ttl.in @@ -0,0 +1,40 @@ +# Lilv Test Plugin - Failed instantiation +# Copyright 2011-2016 David Robillard <d@drobilla.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . + +<http://example.org/failed-instantiation> + a lv2:Plugin ; + doap:license <http://opensource.org/licenses/isc> ; + doap:name "New version" ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:minorVersion 2 ; + lv2:microVersion 1 ; + lv2:port [ + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "input" ; + lv2:name "Input" + ] , [ + a lv2:OutputPort , + lv2:ControlPort ; + lv2:index 1 ; + lv2:symbol "output" ; + lv2:name "Output" + ] . diff --git a/test/failed_instantiation.lv2/manifest.ttl.in b/test/failed_instantiation.lv2/manifest.ttl.in new file mode 100644 index 0000000..d55a573 --- /dev/null +++ b/test/failed_instantiation.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://example.org/failed-instantiation> + a lv2:Plugin ; + lv2:binary <failed_instantiation@SHLIB_EXT@> ; + rdfs:seeAlso <failed_instantiation.ttl> . diff --git a/test/failed_instantiation.lv2/test_failed_instantiation.c b/test/failed_instantiation.lv2/test_failed_instantiation.c new file mode 100644 index 0000000..6efa0ae --- /dev/null +++ b/test/failed_instantiation.lv2/test_failed_instantiation.c @@ -0,0 +1,45 @@ +#include "lilv/lilv.h" +#include "../src/lilv_internal.h" + +#define PLUGIN_URI "http://example.org/failed-instantiation" + +#define TEST_ASSERT(check) do {\ + if (!(check)) {\ + fprintf(stderr, "%s:%d: failed test: %s\n", __FILE__, __LINE__, #check);\ + return 1;\ + }\ +} while (0) + +int +main(int argc, char** argv) +{ + if (argc != 2) { + fprintf(stderr, "USAGE: %s BUNDLE\n", argv[0]); + return 1; + } + + const char* bundle_path = argv[1]; + LilvWorld* world = lilv_world_new(); + + // Load test plugin bundle + uint8_t* abs_bundle = (uint8_t*)lilv_path_absolute(bundle_path); + SerdNode bundle = serd_node_new_file_uri(abs_bundle, 0, 0, true); + LilvNode* bundle_uri = lilv_new_uri(world, (const char*)bundle.buf); + lilv_world_load_bundle(world, bundle_uri); + free(abs_bundle); + serd_node_free(&bundle); + lilv_node_free(bundle_uri); + + LilvNode* plugin_uri = lilv_new_uri(world, PLUGIN_URI); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + TEST_ASSERT(plugin); + + TEST_ASSERT(!lilv_plugin_instantiate(plugin, 48000, NULL)); + + lilv_node_free(plugin_uri); + lilv_world_free(world); + + return 0; +} + diff --git a/test/failed_lib_descriptor.lv2/failed_lib_descriptor.c b/test/failed_lib_descriptor.lv2/failed_lib_descriptor.c new file mode 100644 index 0000000..6a1fd90 --- /dev/null +++ b/test/failed_lib_descriptor.lv2/failed_lib_descriptor.c @@ -0,0 +1,30 @@ +/* + Lilv Test Plugin - Failed lib descriptor + Copyright 2011-2015 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <stdlib.h> + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define PLUGIN_URI "http://example.org/failed-lib-descriptor" + +LV2_SYMBOL_EXPORT +const LV2_Lib_Descriptor* +lv2_lib_descriptor(const char* bundle_path, + const LV2_Feature*const* features) +{ + return NULL; +} diff --git a/test/failed_lib_descriptor.lv2/failed_lib_descriptor.ttl.in b/test/failed_lib_descriptor.lv2/failed_lib_descriptor.ttl.in new file mode 100644 index 0000000..f41545f --- /dev/null +++ b/test/failed_lib_descriptor.lv2/failed_lib_descriptor.ttl.in @@ -0,0 +1,38 @@ +# Lilv Test Plugin - Failed lib descriptor +# Copyright 2011-2015 David Robillard <d@drobilla.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . + +<http://example.org/failed-lib-descriptor> + a lv2:Plugin ; + doap:name "Missing descriptor test" ; + doap:license <http://opensource.org/licenses/isc> ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:port [ + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "input" ; + lv2:name "Input" + ] , [ + a lv2:OutputPort , + lv2:ControlPort ; + lv2:index 1 ; + lv2:symbol "output" ; + lv2:name "Output" + ] . diff --git a/test/failed_lib_descriptor.lv2/manifest.ttl.in b/test/failed_lib_descriptor.lv2/manifest.ttl.in new file mode 100644 index 0000000..4724f0f --- /dev/null +++ b/test/failed_lib_descriptor.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://example.org/failed-lib-descriptor> + a lv2:Plugin ; + lv2:binary <failed_lib_descriptor@SHLIB_EXT@> ; + rdfs:seeAlso <failed_lib_descriptor.ttl> . diff --git a/test/failed_lib_descriptor.lv2/test_failed_lib_descriptor.c b/test/failed_lib_descriptor.lv2/test_failed_lib_descriptor.c new file mode 100644 index 0000000..b82eb0a --- /dev/null +++ b/test/failed_lib_descriptor.lv2/test_failed_lib_descriptor.c @@ -0,0 +1,46 @@ +#include "lilv/lilv.h" +#include "../src/lilv_internal.h" + +#define PLUGIN_URI "http://example.org/failed-lib-descriptor" + +#define TEST_ASSERT(check) do {\ + if (!(check)) {\ + fprintf(stderr, "%s:%d: failed test: %s\n", __FILE__, __LINE__, #check);\ + return 1;\ + }\ +} while (0) + +int +main(int argc, char** argv) +{ + if (argc != 2) { + fprintf(stderr, "USAGE: %s BUNDLE\n", argv[0]); + return 1; + } + + const char* bundle_path = argv[1]; + LilvWorld* world = lilv_world_new(); + + // Load test plugin bundle + uint8_t* abs_bundle = (uint8_t*)lilv_path_absolute(bundle_path); + SerdNode bundle = serd_node_new_file_uri(abs_bundle, 0, 0, true); + LilvNode* bundle_uri = lilv_new_uri(world, (const char*)bundle.buf); + lilv_world_load_bundle(world, bundle_uri); + free(abs_bundle); + serd_node_free(&bundle); + lilv_node_free(bundle_uri); + + LilvNode* plugin_uri = lilv_new_uri(world, PLUGIN_URI); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + TEST_ASSERT(plugin); + + LilvInstance* instance = lilv_plugin_instantiate(plugin, 48000.0, NULL); + TEST_ASSERT(!instance); + + lilv_node_free(plugin_uri); + lilv_world_free(world); + + return 0; +} + diff --git a/test/lib_descriptor.lv2/lib_descriptor.c b/test/lib_descriptor.lv2/lib_descriptor.c new file mode 100644 index 0000000..48dc775 --- /dev/null +++ b/test/lib_descriptor.lv2/lib_descriptor.c @@ -0,0 +1,112 @@ +/* + Lilv Test Plugin - Missing descriptor + Copyright 2011-2015 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <stdlib.h> + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define PLUGIN_URI "http://example.org/lib-descriptor" + +enum { + TEST_INPUT = 0, + TEST_OUTPUT = 1 +}; + +typedef struct { + float* input; + float* output; +} Test; + +static void +cleanup(LV2_Handle instance) +{ + free((Test*)instance); +} + +static void +connect_port(LV2_Handle instance, uint32_t port, void* data) +{ + Test* test = (Test*)instance; + switch (port) { + case TEST_INPUT: + test->input = (float*)data; + break; + case TEST_OUTPUT: + test->output = (float*)data; + break; + default: + break; + } +} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* path, + const LV2_Feature* const* features) +{ + Test* test = (Test*)calloc(1, sizeof(Test)); + if (!test) { + return NULL; + } + + return (LV2_Handle)test; +} + +static void +run(LV2_Handle instance, uint32_t sample_count) +{ + Test* test = (Test*)instance; + + *test->output = *test->input; +} + +static const LV2_Descriptor descriptor = { + PLUGIN_URI, + instantiate, + connect_port, + NULL, // activate, + run, + NULL, // deactivate, + cleanup, + NULL // extension_data +}; + +static const LV2_Descriptor* +get_plugin(LV2_Lib_Handle handle, uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} + +static const LV2_Lib_Descriptor lib = { + NULL, + sizeof(LV2_Lib_Descriptor), + NULL, + get_plugin }; + +LV2_SYMBOL_EXPORT +const LV2_Lib_Descriptor* +lv2_lib_descriptor(const char* bundle_path, + const LV2_Feature*const* features) +{ + return &lib; +} diff --git a/test/lib_descriptor.lv2/lib_descriptor.ttl.in b/test/lib_descriptor.lv2/lib_descriptor.ttl.in new file mode 100644 index 0000000..19c8c4a --- /dev/null +++ b/test/lib_descriptor.lv2/lib_descriptor.ttl.in @@ -0,0 +1,41 @@ +# Lilv Test Plugin - Missing descriptor +# Copyright 2011-2015 David Robillard <d@drobilla.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . + +<http://example.org/lib-descriptor> + a lv2:Plugin ; + doap:name "Missing descriptor test" ; + doap:license <http://opensource.org/licenses/isc> ; + lv2:optionalFeature lv2:hardRTCapable ; + <http://example.org/blob> "aGVsbG8sIHdvcmxk"^^xsd:base64Binary ; + <http://example.org/junk> "opaque"^^<http://example.org/binary> ; + lv2:port [ + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "input" ; + lv2:name "Input" + ] , [ + a lv2:OutputPort , + lv2:ControlPort ; + lv2:index 1 ; + lv2:symbol "output" ; + lv2:name "Output" + ] . diff --git a/test/lib_descriptor.lv2/manifest.ttl.in b/test/lib_descriptor.lv2/manifest.ttl.in new file mode 100644 index 0000000..5f4eced --- /dev/null +++ b/test/lib_descriptor.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://example.org/lib-descriptor> + a lv2:Plugin ; + lv2:binary <lib_descriptor@SHLIB_EXT@> ; + rdfs:seeAlso <lib_descriptor.ttl> . diff --git a/test/lib_descriptor.lv2/test_lib_descriptor.c b/test/lib_descriptor.lv2/test_lib_descriptor.c new file mode 100644 index 0000000..ff14763 --- /dev/null +++ b/test/lib_descriptor.lv2/test_lib_descriptor.c @@ -0,0 +1,59 @@ +#include "lilv/lilv.h" +#include "../src/lilv_internal.h" + +#define PLUGIN_URI "http://example.org/lib-descriptor" + +#define TEST_ASSERT(check) do {\ + if (!(check)) {\ + fprintf(stderr, "%s:%d: failed test: %s\n", __FILE__, __LINE__, #check);\ + return 1;\ + }\ +} while (0) + +int +main(int argc, char** argv) +{ + if (argc != 2) { + fprintf(stderr, "USAGE: %s BUNDLE\n", argv[0]); + return 1; + } + + const char* bundle_path = argv[1]; + LilvWorld* world = lilv_world_new(); + + // Load test plugin bundle + uint8_t* abs_bundle = (uint8_t*)lilv_path_absolute(bundle_path); + SerdNode bundle = serd_node_new_file_uri(abs_bundle, 0, 0, true); + LilvNode* bundle_uri = lilv_new_uri(world, (const char*)bundle.buf); + lilv_world_load_bundle(world, bundle_uri); + free(abs_bundle); + serd_node_free(&bundle); + lilv_node_free(bundle_uri); + + LilvNode* plugin_uri = lilv_new_uri(world, PLUGIN_URI); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + TEST_ASSERT(plugin); + + LilvInstance* instance = lilv_plugin_instantiate(plugin, 48000.0, NULL); + TEST_ASSERT(instance); + lilv_instance_free(instance); + + LilvNode* eg_blob = lilv_new_uri(world, "http://example.org/blob"); + LilvNode* blob = lilv_world_get(world, plugin_uri, eg_blob, NULL); + TEST_ASSERT(lilv_node_is_literal(blob)); + lilv_node_free(blob); + lilv_node_free(eg_blob); + + LilvNode* eg_junk = lilv_new_uri(world, "http://example.org/junk"); + LilvNode* junk = lilv_world_get(world, plugin_uri, eg_junk, NULL); + TEST_ASSERT(lilv_node_is_literal(junk)); + lilv_node_free(junk); + lilv_node_free(eg_junk); + + lilv_node_free(plugin_uri); + lilv_world_free(world); + + return 0; +} + diff --git a/test/lilv_cxx_test.cpp b/test/lilv_cxx_test.cpp new file mode 100644 index 0000000..25f30bf --- /dev/null +++ b/test/lilv_cxx_test.cpp @@ -0,0 +1,23 @@ +/* + Copyright 2017 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "lilv/lilvmm.hpp" + +int +main() +{ + return 0; +} diff --git a/test/lilv_test.c b/test/lilv_test.c new file mode 100644 index 0000000..6b81cb1 --- /dev/null +++ b/test/lilv_test.c @@ -0,0 +1,2296 @@ +/* + Copyright 2007-2016 David Robillard <http://drobilla.net> + Copyright 2008 Krzysztof Foltman + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#define _POSIX_C_SOURCE 200809L /* for setenv */ +#define _XOPEN_SOURCE 600 /* for mkstemp */ + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <float.h> +#include <limits.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#ifdef _WIN32 +# include <direct.h> +# include <io.h> +# define mkdir(path, flags) _mkdir(path) +# define setenv(n, v, r) SetEnvironmentVariable((n), (v)) +# define unsetenv(n) SetEnvironmentVariable((n), NULL) +# define mkstemp(pat) _mktemp(pat) +#else +# include <dirent.h> +# include <unistd.h> +#endif + +#include "lilv/lilv.h" +#include "../src/lilv_internal.h" + +#include "lv2/lv2plug.in/ns/ext/presets/presets.h" +#include "lv2/lv2plug.in/ns/ext/state/state.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" + +#define TEST_PATH_MAX 1024 + +#if defined(__APPLE__) +# define SHLIB_EXT ".dylib" +#elif defined(_WIN32) +# define SHLIB_EXT ".dll" +#else +# define SHLIB_EXT ".so" +#endif + +static char bundle_dir_name[TEST_PATH_MAX + sizeof("/.lv2/lilv-test.lv2")]; +static char bundle_dir_uri[sizeof(bundle_dir_name) + sizeof("file:///")]; +static char manifest_name[sizeof(bundle_dir_name) + sizeof("/manifest.ttl")]; +static char content_name[sizeof(bundle_dir_name) + sizeof("plugin.ttl")]; + +static LilvWorld* world; + +int test_count = 0; +int error_count = 0; + +static void +delete_bundle(void) +{ + unlink(content_name); + unlink(manifest_name); + remove(bundle_dir_name); +} + +static void +init_tests(void) +{ + snprintf(bundle_dir_name, strlen(bundle_dir_name), "%s/.lv2/lilv-test.lv2", + getenv("HOME")); + lilv_mkdir_p(bundle_dir_name); + + snprintf(bundle_dir_uri, sizeof(bundle_dir_uri), "file://%s/", + bundle_dir_name); + snprintf(manifest_name, sizeof(manifest_name), "%s/manifest.ttl", + bundle_dir_name); + snprintf(content_name, sizeof(content_name), "%s/plugin.ttl", + bundle_dir_name); + + delete_bundle(); +} + +static void +fatal_error(const char* err, const char* arg) +{ + /* TODO: possibly change to vfprintf later */ + fprintf(stderr, err, arg); + /* IMHO, the bundle should be left in place after an error, for possible investigation */ + /* delete_bundle(); */ + exit(1); +} + +static void +write_file(const char* name, const char* content) +{ + FILE* f = fopen(name, "w"); + size_t len = strlen(content); + if (fwrite(content, 1, len, f) != len) { + fatal_error("Cannot write file %s\n", name); + } + fclose(f); +} + +static int +init_world(void) +{ + world = lilv_world_new(); + return world != NULL; +} + +static int +load_all_bundles(void) +{ + if (!init_world()) { + return 0; + } + lilv_world_load_all(world); + return 1; +} + +static void +create_bundle(const char* manifest, const char* content) +{ + if (mkdir(bundle_dir_name, 0700) && errno != EEXIST) { + fatal_error("Cannot create directory %s\n", bundle_dir_name); + } + write_file(manifest_name, manifest); + write_file(content_name, content); +} + +static int +start_bundle(const char* manifest, const char* content) +{ + create_bundle(manifest, content); + return load_all_bundles(); +} + +static void +unload_bundle(void) +{ + if (world) { + lilv_world_free(world); + } + world = NULL; +} + +static void +cleanup(void) +{ + delete_bundle(); +} + +/*****************************************************************************/ + +#define TEST_CASE(name) { #name, test_##name } +#define TEST_ASSERT(check) do {\ + test_count++;\ + if (!(check)) {\ + error_count++;\ + fprintf(stderr, "lilv_test.c:%d: error: test `%s' failed\n", __LINE__, #check);\ + abort();\ + }\ +} while (0) + +typedef int (*TestFunc)(void); + +struct TestCase { + const char* title; + TestFunc func; +}; + +#define PREFIX_ATOM "@prefix atom: <http://lv2plug.in/ns/ext/atom#> . \n" +#define PREFIX_LINE "@prefix : <http://example.org/> .\n" +#define PREFIX_LV2 "@prefix lv2: <http://lv2plug.in/ns/lv2core#> .\n" +#define PREFIX_LV2EV "@prefix lv2ev: <http://lv2plug.in/ns/ext/event#> . \n" +#define PREFIX_LV2UI "@prefix lv2ui: <http://lv2plug.in/ns/extensions/ui#> .\n" +#define PREFIX_RDF "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n" +#define PREFIX_RDFS "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n" +#define PREFIX_FOAF "@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n" +#define PREFIX_DOAP "@prefix doap: <http://usefulinc.com/ns/doap#> .\n" +#define PREFIX_PSET "@prefix pset: <http://lv2plug.in/ns/ext/presets#> .\n" + +#define MANIFEST_PREFIXES PREFIX_LINE PREFIX_LV2 PREFIX_RDFS +#define BUNDLE_PREFIXES PREFIX_ATOM PREFIX_LINE PREFIX_LV2 PREFIX_RDF PREFIX_RDFS PREFIX_FOAF PREFIX_DOAP PREFIX_PSET +#define PLUGIN_NAME(name) "doap:name \"" name "\"" +#define LICENSE_GPL "doap:license <http://usefulinc.com/doap/licenses/gpl>" + +static const char* uris_plugin = "http://example.org/plug"; +static LilvNode* plugin_uri_value; +static LilvNode* plugin2_uri_value; + +/*****************************************************************************/ + +static void +init_uris(void) +{ + plugin_uri_value = lilv_new_uri(world, uris_plugin); + plugin2_uri_value = lilv_new_uri(world, "http://example.org/foobar"); + TEST_ASSERT(plugin_uri_value); + TEST_ASSERT(plugin2_uri_value); +} + +static void +cleanup_uris(void) +{ + lilv_node_free(plugin2_uri_value); + lilv_node_free(plugin_uri_value); + plugin2_uri_value = NULL; + plugin_uri_value = NULL; +} + +/*****************************************************************************/ + +static int +test_value(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"Foo\" ; " + "] .")) { + return 0; + } + + init_uris(); + + LilvNode* uval = lilv_new_uri(world, "http://example.org"); + LilvNode* sval = lilv_new_string(world, "Foo"); + LilvNode* ival = lilv_new_int(world, 42); + LilvNode* fval = lilv_new_float(world, 1.6180); + + TEST_ASSERT(lilv_node_is_uri(uval)); + TEST_ASSERT(lilv_node_is_string(sval)); + TEST_ASSERT(lilv_node_is_int(ival)); + TEST_ASSERT(lilv_node_is_float(fval)); + + TEST_ASSERT(!lilv_node_is_literal(NULL)); + TEST_ASSERT(!lilv_node_is_literal(uval)); + TEST_ASSERT(lilv_node_is_literal(sval)); + TEST_ASSERT(lilv_node_is_literal(ival)); + TEST_ASSERT(lilv_node_is_literal(fval)); + TEST_ASSERT(!lilv_node_get_path(fval, NULL)); + + TEST_ASSERT(!strcmp(lilv_node_as_uri(uval), "http://example.org")); + TEST_ASSERT(!strcmp(lilv_node_as_string(sval), "Foo")); + TEST_ASSERT(lilv_node_as_int(ival) == 42); + TEST_ASSERT(fabs(lilv_node_as_float(fval) - 1.6180) < FLT_EPSILON); + TEST_ASSERT(isnan(lilv_node_as_float(sval))); + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +#elif __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + TEST_ASSERT(!strcmp(lilv_uri_to_path("file:///foo"), "/foo")); + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic pop +#endif + + LilvNode* loc_abs = lilv_new_file_uri(world, NULL, "/foo/bar"); + LilvNode* loc_rel = lilv_new_file_uri(world, NULL, "foo"); + LilvNode* host_abs = lilv_new_file_uri(world, "host", "/foo/bar"); + LilvNode* host_rel = lilv_new_file_uri(world, "host", "foo"); + + TEST_ASSERT(!strcmp(lilv_node_as_uri(loc_abs), "file:///foo/bar")); + TEST_ASSERT(!strncmp(lilv_node_as_uri(loc_rel), "file:///", 8)); + TEST_ASSERT(!strcmp(lilv_node_as_uri(host_abs), "file://host/foo/bar")); + TEST_ASSERT(!strncmp(lilv_node_as_uri(host_rel), "file://host/", 12)); + + lilv_node_free(host_rel); + lilv_node_free(host_abs); + lilv_node_free(loc_rel); + lilv_node_free(loc_abs); + + char* tok = lilv_node_get_turtle_token(uval); + TEST_ASSERT(!strcmp(tok, "<http://example.org>")); + lilv_free(tok); + tok = lilv_node_get_turtle_token(sval); + TEST_ASSERT(!strcmp(tok, "Foo")); + lilv_free(tok); + tok = lilv_node_get_turtle_token(ival); + TEST_ASSERT(!strcmp(tok, "42")); + lilv_free(tok); + tok = lilv_node_get_turtle_token(fval); + TEST_ASSERT(!strncmp(tok, "1.6180", 6)); + lilv_free(tok); + + LilvNode* uval_e = lilv_new_uri(world, "http://example.org"); + LilvNode* sval_e = lilv_new_string(world, "Foo"); + LilvNode* ival_e = lilv_new_int(world, 42); + LilvNode* fval_e = lilv_new_float(world, 1.6180); + LilvNode* uval_ne = lilv_new_uri(world, "http://no-example.org"); + LilvNode* sval_ne = lilv_new_string(world, "Bar"); + LilvNode* ival_ne = lilv_new_int(world, 24); + LilvNode* fval_ne = lilv_new_float(world, 3.14159); + + TEST_ASSERT(lilv_node_equals(uval, uval_e)); + TEST_ASSERT(lilv_node_equals(sval, sval_e)); + TEST_ASSERT(lilv_node_equals(ival, ival_e)); + TEST_ASSERT(lilv_node_equals(fval, fval_e)); + + TEST_ASSERT(!lilv_node_equals(uval, uval_ne)); + TEST_ASSERT(!lilv_node_equals(sval, sval_ne)); + TEST_ASSERT(!lilv_node_equals(ival, ival_ne)); + TEST_ASSERT(!lilv_node_equals(fval, fval_ne)); + + TEST_ASSERT(!lilv_node_equals(uval, sval)); + TEST_ASSERT(!lilv_node_equals(sval, ival)); + TEST_ASSERT(!lilv_node_equals(ival, fval)); + + LilvNode* uval_dup = lilv_node_duplicate(uval); + TEST_ASSERT(lilv_node_equals(uval, uval_dup)); + + LilvNode* ifval = lilv_new_float(world, 42.0); + TEST_ASSERT(!lilv_node_equals(ival, ifval)); + lilv_node_free(ifval); + + LilvNode* nil = NULL; + TEST_ASSERT(!lilv_node_equals(uval, nil)); + TEST_ASSERT(!lilv_node_equals(nil, uval)); + TEST_ASSERT(lilv_node_equals(nil, nil)); + + LilvNode* nil2 = lilv_node_duplicate(nil); + TEST_ASSERT(lilv_node_equals(nil, nil2)); + + lilv_node_free(uval); + lilv_node_free(sval); + lilv_node_free(ival); + lilv_node_free(fval); + lilv_node_free(uval_e); + lilv_node_free(sval_e); + lilv_node_free(ival_e); + lilv_node_free(fval_e); + lilv_node_free(uval_ne); + lilv_node_free(sval_ne); + lilv_node_free(ival_ne); + lilv_node_free(fval_ne); + lilv_node_free(uval_dup); + lilv_node_free(nil2); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_util(void) +{ + TEST_ASSERT(!lilv_realpath(NULL)); + + char a_path[16]; + char b_path[16]; + strncpy(a_path, "copy_a_XXXXXX", sizeof(a_path)); + strncpy(b_path, "copy_b_XXXXXX", sizeof(b_path)); + mkstemp(a_path); + mkstemp(b_path); + + FILE* fa = fopen(a_path, "w"); + FILE* fb = fopen(b_path, "w"); + fprintf(fa, "AA\n"); + fprintf(fb, "AB\n"); + fclose(fa); + fclose(fb); + + TEST_ASSERT(lilv_copy_file("does/not/exist", "copy")); + TEST_ASSERT(lilv_copy_file(a_path, "not/a/dir/copy")); + TEST_ASSERT(!lilv_copy_file(a_path, "copy_c")); + TEST_ASSERT(!lilv_file_equals(a_path, b_path)); + TEST_ASSERT(lilv_file_equals(a_path, a_path)); + TEST_ASSERT(lilv_file_equals(a_path, "copy_c")); + TEST_ASSERT(!lilv_file_equals("does/not/exist", b_path)); + TEST_ASSERT(!lilv_file_equals(a_path, "does/not/exist")); + TEST_ASSERT(!lilv_file_equals("does/not/exist", "/does/not/either")); + return 1; +} + +/*****************************************************************************/ + +static int discovery_plugin_found = 0; + +static void +discovery_verify_plugin(const LilvPlugin* plugin) +{ + const LilvNode* value = lilv_plugin_get_uri(plugin); + if (lilv_node_equals(value, plugin_uri_value)) { + const LilvNode* lib_uri = NULL; + TEST_ASSERT(!lilv_node_equals(value, plugin2_uri_value)); + discovery_plugin_found = 1; + lib_uri = lilv_plugin_get_library_uri(plugin); + TEST_ASSERT(lib_uri); + TEST_ASSERT(lilv_node_is_uri(lib_uri)); + TEST_ASSERT(lilv_node_as_uri(lib_uri)); + TEST_ASSERT(strstr(lilv_node_as_uri(lib_uri), "foo" SHLIB_EXT)); + TEST_ASSERT(lilv_plugin_verify(plugin)); + } +} + +static int +test_discovery(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ;" + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "lv2:port [ a lv2:ControlPort ; a lv2:InputPort ;" + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; ] .")) { + return 0; + } + + init_uris(); + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + TEST_ASSERT(lilv_plugins_size(plugins) > 0); + + const LilvPlugin* explug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(explug != NULL); + const LilvPlugin* explug2 = lilv_plugins_get_by_uri(plugins, plugin2_uri_value); + TEST_ASSERT(explug2 == NULL); + + if (explug) { + LilvNode* name = lilv_plugin_get_name(explug); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "Test plugin")); + lilv_node_free(name); + } + + discovery_plugin_found = 0; + LILV_FOREACH(plugins, i, plugins) + discovery_verify_plugin(lilv_plugins_get(plugins, i)); + + TEST_ASSERT(discovery_plugin_found); + plugins = NULL; + + cleanup_uris(); + + return 1; +} + +/*****************************************************************************/ + +static int +test_lv2_path(void) +{ +#ifndef _WIN32 + char* orig_lv2_path = lilv_strdup(getenv("LV2_PATH")); + + setenv("LV2_PATH", "~/.lv2:/usr/local/lib/lv2:/usr/lib/lv2", 1); + + world = lilv_world_new(); + lilv_world_load_all(world); + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const size_t n_plugins = lilv_plugins_size(plugins); + + lilv_world_free(world); + + setenv("LV2_PATH", "$HOME/.lv2:/usr/local/lib/lv2:/usr/lib/lv2", 1); + world = lilv_world_new(); + lilv_world_load_all(world); + plugins = lilv_world_get_all_plugins(world); + TEST_ASSERT(lilv_plugins_size(plugins) == n_plugins); + lilv_world_free(world); + world = NULL; + + if (orig_lv2_path) { + setenv("LV2_PATH", orig_lv2_path, 1); + } else { + unsetenv("LV2_PATH"); + } + free(orig_lv2_path); +#endif + return 1; +} + +/*****************************************************************************/ + +static int +test_verify(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "lv2:port [ a lv2:ControlPort ; a lv2:InputPort ;" + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ] .")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* explug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(explug); + TEST_ASSERT(lilv_plugin_verify(explug)); + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_no_verify(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin . ")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* explug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(explug); + TEST_ASSERT(!lilv_plugin_verify(explug)); + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_classes(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"Foo\" ; " + "] .")) { + return 0; + } + + init_uris(); + const LilvPluginClass* plugin = lilv_world_get_plugin_class(world); + const LilvPluginClasses* classes = lilv_world_get_plugin_classes(world); + LilvPluginClasses* children = lilv_plugin_class_get_children(plugin); + + TEST_ASSERT(lilv_plugin_class_get_parent_uri(plugin) == NULL); + TEST_ASSERT(lilv_plugin_classes_size(classes) > lilv_plugin_classes_size(children)); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_plugin_class_get_label(plugin)), "Plugin")); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_plugin_class_get_uri(plugin)), + "http://lv2plug.in/ns/lv2core#Plugin")); + + LILV_FOREACH(plugin_classes, i, children) { + TEST_ASSERT(lilv_node_equals( + lilv_plugin_class_get_parent_uri(lilv_plugin_classes_get(children, i)), + lilv_plugin_class_get_uri(plugin))); + } + + LilvNode* some_uri = lilv_new_uri(world, "http://example.org/whatever"); + TEST_ASSERT(lilv_plugin_classes_get_by_uri(classes, some_uri) == NULL); + lilv_node_free(some_uri); + + lilv_plugin_classes_free(children); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_plugin(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "lv2:optionalFeature lv2:hardRTCapable ; " + "lv2:requiredFeature <http://lv2plug.in/ns/ext/event> ; " + "lv2:extensionData <http://example.org/extdata> ;" + ":foo 1.6180 ; " + ":bar true ; " + ":baz false ; " + ":blank [ a <http://example.org/blank> ] ; " + "doap:maintainer [ foaf:name \"David Robillard\" ; " + " foaf:homepage <http://drobilla.net> ; foaf:mbox <mailto:d@drobilla.net> ] ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 " + "] , [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"bar\" ; lv2:name \"Baz\" ; " + " lv2:minimum -2.0 ; lv2:maximum 2.0 ; lv2:default 1.0 " + "] , [ " + " a lv2:ControlPort ; a lv2:OutputPort ; " + " lv2:index 2 ; lv2:symbol \"latency\" ; lv2:name \"Latency\" ; " + " lv2:portProperty lv2:reportsLatency ; " + " lv2:designation lv2:latency " + "] . \n" + ":thing doap:name \"Something else\" .\n")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + const LilvPluginClass* klass = lilv_plugin_get_class(plug); + const LilvNode* klass_uri = lilv_plugin_class_get_uri(klass); + TEST_ASSERT(!strcmp(lilv_node_as_string(klass_uri), + "http://lv2plug.in/ns/lv2core#CompressorPlugin")); + + LilvNode* rdf_type = lilv_new_uri( + world, "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"); + TEST_ASSERT(lilv_world_ask(world, + lilv_plugin_get_uri(plug), + rdf_type, + klass_uri)); + lilv_node_free(rdf_type); + + TEST_ASSERT(!lilv_plugin_is_replaced(plug)); + TEST_ASSERT(!lilv_plugin_get_related(plug, NULL)); + + const LilvNode* plug_bundle_uri = lilv_plugin_get_bundle_uri(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(plug_bundle_uri), bundle_dir_uri)); + + const LilvNodes* data_uris = lilv_plugin_get_data_uris(plug); + TEST_ASSERT(lilv_nodes_size(data_uris) == 2); + + LilvNode* project = lilv_plugin_get_project(plug); + TEST_ASSERT(!project); + + char* manifest_uri = (char*)malloc(TEST_PATH_MAX); + char* data_uri = (char*)malloc(TEST_PATH_MAX); + snprintf(manifest_uri, TEST_PATH_MAX, "%s%s", + lilv_node_as_string(plug_bundle_uri), "manifest.ttl"); + snprintf(data_uri, TEST_PATH_MAX, "%s%s", + lilv_node_as_string(plug_bundle_uri), "plugin.ttl"); + + LilvNode* manifest_uri_val = lilv_new_uri(world, manifest_uri); + TEST_ASSERT(lilv_nodes_contains(data_uris, manifest_uri_val)); + lilv_node_free(manifest_uri_val); + + LilvNode* data_uri_val = lilv_new_uri(world, data_uri); + TEST_ASSERT(lilv_nodes_contains(data_uris, data_uri_val)); + lilv_node_free(data_uri_val); + + LilvNode* unknown_uri_val = lilv_new_uri(world, "http://example.org/unknown"); + TEST_ASSERT(!lilv_nodes_contains(data_uris, unknown_uri_val)); + lilv_node_free(unknown_uri_val); + + free(manifest_uri); + free(data_uri); + + float mins[3]; + float maxs[3]; + float defs[3]; + lilv_plugin_get_port_ranges_float(plug, mins, maxs, defs); + TEST_ASSERT(mins[0] == -1.0f); + TEST_ASSERT(maxs[0] == 1.0f); + TEST_ASSERT(defs[0] == 0.5f); + + LilvNode* audio_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#AudioPort"); + LilvNode* control_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#ControlPort"); + LilvNode* in_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#InputPort"); + LilvNode* out_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#OutputPort"); + + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, control_class, NULL) == 3); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, audio_class, NULL) == 0); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, in_class, NULL) == 2); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, out_class, NULL) == 1); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, control_class, in_class, NULL) == 2); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, control_class, out_class, NULL) == 1); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, audio_class, in_class, NULL) == 0); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, audio_class, out_class, NULL) == 0); + + TEST_ASSERT(lilv_plugin_has_latency(plug)); + TEST_ASSERT(lilv_plugin_get_latency_port_index(plug) == 2); + + LilvNode* lv2_latency = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#latency"); + const LilvPort* latency_port = lilv_plugin_get_port_by_designation( + plug, out_class, lv2_latency); + lilv_node_free(lv2_latency); + + TEST_ASSERT(latency_port); + TEST_ASSERT(lilv_port_get_index(plug, latency_port) == 2); + TEST_ASSERT(lilv_node_is_blank(lilv_port_get_node(plug, latency_port))); + + LilvNode* rt_feature = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#hardRTCapable"); + LilvNode* event_feature = lilv_new_uri(world, + "http://lv2plug.in/ns/ext/event"); + LilvNode* pretend_feature = lilv_new_uri(world, + "http://example.org/solvesWorldHunger"); + + TEST_ASSERT(lilv_plugin_has_feature(plug, rt_feature)); + TEST_ASSERT(lilv_plugin_has_feature(plug, event_feature)); + TEST_ASSERT(!lilv_plugin_has_feature(plug, pretend_feature)); + + lilv_node_free(rt_feature); + lilv_node_free(event_feature); + lilv_node_free(pretend_feature); + + LilvNodes* supported = lilv_plugin_get_supported_features(plug); + LilvNodes* required = lilv_plugin_get_required_features(plug); + LilvNodes* optional = lilv_plugin_get_optional_features(plug); + TEST_ASSERT(lilv_nodes_size(supported) == 2); + TEST_ASSERT(lilv_nodes_size(required) == 1); + TEST_ASSERT(lilv_nodes_size(optional) == 1); + lilv_nodes_free(supported); + lilv_nodes_free(required); + lilv_nodes_free(optional); + + LilvNode* foo_p = lilv_new_uri(world, "http://example.org/foo"); + LilvNodes* foos = lilv_plugin_get_value(plug, foo_p); + TEST_ASSERT(lilv_nodes_size(foos) == 1); + TEST_ASSERT(fabs(lilv_node_as_float(lilv_nodes_get_first(foos)) - 1.6180) < FLT_EPSILON); + lilv_node_free(foo_p); + lilv_nodes_free(foos); + + LilvNode* bar_p = lilv_new_uri(world, "http://example.org/bar"); + LilvNodes* bars = lilv_plugin_get_value(plug, bar_p); + TEST_ASSERT(lilv_nodes_size(bars) == 1); + TEST_ASSERT(lilv_node_as_bool(lilv_nodes_get_first(bars)) == true); + lilv_node_free(bar_p); + lilv_nodes_free(bars); + + LilvNode* baz_p = lilv_new_uri(world, "http://example.org/baz"); + LilvNodes* bazs = lilv_plugin_get_value(plug, baz_p); + TEST_ASSERT(lilv_nodes_size(bazs) == 1); + TEST_ASSERT(lilv_node_as_bool(lilv_nodes_get_first(bazs)) == false); + lilv_node_free(baz_p); + lilv_nodes_free(bazs); + + LilvNode* blank_p = lilv_new_uri(world, "http://example.org/blank"); + LilvNodes* blanks = lilv_plugin_get_value(plug, blank_p); + TEST_ASSERT(lilv_nodes_size(blanks) == 1); + LilvNode* blank = lilv_nodes_get_first(blanks); + TEST_ASSERT(lilv_node_is_blank(blank)); + const char* blank_str = lilv_node_as_blank(blank); + char* blank_tok = lilv_node_get_turtle_token(blank); + TEST_ASSERT(!strncmp(blank_tok, "_:", 2)); + TEST_ASSERT(!strcmp(blank_tok + 2, blank_str)); + lilv_free(blank_tok); + lilv_node_free(blank_p); + lilv_nodes_free(blanks); + + LilvNode* author_name = lilv_plugin_get_author_name(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(author_name), "David Robillard")); + lilv_node_free(author_name); + + LilvNode* author_email = lilv_plugin_get_author_email(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(author_email), "mailto:d@drobilla.net")); + lilv_node_free(author_email); + + LilvNode* author_homepage = lilv_plugin_get_author_homepage(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(author_homepage), "http://drobilla.net")); + lilv_node_free(author_homepage); + + LilvNode* thing_uri = lilv_new_uri(world, "http://example.org/thing"); + LilvNode* name_p = lilv_new_uri(world, "http://usefulinc.com/ns/doap#name"); + LilvNodes* thing_names = lilv_world_find_nodes(world, thing_uri, name_p, NULL); + TEST_ASSERT(lilv_nodes_size(thing_names) == 1); + LilvNode* thing_name = lilv_nodes_get_first(thing_names); + TEST_ASSERT(thing_name); + TEST_ASSERT(lilv_node_is_string(thing_name)); + TEST_ASSERT(!strcmp(lilv_node_as_string(thing_name), "Something else")); + LilvNode* thing_name2 = lilv_world_get(world, thing_uri, name_p, NULL); + TEST_ASSERT(lilv_node_equals(thing_name, thing_name2)); + + LilvUIs* uis = lilv_plugin_get_uis(plug); + TEST_ASSERT(lilv_uis_size(uis) == 0); + lilv_uis_free(uis); + + LilvNode* extdata = lilv_new_uri(world, "http://example.org/extdata"); + LilvNode* noextdata = lilv_new_uri(world, "http://example.org/noextdata"); + LilvNodes* extdatas = lilv_plugin_get_extension_data(plug); + TEST_ASSERT(lilv_plugin_has_extension_data(plug, extdata)); + TEST_ASSERT(!lilv_plugin_has_extension_data(plug, noextdata)); + TEST_ASSERT(lilv_nodes_size(extdatas) == 1); + TEST_ASSERT(lilv_node_equals(lilv_nodes_get_first(extdatas), extdata)); + lilv_node_free(noextdata); + lilv_node_free(extdata); + lilv_nodes_free(extdatas); + + lilv_nodes_free(thing_names); + lilv_node_free(thing_uri); + lilv_node_free(thing_name2); + lilv_node_free(name_p); + lilv_node_free(control_class); + lilv_node_free(audio_class); + lilv_node_free(in_class); + lilv_node_free(out_class); + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_project(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin with project") " ; " + LICENSE_GPL " ; " + "lv2:project [ " + " doap:maintainer [ " + " foaf:name \"David Robillard\" ; " + " foaf:homepage <http://drobilla.net> ; foaf:mbox <mailto:d@drobilla.net> ] ; " + "] ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 " + "] , [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"bar\" ; lv2:name \"Baz\" ; " + " lv2:minimum -2.0 ; lv2:maximum 2.0 ; lv2:default 1.0 " + "] , [ " + " a lv2:ControlPort ; a lv2:OutputPort ; " + " lv2:index 2 ; lv2:symbol \"latency\" ; lv2:name \"Latency\" ; " + " lv2:portProperty lv2:reportsLatency ; " + " lv2:designation lv2:latency " + "] . \n" + ":thing doap:name \"Something else\" .\n")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + LilvNode* author_name = lilv_plugin_get_author_name(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(author_name), "David Robillard")); + lilv_node_free(author_name); + + LilvNode* author_email = lilv_plugin_get_author_email(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(author_email), "mailto:d@drobilla.net")); + lilv_node_free(author_email); + + LilvNode* author_homepage = lilv_plugin_get_author_homepage(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(author_homepage), "http://drobilla.net")); + lilv_node_free(author_homepage); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_no_author(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin with project") " ; " + LICENSE_GPL " ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 " + "] , [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"bar\" ; lv2:name \"Baz\" ; " + " lv2:minimum -2.0 ; lv2:maximum 2.0 ; lv2:default 1.0 " + "] , [ " + " a lv2:ControlPort ; a lv2:OutputPort ; " + " lv2:index 2 ; lv2:symbol \"latency\" ; lv2:name \"Latency\" ; " + " lv2:portProperty lv2:reportsLatency ; " + " lv2:designation lv2:latency " + "] . \n" + ":thing doap:name \"Something else\" .\n")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + LilvNode* author_name = lilv_plugin_get_author_name(plug); + TEST_ASSERT(!author_name); + + LilvNode* author_email = lilv_plugin_get_author_email(plug); + TEST_ASSERT(!author_email); + + LilvNode* author_homepage = lilv_plugin_get_author_homepage(plug); + TEST_ASSERT(!author_homepage); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_project_no_author(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin with project") " ; " + LICENSE_GPL " ; " + "lv2:project [ " + " doap:name \"Fake project\" ;" + "] ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 " + "] , [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"bar\" ; lv2:name \"Baz\" ; " + " lv2:minimum -2.0 ; lv2:maximum 2.0 ; lv2:default 1.0 " + "] , [ " + " a lv2:ControlPort ; a lv2:OutputPort ; " + " lv2:index 2 ; lv2:symbol \"latency\" ; lv2:name \"Latency\" ; " + " lv2:portProperty lv2:reportsLatency ; " + " lv2:designation lv2:latency " + "] . \n" + ":thing doap:name \"Something else\" .\n")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + LilvNode* author_name = lilv_plugin_get_author_name(plug); + TEST_ASSERT(!author_name); + + LilvNode* author_email = lilv_plugin_get_author_email(plug); + TEST_ASSERT(!author_email); + + LilvNode* author_homepage = lilv_plugin_get_author_homepage(plug); + TEST_ASSERT(!author_homepage); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_preset(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin with project") " ; " + LICENSE_GPL " ; " + "lv2:project [ " + " doap:name \"Fake project\" ;" + "] ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 " + "] , [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"bar\" ; lv2:name \"Baz\" ; " + " lv2:minimum -2.0 ; lv2:maximum 2.0 ; lv2:default 1.0 " + "] , [ " + " a lv2:ControlPort ; a lv2:OutputPort ; " + " lv2:index 2 ; lv2:symbol \"latency\" ; lv2:name \"Latency\" ; " + " lv2:portProperty lv2:reportsLatency ; " + " lv2:designation lv2:latency " + "] . \n" + "<http://example.org/preset> a pset:Preset ;" + " lv2:appliesTo :plug ;" + " rdfs:label \"some preset\" .\n")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + LilvNode* pset_Preset = lilv_new_uri(world, LV2_PRESETS__Preset); + LilvNodes* related = lilv_plugin_get_related(plug, pset_Preset); + + TEST_ASSERT(lilv_nodes_size(related) == 1); + + lilv_node_free(pset_Preset); + lilv_nodes_free(related); + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_prototype(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":prot a lv2:PluginBase ; rdfs:seeAlso <plugin.ttl> .\n" + ":plug a lv2:Plugin ; lv2:binary <inst" SHLIB_EXT "> ; lv2:prototype :prot .\n", + BUNDLE_PREFIXES + ":prot a lv2:Plugin ; a lv2:CompressorPlugin ; " + LICENSE_GPL " ; " + "lv2:project [ " + " doap:name \"Fake project\" ;" + "] ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 " + "] , [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"bar\" ; lv2:name \"Baz\" ; " + " lv2:minimum -2.0 ; lv2:maximum 2.0 ; lv2:default 1.0 " + "] , [ " + " a lv2:ControlPort ; a lv2:OutputPort ; " + " lv2:index 2 ; lv2:symbol \"latency\" ; lv2:name \"Latency\" ; " + " lv2:portProperty lv2:reportsLatency ; " + " lv2:designation lv2:latency " + "] . \n" + ":plug doap:name \"Instance\" .\n")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + // Test non-inherited property + LilvNode* name = lilv_plugin_get_name(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "Instance")); + lilv_node_free(name); + + // Test inherited property + const LilvNode* binary = lilv_plugin_get_library_uri(plug); + TEST_ASSERT(strstr(lilv_node_as_string(binary), "inst" SHLIB_EXT)); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_port(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES PREFIX_LV2EV + ":plug a lv2:Plugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "doap:homepage <http://example.org/someplug> ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; " + " lv2:name \"store\" ; " + " lv2:name \"dépanneur\"@fr-ca ; lv2:name \"épicerie\"@fr-fr ; " + " lv2:name \"tienda\"@es ; " + " rdfs:comment \"comment\"@en , \"commentaires\"@fr ; " + " lv2:portProperty lv2:integer ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 ; " + " lv2:scalePoint [ rdfs:label \"Sin\"; rdf:value 3 ] ; " + " lv2:scalePoint [ rdfs:label \"Cos\"; rdf:value 4 ] " + "] , [\n" + " a lv2:EventPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"event_in\" ; " + " lv2:name \"Event Input\" ; " + " lv2ev:supportsEvent <http://example.org/event> ;" + " atom:supports <http://example.org/atomEvent> " + "] , [\n" + " a lv2:AudioPort ; a lv2:InputPort ; " + " lv2:index 2 ; lv2:symbol \"audio_in\" ; " + " lv2:name \"Audio Input\" ; " + "] , [\n" + " a lv2:AudioPort ; a lv2:OutputPort ; " + " lv2:index 3 ; lv2:symbol \"audio_out\" ; " + " lv2:name \"Audio Output\" ; " + "] .")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + LilvNode* psym = lilv_new_string(world, "foo"); + const LilvPort* p = lilv_plugin_get_port_by_index(plug, 0); + const LilvPort* p2 = lilv_plugin_get_port_by_symbol(plug, psym); + lilv_node_free(psym); + TEST_ASSERT(p != NULL); + TEST_ASSERT(p2 != NULL); + TEST_ASSERT(p == p2); + + LilvNode* nopsym = lilv_new_string(world, "thisaintnoportfoo"); + const LilvPort* p3 = lilv_plugin_get_port_by_symbol(plug, nopsym); + TEST_ASSERT(p3 == NULL); + lilv_node_free(nopsym); + + // Try getting an invalid property + LilvNode* num = lilv_new_int(world, 1); + LilvNodes* nothing = lilv_port_get_value(plug, p, num); + TEST_ASSERT(!nothing); + lilv_node_free(num); + + LilvNode* audio_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#AudioPort"); + LilvNode* control_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#ControlPort"); + LilvNode* in_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#InputPort"); + LilvNode* out_class = lilv_new_uri(world, + "http://lv2plug.in/ns/lv2core#OutputPort"); + + TEST_ASSERT(lilv_nodes_size(lilv_port_get_classes(plug, p)) == 2); + TEST_ASSERT(lilv_plugin_get_num_ports(plug) == 4); + TEST_ASSERT(lilv_port_is_a(plug, p, control_class)); + TEST_ASSERT(lilv_port_is_a(plug, p, in_class)); + TEST_ASSERT(!lilv_port_is_a(plug, p, audio_class)); + + LilvNodes* port_properties = lilv_port_get_properties(plug, p); + TEST_ASSERT(lilv_nodes_size(port_properties) == 1); + lilv_nodes_free(port_properties); + + // Untranslated name (current locale is set to "C" in main) + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_port_get_symbol(plug, p)), "foo")); + LilvNode* name = lilv_port_get_name(plug, p); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "store")); + lilv_node_free(name); + + // Exact language match + setenv("LANG", "fr_FR", 1); + name = lilv_port_get_name(plug, p); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "épicerie")); + lilv_node_free(name); + + // Exact language match (with charset suffix) + setenv("LANG", "fr_CA.utf8", 1); + name = lilv_port_get_name(plug, p); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "dépanneur")); + lilv_node_free(name); + + // Partial language match (choose value translated for different country) + setenv("LANG", "fr_BE", 1); + name = lilv_port_get_name(plug, p); + TEST_ASSERT((!strcmp(lilv_node_as_string(name), "dépanneur")) + ||(!strcmp(lilv_node_as_string(name), "épicerie"))); + lilv_node_free(name); + + // Partial language match (choose country-less language tagged value) + setenv("LANG", "es_MX", 1); + name = lilv_port_get_name(plug, p); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "tienda")); + lilv_node_free(name); + + // No language match (choose untranslated value) + setenv("LANG", "cn", 1); + name = lilv_port_get_name(plug, p); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "store")); + lilv_node_free(name); + + // Invalid language + setenv("LANG", "1!", 1); + name = lilv_port_get_name(plug, p); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "store")); + lilv_node_free(name); + + setenv("LANG", "en_CA.utf-8", 1); + + // Language tagged value with no untranslated values + LilvNode* rdfs_comment = lilv_new_uri(world, LILV_NS_RDFS "comment"); + LilvNodes* comments = lilv_port_get_value(plug, p, rdfs_comment); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_nodes_get_first(comments)), + "comment")); + LilvNode* comment = lilv_port_get(plug, p, rdfs_comment); + TEST_ASSERT(!strcmp(lilv_node_as_string(comment), "comment")); + lilv_node_free(comment); + lilv_nodes_free(comments); + + setenv("LANG", "fr", 1); + + comments = lilv_port_get_value(plug, p, rdfs_comment); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_nodes_get_first(comments)), + "commentaires")); + lilv_nodes_free(comments); + + setenv("LANG", "cn", 1); + + comments = lilv_port_get_value(plug, p, rdfs_comment); + TEST_ASSERT(!comments); + lilv_nodes_free(comments); + + lilv_node_free(rdfs_comment); + + setenv("LANG", "C", 1); // Reset locale + + LilvScalePoints* points = lilv_port_get_scale_points(plug, p); + TEST_ASSERT(lilv_scale_points_size(points) == 2); + + LilvIter* sp_iter = lilv_scale_points_begin(points); + const LilvScalePoint* sp0 = lilv_scale_points_get(points, sp_iter); + TEST_ASSERT(sp0); + sp_iter = lilv_scale_points_next(points, sp_iter); + const LilvScalePoint* sp1 = lilv_scale_points_get(points, sp_iter); + TEST_ASSERT(sp1); + + TEST_ASSERT( + ((!strcmp(lilv_node_as_string(lilv_scale_point_get_label(sp0)), "Sin") + && lilv_node_as_float(lilv_scale_point_get_value(sp0)) == 3) + && + (!strcmp(lilv_node_as_string(lilv_scale_point_get_label(sp1)), "Cos") + && lilv_node_as_float(lilv_scale_point_get_value(sp1)) == 4)) + || + ((!strcmp(lilv_node_as_string(lilv_scale_point_get_label(sp0)), "Cos") + && lilv_node_as_float(lilv_scale_point_get_value(sp0)) == 4) + && + (!strcmp(lilv_node_as_string(lilv_scale_point_get_label(sp1)), "Sin") + && lilv_node_as_float(lilv_scale_point_get_value(sp1)) == 3))); + + LilvNode* homepage_p = lilv_new_uri(world, "http://usefulinc.com/ns/doap#homepage"); + LilvNodes* homepages = lilv_plugin_get_value(plug, homepage_p); + TEST_ASSERT(lilv_nodes_size(homepages) == 1); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_nodes_get_first(homepages)), + "http://example.org/someplug")); + + LilvNode *min, *max, *def; + lilv_port_get_range(plug, p, &def, &min, &max); + TEST_ASSERT(def); + TEST_ASSERT(min); + TEST_ASSERT(max); + TEST_ASSERT(lilv_node_as_float(def) == 0.5); + TEST_ASSERT(lilv_node_as_float(min) == -1.0); + TEST_ASSERT(lilv_node_as_float(max) == 1.0); + + LilvNode* integer_prop = lilv_new_uri(world, "http://lv2plug.in/ns/lv2core#integer"); + LilvNode* toggled_prop = lilv_new_uri(world, "http://lv2plug.in/ns/lv2core#toggled"); + + TEST_ASSERT(lilv_port_has_property(plug, p, integer_prop)); + TEST_ASSERT(!lilv_port_has_property(plug, p, toggled_prop)); + + const LilvPort* ep = lilv_plugin_get_port_by_index(plug, 1); + + LilvNode* event_type = lilv_new_uri(world, "http://example.org/event"); + LilvNode* event_type_2 = lilv_new_uri(world, "http://example.org/otherEvent"); + LilvNode* atom_event = lilv_new_uri(world, "http://example.org/atomEvent"); + TEST_ASSERT(lilv_port_supports_event(plug, ep, event_type)); + TEST_ASSERT(!lilv_port_supports_event(plug, ep, event_type_2)); + TEST_ASSERT(lilv_port_supports_event(plug, ep, atom_event)); + + LilvNode* name_p = lilv_new_uri(world, "http://lv2plug.in/ns/lv2core#name"); + LilvNodes* names = lilv_port_get_value(plug, p, name_p); + TEST_ASSERT(lilv_nodes_size(names) == 1); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_nodes_get_first(names)), + "store")); + lilv_nodes_free(names); + + LilvNode* true_val = lilv_new_bool(world, true); + LilvNode* false_val = lilv_new_bool(world, false); + + TEST_ASSERT(!lilv_node_equals(true_val, false_val)); + + lilv_world_set_option(world, LILV_OPTION_FILTER_LANG, false_val); + names = lilv_port_get_value(plug, p, name_p); + TEST_ASSERT(lilv_nodes_size(names) == 4); + lilv_nodes_free(names); + lilv_world_set_option(world, LILV_OPTION_FILTER_LANG, true_val); + + lilv_node_free(false_val); + lilv_node_free(true_val); + + names = lilv_port_get_value(plug, ep, name_p); + TEST_ASSERT(lilv_nodes_size(names) == 1); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_nodes_get_first(names)), + "Event Input")); + + const LilvPort* ap_in = lilv_plugin_get_port_by_index(plug, 2); + + TEST_ASSERT(lilv_port_is_a(plug, ap_in, in_class)); + TEST_ASSERT(!lilv_port_is_a(plug, ap_in, out_class)); + TEST_ASSERT(lilv_port_is_a(plug, ap_in, audio_class)); + TEST_ASSERT(!lilv_port_is_a(plug, ap_in, control_class)); + + const LilvPort* ap_out = lilv_plugin_get_port_by_index(plug, 3); + + TEST_ASSERT(lilv_port_is_a(plug, ap_out, out_class)); + TEST_ASSERT(!lilv_port_is_a(plug, ap_out, in_class)); + TEST_ASSERT(lilv_port_is_a(plug, ap_out, audio_class)); + TEST_ASSERT(!lilv_port_is_a(plug, ap_out, control_class)); + + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, control_class, in_class , NULL) == 1); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, audio_class , in_class , NULL) == 1); + TEST_ASSERT(lilv_plugin_get_num_ports_of_class(plug, audio_class , out_class, NULL) == 1); + + lilv_nodes_free(names); + lilv_node_free(name_p); + + lilv_node_free(integer_prop); + lilv_node_free(toggled_prop); + lilv_node_free(event_type); + lilv_node_free(event_type_2); + lilv_node_free(atom_event); + + lilv_node_free(min); + lilv_node_free(max); + lilv_node_free(def); + + lilv_node_free(homepage_p); + lilv_nodes_free(homepages); + + lilv_scale_points_free(points); + lilv_node_free(control_class); + lilv_node_free(audio_class); + lilv_node_free(out_class); + lilv_node_free(in_class); + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static unsigned +ui_supported(const char* container_type_uri, + const char* ui_type_uri) +{ + return !strcmp(container_type_uri, ui_type_uri); +} + +static int +test_ui(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES PREFIX_LV2UI + ":plug a lv2:Plugin ; a lv2:CompressorPlugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "lv2:optionalFeature lv2:hardRTCapable ; " + "lv2:requiredFeature <http://lv2plug.in/ns/ext/event> ; " + "lv2ui:ui :ui , :ui2 , :ui3 , :ui4 ; " + "doap:maintainer [ foaf:name \"David Robillard\" ; " + " foaf:homepage <http://drobilla.net> ; foaf:mbox <mailto:d@drobilla.net> ] ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"foo\" ; lv2:name \"bar\" ; " + " lv2:minimum -1.0 ; lv2:maximum 1.0 ; lv2:default 0.5 " + "] , [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 1 ; lv2:symbol \"bar\" ; lv2:name \"Baz\" ; " + " lv2:minimum -2.0 ; lv2:maximum 2.0 ; lv2:default 1.0 " + "] , [ " + " a lv2:ControlPort ; a lv2:OutputPort ; " + " lv2:index 2 ; lv2:symbol \"latency\" ; lv2:name \"Latency\" ; " + " lv2:portProperty lv2:reportsLatency " + "] .\n" + ":ui a lv2ui:GtkUI ; " + " lv2ui:requiredFeature lv2ui:makeResident ; " + " lv2ui:binary <ui" SHLIB_EXT "> ; " + " lv2ui:optionalFeature lv2ui:ext_presets . " + ":ui2 a lv2ui:GtkUI ; lv2ui:binary <ui2" SHLIB_EXT "> . " + ":ui3 a lv2ui:GtkUI ; lv2ui:binary <ui3" SHLIB_EXT "> . " + ":ui4 a lv2ui:GtkUI ; lv2ui:binary <ui4" SHLIB_EXT "> . ")) { + return 0; + } + + init_uris(); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + LilvUIs* uis = lilv_plugin_get_uis(plug); + TEST_ASSERT(lilv_uis_size(uis) == 4); + + const LilvUI* ui0 = lilv_uis_get(uis, lilv_uis_begin(uis)); + TEST_ASSERT(ui0); + + LilvNode* ui_uri = lilv_new_uri(world, "http://example.org/ui"); + LilvNode* ui2_uri = lilv_new_uri(world, "http://example.org/ui3"); + LilvNode* ui3_uri = lilv_new_uri(world, "http://example.org/ui4"); + LilvNode* noui_uri = lilv_new_uri(world, "http://example.org/notaui"); + + const LilvUI* ui0_2 = lilv_uis_get_by_uri(uis, ui_uri); + TEST_ASSERT(ui0 == ui0_2); + TEST_ASSERT(lilv_node_equals(lilv_ui_get_uri(ui0_2), ui_uri)); + + const LilvUI* ui2 = lilv_uis_get_by_uri(uis, ui2_uri); + TEST_ASSERT(ui2 != ui0); + + const LilvUI* ui3 = lilv_uis_get_by_uri(uis, ui3_uri); + TEST_ASSERT(ui3 != ui0); + + const LilvUI* noui = lilv_uis_get_by_uri(uis, noui_uri); + TEST_ASSERT(noui == NULL); + + const LilvNodes* classes = lilv_ui_get_classes(ui0); + TEST_ASSERT(lilv_nodes_size(classes) == 1); + + LilvNode* ui_class_uri = lilv_new_uri(world, + "http://lv2plug.in/ns/extensions/ui#GtkUI"); + + LilvNode* unknown_ui_class_uri = lilv_new_uri(world, + "http://example.org/mysteryUI"); + + TEST_ASSERT(lilv_node_equals(lilv_nodes_get_first(classes), ui_class_uri)); + TEST_ASSERT(lilv_ui_is_a(ui0, ui_class_uri)); + + const LilvNode* ui_type = NULL; + TEST_ASSERT(lilv_ui_is_supported(ui0, ui_supported, ui_class_uri, &ui_type)); + TEST_ASSERT(!lilv_ui_is_supported(ui0, ui_supported, unknown_ui_class_uri, &ui_type)); + TEST_ASSERT(lilv_node_equals(ui_type, ui_class_uri)); + + const LilvNode* plug_bundle_uri = lilv_plugin_get_bundle_uri(plug); + const LilvNode* ui_bundle_uri = lilv_ui_get_bundle_uri(ui0); + TEST_ASSERT(lilv_node_equals(plug_bundle_uri, ui_bundle_uri)); + + char* ui_binary_uri_str = (char*)malloc(TEST_PATH_MAX); + snprintf(ui_binary_uri_str, TEST_PATH_MAX, "%s%s", + lilv_node_as_string(plug_bundle_uri), "ui" SHLIB_EXT); + + const LilvNode* ui_binary_uri = lilv_ui_get_binary_uri(ui0); + + LilvNode* expected_uri = lilv_new_uri(world, ui_binary_uri_str); + TEST_ASSERT(lilv_node_equals(expected_uri, ui_binary_uri)); + + free(ui_binary_uri_str); + lilv_node_free(unknown_ui_class_uri); + lilv_node_free(ui_class_uri); + lilv_node_free(ui_uri); + lilv_node_free(ui2_uri); + lilv_node_free(ui3_uri); + lilv_node_free(noui_uri); + lilv_node_free(expected_uri); + lilv_uis_free(uis); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +uint32_t atom_Float = 0; +float in = 1.0; +float out = 42.0; +float control = 1234.0; + +static const void* +get_port_value(const char* port_symbol, + void* user_data, + uint32_t* size, + uint32_t* type) +{ + if (!strcmp(port_symbol, "input")) { + *size = sizeof(float); + *type = atom_Float; + return ∈ + } else if (!strcmp(port_symbol, "output")) { + *size = sizeof(float); + *type = atom_Float; + return &out; + } else if (!strcmp(port_symbol, "control")) { + *size = sizeof(float); + *type = atom_Float; + return &control; + } else { + fprintf(stderr, "error: get_port_value for nonexistent port `%s'\n", + port_symbol); + *size = *type = 0; + return NULL; + } +} + +static void +set_port_value(const char* port_symbol, + void* user_data, + const void* value, + uint32_t size, + uint32_t type) +{ + if (!strcmp(port_symbol, "input")) { + in = *(const float*)value; + } else if (!strcmp(port_symbol, "output")) { + out = *(const float*)value; + } else if (!strcmp(port_symbol, "control")) { + control = *(const float*)value; + } else { + fprintf(stderr, "error: set_port_value for nonexistent port `%s'\n", + port_symbol); + } +} + +char** uris = NULL; +size_t n_uris = 0; + +static LV2_URID +map_uri(LV2_URID_Map_Handle handle, + const char* uri) +{ + for (size_t i = 0; i < n_uris; ++i) { + if (!strcmp(uris[i], uri)) { + return i + 1; + } + } + + assert(serd_uri_string_has_scheme((const uint8_t*)uri)); + uris = (char**)realloc(uris, ++n_uris * sizeof(char*)); + uris[n_uris - 1] = lilv_strdup(uri); + return n_uris; +} + +static const char* +unmap_uri(LV2_URID_Map_Handle handle, + LV2_URID urid) +{ + if (urid > 0 && urid <= n_uris) { + return uris[urid - 1]; + } + return NULL; +} + +static char* temp_dir = NULL; + +static char* +lilv_make_path(LV2_State_Make_Path_Handle handle, + const char* path) +{ + return lilv_path_join(temp_dir, path); +} + +static int +test_state(void) +{ + init_world(); + + uint8_t* abs_bundle = (uint8_t*)lilv_path_absolute(LILV_TEST_BUNDLE); + SerdNode bundle = serd_node_new_file_uri(abs_bundle, 0, 0, true); + LilvNode* bundle_uri = lilv_new_uri(world, (const char*)bundle.buf); + LilvNode* plugin_uri = lilv_new_uri(world, + "http://example.org/lilv-test-plugin"); + lilv_world_load_bundle(world, bundle_uri); + free(abs_bundle); + serd_node_free(&bundle); + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + TEST_ASSERT(plugin); + + LV2_URID_Map map = { NULL, map_uri }; + LV2_Feature map_feature = { LV2_URID_MAP_URI, &map }; + LV2_URID_Unmap unmap = { NULL, unmap_uri }; + LV2_Feature unmap_feature = { LV2_URID_UNMAP_URI, &unmap }; + const LV2_Feature* features[] = { &map_feature, &unmap_feature, NULL }; + + atom_Float = map.map(map.handle, "http://lv2plug.in/ns/ext/atom#Float"); + + LilvNode* num = lilv_new_int(world, 5); + LilvState* nostate = lilv_state_new_from_file(world, &map, num, "/junk"); + TEST_ASSERT(!nostate); + + LilvInstance* instance = lilv_plugin_instantiate(plugin, 48000.0, features); + TEST_ASSERT(instance); + lilv_instance_activate(instance); + lilv_instance_connect_port(instance, 0, &in); + lilv_instance_connect_port(instance, 1, &out); + lilv_instance_run(instance, 1); + TEST_ASSERT(in == 1.0); + TEST_ASSERT(out == 1.0); + + temp_dir = lilv_realpath("temp"); + + const char* file_dir = NULL; + char* copy_dir = NULL; + char* link_dir = NULL; + char* save_dir = NULL; + + // Get instance state state + LilvState* state = lilv_state_new_from_instance( + plugin, instance, &map, + file_dir, copy_dir, link_dir, save_dir, + get_port_value, world, 0, NULL); + + // Get another instance state + LilvState* state2 = lilv_state_new_from_instance( + plugin, instance, &map, + file_dir, copy_dir, link_dir, save_dir, + get_port_value, world, 0, NULL); + + // Ensure they are equal + TEST_ASSERT(lilv_state_equals(state, state2)); + + // Check that we can't delete unsaved state + TEST_ASSERT(lilv_state_delete(world, state)); + + // Check that state has no URI + TEST_ASSERT(!lilv_state_get_uri(state)); + + // Check that we can't save a state with no URI + char* bad_state_str = lilv_state_to_string( + world, &map, &unmap, state, NULL, NULL); + TEST_ASSERT(!bad_state_str); + + // Check that we can't restore the NULL string (and it doesn't crash) + LilvState* bad_state = lilv_state_new_from_string(world, &map, NULL); + TEST_ASSERT(!bad_state); + + // Save state to a string + char* state1_str = lilv_state_to_string( + world, &map, &unmap, state, "http://example.org/state1", NULL); + + // Restore from string + LilvState* from_str = lilv_state_new_from_string(world, &map, state1_str); + + // Ensure they are equal + TEST_ASSERT(lilv_state_equals(state, from_str)); + free(state1_str); + + const LilvNode* state_plugin_uri = lilv_state_get_plugin_uri(state); + TEST_ASSERT(lilv_node_equals(state_plugin_uri, plugin_uri)); + + // Tinker with the label of the first state + TEST_ASSERT(lilv_state_get_label(state) == NULL); + lilv_state_set_label(state, "Test State Old Label"); + TEST_ASSERT(!strcmp(lilv_state_get_label(state), "Test State Old Label")); + lilv_state_set_label(state, "Test State"); + TEST_ASSERT(!strcmp(lilv_state_get_label(state), "Test State")); + + TEST_ASSERT(!lilv_state_equals(state, state2)); // Label changed + + // Run and get a new instance state (which should now differ) + lilv_instance_run(instance, 1); + LilvState* state3 = lilv_state_new_from_instance( + plugin, instance, &map, + file_dir, copy_dir, link_dir, save_dir, + get_port_value, world, 0, NULL); + TEST_ASSERT(!lilv_state_equals(state2, state3)); // num_runs changed + + // Restore instance state to original state + lilv_state_restore(state2, instance, set_port_value, NULL, 0, NULL); + + // Take a new snapshot and ensure it matches the set state + LilvState* state4 = lilv_state_new_from_instance( + plugin, instance, &map, + file_dir, copy_dir, link_dir, save_dir, + get_port_value, world, 0, NULL); + TEST_ASSERT(lilv_state_equals(state2, state4)); + + // Set some metadata properties + lilv_state_set_metadata(state, map.map(map.handle, LILV_NS_RDFS "comment"), + "This is a comment", + strlen("This is a comment") + 1, + map.map(map.handle, "http://lv2plug.in/ns/ext/atom#Literal"), + LV2_STATE_IS_POD); + lilv_state_set_metadata(state, map.map(map.handle, "http://example.org/metablob"), + "LIVEBEEF", + strlen("LIVEBEEF") + 1, + map.map(map.handle, "http://example.org/MetaBlob"), + 0); + + // Save state to a directory + int ret = lilv_state_save(world, &map, &unmap, state, NULL, + "state/state.lv2", "state.ttl"); + TEST_ASSERT(!ret); + + // Load state from directory + LilvState* state5 = lilv_state_new_from_file(world, &map, NULL, + "state/state.lv2/state.ttl"); + + TEST_ASSERT(lilv_state_equals(state, state5)); // Round trip accuracy + TEST_ASSERT(lilv_state_get_num_properties(state) == 8); + + // Attempt to save state to nowhere (error) + ret = lilv_state_save(world, &map, &unmap, state, NULL, NULL, NULL); + TEST_ASSERT(ret); + + // Save another state to the same directory (update manifest) + ret = lilv_state_save(world, &map, &unmap, state, NULL, + "state/state.lv2", "state2.ttl"); + TEST_ASSERT(!ret); + + // Save state with URI to a directory + const char* state_uri = "http://example.org/state"; + ret = lilv_state_save(world, &map, &unmap, state, state_uri, + "state/state6.lv2", "state6.ttl"); + TEST_ASSERT(!ret); + + // Load default bundle into world and load state from it + uint8_t* state6_path = (uint8_t*)lilv_path_absolute("state/state6.lv2/"); + SerdNode state6_uri = serd_node_new_file_uri(state6_path, 0, 0, true); + LilvNode* test_state_bundle = lilv_new_uri(world, (const char*)state6_uri.buf); + LilvNode* test_state_node = lilv_new_uri(world, state_uri); + lilv_world_load_bundle(world, test_state_bundle); + lilv_world_load_resource(world, test_state_node); + serd_node_free(&state6_uri); + free(state6_path); + + LilvState* state6 = lilv_state_new_from_world(world, &map, test_state_node); + TEST_ASSERT(lilv_state_equals(state, state6)); // Round trip accuracy + + // Check that loaded state has correct URI + TEST_ASSERT(lilv_state_get_uri(state6)); + TEST_ASSERT(!strcmp(lilv_node_as_string(lilv_state_get_uri(state6)), + state_uri)); + + lilv_world_unload_resource(world, test_state_node); + lilv_world_unload_bundle(world, test_state_bundle); + + LilvState* state6_2 = lilv_state_new_from_world(world, &map, test_state_node); + TEST_ASSERT(!state6_2); // No longer present + lilv_state_free(state6_2); + + lilv_node_free(test_state_bundle); + lilv_node_free(test_state_node); + + unsetenv("LV2_STATE_BUNDLE"); + + // Make directories and test files support + mkdir("temp", 0700); + file_dir = temp_dir; + mkdir("files", 0700); + copy_dir = lilv_realpath("files"); + mkdir("links", 0700); + link_dir = lilv_realpath("links"); + + LV2_State_Make_Path make_path = { NULL, lilv_make_path }; + LV2_Feature make_path_feature = { LV2_STATE__makePath, &make_path }; + const LV2_Feature* ffeatures[] = { &make_path_feature, &map_feature, NULL }; + + lilv_instance_deactivate(instance); + lilv_instance_free(instance); + instance = lilv_plugin_instantiate(plugin, 48000.0, ffeatures); + lilv_instance_activate(instance); + lilv_instance_connect_port(instance, 0, &in); + lilv_instance_connect_port(instance, 1, &out); + lilv_instance_run(instance, 1); + + // Test instantiating twice + LilvInstance* instance2 = lilv_plugin_instantiate(plugin, 48000.0, ffeatures); + if (!instance2) { + fatal_error("Failed to create multiple instances of <%s>\n", + lilv_node_as_uri(state_plugin_uri)); + return 0; + } + lilv_instance_free(instance2); + + // Get instance state state + LilvState* fstate = lilv_state_new_from_instance( + plugin, instance, &map, + file_dir, copy_dir, link_dir, "state/fstate.lv2", + get_port_value, world, 0, ffeatures); + + // Get another instance state + LilvState* fstate2 = lilv_state_new_from_instance( + plugin, instance, &map, + file_dir, copy_dir, link_dir, "state/fstate2.lv2", + get_port_value, world, 0, ffeatures); + + // Should be identical + TEST_ASSERT(lilv_state_equals(fstate, fstate2)); + + // Run, writing more to rec file + lilv_instance_run(instance, 2); + + // Get yet another instance state + LilvState* fstate3 = lilv_state_new_from_instance( + plugin, instance, &map, file_dir, copy_dir, link_dir, "state/fstate3.lv2", + get_port_value, world, 0, ffeatures); + + // Should be different + TEST_ASSERT(!lilv_state_equals(fstate, fstate3)); + + // Save state to a directory + ret = lilv_state_save(world, &map, &unmap, fstate, NULL, + "state/fstate.lv2", "fstate.ttl"); + TEST_ASSERT(!ret); + + // Load state from directory + LilvState* fstate4 = lilv_state_new_from_file(world, &map, NULL, + "state/fstate.lv2/fstate.ttl"); + TEST_ASSERT(lilv_state_equals(fstate, fstate4)); // Round trip accuracy + + // Restore instance state to loaded state + lilv_state_restore(fstate4, instance, set_port_value, NULL, 0, ffeatures); + + // Take a new snapshot and ensure it matches + LilvState* fstate5 = lilv_state_new_from_instance( + plugin, instance, &map, + file_dir, copy_dir, link_dir, "state/fstate5.lv2", + get_port_value, world, 0, ffeatures); + TEST_ASSERT(lilv_state_equals(fstate3, fstate5)); + + // Save state to a (different) directory again + ret = lilv_state_save(world, &map, &unmap, fstate, NULL, + "state/fstate6.lv2", "fstate6.ttl"); + TEST_ASSERT(!ret); + + // Reload it and ensure it's identical to the other loaded version + LilvState* fstate6 = lilv_state_new_from_file(world, &map, NULL, + "state/fstate6.lv2/fstate6.ttl"); + TEST_ASSERT(lilv_state_equals(fstate4, fstate6)); + + // Run, changing rec file (without changing size) + lilv_instance_run(instance, 3); + + // Take a new snapshot + LilvState* fstate7 = lilv_state_new_from_instance( + plugin, instance, &map, + file_dir, copy_dir, link_dir, "state/fstate7.lv2", + get_port_value, world, 0, ffeatures); + TEST_ASSERT(!lilv_state_equals(fstate6, fstate7)); + + // Save the changed state to a (different) directory again + ret = lilv_state_save(world, &map, &unmap, fstate7, NULL, + "state/fstate7.lv2", "fstate7.ttl"); + TEST_ASSERT(!ret); + + // Reload it and ensure it's changed + LilvState* fstate72 = lilv_state_new_from_file(world, &map, NULL, + "state/fstate7.lv2/fstate7.ttl"); + TEST_ASSERT(lilv_state_equals(fstate72, fstate7)); + TEST_ASSERT(!lilv_state_equals(fstate6, fstate72)); + + // Delete saved state + lilv_state_delete(world, fstate7); + + lilv_instance_deactivate(instance); + lilv_instance_free(instance); + + lilv_node_free(num); + + lilv_state_free(state); + lilv_state_free(from_str); + lilv_state_free(state2); + lilv_state_free(state3); + lilv_state_free(state4); + lilv_state_free(state5); + lilv_state_free(state6); + lilv_state_free(fstate); + lilv_state_free(fstate2); + lilv_state_free(fstate3); + lilv_state_free(fstate4); + lilv_state_free(fstate5); + lilv_state_free(fstate6); + lilv_state_free(fstate7); + lilv_state_free(fstate72); + + // Free URI map + for (size_t i = 0; i < n_uris; ++i) { + free(uris[i]); + } + free(uris); + n_uris = 0; + + lilv_node_free(plugin_uri); + lilv_node_free(bundle_uri); + free(link_dir); + free(copy_dir); + free(temp_dir); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_bad_port_symbol(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES PREFIX_LV2EV + ":plug a lv2:Plugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "doap:homepage <http://example.org/someplug> ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index 0 ; lv2:symbol \"0invalid\" ;" + " lv2:name \"Invalid\" ; " + "] .")) { + return 0; + } + + init_uris(); + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + + uint32_t n_ports = lilv_plugin_get_num_ports(plug); + TEST_ASSERT(n_ports == 0); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_bad_port_index(void) +{ + if (!start_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES PREFIX_LV2EV + ":plug a lv2:Plugin ; " + PLUGIN_NAME("Test plugin") " ; " + LICENSE_GPL " ; " + "doap:homepage <http://example.org/someplug> ; " + "lv2:port [ " + " a lv2:ControlPort ; a lv2:InputPort ; " + " lv2:index \"notaninteger\" ; lv2:symbol \"invalid\" ;" + " lv2:name \"Invalid\" ; " + "] .")) { + return 0; + } + + init_uris(); + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + + uint32_t n_ports = lilv_plugin_get_num_ports(plug); + TEST_ASSERT(n_ports == 0); + + cleanup_uris(); + return 1; +} + +/*****************************************************************************/ + +static int +test_string(void) +{ + char* s = NULL; + + TEST_ASSERT(!strcmp((s = lilv_dirname("/foo/bar")), "/foo")); free(s); + TEST_ASSERT(!strcmp((s = lilv_dirname("/foo/bar/")), "/foo")); free(s); + TEST_ASSERT(!strcmp((s = lilv_dirname("/foo///bar/")), "/foo")); free(s); + TEST_ASSERT(!strcmp((s = lilv_dirname("/foo///bar//")), "/foo")); free(s); + TEST_ASSERT(!strcmp((s = lilv_dirname("foo")), ".")); free(s); + TEST_ASSERT(!strcmp((s = lilv_dirname("/foo")), "/")); free(s); + TEST_ASSERT(!strcmp((s = lilv_dirname("/")), "/")); free(s); + TEST_ASSERT(!strcmp((s = lilv_dirname("//")), "/")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_relative_to("/a/b", "/a/")), "b")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_relative_to("/a", "/b/c/")), "/a")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_relative_to("/a/b/c", "/a/b/d/")), "../c")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_relative_to("/a/b/c", "/a/b/d/e/")), "../../c")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_join("/a", "b")), "/a/b")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_join("/a", "/b")), "/a/b")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_join("/a/", "/b")), "/a/b")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_join("/a/", "b")), "/a/b")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_join("/a", NULL)), "/a/")); free(s); + TEST_ASSERT(!strcmp((s = lilv_path_join(NULL, "/b")), "/b")); free(s); + +#ifndef _WIN32 + setenv("LILV_TEST_1", "test", 1); + char* home_foo = lilv_strjoin(getenv("HOME"), "/foo", NULL); + TEST_ASSERT(!strcmp((s = lilv_expand("$LILV_TEST_1")), "test")); free(s); + TEST_ASSERT(!strcmp((s = lilv_expand("~")), getenv("HOME"))); free(s); + TEST_ASSERT(!strcmp((s = lilv_expand("~foo")), "~foo")); free(s); + TEST_ASSERT(!strcmp((s = lilv_expand("~/foo")), home_foo)); free(s); + TEST_ASSERT(!strcmp((s = lilv_expand("$NOT_A_VAR")), "$NOT_A_VAR")); free(s); + free(home_foo); + unsetenv("LILV_TEST_1"); +#endif + + return 1; +} + +/*****************************************************************************/ + +static int +test_world(void) +{ + if (!init_world()) { + return 0; + } + + LilvNode* num = lilv_new_int(world, 4); + LilvNode* uri = lilv_new_uri(world, "http://example.org/object"); + + LilvNodes* matches = lilv_world_find_nodes(world, num, NULL, NULL); + TEST_ASSERT(!matches); + + matches = lilv_world_find_nodes(world, NULL, num, NULL); + TEST_ASSERT(!matches); + + matches = lilv_world_find_nodes(world, NULL, uri, NULL); + TEST_ASSERT(!matches); + + lilv_node_free(uri); + lilv_node_free(num); + + lilv_world_unload_bundle(world, NULL); + + return 1; +} + +/*****************************************************************************/ + +static int +test_reload_bundle(void) +{ + // Create a simple plugin bundle + create_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; " + PLUGIN_NAME("First name") " ."); + + if (!init_world()) { + return 0; + } + + init_uris(); + lilv_world_load_specifications(world); + + // Load bundle + LilvNode* bundle_uri = lilv_new_uri(world, bundle_dir_uri); + lilv_world_load_bundle(world, bundle_uri); + + // Check that plugin is present + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plug = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug); + + // Check that plugin name is correct + LilvNode* name = lilv_plugin_get_name(plug); + TEST_ASSERT(!strcmp(lilv_node_as_string(name), "First name")); + lilv_node_free(name); + + // Unload bundle from world and delete it + lilv_world_unload_bundle(world, bundle_uri); + delete_bundle(); + + // Create a new version of the same bundle, but with a different name + create_bundle(MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES + ":plug a lv2:Plugin ; " + PLUGIN_NAME("Second name") " ."); + + // Check that plugin is no longer in the world's plugin list + TEST_ASSERT(lilv_plugins_size(plugins) == 0); + + // Load new bundle + lilv_world_load_bundle(world, bundle_uri); + + // Check that plugin is present again and is the same LilvPlugin + const LilvPlugin* plug2 = lilv_plugins_get_by_uri(plugins, plugin_uri_value); + TEST_ASSERT(plug2); + TEST_ASSERT(plug2 == plug); + + // Check that plugin now has new name + LilvNode* name2 = lilv_plugin_get_name(plug2); + TEST_ASSERT(name2); + TEST_ASSERT(!strcmp(lilv_node_as_string(name2), "Second name")); + lilv_node_free(name2); + + // Load new bundle again (noop) + lilv_world_load_bundle(world, bundle_uri); + + cleanup_uris(); + lilv_node_free(bundle_uri); + lilv_world_free(world); + world = NULL; + + return 1; +} + +/*****************************************************************************/ + +static int +test_replace_version(void) +{ + if (!init_world()) { + return 0; + } + + LilvNode* plug_uri = lilv_new_uri(world, "http://example.org/versioned"); + LilvNode* lv2_minorVersion = lilv_new_uri(world, LV2_CORE__minorVersion); + LilvNode* lv2_microVersion = lilv_new_uri(world, LV2_CORE__microVersion); + LilvNode* minor = NULL; + LilvNode* micro = NULL; + + char* old_bundle_path = lilv_strjoin(LILV_TEST_DIR, "old_version.lv2/", 0); + + // Load plugin from old bundle + LilvNode* old_bundle = lilv_new_file_uri(world, NULL, old_bundle_path); + lilv_world_load_bundle(world, old_bundle); + lilv_world_load_resource(world, plug_uri); + + // Check version + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* old_plug = lilv_plugins_get_by_uri(plugins, plug_uri); + TEST_ASSERT(old_plug); + minor = lilv_world_get(world, plug_uri, lv2_minorVersion, 0); + micro = lilv_world_get(world, plug_uri, lv2_microVersion, 0); + TEST_ASSERT(!strcmp(lilv_node_as_string(minor), "1")); + TEST_ASSERT(!strcmp(lilv_node_as_string(micro), "0")); + lilv_node_free(micro); + lilv_node_free(minor); + + char* new_bundle_path = lilv_strjoin(LILV_TEST_DIR, "new_version.lv2/", 0); + + // Load plugin from new bundle + LilvNode* new_bundle = lilv_new_file_uri(world, NULL, new_bundle_path); + lilv_world_load_bundle(world, new_bundle); + lilv_world_load_resource(world, plug_uri); + + // Check that version in the world model has changed + plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* new_plug = lilv_plugins_get_by_uri(plugins, plug_uri); + TEST_ASSERT(new_plug); + TEST_ASSERT(lilv_node_equals(lilv_plugin_get_bundle_uri(new_plug), new_bundle)); + minor = lilv_world_get(world, plug_uri, lv2_minorVersion, 0); + micro = lilv_world_get(world, plug_uri, lv2_microVersion, 0); + TEST_ASSERT(!strcmp(lilv_node_as_string(minor), "2")); + TEST_ASSERT(!strcmp(lilv_node_as_string(micro), "1")); + lilv_node_free(micro); + lilv_node_free(minor); + + // Try to load the old version again + lilv_world_load_bundle(world, old_bundle); + lilv_world_load_resource(world, plug_uri); + + // Check that version in the world model has not changed + plugins = lilv_world_get_all_plugins(world); + new_plug = lilv_plugins_get_by_uri(plugins, plug_uri); + TEST_ASSERT(new_plug); + minor = lilv_world_get(world, plug_uri, lv2_minorVersion, 0); + micro = lilv_world_get(world, plug_uri, lv2_microVersion, 0); + TEST_ASSERT(!strcmp(lilv_node_as_string(minor), "2")); + TEST_ASSERT(!strcmp(lilv_node_as_string(micro), "1")); + lilv_node_free(micro); + lilv_node_free(minor); + + lilv_node_free(new_bundle); + lilv_node_free(old_bundle); + free(new_bundle_path); + free(old_bundle_path); + lilv_node_free(plug_uri); + lilv_node_free(lv2_minorVersion); + lilv_node_free(lv2_microVersion); + return 1; +} + +/*****************************************************************************/ + +static int +test_get_symbol(void) +{ + if (!start_bundle( + MANIFEST_PREFIXES + ":plug a lv2:Plugin ; lv2:symbol \"plugsym\" ; lv2:binary <foo" SHLIB_EXT "> ; rdfs:seeAlso <plugin.ttl> .\n", + BUNDLE_PREFIXES PREFIX_LV2EV + ":plug a lv2:Plugin ; " + PLUGIN_NAME("Test plugin") " ; " + "lv2:symbol \"plugsym\" .")) { + return 0; + } + + init_uris(); + + LilvNode* plug_sym = lilv_world_get_symbol(world, plugin_uri_value); + LilvNode* path = lilv_new_uri(world, "http://example.org/foo"); + LilvNode* path_sym = lilv_world_get_symbol(world, path); + LilvNode* query = lilv_new_uri(world, "http://example.org/foo?bar=baz"); + LilvNode* query_sym = lilv_world_get_symbol(world, query); + LilvNode* frag = lilv_new_uri(world, "http://example.org/foo#bar"); + LilvNode* frag_sym = lilv_world_get_symbol(world, frag); + LilvNode* queryfrag = lilv_new_uri(world, "http://example.org/foo?bar=baz#quux"); + LilvNode* queryfrag_sym = lilv_world_get_symbol(world, queryfrag); + LilvNode* nonuri = lilv_new_int(world, 42); + + TEST_ASSERT(lilv_world_get_symbol(world, nonuri) == NULL); + TEST_ASSERT(!strcmp(lilv_node_as_string(plug_sym), "plugsym")); + TEST_ASSERT(!strcmp(lilv_node_as_string(path_sym), "foo")); + TEST_ASSERT(!strcmp(lilv_node_as_string(query_sym), "bar_baz")); + TEST_ASSERT(!strcmp(lilv_node_as_string(frag_sym), "bar")); + TEST_ASSERT(!strcmp(lilv_node_as_string(queryfrag_sym), "quux")); + + lilv_node_free(nonuri); + lilv_node_free(queryfrag_sym); + lilv_node_free(queryfrag); + lilv_node_free(frag_sym); + lilv_node_free(frag); + lilv_node_free(query_sym); + lilv_node_free(query); + lilv_node_free(path_sym); + lilv_node_free(path); + lilv_node_free(plug_sym); + cleanup_uris(); + + return 1; +} + +/*****************************************************************************/ + +/* add tests here */ +static struct TestCase tests[] = { + TEST_CASE(util), + TEST_CASE(value), + TEST_CASE(verify), + TEST_CASE(no_verify), + TEST_CASE(discovery), + TEST_CASE(lv2_path), + TEST_CASE(classes), + TEST_CASE(plugin), + TEST_CASE(project), + TEST_CASE(no_author), + TEST_CASE(project_no_author), + TEST_CASE(preset), + TEST_CASE(prototype), + TEST_CASE(port), + TEST_CASE(ui), + TEST_CASE(bad_port_symbol), + TEST_CASE(bad_port_index), + TEST_CASE(bad_port_index), + TEST_CASE(string), + TEST_CASE(world), + TEST_CASE(state), + TEST_CASE(reload_bundle), + TEST_CASE(replace_version), + TEST_CASE(get_symbol), + { NULL, NULL } +}; + +static void +run_tests(void) +{ + int i; + for (i = 0; tests[i].title; i++) { + printf("*** Test %s\n", tests[i].title); + if (!tests[i].func()) { + printf("\nTest failed\n"); + /* test case that wasn't able to be executed at all counts as 1 test + 1 error */ + error_count++; + test_count++; + } + unload_bundle(); + cleanup(); + } +} + +int +main(int argc, char* argv[]) +{ + if (argc != 1) { + printf("Syntax: %s\n", argv[0]); + return 0; + } + setenv("LANG", "C", 1); + init_tests(); + run_tests(); + cleanup(); + printf("\n*** Test Results: %d tests, %d errors\n\n", test_count, error_count); + return error_count ? 1 : 0; +} diff --git a/test/missing_descriptor.lv2/manifest.ttl.in b/test/missing_descriptor.lv2/manifest.ttl.in new file mode 100644 index 0000000..789d1ec --- /dev/null +++ b/test/missing_descriptor.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://example.org/missing-descriptor> + a lv2:Plugin ; + lv2:binary <missing_descriptor@SHLIB_EXT@> ; + rdfs:seeAlso <missing_descriptor.ttl> . diff --git a/test/missing_descriptor.lv2/missing_descriptor.c b/test/missing_descriptor.lv2/missing_descriptor.c new file mode 100644 index 0000000..0c49f23 --- /dev/null +++ b/test/missing_descriptor.lv2/missing_descriptor.c @@ -0,0 +1,21 @@ +/* + Lilv Test Plugin - Missing descriptor + Copyright 2011-2015 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +LV2_SYMBOL_EXPORT +const char* msg = "this is not the thing you're looking for"; diff --git a/test/missing_descriptor.lv2/missing_descriptor.ttl.in b/test/missing_descriptor.lv2/missing_descriptor.ttl.in new file mode 100644 index 0000000..9e2aad8 --- /dev/null +++ b/test/missing_descriptor.lv2/missing_descriptor.ttl.in @@ -0,0 +1,38 @@ +# Lilv Test Plugin - Missing descriptor +# Copyright 2011-2015 David Robillard <d@drobilla.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . + +<http://example.org/missing-descriptor> + a lv2:Plugin ; + doap:name "Missing descriptor test" ; + doap:license <http://opensource.org/licenses/isc> ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:port [ + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "input" ; + lv2:name "Input" + ] , [ + a lv2:OutputPort , + lv2:ControlPort ; + lv2:index 1 ; + lv2:symbol "output" ; + lv2:name "Output" + ] . diff --git a/test/missing_descriptor.lv2/test_missing_descriptor.c b/test/missing_descriptor.lv2/test_missing_descriptor.c new file mode 100644 index 0000000..49909e1 --- /dev/null +++ b/test/missing_descriptor.lv2/test_missing_descriptor.c @@ -0,0 +1,46 @@ +#include "lilv/lilv.h" +#include "../src/lilv_internal.h" + +#define PLUGIN_URI "http://example.org/missing-descriptor" + +#define TEST_ASSERT(check) do {\ + if (!(check)) {\ + fprintf(stderr, "%s:%d: failed test: %s\n", __FILE__, __LINE__, #check);\ + return 1;\ + }\ +} while (0) + +int +main(int argc, char** argv) +{ + if (argc != 2) { + fprintf(stderr, "USAGE: %s BUNDLE\n", argv[0]); + return 1; + } + + const char* bundle_path = argv[1]; + LilvWorld* world = lilv_world_new(); + + // Load test plugin bundle + uint8_t* abs_bundle = (uint8_t*)lilv_path_absolute(bundle_path); + SerdNode bundle = serd_node_new_file_uri(abs_bundle, 0, 0, true); + LilvNode* bundle_uri = lilv_new_uri(world, (const char*)bundle.buf); + lilv_world_load_bundle(world, bundle_uri); + free(abs_bundle); + serd_node_free(&bundle); + lilv_node_free(bundle_uri); + + LilvNode* plugin_uri = lilv_new_uri(world, PLUGIN_URI); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + TEST_ASSERT(plugin); + + LilvInstance* instance = lilv_plugin_instantiate(plugin, 48000.0, NULL); + TEST_ASSERT(!instance); + + lilv_node_free(plugin_uri); + lilv_world_free(world); + + return 0; +} + diff --git a/test/missing_name.lv2/manifest.ttl.in b/test/missing_name.lv2/manifest.ttl.in new file mode 100644 index 0000000..62f4813 --- /dev/null +++ b/test/missing_name.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://example.org/missing-name> + a lv2:Plugin ; + lv2:binary <missing_name@SHLIB_EXT@> ; + rdfs:seeAlso <missing_name.ttl> . diff --git a/test/missing_name.lv2/missing_name.c b/test/missing_name.lv2/missing_name.c new file mode 100644 index 0000000..6b86e09 --- /dev/null +++ b/test/missing_name.lv2/missing_name.c @@ -0,0 +1,93 @@ +/* + Lilv Test Plugin - Missing name + Copyright 2011-2015 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <stdlib.h> + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define PLUGIN_URI "http://example.org/missing-name" + +enum { + TEST_INPUT = 0, + TEST_OUTPUT = 1 +}; + +typedef struct { + float* input; + float* output; +} Test; + +static void +cleanup(LV2_Handle instance) +{ + free((Test*)instance); +} + +static void +connect_port(LV2_Handle instance, uint32_t port, void* data) +{ + Test* test = (Test*)instance; + switch (port) { + case TEST_INPUT: + test->input = (float*)data; + break; + case TEST_OUTPUT: + test->output = (float*)data; + break; + default: + break; + } +} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* path, + const LV2_Feature* const* features) +{ + Test* test = (Test*)calloc(1, sizeof(Test)); + if (!test) { + return NULL; + } + + return (LV2_Handle)test; +} + +static void +run(LV2_Handle instance, uint32_t sample_count) +{ + Test* test = (Test*)instance; + + *test->output = *test->input; +} + +static const LV2_Descriptor descriptor = { + PLUGIN_URI, + instantiate, + connect_port, + NULL, // activate, + run, + NULL, // deactivate, + cleanup, + NULL // extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* lv2_descriptor(uint32_t index) +{ + return (index == 0) ? &descriptor : NULL; +} diff --git a/test/missing_name.lv2/missing_name.ttl.in b/test/missing_name.lv2/missing_name.ttl.in new file mode 100644 index 0000000..68ce23e --- /dev/null +++ b/test/missing_name.lv2/missing_name.ttl.in @@ -0,0 +1,37 @@ +# Lilv Test Plugin - Missing plugin name +# Copyright 2011-2015 David Robillard <d@drobilla.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . + +<http://example.org/missing-name> + a lv2:Plugin ; + doap:license <http://opensource.org/licenses/isc> ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:port [ + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "input" ; + lv2:name "Input" + ] , [ + a lv2:OutputPort , + lv2:ControlPort ; + lv2:index 1 ; + lv2:symbol "output" ; + lv2:name "Output" + ] . diff --git a/test/missing_name.lv2/test_missing_name.c b/test/missing_name.lv2/test_missing_name.c new file mode 100644 index 0000000..960eec0 --- /dev/null +++ b/test/missing_name.lv2/test_missing_name.c @@ -0,0 +1,47 @@ +#include "lilv/lilv.h" +#include "../src/lilv_internal.h" + +#define PLUGIN_URI "http://example.org/missing-name" + +#define TEST_ASSERT(check) do {\ + if (!(check)) {\ + fprintf(stderr, "%s:%d: failed test: %s\n", __FILE__, __LINE__, #check);\ + return 1;\ + }\ +} while (0) + +int +main(int argc, char** argv) +{ + if (argc != 2) { + fprintf(stderr, "USAGE: %s BUNDLE\n", argv[0]); + return 1; + } + + const char* bundle_path = argv[1]; + LilvWorld* world = lilv_world_new(); + + // Load test plugin bundle + uint8_t* abs_bundle = (uint8_t*)lilv_path_absolute(bundle_path); + SerdNode bundle = serd_node_new_file_uri(abs_bundle, 0, 0, true); + LilvNode* bundle_uri = lilv_new_uri(world, (const char*)bundle.buf); + lilv_world_load_bundle(world, bundle_uri); + free(abs_bundle); + serd_node_free(&bundle); + lilv_node_free(bundle_uri); + + LilvNode* plugin_uri = lilv_new_uri(world, PLUGIN_URI); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + TEST_ASSERT(plugin); + + LilvInstance* instance = lilv_plugin_instantiate(plugin, 48000.0, NULL); + TEST_ASSERT(instance); + lilv_instance_free(instance); + + lilv_node_free(plugin_uri); + lilv_world_free(world); + + return 0; +} + diff --git a/test/missing_plugin.lv2/manifest.ttl.in b/test/missing_plugin.lv2/manifest.ttl.in new file mode 100644 index 0000000..d969cec --- /dev/null +++ b/test/missing_plugin.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://example.org/missing-plugin> + a lv2:Plugin ; + lv2:binary <missing_plugin@SHLIB_EXT@> ; + rdfs:seeAlso <missing_plugin.ttl> . diff --git a/test/missing_plugin.lv2/missing_plugin.c b/test/missing_plugin.lv2/missing_plugin.c new file mode 100644 index 0000000..4d21226 --- /dev/null +++ b/test/missing_plugin.lv2/missing_plugin.c @@ -0,0 +1,43 @@ +/* + Lilv Test Plugin - Missing plugin + Copyright 2011-2015 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <stdlib.h> + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define PLUGIN_URI "http://example.org/missing-plugin" + +static const LV2_Descriptor descriptor = { + "http://example.org/not-the-plugin-you-are-looking-for", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* lv2_descriptor(uint32_t index) +{ + if (index == 0) { + return &descriptor; + } + + return NULL; +} diff --git a/test/missing_plugin.lv2/missing_plugin.ttl.in b/test/missing_plugin.lv2/missing_plugin.ttl.in new file mode 100644 index 0000000..ed8a7f3 --- /dev/null +++ b/test/missing_plugin.lv2/missing_plugin.ttl.in @@ -0,0 +1,38 @@ +# Lilv Test Plugin - Missing plugin +# Copyright 2011-2015 David Robillard <d@drobilla.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . + +<http://example.org/missing-plugin> + a lv2:Plugin ; + doap:name "Missing descriptor test" ; + doap:license <http://opensource.org/licenses/isc> ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:port [ + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "input" ; + lv2:name "Input" + ] , [ + a lv2:OutputPort , + lv2:ControlPort ; + lv2:index 1 ; + lv2:symbol "output" ; + lv2:name "Output" + ] . diff --git a/test/missing_plugin.lv2/test_missing_plugin.c b/test/missing_plugin.lv2/test_missing_plugin.c new file mode 100644 index 0000000..bfc695b --- /dev/null +++ b/test/missing_plugin.lv2/test_missing_plugin.c @@ -0,0 +1,46 @@ +#include "lilv/lilv.h" +#include "../src/lilv_internal.h" + +#define PLUGIN_URI "http://example.org/missing-plugin" + +#define TEST_ASSERT(check) do {\ + if (!(check)) {\ + fprintf(stderr, "%s:%d: failed test: %s\n", __FILE__, __LINE__, #check);\ + return 1;\ + }\ +} while (0) + +int +main(int argc, char** argv) +{ + if (argc != 2) { + fprintf(stderr, "USAGE: %s BUNDLE\n", argv[0]); + return 1; + } + + const char* bundle_path = argv[1]; + LilvWorld* world = lilv_world_new(); + + // Load test plugin bundle + uint8_t* abs_bundle = (uint8_t*)lilv_path_absolute(bundle_path); + SerdNode bundle = serd_node_new_file_uri(abs_bundle, 0, 0, true); + LilvNode* bundle_uri = lilv_new_uri(world, (const char*)bundle.buf); + lilv_world_load_bundle(world, bundle_uri); + free(abs_bundle); + serd_node_free(&bundle); + lilv_node_free(bundle_uri); + + LilvNode* plugin_uri = lilv_new_uri(world, PLUGIN_URI); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + TEST_ASSERT(plugin); + + LilvInstance* instance = lilv_plugin_instantiate(plugin, 48000.0, NULL); + TEST_ASSERT(!instance); + + lilv_node_free(plugin_uri); + lilv_world_free(world); + + return 0; +} + diff --git a/test/missing_port.lv2/manifest.ttl.in b/test/missing_port.lv2/manifest.ttl.in new file mode 100644 index 0000000..c090042 --- /dev/null +++ b/test/missing_port.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://example.org/missing-port> + a lv2:Plugin ; + lv2:binary <missing_port@SHLIB_EXT@> ; + rdfs:seeAlso <missing_port.ttl> . diff --git a/test/missing_port.lv2/missing_port.c b/test/missing_port.lv2/missing_port.c new file mode 100644 index 0000000..dd7e9ff --- /dev/null +++ b/test/missing_port.lv2/missing_port.c @@ -0,0 +1,93 @@ +/* + Lilv Test Plugin - Missing port + Copyright 2011-2016 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <stdlib.h> + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define PLUGIN_URI "http://example.org/missing-port" + +enum { + TEST_INPUT = 0, + TEST_OUTPUT = 1 +}; + +typedef struct { + float* input; + float* output; +} Test; + +static void +cleanup(LV2_Handle instance) +{ + free((Test*)instance); +} + +static void +connect_port(LV2_Handle instance, uint32_t port, void* data) +{ + Test* test = (Test*)instance; + switch (port) { + case TEST_INPUT: + test->input = (float*)data; + break; + case TEST_OUTPUT: + test->output = (float*)data; + break; + default: + break; + } +} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* path, + const LV2_Feature* const* features) +{ + Test* test = (Test*)calloc(1, sizeof(Test)); + if (!test) { + return NULL; + } + + return (LV2_Handle)test; +} + +static void +run(LV2_Handle instance, uint32_t sample_count) +{ + Test* test = (Test*)instance; + + *test->output = *test->input; +} + +static const LV2_Descriptor descriptor = { + PLUGIN_URI, + instantiate, + connect_port, + NULL, // activate, + run, + NULL, // deactivate, + cleanup, + NULL // extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* lv2_descriptor(uint32_t index) +{ + return (index == 0) ? &descriptor : NULL; +} diff --git a/test/missing_port.lv2/missing_port.ttl.in b/test/missing_port.lv2/missing_port.ttl.in new file mode 100644 index 0000000..0dec1cf --- /dev/null +++ b/test/missing_port.lv2/missing_port.ttl.in @@ -0,0 +1,31 @@ +# Lilv Test Plugin - Missing plugin port +# Copyright 2011-2016 David Robillard <d@drobilla.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . + +<http://example.org/missing-port> + a lv2:Plugin ; + doap:license <http://opensource.org/licenses/isc> ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:port [ + a lv2:OutputPort , + lv2:ControlPort ; + lv2:index 1 ; + lv2:symbol "output" ; + lv2:name "Output" + ] . diff --git a/test/missing_port.lv2/test_missing_port.c b/test/missing_port.lv2/test_missing_port.c new file mode 100644 index 0000000..c67a1b8 --- /dev/null +++ b/test/missing_port.lv2/test_missing_port.c @@ -0,0 +1,45 @@ +#include "lilv/lilv.h" +#include "../src/lilv_internal.h" + +#define PLUGIN_URI "http://example.org/missing-port" + +#define TEST_ASSERT(check) do {\ + if (!(check)) {\ + fprintf(stderr, "%s:%d: failed test: %s\n", __FILE__, __LINE__, #check);\ + return 1;\ + }\ +} while (0) + +int +main(int argc, char** argv) +{ + if (argc != 2) { + fprintf(stderr, "USAGE: %s BUNDLE\n", argv[0]); + return 1; + } + + const char* bundle_path = argv[1]; + LilvWorld* world = lilv_world_new(); + + // Load test plugin bundle + uint8_t* abs_bundle = (uint8_t*)lilv_path_absolute(bundle_path); + SerdNode bundle = serd_node_new_file_uri(abs_bundle, 0, 0, true); + LilvNode* bundle_uri = lilv_new_uri(world, (const char*)bundle.buf); + lilv_world_load_bundle(world, bundle_uri); + free(abs_bundle); + serd_node_free(&bundle); + lilv_node_free(bundle_uri); + + LilvNode* plugin_uri = lilv_new_uri(world, PLUGIN_URI); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + + // Check that all ports are ignored + TEST_ASSERT(lilv_plugin_get_num_ports(plugin) == 0); + + lilv_node_free(plugin_uri); + lilv_world_free(world); + + return 0; +} + diff --git a/test/missing_port_name.lv2/manifest.ttl.in b/test/missing_port_name.lv2/manifest.ttl.in new file mode 100644 index 0000000..d6a4e39 --- /dev/null +++ b/test/missing_port_name.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://example.org/missing-port-name> + a lv2:Plugin ; + lv2:binary <missing_port_name@SHLIB_EXT@> ; + rdfs:seeAlso <missing_port_name.ttl> . diff --git a/test/missing_port_name.lv2/missing_port_name.c b/test/missing_port_name.lv2/missing_port_name.c new file mode 100644 index 0000000..4ed44ed --- /dev/null +++ b/test/missing_port_name.lv2/missing_port_name.c @@ -0,0 +1,93 @@ +/* + Lilv Test Plugin - Missing port name + Copyright 2011-2015 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <stdlib.h> + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define PLUGIN_URI "http://example.org/missing-port-name" + +enum { + TEST_INPUT = 0, + TEST_OUTPUT = 1 +}; + +typedef struct { + float* input; + float* output; +} Test; + +static void +cleanup(LV2_Handle instance) +{ + free((Test*)instance); +} + +static void +connect_port(LV2_Handle instance, uint32_t port, void* data) +{ + Test* test = (Test*)instance; + switch (port) { + case TEST_INPUT: + test->input = (float*)data; + break; + case TEST_OUTPUT: + test->output = (float*)data; + break; + default: + break; + } +} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* path, + const LV2_Feature* const* features) +{ + Test* test = (Test*)calloc(1, sizeof(Test)); + if (!test) { + return NULL; + } + + return (LV2_Handle)test; +} + +static void +run(LV2_Handle instance, uint32_t sample_count) +{ + Test* test = (Test*)instance; + + *test->output = *test->input; +} + +static const LV2_Descriptor descriptor = { + PLUGIN_URI, + instantiate, + connect_port, + NULL, // activate, + run, + NULL, // deactivate, + cleanup, + NULL // extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* lv2_descriptor(uint32_t index) +{ + return (index == 0) ? &descriptor : NULL; +} diff --git a/test/missing_port_name.lv2/missing_port_name.ttl.in b/test/missing_port_name.lv2/missing_port_name.ttl.in new file mode 100644 index 0000000..5a58a80 --- /dev/null +++ b/test/missing_port_name.lv2/missing_port_name.ttl.in @@ -0,0 +1,30 @@ +# Lilv Test Plugin - Missing port name +# Copyright 2011-2015 David Robillard <d@drobilla.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . + +<http://example.org/missing-port-name> + a lv2:Plugin ; + doap:license <http://opensource.org/licenses/isc> ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:port [ + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "input" ; + ] .
\ No newline at end of file diff --git a/test/missing_port_name.lv2/test_missing_port_name.c b/test/missing_port_name.lv2/test_missing_port_name.c new file mode 100644 index 0000000..c000247 --- /dev/null +++ b/test/missing_port_name.lv2/test_missing_port_name.c @@ -0,0 +1,49 @@ +#include "lilv/lilv.h" +#include "../src/lilv_internal.h" + +#define PLUGIN_URI "http://example.org/missing-port-name" + +#define TEST_ASSERT(check) do {\ + if (!(check)) {\ + fprintf(stderr, "%s:%d: failed test: %s\n", __FILE__, __LINE__, #check);\ + return 1;\ + }\ +} while (0) + +int +main(int argc, char** argv) +{ + if (argc != 2) { + fprintf(stderr, "USAGE: %s BUNDLE\n", argv[0]); + return 1; + } + + const char* bundle_path = argv[1]; + LilvWorld* world = lilv_world_new(); + + // Load test plugin bundle + uint8_t* abs_bundle = (uint8_t*)lilv_path_absolute(bundle_path); + SerdNode bundle = serd_node_new_file_uri(abs_bundle, 0, 0, true); + LilvNode* bundle_uri = lilv_new_uri(world, (const char*)bundle.buf); + lilv_world_load_bundle(world, bundle_uri); + free(abs_bundle); + serd_node_free(&bundle); + lilv_node_free(bundle_uri); + + LilvNode* plugin_uri = lilv_new_uri(world, PLUGIN_URI); + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(plugins, plugin_uri); + TEST_ASSERT(plugin); + + const LilvPort* port = lilv_plugin_get_port_by_index(plugin, 0); + TEST_ASSERT(port); + LilvNode* name = lilv_port_get_name(plugin, port); + TEST_ASSERT(!name); + lilv_node_free(name); + + lilv_node_free(plugin_uri); + lilv_world_free(world); + + return 0; +} + diff --git a/test/new_version.lv2/manifest.ttl.in b/test/new_version.lv2/manifest.ttl.in new file mode 100644 index 0000000..e76f7cf --- /dev/null +++ b/test/new_version.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://example.org/versioned> + a lv2:Plugin ; + lv2:binary <new_version@SHLIB_EXT@> ; + rdfs:seeAlso <new_version.ttl> . diff --git a/test/new_version.lv2/new_version.c b/test/new_version.lv2/new_version.c new file mode 100644 index 0000000..37d9fbf --- /dev/null +++ b/test/new_version.lv2/new_version.c @@ -0,0 +1,93 @@ +/* + Lilv Test Plugin - New version + Copyright 2011-2016 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <stdlib.h> + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define PLUGIN_URI "http://example.org/versioned" + +enum { + TEST_INPUT = 0, + TEST_OUTPUT = 1 +}; + +typedef struct { + float* input; + float* output; +} Test; + +static void +cleanup(LV2_Handle instance) +{ + free((Test*)instance); +} + +static void +connect_port(LV2_Handle instance, uint32_t port, void* data) +{ + Test* test = (Test*)instance; + switch (port) { + case TEST_INPUT: + test->input = (float*)data; + break; + case TEST_OUTPUT: + test->output = (float*)data; + break; + default: + break; + } +} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* path, + const LV2_Feature* const* features) +{ + Test* test = (Test*)calloc(1, sizeof(Test)); + if (!test) { + return NULL; + } + + return (LV2_Handle)test; +} + +static void +run(LV2_Handle instance, uint32_t sample_count) +{ + Test* test = (Test*)instance; + + *test->output = *test->input; +} + +static const LV2_Descriptor descriptor = { + PLUGIN_URI, + instantiate, + connect_port, + NULL, // activate, + run, + NULL, // deactivate, + cleanup, + NULL // extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* lv2_descriptor(uint32_t index) +{ + return (index == 0) ? &descriptor : NULL; +} diff --git a/test/new_version.lv2/new_version.ttl.in b/test/new_version.lv2/new_version.ttl.in new file mode 100644 index 0000000..7994666 --- /dev/null +++ b/test/new_version.lv2/new_version.ttl.in @@ -0,0 +1,40 @@ +# Lilv Test Plugin - New version +# Copyright 2011-2016 David Robillard <d@drobilla.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . + +<http://example.org/versioned> + a lv2:Plugin ; + doap:license <http://opensource.org/licenses/isc> ; + doap:name "New version" ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:minorVersion 2 ; + lv2:microVersion 1 ; + lv2:port [ + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "input" ; + lv2:name "Input" + ] , [ + a lv2:OutputPort , + lv2:ControlPort ; + lv2:index 1 ; + lv2:symbol "output" ; + lv2:name "Output" + ] . diff --git a/test/old_version.lv2/manifest.ttl.in b/test/old_version.lv2/manifest.ttl.in new file mode 100644 index 0000000..3c96cb5 --- /dev/null +++ b/test/old_version.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://example.org/versioned> + a lv2:Plugin ; + lv2:binary <old_version@SHLIB_EXT@> ; + rdfs:seeAlso <old_version.ttl> . diff --git a/test/old_version.lv2/old_version.c b/test/old_version.lv2/old_version.c new file mode 100644 index 0000000..303f09c --- /dev/null +++ b/test/old_version.lv2/old_version.c @@ -0,0 +1,93 @@ +/* + Lilv Test Plugin - Old version + Copyright 2011-2016 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <stdlib.h> + +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#define PLUGIN_URI "http://example.org/versioned" + +enum { + TEST_INPUT = 0, + TEST_OUTPUT = 1 +}; + +typedef struct { + float* input; + float* output; +} Test; + +static void +cleanup(LV2_Handle instance) +{ + free((Test*)instance); +} + +static void +connect_port(LV2_Handle instance, uint32_t port, void* data) +{ + Test* test = (Test*)instance; + switch (port) { + case TEST_INPUT: + test->input = (float*)data; + break; + case TEST_OUTPUT: + test->output = (float*)data; + break; + default: + break; + } +} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* path, + const LV2_Feature* const* features) +{ + Test* test = (Test*)calloc(1, sizeof(Test)); + if (!test) { + return NULL; + } + + return (LV2_Handle)test; +} + +static void +run(LV2_Handle instance, uint32_t sample_count) +{ + Test* test = (Test*)instance; + + *test->output = *test->input; +} + +static const LV2_Descriptor descriptor = { + PLUGIN_URI, + instantiate, + connect_port, + NULL, // activate, + run, + NULL, // deactivate, + cleanup, + NULL // extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* lv2_descriptor(uint32_t index) +{ + return (index == 0) ? &descriptor : NULL; +} diff --git a/test/old_version.lv2/old_version.ttl.in b/test/old_version.lv2/old_version.ttl.in new file mode 100644 index 0000000..2b68f76 --- /dev/null +++ b/test/old_version.lv2/old_version.ttl.in @@ -0,0 +1,40 @@ +# Lilv Test Plugin - Old version +# Copyright 2011-2016 David Robillard <d@drobilla.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . + +<http://example.org/versioned> + a lv2:Plugin ; + doap:license <http://opensource.org/licenses/isc> ; + doap:name "Old version" ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:minorVersion 1 ; + lv2:microVersion 0 ; + lv2:port [ + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "input" ; + lv2:name "Input" + ] , [ + a lv2:OutputPort , + lv2:ControlPort ; + lv2:index 1 ; + lv2:symbol "output" ; + lv2:name "Output" + ] . diff --git a/test/test.lv2/manifest.ttl.in b/test/test.lv2/manifest.ttl.in new file mode 100644 index 0000000..bc3952c --- /dev/null +++ b/test/test.lv2/manifest.ttl.in @@ -0,0 +1,7 @@ +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<http://example.org/lilv-test-plugin> + a lv2:Plugin ; + lv2:binary <test@SHLIB_EXT@> ; + rdfs:seeAlso <test.ttl> . diff --git a/test/test.lv2/test.c b/test/test.lv2/test.c new file mode 100644 index 0000000..d16e4a9 --- /dev/null +++ b/test/test.lv2/test.c @@ -0,0 +1,394 @@ +/* + Lilv Test Plugin + Copyright 2011-2017 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#define _POSIX_C_SOURCE 200809L + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" +#include "lv2/lv2plug.in/ns/ext/state/state.h" +#include "lv2/lv2plug.in/ns/ext/urid/urid.h" +#include "lv2/lv2plug.in/ns/lv2core/lv2.h" + +#ifdef _WIN32 +# include <io.h> +# define mkstemp(pat) _mktemp(pat) +#endif + +#define TEST_URI "http://example.org/lilv-test-plugin" + +#define TMP_TEMPLATE "lilv_testXXXXXX" + +enum { + TEST_INPUT = 0, + TEST_OUTPUT = 1, + TEST_CONTROL = 2 +}; + +typedef struct { + LV2_URID_Map* map; + + struct { + LV2_URID atom_Float; + } uris; + + char tmp_file_path[sizeof(TMP_TEMPLATE)]; + char* rec_file_path; + FILE* rec_file; + + float* input; + float* output; + unsigned num_runs; +} Test; + +static void +cleanup(LV2_Handle instance) +{ + Test* test = (Test*)instance; + if (test->rec_file) { + fclose(test->rec_file); + } + free(test->rec_file_path); + free(instance); +} + +static void +connect_port(LV2_Handle instance, + uint32_t port, + void* data) +{ + Test* test = (Test*)instance; + switch (port) { + case TEST_INPUT: + test->input = (float*)data; + break; + case TEST_OUTPUT: + test->output = (float*)data; + break; + case TEST_CONTROL: + test->output = (float*)data; + break; + default: + break; + } +} + +static LV2_Handle +instantiate(const LV2_Descriptor* descriptor, + double rate, + const char* path, + const LV2_Feature* const* features) +{ + Test* test = (Test*)calloc(1, sizeof(Test)); + if (!test) { + return NULL; + } + + strncpy(test->tmp_file_path, TMP_TEMPLATE, strlen(TMP_TEMPLATE) + 1); + mkstemp(test->tmp_file_path); + + LV2_State_Make_Path* make_path = NULL; + + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { + test->map = (LV2_URID_Map*)features[i]->data; + test->uris.atom_Float = test->map->map( + test->map->handle, LV2_ATOM__Float); + } else if (!strcmp(features[i]->URI, LV2_STATE__makePath)) { + make_path = (LV2_State_Make_Path*)features[i]->data; + } + } + + if (!test->map) { + fprintf(stderr, "Host does not support urid:map\n"); + free(test); + return NULL; + } + + if (make_path) { + test->rec_file_path = make_path->path(make_path->handle, "recfile"); + if (!(test->rec_file = fopen(test->rec_file_path, "w"))) { + fprintf(stderr, "ERROR: Failed to open rec file\n"); + } + fprintf(test->rec_file, "instantiate\n"); + } + + return (LV2_Handle)test; +} + +static void +run(LV2_Handle instance, + uint32_t sample_count) +{ + Test* test = (Test*)instance; + *test->output = *test->input; + if (sample_count == 1) { + ++test->num_runs; + } else if (sample_count == 2 && test->rec_file) { + // Append to rec file (changes size) + fprintf(test->rec_file, "run\n"); + } else if (sample_count == 3 && test->rec_file) { + // Change the first byte of rec file (doesn't change size) + fseek(test->rec_file, 0, SEEK_SET); + fprintf(test->rec_file, "X"); + fseek(test->rec_file, 0, SEEK_END); + } +} + +static uint32_t +map_uri(Test* plugin, const char* uri) +{ + return plugin->map->map(plugin->map->handle, uri); +} + +static LV2_State_Status +save(LV2_Handle instance, + LV2_State_Store_Function store, + void* callback_data, + uint32_t flags, + const LV2_Feature* const* features) +{ + Test* plugin = (Test*)instance; + + LV2_State_Map_Path* map_path = NULL; + LV2_State_Make_Path* make_path = NULL; + for (int i = 0; features && features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_STATE__mapPath)) { + map_path = (LV2_State_Map_Path*)features[i]->data; + } else if (!strcmp(features[i]->URI, LV2_STATE__makePath)) { + make_path = (LV2_State_Make_Path*)features[i]->data; + } + } + + store(callback_data, + map_uri(plugin, "http://example.org/greeting"), + "hello", + strlen("hello") + 1, + map_uri(plugin, LV2_ATOM__String), + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + const uint32_t urid = map_uri(plugin, "http://example.org/urivalue"); + store(callback_data, + map_uri(plugin, "http://example.org/uri"), + &urid, + sizeof(uint32_t), + map_uri(plugin, LV2_ATOM__URID), + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + store(callback_data, + map_uri(plugin, "http://example.org/num-runs"), + &plugin->num_runs, + sizeof(plugin->num_runs), + map_uri(plugin, LV2_ATOM__Int), + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + const float two = 2.0f; + store(callback_data, + map_uri(plugin, "http://example.org/two"), + &two, + sizeof(two), + map_uri(plugin, LV2_ATOM__Float), + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + const uint32_t affirmative = 1; + store(callback_data, + map_uri(plugin, "http://example.org/true"), + &affirmative, + sizeof(affirmative), + map_uri(plugin, LV2_ATOM__Bool), + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + const uint32_t negative = 0; + store(callback_data, + map_uri(plugin, "http://example.org/false"), + &negative, + sizeof(negative), + map_uri(plugin, LV2_ATOM__Bool), + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + const uint8_t blob[] = "I am a blob of arbitrary data."; + store(callback_data, + map_uri(plugin, "http://example.org/blob"), + blob, + sizeof(blob), + map_uri(plugin, "http://example.org/SomeUnknownType"), + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + if (map_path) { + FILE* file = fopen(plugin->tmp_file_path, "w"); + fprintf(file, "Hello\n"); + fclose(file); + char* apath = map_path->abstract_path(map_path->handle, + plugin->tmp_file_path); + char* apath2 = map_path->abstract_path(map_path->handle, + plugin->tmp_file_path); + if (strcmp(apath, apath2)) { + fprintf(stderr, "ERROR: Path %s != %s\n", apath, apath2); + } + + store(callback_data, + map_uri(plugin, "http://example.org/extfile"), + apath, + strlen(apath) + 1, + map_uri(plugin, LV2_ATOM__Path), + LV2_STATE_IS_POD); + + free(apath); + free(apath2); + + if (plugin->rec_file) { + fflush(plugin->rec_file); + apath = map_path->abstract_path(map_path->handle, + plugin->rec_file_path); + + store(callback_data, + map_uri(plugin, "http://example.org/recfile"), + apath, + strlen(apath) + 1, + map_uri(plugin, LV2_ATOM__Path), + LV2_STATE_IS_POD); + + free(apath); + } + + if (make_path) { + char* spath = make_path->path(make_path->handle, "save"); + FILE* sfile = fopen(spath, "w"); + fprintf(sfile, "save"); + fclose(sfile); + + apath = map_path->abstract_path(map_path->handle, spath); + store(callback_data, + map_uri(plugin, "http://example.org/save-file"), + apath, + strlen(apath) + 1, + map_uri(plugin, LV2_ATOM__Path), + LV2_STATE_IS_POD); + free(apath); + free(spath); + } + } + + return LV2_STATE_SUCCESS; +} + +static LV2_State_Status +restore(LV2_Handle instance, + LV2_State_Retrieve_Function retrieve, + void* callback_data, + uint32_t flags, + const LV2_Feature* const* features) +{ + Test* plugin = (Test*)instance; + + LV2_State_Map_Path* map_path = NULL; + for (int i = 0; features && features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_STATE__mapPath)) { + map_path = (LV2_State_Map_Path*)features[i]->data; + } + } + + size_t size; + uint32_t type; + uint32_t valflags; + + plugin->num_runs = *(int32_t*)retrieve( + callback_data, + map_uri(plugin, "http://example.org/num-runs"), + &size, &type, &valflags); + + if (!map_path) { + return LV2_STATE_ERR_NO_FEATURE; + } + + char* apath = (char*)retrieve( + callback_data, + map_uri(plugin, "http://example.org/extfile"), + &size, &type, &valflags); + + if (valflags != LV2_STATE_IS_POD) { + fprintf(stderr, "error: Restored bad file flags\n"); + return LV2_STATE_ERR_BAD_FLAGS; + } + + if (apath) { + char* path = map_path->absolute_path(map_path->handle, apath); + FILE* f = fopen(path, "r"); + char str[8]; + size_t n_read = fread(str, 1, sizeof(str), f); + fclose(f); + if (strncmp(str, "Hello\n", n_read)) { + fprintf(stderr, "error: Restored bad file contents `%s' != `Hello'\n", + str); + } + free(path); + } + + apath = (char*)retrieve( + callback_data, + map_uri(plugin, "http://example.org/save-file"), + &size, &type, &valflags); + if (apath) { + char* spath = map_path->absolute_path(map_path->handle, apath); + FILE* sfile = fopen(spath, "r"); + if (!sfile) { + fprintf(stderr, "error: Failed to open save file %s\n", spath); + } else { + fclose(sfile); + } + free(spath); + } else { + fprintf(stderr, "error: Failed to restore save file.\n"); + } + + return LV2_STATE_SUCCESS; +} + +static const void* +extension_data(const char* uri) +{ + static const LV2_State_Interface state = { save, restore }; + if (!strcmp(uri, LV2_STATE__interface)) { + return &state; + } + return NULL; +} + +static const LV2_Descriptor descriptor = { + TEST_URI, + instantiate, + connect_port, + NULL, // activate, + run, + NULL, // deactivate, + cleanup, + extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2_Descriptor* lv2_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} diff --git a/test/test.lv2/test.ttl.in b/test/test.lv2/test.ttl.in new file mode 100644 index 0000000..1c16b4c --- /dev/null +++ b/test/test.lv2/test.ttl.in @@ -0,0 +1,46 @@ +# Lilv Test Plugin +# Copyright 2011-2015 David Robillard <d@drobilla.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +@prefix doap: <http://usefulinc.com/ns/doap#> . +@prefix foaf: <http://xmlns.com/foaf/0.1/> . +@prefix lv2: <http://lv2plug.in/ns/lv2core#> . +@prefix ui: <http://lv2plug.in/ns/extensions/ui#> . + +<http://example.org/lilv-test-plugin> + a lv2:Plugin ; + doap:name "Lilv Test" ; + doap:license <http://opensource.org/licenses/isc> ; + lv2:requiredFeature <http://lv2plug.in/ns/ext/urid#Mapper> ; + lv2:optionalFeature lv2:hardRTCapable ; + lv2:extensionData <http://lv2plug.in/ns/ext/state#Interface> ; + lv2:port [ + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "input" ; + lv2:name "Input" + ] , [ + a lv2:OutputPort , + lv2:ControlPort ; + lv2:index 1 ; + lv2:symbol "output" ; + lv2:name "Output" + ] , [ + a lv2:InputPort , + lv2:ControlPort ; + lv2:index 2 ; + lv2:symbol "control" ; + lv2:name "Control" + ] . diff --git a/utils/bench.h b/utils/bench.h new file mode 100644 index 0000000..83d7853 --- /dev/null +++ b/utils/bench.h @@ -0,0 +1,52 @@ +/* + Copyright 2011-2014 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file bench.h A simple real-time benchmarking API. +*/ + +#ifndef BENCH_H +#define BENCH_H + +#define _POSIX_C_SOURCE 200809L + +#include <time.h> +#include <sys/time.h> + +static inline double +bench_elapsed_s(const struct timespec* start, const struct timespec* end) +{ + return ((end->tv_sec - start->tv_sec) + + ((end->tv_nsec - start->tv_nsec) * 0.000000001)); +} + +static inline struct timespec +bench_start(void) +{ + struct timespec start_t; + clock_gettime(CLOCK_REALTIME, &start_t); + return start_t; +} + +static inline double +bench_end(const struct timespec* start_t) +{ + struct timespec end_t; + clock_gettime(CLOCK_REALTIME, &end_t); + return bench_elapsed_s(start_t, &end_t); +} + +#endif /* BENCH_H */ diff --git a/utils/lilv-bench.c b/utils/lilv-bench.c new file mode 100644 index 0000000..c0355df --- /dev/null +++ b/utils/lilv-bench.c @@ -0,0 +1,38 @@ +/* + Copyright 2007-2012 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <stdio.h> + +#include "lilv/lilv.h" + +#include "lilv_config.h" + +int +main(int argc, char** argv) +{ + LilvWorld* world = lilv_world_new(); + lilv_world_load_all(world); + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + LILV_FOREACH(plugins, p, plugins) { + const LilvPlugin* plugin = lilv_plugins_get(plugins, p); + lilv_plugin_get_class(plugin); + } + + lilv_world_free(world); + + return 0; +} diff --git a/utils/lilv.bash_completion b/utils/lilv.bash_completion new file mode 100644 index 0000000..4a553a7 --- /dev/null +++ b/utils/lilv.bash_completion @@ -0,0 +1,59 @@ +# Bash auto-completion script written for lv2info and lv2jack. +# Could be adapted to any other program that takes an +# LV2 plugin URI as parameter. + +# Updated for Lilv by David Robillard <d@drobilla.net> on 2012-01-08. +# Written by Lars Luthman <lars.luthman@gmail.com> on 2009-10-12. +# No copyright claimed for this script. Do what you want with it. + +# For some reason Bash splits the command line not only at whitespace +# but also at ':' signs before putting the parts into COMP_WORDS. +# Since ':' is used in all URIs, which are what we want to complete, +# we have to put the URI back together before we can complete it +# and then cut off the parts we prepended from the completions. +# It probably breaks in some special cases but for most common uses +# it should work fine. + +function _lv2info() { + local uri cur opts w wn raw_reply len type + opts=`lv2ls | xargs -n1 echo -n " "` + + # This is the last "word", as split by Bash. + cur="${COMP_WORDS[COMP_CWORD]}" + w="$cur" + + # Add the previous word while it or this one is a word break character + for i in `seq $(( $COMP_CWORD - 1 )) -1 1`; do + wn="${COMP_WORDS[i]}" + if expr "$COMP_WORDBREAKS" : ".*$wn" > /dev/null; then + if expr "$COMP_WORDBREAKS" : ".*$w" > /dev/null; then + break + fi + fi + w="$wn" + uri="$w$uri" + done + + # Check the length of the words we prepend + len=${#uri} + uri="$uri$cur" + raw_reply="$(compgen -W "${opts}" -- ${uri})" + + # If we are listing alternatives, just print the full URIs. + type=`echo $COMP_TYPE | awk '{ printf "%c", $1 }'` + if expr "?!@%" : ".*$type" > /dev/null; then + COMPREPLY=( $raw_reply ) + return 0 + fi + + # Otherwise, strip the prepended words from all completion suggestions. + COMPREPLY=() + for i in $raw_reply; do + COMPREPLY=( ${COMPREPLY[@]} ${i:len} ) + done +} + +complete -F _lv2info lv2info + +# And the same for lv2jack. +complete -F _lv2info lv2jack diff --git a/utils/lv2apply.c b/utils/lv2apply.c new file mode 100644 index 0000000..66a8c3a --- /dev/null +++ b/utils/lv2apply.c @@ -0,0 +1,354 @@ +/* + Copyright 2007-2016 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <assert.h> +#include <math.h> +#include <sndfile.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "lilv/lilv.h" + +/** Control port value set from the command line */ +typedef struct Param { + const char* sym; ///< Port symbol + float value; ///< Control value +} Param; + +/** Port type (only float ports are supported) */ +typedef enum { + TYPE_CONTROL, + TYPE_AUDIO +} PortType; + +/** Runtime port information */ +typedef struct { + const LilvPort* lilv_port; ///< Port description + PortType type; ///< Datatype + uint32_t index; ///< Port index + float value; ///< Control value (if applicable) + bool is_input; ///< True iff an input port + bool optional; ///< True iff connection optional +} Port; + +/** Application state */ +typedef struct { + LilvWorld* world; + const LilvPlugin* plugin; + LilvInstance* instance; + const char* in_path; + const char* out_path; + SNDFILE* in_file; + SNDFILE* out_file; + unsigned n_params; + Param* params; + unsigned n_ports; + unsigned n_audio_in; + unsigned n_audio_out; + Port* ports; +} LV2Apply; + +static int fatal(LV2Apply* self, int status, const char* fmt, ...); + +/** Open a sound file with error handling. */ +static SNDFILE* +sopen(LV2Apply* self, const char* path, int mode, SF_INFO* fmt) +{ + SNDFILE* file = sf_open(path, mode, fmt); + const int st = sf_error(file); + if (st) { + fatal(self, 1, "Failed to open %s (%s)\n", path, sf_error_number(st)); + return NULL; + } + return file; +} + +/** Close a sound file with error handling. */ +static void +sclose(const char* path, SNDFILE* file) +{ + int st; + if (file && (st = sf_close(file))) { + fatal(NULL, 1, "Failed to close %s (%s)\n", path, sf_error_number(st)); + } +} + +/** + Read a single frame from a file into an interleaved buffer. + + If more channels are required than are available in the file, the remaining + channels are distributed in a round-robin fashion (LRLRL). +*/ +static bool +sread(SNDFILE* file, unsigned file_chans, float* buf, unsigned buf_chans) +{ + const sf_count_t n_read = sf_readf_float(file, buf, 1); + for (unsigned i = file_chans - 1; i < buf_chans; ++i) { + buf[i] = buf[i % file_chans]; + } + return n_read == 1; +} + +/** Clean up all resources. */ +static int +cleanup(int status, LV2Apply* self) +{ + sclose(self->in_path, self->in_file); + sclose(self->out_path, self->out_file); + lilv_instance_free(self->instance); + lilv_world_free(self->world); + free(self->ports); + free(self->params); + return status; +} + +/** Print a fatal error and clean up for exit. */ +static int +fatal(LV2Apply* self, int status, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + fprintf(stderr, "error: "); + vfprintf(stderr, fmt, args); + va_end(args); + return self ? cleanup(status, self) : status; +} + +/** + Create port structures from data (via create_port()) for all ports. +*/ +static int +create_ports(LV2Apply* self) +{ + LilvWorld* world = self->world; + const uint32_t n_ports = lilv_plugin_get_num_ports(self->plugin); + + self->n_ports = n_ports; + self->ports = (Port*)calloc(self->n_ports, sizeof(Port)); + + /* Get default values for all ports */ + float* values = (float*)calloc(n_ports, sizeof(float)); + lilv_plugin_get_port_ranges_float(self->plugin, NULL, NULL, values); + + LilvNode* lv2_InputPort = lilv_new_uri(world, LV2_CORE__InputPort); + LilvNode* lv2_OutputPort = lilv_new_uri(world, LV2_CORE__OutputPort); + LilvNode* lv2_AudioPort = lilv_new_uri(world, LV2_CORE__AudioPort); + LilvNode* lv2_ControlPort = lilv_new_uri(world, LV2_CORE__ControlPort); + LilvNode* lv2_connectionOptional = lilv_new_uri(world, LV2_CORE__connectionOptional); + + for (uint32_t i = 0; i < n_ports; ++i) { + Port* port = &self->ports[i]; + const LilvPort* lport = lilv_plugin_get_port_by_index(self->plugin, i); + + port->lilv_port = lport; + port->index = i; + port->value = isnan(values[i]) ? 0.0f : values[i]; + port->optional = lilv_port_has_property( + self->plugin, lport, lv2_connectionOptional); + + /* Check if port is an input or output */ + if (lilv_port_is_a(self->plugin, lport, lv2_InputPort)) { + port->is_input = true; + } else if (!lilv_port_is_a(self->plugin, lport, lv2_OutputPort) && + !port->optional) { + return fatal(self, 1, "Port %d is neither input nor output\n", i); + } + + /* Check if port is an audio or control port */ + if (lilv_port_is_a(self->plugin, lport, lv2_ControlPort)) { + port->type = TYPE_CONTROL; + } else if (lilv_port_is_a(self->plugin, lport, lv2_AudioPort)) { + port->type = TYPE_AUDIO; + if (port->is_input) { + ++self->n_audio_in; + } else { + ++self->n_audio_out; + } + } else if (!port->optional) { + return fatal(self, 1, "Port %d has unsupported type\n", i); + } + } + + lilv_node_free(lv2_connectionOptional); + lilv_node_free(lv2_ControlPort); + lilv_node_free(lv2_AudioPort); + lilv_node_free(lv2_OutputPort); + lilv_node_free(lv2_InputPort); + free(values); + + return 0; +} + +static void +print_version(void) +{ + printf( + "lv2apply (lilv) " LILV_VERSION "\n" + "Copyright 2007-2016 David Robillard <http://drobilla.net>\n" + "License: <http://www.opensource.org/licenses/isc-license>\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n"); +} + +static int +print_usage(int status) +{ + fprintf(status ? stderr : stdout, + "Usage: lv2apply [OPTION]... PLUGIN_URI\n" + "Apply an LV2 plugin to an audio file.\n\n" + " -i IN_FILE Input file\n" + " -o OUT_FILE Output file\n" + " -c SYM VAL Control value\n" + " --help Display this help and exit\n" + " --version Display version information and exit\n"); + return status; +} + +int +main(int argc, char** argv) +{ + LV2Apply self = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0, NULL + }; + + /* Parse command line arguments */ + const char* plugin_uri = NULL; + for (int i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "--version")) { + free(self.params); + print_version(); + return 0; + } else if (!strcmp(argv[i], "--help")) { + free(self.params); + return print_usage(0); + } else if (!strcmp(argv[i], "-i")) { + self.in_path = argv[++i]; + } else if (!strcmp(argv[i], "-o")) { + self.out_path = argv[++i]; + } else if (!strcmp(argv[i], "-c")) { + if (argc < i + 3) { + return fatal(&self, 1, "Missing argument for -c\n"); + } + self.params = (Param*)realloc(self.params, + ++self.n_params * sizeof(Param)); + self.params[self.n_params - 1].sym = argv[++i]; + self.params[self.n_params - 1].value = atof(argv[++i]); + } else if (argv[i][0] == '-') { + free(self.params); + return print_usage(1); + } else if (i == argc - 1) { + plugin_uri = argv[i]; + } + } + + /* Check that required arguments are given */ + if (!self.in_path || !self.out_path || !plugin_uri) { + free(self.params); + return print_usage(1); + } + + /* Create world and plugin URI */ + self.world = lilv_world_new(); + LilvNode* uri = lilv_new_uri(self.world, plugin_uri); + if (!uri) { + return fatal(&self, 2, "Invalid plugin URI <%s>\n", plugin_uri); + } + + /* Discover world */ + lilv_world_load_all(self.world); + + /* Get plugin */ + const LilvPlugins* plugins = lilv_world_get_all_plugins(self.world); + const LilvPlugin* plugin = lilv_plugins_get_by_uri(plugins, uri); + lilv_node_free(uri); + if (!(self.plugin = plugin)) { + return fatal(&self, 3, "Plugin <%s> not found\n", plugin_uri); + } + + /* Open input file */ + SF_INFO in_fmt = { 0, 0, 0, 0, 0, 0 }; + if (!(self.in_file = sopen(&self, self.in_path, SFM_READ, &in_fmt))) { + return 4; + } + + /* Create port structures */ + if (create_ports(&self)) { + return 5; + } + + if (self.n_audio_in == 0 || + (in_fmt.channels != (int)self.n_audio_in && in_fmt.channels != 1)) { + return fatal(&self, 6, "Unable to map %d inputs to %d ports\n", + in_fmt.channels, self.n_audio_in); + } + + /* Set control values */ + for (unsigned i = 0; i < self.n_params; ++i) { + const Param* param = &self.params[i]; + LilvNode* sym = lilv_new_string(self.world, param->sym); + const LilvPort* port = lilv_plugin_get_port_by_symbol(plugin, sym); + lilv_node_free(sym); + if (!port) { + return fatal(&self, 7, "Unknown port `%s'\n", param->sym); + } + + self.ports[lilv_port_get_index(plugin, port)].value = param->value; + } + + /* Open output file */ + SF_INFO out_fmt = in_fmt; + out_fmt.channels = self.n_audio_out; + if (!(self.out_file = sopen(&self, self.out_path, SFM_WRITE, &out_fmt))) { + return 8; + } + + /* Instantiate plugin and connect ports */ + const uint32_t n_ports = lilv_plugin_get_num_ports(plugin); + float in_buf[self.n_audio_in]; + float out_buf[self.n_audio_out]; + self.instance = lilv_plugin_instantiate( + self.plugin, in_fmt.samplerate, NULL); + for (uint32_t p = 0, i = 0, o = 0; p < n_ports; ++p) { + if (self.ports[p].type == TYPE_CONTROL) { + lilv_instance_connect_port(self.instance, p, &self.ports[p].value); + } else if (self.ports[p].type == TYPE_AUDIO) { + if (self.ports[p].is_input) { + lilv_instance_connect_port(self.instance, p, in_buf + i++); + } else { + lilv_instance_connect_port(self.instance, p, out_buf + o++); + } + } else { + lilv_instance_connect_port(self.instance, p, NULL); + } + } + + /* Ports are now connected to buffers in interleaved format, so we can run + a single frame at a time and avoid having to interleave buffers to + read/write from/to sndfile. */ + + lilv_instance_activate(self.instance); + while (sread(self.in_file, in_fmt.channels, in_buf, self.n_audio_in)) { + lilv_instance_run(self.instance, 1); + if (sf_writef_float(self.out_file, out_buf, 1) != 1) { + return fatal(&self, 9, "Failed to write to output file\n"); + } + } + lilv_instance_deactivate(self.instance); + + return cleanup(0, &self); +} diff --git a/utils/lv2bench.c b/utils/lv2bench.c new file mode 100644 index 0000000..e3eb57f --- /dev/null +++ b/utils/lv2bench.c @@ -0,0 +1,268 @@ +/* + Copyright 2012-2017 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#define _POSIX_C_SOURCE 200809L + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "lilv/lilv.h" +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" + +#include "lilv_config.h" +#include "bench.h" +#include "uri_table.h" + +static LilvNode* atom_AtomPort = NULL; +static LilvNode* atom_Sequence = NULL; +static LilvNode* lv2_AudioPort = NULL; +static LilvNode* lv2_CVPort = NULL; +static LilvNode* lv2_ControlPort = NULL; +static LilvNode* lv2_InputPort = NULL; +static LilvNode* lv2_OutputPort = NULL; +static LilvNode* urid_map = NULL; + +static bool full_output = false; + +static void +print_version(void) +{ + printf( + "lv2bench (lilv) " LILV_VERSION "\n" + "Copyright 2012-2017 David Robillard <http://drobilla.net>\n" + "License: <http://www.opensource.org/licenses/isc-license>\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n"); +} + +static void +print_usage(void) +{ + printf("lv2bench - Benchmark all installed and supported LV2 plugins.\n"); + printf("Usage: lv2bench [OPTIONS] [PLUGIN_URI]\n"); + printf("\n"); + printf(" -b BLOCK_SIZE Specify block size, in audio frames.\n"); + printf(" -f, --full Full plottable output.\n"); + printf(" -h, --help Display this help and exit.\n"); + printf(" -n FRAMES Total number of audio frames to process\n"); + printf(" --version Display version information and exit\n"); +} + +static double +bench(const LilvPlugin* p, uint32_t sample_count, uint32_t block_size) +{ + URITable uri_table; + uri_table_init(&uri_table); + + LV2_URID_Map map = { &uri_table, uri_table_map }; + LV2_Feature map_feature = { LV2_URID_MAP_URI, &map }; + LV2_URID_Unmap unmap = { &uri_table, uri_table_unmap }; + LV2_Feature unmap_feature = { LV2_URID_UNMAP_URI, &unmap }; + const LV2_Feature* features[] = { &map_feature, &unmap_feature, NULL }; + + float* const buf = (float*)calloc(block_size * 2, sizeof(float)); + float* const in = buf; + float* const out = buf + block_size; + if (!buf) { + fprintf(stderr, "Out of memory\n"); + return 0.0; + } + + const size_t atom_capacity = 1024; + + LV2_Atom_Sequence seq_in = { + { sizeof(LV2_Atom_Sequence_Body), + uri_table_map(&uri_table, LV2_ATOM__Sequence) }, + { 0, 0 } }; + + LV2_Atom_Sequence* seq_out = (LV2_Atom_Sequence*)malloc( + sizeof(LV2_Atom_Sequence) + atom_capacity); + + const char* uri = lilv_node_as_string(lilv_plugin_get_uri(p)); + LilvNodes* required = lilv_plugin_get_required_features(p); + LILV_FOREACH(nodes, i, required) { + const LilvNode* feature = lilv_nodes_get(required, i); + if (!lilv_node_equals(feature, urid_map)) { + fprintf(stderr, "<%s> requires feature <%s>, skipping\n", + uri, lilv_node_as_uri(feature)); + free(buf); + uri_table_destroy(&uri_table); + return 0.0; + } + } + + LilvInstance* instance = lilv_plugin_instantiate(p, 48000.0, features); + if (!instance) { + fprintf(stderr, "Failed to instantiate <%s>\n", + lilv_node_as_uri(lilv_plugin_get_uri(p))); + free(buf); + uri_table_destroy(&uri_table); + return 0.0; + } + + const uint32_t n_ports = lilv_plugin_get_num_ports(p); + float* const mins = (float*)calloc(n_ports, sizeof(float)); + float* const maxes = (float*)calloc(n_ports, sizeof(float)); + float* const controls = (float*)calloc(n_ports, sizeof(float)); + lilv_plugin_get_port_ranges_float(p, mins, maxes, controls); + + for (uint32_t index = 0; index < n_ports; ++index) { + const LilvPort* port = lilv_plugin_get_port_by_index(p, index); + if (lilv_port_is_a(p, port, lv2_ControlPort)) { + if (isnan(controls[index])) { + if (!isnan(mins[index])) { + controls[index] = mins[index]; + } else if (!isnan(maxes[index])) { + controls[index] = maxes[index]; + } else { + controls[index] = 0.0; + } + } + lilv_instance_connect_port(instance, index, &controls[index]); + } else if (lilv_port_is_a(p, port, lv2_AudioPort) || + lilv_port_is_a(p, port, lv2_CVPort)) { + if (lilv_port_is_a(p, port, lv2_InputPort)) { + lilv_instance_connect_port(instance, index, in); + } else if (lilv_port_is_a(p, port, lv2_OutputPort)) { + lilv_instance_connect_port(instance, index, out); + } else { + fprintf(stderr, "<%s> port %d neither input nor output, skipping\n", + uri, index); + lilv_instance_free(instance); + free(seq_out); + free(buf); + free(controls); + uri_table_destroy(&uri_table); + return 0.0; + } + } else if (lilv_port_is_a(p, port, atom_AtomPort)) { + if (lilv_port_is_a(p, port, lv2_InputPort)) { + lilv_instance_connect_port(instance, index, &seq_in); + } else { + lilv_instance_connect_port(instance, index, seq_out); + } + } else { + fprintf(stderr, "<%s> port %d has unknown type, skipping\n", + uri, index); + lilv_instance_free(instance); + free(seq_out); + free(buf); + free(controls); + uri_table_destroy(&uri_table); + return 0.0; + } + } + + lilv_instance_activate(instance); + + struct timespec ts = bench_start(); + for (uint32_t i = 0; i < (sample_count / block_size); ++i) { + seq_in.atom.size = sizeof(LV2_Atom_Sequence_Body); + seq_in.atom.type = uri_table_map(&uri_table, LV2_ATOM__Sequence); + seq_out->atom.size = atom_capacity; + seq_out->atom.type = uri_table_map(&uri_table, LV2_ATOM__Chunk); + + lilv_instance_run(instance, block_size); + } + const double elapsed = bench_end(&ts); + + lilv_instance_deactivate(instance); + lilv_instance_free(instance); + free(seq_out); + + uri_table_destroy(&uri_table); + + if (full_output) { + printf("%d %d ", block_size, sample_count); + } + printf("%lf %s\n", elapsed, uri); + + free(buf); + free(controls); + return elapsed; +} + +int +main(int argc, char** argv) +{ + uint32_t block_size = 512; + uint32_t sample_count = (1 << 19); + + int a = 1; + for (; a < argc; ++a) { + if (!strcmp(argv[a], "--version")) { + print_version(); + return 0; + } else if (!strcmp(argv[a], "--help")) { + print_usage(); + return 0; + } else if (!strcmp(argv[a], "-f")) { + full_output = true; + } else if (!strcmp(argv[a], "-n") && (a + 1 < argc)) { + sample_count = atoi(argv[++a]); + } else if (!strcmp(argv[a], "-b") && (a + 1 < argc)) { + block_size = atoi(argv[++a]); + } else if (argv[a][0] != '-') { + break; + } else { + print_usage(); + return 1; + } + } + + const char* const plugin_uri_str = (a < argc ? argv[a++] : NULL); + + LilvWorld* world = lilv_world_new(); + lilv_world_load_all(world); + + atom_AtomPort = lilv_new_uri(world, LV2_ATOM__AtomPort); + atom_Sequence = lilv_new_uri(world, LV2_ATOM__Sequence); + lv2_AudioPort = lilv_new_uri(world, LV2_CORE__AudioPort); + lv2_CVPort = lilv_new_uri(world, LV2_CORE__CVPort); + lv2_ControlPort = lilv_new_uri(world, LV2_CORE__ControlPort); + lv2_InputPort = lilv_new_uri(world, LV2_CORE__InputPort); + lv2_OutputPort = lilv_new_uri(world, LV2_CORE__OutputPort); + urid_map = lilv_new_uri(world, LV2_URID__map); + + if (full_output) { + printf("# Block Samples Time Plugin\n"); + } + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + if (plugin_uri_str) { + LilvNode* uri = lilv_new_uri(world, plugin_uri_str); + bench(lilv_plugins_get_by_uri(plugins, uri), sample_count, block_size); + } else { + LILV_FOREACH(plugins, i, plugins) { + bench(lilv_plugins_get(plugins, i), sample_count, block_size); + } + } + + lilv_node_free(urid_map); + lilv_node_free(lv2_OutputPort); + lilv_node_free(lv2_InputPort); + lilv_node_free(lv2_ControlPort); + lilv_node_free(lv2_CVPort); + lilv_node_free(lv2_AudioPort); + lilv_node_free(atom_Sequence); + lilv_node_free(atom_AtomPort); + + lilv_world_free(world); + + return 0; +} diff --git a/utils/lv2info.c b/utils/lv2info.c new file mode 100644 index 0000000..75a093f --- /dev/null +++ b/utils/lv2info.c @@ -0,0 +1,460 @@ +/* + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <float.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "lv2/lv2plug.in/ns/ext/port-groups/port-groups.h" +#include "lv2/lv2plug.in/ns/ext/presets/presets.h" +#include "lv2/lv2plug.in/ns/ext/event/event.h" + +#include "lilv/lilv.h" + +#include "lilv_config.h" + +#ifdef _MSC_VER +# define isnan _isnan +#endif + +LilvNode* applies_to_pred = NULL; +LilvNode* control_class = NULL; +LilvNode* event_class = NULL; +LilvNode* group_pred = NULL; +LilvNode* label_pred = NULL; +LilvNode* preset_class = NULL; +LilvNode* designation_pred = NULL; +LilvNode* supports_event_pred = NULL; + +static void +print_port(const LilvPlugin* p, + uint32_t index, + float* mins, + float* maxes, + float* defaults) +{ + const LilvPort* port = lilv_plugin_get_port_by_index(p, index); + + printf("\n\tPort %d:\n", index); + + if (!port) { + printf("\t\tERROR: Illegal/nonexistent port\n"); + return; + } + + bool first = true; + + const LilvNodes* classes = lilv_port_get_classes(p, port); + printf("\t\tType: "); + LILV_FOREACH(nodes, i, classes) { + const LilvNode* value = lilv_nodes_get(classes, i); + if (!first) { + printf("\n\t\t "); + } + printf("%s", lilv_node_as_uri(value)); + first = false; + } + + if (lilv_port_is_a(p, port, event_class)) { + LilvNodes* supported = lilv_port_get_value( + p, port, supports_event_pred); + if (lilv_nodes_size(supported) > 0) { + printf("\n\t\tSupported events:\n"); + LILV_FOREACH(nodes, i, supported) { + const LilvNode* value = lilv_nodes_get(supported, i); + printf("\t\t\t%s\n", lilv_node_as_uri(value)); + } + } + lilv_nodes_free(supported); + } + + LilvScalePoints* points = lilv_port_get_scale_points(p, port); + if (points) { + printf("\n\t\tScale Points:\n"); + } + LILV_FOREACH(scale_points, i, points) { + const LilvScalePoint* point = lilv_scale_points_get(points, i); + printf("\t\t\t%s = \"%s\"\n", + lilv_node_as_string(lilv_scale_point_get_value(point)), + lilv_node_as_string(lilv_scale_point_get_label(point))); + } + lilv_scale_points_free(points); + + const LilvNode* sym = lilv_port_get_symbol(p, port); + printf("\n\t\tSymbol: %s\n", lilv_node_as_string(sym)); + + LilvNode* name = lilv_port_get_name(p, port); + printf("\t\tName: %s\n", lilv_node_as_string(name)); + lilv_node_free(name); + + LilvNodes* groups = lilv_port_get_value(p, port, group_pred); + if (lilv_nodes_size(groups) > 0) { + printf("\t\tGroup: %s\n", + lilv_node_as_string(lilv_nodes_get_first(groups))); + } + lilv_nodes_free(groups); + + LilvNodes* designations = lilv_port_get_value(p, port, designation_pred); + if (lilv_nodes_size(designations) > 0) { + printf("\t\tDesignation: %s\n", + lilv_node_as_string(lilv_nodes_get_first(designations))); + } + lilv_nodes_free(designations); + + if (lilv_port_is_a(p, port, control_class)) { + if (!isnan(mins[index])) { + printf("\t\tMinimum: %f\n", mins[index]); + } + if (!isnan(maxes[index])) { + printf("\t\tMaximum: %f\n", maxes[index]); + } + if (!isnan(defaults[index])) { + printf("\t\tDefault: %f\n", defaults[index]); + } + } + + LilvNodes* properties = lilv_port_get_properties(p, port); + if (lilv_nodes_size(properties) > 0) { + printf("\t\tProperties: "); + } + first = true; + LILV_FOREACH(nodes, i, properties) { + if (!first) { + printf("\t\t "); + } + printf("%s\n", lilv_node_as_uri(lilv_nodes_get(properties, i))); + first = false; + } + if (lilv_nodes_size(properties) > 0) { + printf("\n"); + } + lilv_nodes_free(properties); +} + +static void +print_plugin(LilvWorld* world, + const LilvPlugin* p) +{ + LilvNode* val = NULL; + + printf("%s\n\n", lilv_node_as_uri(lilv_plugin_get_uri(p))); + + val = lilv_plugin_get_name(p); + if (val) { + printf("\tName: %s\n", lilv_node_as_string(val)); + lilv_node_free(val); + } + + const LilvPluginClass* pclass = lilv_plugin_get_class(p); + const LilvNode* class_label = lilv_plugin_class_get_label(pclass); + if (class_label) { + printf("\tClass: %s\n", lilv_node_as_string(class_label)); + } + + val = lilv_plugin_get_author_name(p); + if (val) { + printf("\tAuthor: %s\n", lilv_node_as_string(val)); + lilv_node_free(val); + } + + val = lilv_plugin_get_author_email(p); + if (val) { + printf("\tAuthor Email: %s\n", lilv_node_as_uri(val)); + lilv_node_free(val); + } + + val = lilv_plugin_get_author_homepage(p); + if (val) { + printf("\tAuthor Homepage: %s\n", lilv_node_as_uri(val)); + lilv_node_free(val); + } + + if (lilv_plugin_has_latency(p)) { + uint32_t latency_port = lilv_plugin_get_latency_port_index(p); + printf("\tHas latency: yes, reported by port %d\n", latency_port); + } else { + printf("\tHas latency: no\n"); + } + + printf("\tBundle: %s\n", + lilv_node_as_uri(lilv_plugin_get_bundle_uri(p))); + + const LilvNode* binary_uri = lilv_plugin_get_library_uri(p); + if (binary_uri) { + printf("\tBinary: %s\n", + lilv_node_as_uri(lilv_plugin_get_library_uri(p))); + } + + LilvUIs* uis = lilv_plugin_get_uis(p); + if (lilv_nodes_size(uis) > 0) { + printf("\tUIs:\n"); + LILV_FOREACH(uis, i, uis) { + const LilvUI* ui = lilv_uis_get(uis, i); + printf("\t\t%s\n", lilv_node_as_uri(lilv_ui_get_uri(ui))); + + const char* binary = lilv_node_as_uri(lilv_ui_get_binary_uri(ui)); + + const LilvNodes* types = lilv_ui_get_classes(ui); + LILV_FOREACH(nodes, t, types) { + printf("\t\t\tClass: %s\n", + lilv_node_as_uri(lilv_nodes_get(types, t))); + } + + if (binary) { + printf("\t\t\tBinary: %s\n", binary); + } + + printf("\t\t\tBundle: %s\n", + lilv_node_as_uri(lilv_ui_get_bundle_uri(ui))); + } + } + lilv_uis_free(uis); + + printf("\tData URIs: "); + const LilvNodes* data_uris = lilv_plugin_get_data_uris(p); + bool first = true; + LILV_FOREACH(nodes, i, data_uris) { + if (!first) { + printf("\n\t "); + } + printf("%s", lilv_node_as_uri(lilv_nodes_get(data_uris, i))); + first = false; + } + printf("\n"); + + /* Required Features */ + + LilvNodes* features = lilv_plugin_get_required_features(p); + if (features) { + printf("\tRequired Features: "); + } + first = true; + LILV_FOREACH(nodes, i, features) { + if (!first) { + printf("\n\t "); + } + printf("%s", lilv_node_as_uri(lilv_nodes_get(features, i))); + first = false; + } + if (features) { + printf("\n"); + } + lilv_nodes_free(features); + + /* Optional Features */ + + features = lilv_plugin_get_optional_features(p); + if (features) { + printf("\tOptional Features: "); + } + first = true; + LILV_FOREACH(nodes, i, features) { + if (!first) { + printf("\n\t "); + } + printf("%s", lilv_node_as_uri(lilv_nodes_get(features, i))); + first = false; + } + if (features) { + printf("\n"); + } + lilv_nodes_free(features); + + /* Extension Data */ + + LilvNodes* data = lilv_plugin_get_extension_data(p); + if (data) { + printf("\tExtension Data: "); + } + first = true; + LILV_FOREACH(nodes, i, data) { + if (!first) { + printf("\n\t "); + } + printf("%s", lilv_node_as_uri(lilv_nodes_get(data, i))); + first = false; + } + if (data) { + printf("\n"); + } + lilv_nodes_free(data); + + /* Presets */ + + LilvNodes* presets = lilv_plugin_get_related(p, preset_class); + if (presets) { + printf("\tPresets: \n"); + } + LILV_FOREACH(nodes, i, presets) { + const LilvNode* preset = lilv_nodes_get(presets, i); + lilv_world_load_resource(world, preset); + LilvNodes* titles = lilv_world_find_nodes( + world, preset, label_pred, NULL); + if (titles) { + const LilvNode* title = lilv_nodes_get_first(titles); + printf("\t %s\n", lilv_node_as_string(title)); + lilv_nodes_free(titles); + } else { + fprintf(stderr, "Preset <%s> has no rdfs:label\n", + lilv_node_as_string(lilv_nodes_get(presets, i))); + } + } + lilv_nodes_free(presets); + + /* Ports */ + + const uint32_t num_ports = lilv_plugin_get_num_ports(p); + float* mins = (float*)calloc(num_ports, sizeof(float)); + float* maxes = (float*)calloc(num_ports, sizeof(float)); + float* defaults = (float*)calloc(num_ports, sizeof(float)); + lilv_plugin_get_port_ranges_float(p, mins, maxes, defaults); + + for (uint32_t i = 0; i < num_ports; ++i) { + print_port(p, i, mins, maxes, defaults); + } + + free(mins); + free(maxes); + free(defaults); +} + +static void +print_version(void) +{ + printf( + "lv2info (lilv) " LILV_VERSION "\n" + "Copyright 2007-2014 David Robillard <http://drobilla.net>\n" + "License: <http://www.opensource.org/licenses/isc-license>\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n"); +} + +static void +print_usage(void) +{ + printf( + "Usage: lv2info [OPTION]... PLUGIN_URI\n" + "Print information about an installed LV2 plugin.\n\n" + " -p FILE Write Turtle description of plugin to FILE\n" + " -m FILE Add record of plugin to manifest FILE\n" + " --help Display this help and exit\n" + " --version Display version information and exit\n\n" + "For -p and -m, Turtle files are appended to (not overwritten),\n" + "and @prefix directives are only written if the file was empty.\n" + "This allows several plugins to be added to a single file.\n"); +} + +int +main(int argc, char** argv) +{ + if (argc == 1) { + print_usage(); + return 1; + } + + const char* plugin_file = NULL; + const char* manifest_file = NULL; + const char* plugin_uri = NULL; + for (int i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "--version")) { + print_version(); + return 0; + } else if (!strcmp(argv[i], "--help")) { + print_usage(); + return 0; + } else if (!strcmp(argv[i], "-p")) { + plugin_file = argv[++i]; + } else if (!strcmp(argv[i], "-m")) { + manifest_file = argv[++i]; + } else if (argv[i][0] == '-') { + print_usage(); + return 1; + } else if (i == argc - 1) { + plugin_uri = argv[i]; + } + } + + int ret = 0; + + LilvWorld* world = lilv_world_new(); + lilv_world_load_all(world); + + LilvNode* uri = lilv_new_uri(world, plugin_uri); + if (!uri) { + fprintf(stderr, "Invalid plugin URI\n"); + lilv_world_free(world); + return 1; + } + + applies_to_pred = lilv_new_uri(world, LV2_CORE__appliesTo); + control_class = lilv_new_uri(world, LILV_URI_CONTROL_PORT); + event_class = lilv_new_uri(world, LILV_URI_EVENT_PORT); + group_pred = lilv_new_uri(world, LV2_PORT_GROUPS__group); + label_pred = lilv_new_uri(world, LILV_NS_RDFS "label"); + preset_class = lilv_new_uri(world, LV2_PRESETS__Preset); + designation_pred = lilv_new_uri(world, LV2_CORE__designation); + supports_event_pred = lilv_new_uri(world, LV2_EVENT__supportsEvent); + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + const LilvPlugin* p = lilv_plugins_get_by_uri(plugins, uri); + + if (p && plugin_file) { + LilvNode* base = lilv_new_file_uri(world, NULL, plugin_file); + + FILE* plugin_fd = fopen(plugin_file, "a"); + if (plugin_fd) { + lilv_plugin_write_description(world, p, base, plugin_fd); + fclose(plugin_fd); + } else { + fprintf(stderr, "error: Failed to open %s\n", plugin_file); + } + + if (manifest_file) { + FILE* manifest_fd = fopen(manifest_file, "a"); + if (manifest_fd) { + lilv_plugin_write_manifest_entry( + world, p, base, manifest_fd, plugin_file); + fclose(manifest_fd); + } else { + fprintf(stderr, "error: Failed to open %s\n", manifest_file); + } + } + lilv_node_free(base); + } else if (p) { + print_plugin(world, p); + } else { + fprintf(stderr, "Plugin not found.\n"); + } + + ret = (p != NULL ? 0 : -1); + + lilv_node_free(uri); + + lilv_node_free(supports_event_pred); + lilv_node_free(designation_pred); + lilv_node_free(preset_class); + lilv_node_free(label_pred); + lilv_node_free(group_pred); + lilv_node_free(event_class); + lilv_node_free(control_class); + lilv_node_free(applies_to_pred); + + lilv_world_free(world); + return ret; +} + diff --git a/utils/lv2ls.c b/utils/lv2ls.c new file mode 100644 index 0000000..1f007f1 --- /dev/null +++ b/utils/lv2ls.c @@ -0,0 +1,93 @@ +/* + Copyright 2007-2012 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include <stdio.h> +#include <string.h> + +#include "lilv/lilv.h" + +#include "lilv_config.h" + +static void +list_plugins(const LilvPlugins* list, bool show_names) +{ + LILV_FOREACH(plugins, i, list) { + const LilvPlugin* p = lilv_plugins_get(list, i); + if (show_names) { + LilvNode* n = lilv_plugin_get_name(p); + printf("%s\n", lilv_node_as_string(n)); + lilv_node_free(n); + } else { + printf("%s\n", lilv_node_as_uri(lilv_plugin_get_uri(p))); + } + } +} + +static void +print_version(void) +{ + printf( + "lv2ls (lilv) " LILV_VERSION "\n" + "Copyright 2007-2012 David Robillard <http://drobilla.net>\n" + "License: <http://www.opensource.org/licenses/isc-license>\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n"); +} + +static void +print_usage(void) +{ + printf("Usage: lv2ls [OPTION]...\n"); + printf("List all installed LV2 plugins.\n"); + printf("\n"); + printf(" -n, --names Show names instead of URIs\n"); + printf(" --help Display this help and exit\n"); + printf(" --version Display version information and exit\n"); + printf("\n"); + printf("The environment variable LV2_PATH can be used to control where\n"); + printf("this (and all other lilv based LV2 hosts) will search for plugins.\n"); +} + +int +main(int argc, char** argv) +{ + bool show_names = false; + for (int i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "--names") || !strcmp(argv[i], "-n")) { + show_names = true; + } else if (!strcmp(argv[i], "--version")) { + print_version(); + return 0; + } else if (!strcmp(argv[i], "--help")) { + print_usage(); + return 0; + } else { + print_usage(); + return 1; + } + } + + LilvWorld* world = lilv_world_new(); + lilv_world_load_all(world); + + const LilvPlugins* plugins = lilv_world_get_all_plugins(world); + + list_plugins(plugins, show_names); + + lilv_world_free(world); + + return 0; +} diff --git a/utils/uri_table.h b/utils/uri_table.h new file mode 100644 index 0000000..880d560 --- /dev/null +++ b/utils/uri_table.h @@ -0,0 +1,73 @@ +/* + Copyright 2011-2012 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/** + @file uri_table.h A toy URI map/unmap implementation. + + This file contains function definitions and must only be included once. +*/ + +#ifndef URI_TABLE_H +#define URI_TABLE_H + +typedef struct { + char** uris; + size_t n_uris; +} URITable; + +static void +uri_table_init(URITable* table) +{ + table->uris = NULL; + table->n_uris = 0; +} + +static void +uri_table_destroy(URITable* table) +{ + free(table->uris); +} + +static LV2_URID +uri_table_map(LV2_URID_Map_Handle handle, + const char* uri) +{ + URITable* table = (URITable*)handle; + for (size_t i = 0; i < table->n_uris; ++i) { + if (!strcmp(table->uris[i], uri)) { + return i + 1; + } + } + + const size_t len = strlen(uri); + table->uris = (char**)realloc(table->uris, ++table->n_uris * sizeof(char*)); + table->uris[table->n_uris - 1] = (char*)malloc(len + 1); + memcpy(table->uris[table->n_uris - 1], uri, len + 1); + return table->n_uris; +} + +static const char* +uri_table_unmap(LV2_URID_Map_Handle handle, + LV2_URID urid) +{ + URITable* table = (URITable*)handle; + if (urid > 0 && urid <= table->n_uris) { + return table->uris[urid - 1]; + } + return NULL; +} + +#endif /* URI_TABLE_H */ @@ -1,16 +1,169 @@ #!/usr/bin/env python +# encoding: latin-1 +# Thomas Nagy, 2005-2018 +# +""" +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: -# Minimal waf script for projects that include waflib directly +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. -from waflib import Context, Scripting +2. 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. -import inspect -import os +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. -def main(): - script_path = os.path.abspath(inspect.getfile(inspect.getmodule(main))) - project_path = os.path.dirname(script_path) - Scripting.waf_entry_point(os.getcwd(), Context.WAFVERSION, project_path) +THIS SOFTWARE IS PROVIDED BY THE AUTHOR "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 AUTHOR 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. +""" + +import os, sys, inspect + +VERSION="2.0.9" +REVISION="7ea8f0d5002fd7cec19aa8d10c4784ce" +GIT="x" +INSTALL='' +C1='#W' +C2='#3' +C3='#0' +cwd = os.getcwd() +join = os.path.join + + +WAF='waf' +def b(x): + return x +if sys.hexversion>0x300000f: + WAF='waf3' + def b(x): + return x.encode() + +def err(m): + print(('\033[91mError: %s\033[0m' % m)) + sys.exit(1) + +def unpack_wafdir(dir, src): + f = open(src,'rb') + c = 'corrupt archive (%d)' + while 1: + line = f.readline() + if not line: err('run waf-light from a folder containing waflib') + if line == b('#==>\n'): + txt = f.readline() + if not txt: err(c % 1) + if f.readline() != b('#<==\n'): err(c % 2) + break + if not txt: err(c % 3) + txt = txt[1:-1].replace(b(C1), b('\n')).replace(b(C2), b('\r')).replace(b(C3), b('\x00')) + + import shutil, tarfile + try: shutil.rmtree(dir) + except OSError: pass + try: + for x in ('Tools', 'extras'): + os.makedirs(join(dir, 'waflib', x)) + except OSError: + err("Cannot unpack waf lib into %s\nMove waf in a writable directory" % dir) + + os.chdir(dir) + tmp = 't.bz2' + t = open(tmp,'wb') + try: t.write(txt) + finally: t.close() + + try: + t = tarfile.open(tmp) + except: + try: + os.system('bunzip2 t.bz2') + t = tarfile.open('t') + tmp = 't' + except: + os.chdir(cwd) + try: shutil.rmtree(dir) + except OSError: pass + err("Waf cannot be unpacked, check that bzip2 support is present") + + try: + for x in t: t.extract(x) + finally: + t.close() + + for x in ('Tools', 'extras'): + os.chmod(join('waflib',x), 493) + + if sys.hexversion<0x300000f: + sys.path = [join(dir, 'waflib')] + sys.path + import fixpy2 + fixpy2.fixdir(dir) + + os.remove(tmp) + os.chdir(cwd) + + try: dir = unicode(dir, 'mbcs') + except: pass + try: + from ctypes import windll + windll.kernel32.SetFileAttributesW(dir, 2) + except: + pass + +def test(dir): + try: + os.stat(join(dir, 'waflib')) + return os.path.abspath(dir) + except OSError: + pass + +def find_lib(): + src = os.path.abspath(inspect.getfile(inspect.getmodule(err))) + base, name = os.path.split(src) + + #devs use $WAFDIR + w=test(os.environ.get('WAFDIR', '')) + if w: return w + + #waf-light + if name.endswith('waf-light'): + w = test(base) + if w: return w + err('waf-light requires waflib -> export WAFDIR=/folder') + + dirname = '%s-%s-%s' % (WAF, VERSION, REVISION) + for i in (INSTALL,'/usr','/usr/local','/opt'): + w = test(i + '/lib/' + dirname) + if w: return w + + #waf-local + dir = join(base, (sys.platform != 'win32' and '.' or '') + dirname) + w = test(dir) + if w: return w + + #unpack + unpack_wafdir(dir, src) + return dir + +wafdir = find_lib() +sys.path.insert(0, wafdir) if __name__ == '__main__': - main() + + from waflib import Scripting + Scripting.waf_entry_point(cwd, VERSION, wafdir) + +#==> +#BZh91AY&SYòqK¦lÿÿÿ°ðÿÿÿÿÿÿÿÿÿÿÿÿۄ 0˜(bKü÷½»�#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0}^úË«m†0 îÅ.ÕR•¶¨Õów2æ¦Óc[#0«’÷uW¬Ü¾ñèíîçg§¯íʧm¬Ù©»™Ý¹WMUõï}»¾Û‚dë;¤”2#0ëªU–ÝÝl·l4Td:è÷›ÞõëÒQ 7¾o'Zã<ÛžÞñÓm³XU‡»·Ý¾}žð»¸“ïµÖ¯»³×v‡‘×Þ×c6ï¾W#0#0#0#0#0zÐ#0#3(=}às #0}÷À¦íŒE7L4îmP탉£šØУ :ÐqËwÛhÝš%44)è(CBªºë€%)UR”#0vâIE* Š(€¥:ÞÜBóëÓÞ¾íÓÞïxGן>×}Ç=ïWºÑ^örmŒjÙ·§"]²÷g^ùì¯#Üuv€æû&ª×Ÿ3]½WJóK¦™îqéìÉíç2zÛÛ®ö˽÷zÎÓÙÓ‘;ϼôïs·ÓÞç5îdŸo¢w—m¡ª#WÝ°»fƒ×#WB¤€›#W½·ftжêGëÇ=¯rî2ŠtÑ®–24ã#WQ*›^÷²Þh#0 ¤¥@½™/L:hô:€#0ÝÜ…vÎóÝëÛvÝïP¯lo¾ï¶óîñ¯½ƒÐèötO_nœ‘#3´klÓ¶«ãA¶÷®áVÌ÷pøí|{ é¯/;ܯ¹ÖßxwŒõëèé.÷ÞÁíž5ôÞ—V‹šîg@!'ÝÇ¥÷rù@u̶ÏòÓçÛëzMm+º;›vÓs¸¸[uíæͳxãœÛºÝÛvîÕ½{×»•Úß0_7¶ûw+£#W”ê·nk®Ý{q÷›{;˜úﶸG.ض¸·¯»×÷Ø[;WqÞ۽ϚOi¸«Dß>ê{žéìvÍÞ½ìŸn½kg±}Â÷zÛVK«Ú»Þµ¶<Ž/võIæ½Ó4vžÇ¼9Û¸#0;]5盧OswÜÓ½ÝÏ<Ý*U(#W#Wˆ¡ÛRØ+FíÕm—µš*ÐTS·ko¥=ÀÖÝ2#;4÷o4zÒºžËÛI+ºôê¹áï2•O{×»Ú΄ì«o[ºÜx#0#0ºçW\€#0%kÞÛµìî¾}œ÷Åê‹\”r¨÷vû½ï\èÅìÇ{·Þc´»S½±½Ói¨l{7[]¯ŸYò÷³Û³¡»‡8š¹ÑQv¸ë´ío|ÏkŸ÷×ß|ÕÝ>#0•NŒº¶onÃ×*¼LÝ4l¹©wÝþ7Ü1o"ìûn{ñMÍ«ÝÎCîë›ÆãŒ6öî>¤æ®uÓỞ]ŽÜ¦ÛŽg&Á°-±4ïg¦Ø¦îõî}d®o·Û°s jÖ³Nx°ç¶.ÝÇÞÍ;»Jëžî`#0¢zScCsmÛ.ö놞ŽçG]7z—{½8Tö™n{Í=ç[}ŸFûÎs¹ÛW%Kkîl7®ûÛãçfƒK`t#0½´([¶!]w\öyÐè3«Áp;´©!¸Ýhï^ï¯O lò£Ð:ݶúvÝÀgÒº(#3˜qå^s»ï‰ÝÏw›€.Í#3#3#0#3µ-î¯{nâ€#¼ÅÆwIÝ×G Föà´Ówf›¹:r‹ZTT”Q—#3t´¤Ó»nv©Ku15Ü{¢ÞÞìI«9¼áFñÎtžùr¼@û¢Y|ÔjíÓÀD-bi&Ù{zñæfÞ7…—7»-»RÞçsÃMî9ç³n̯mmw{põñtàùx¹èèúæe㛆š L€ #0€Ð#0ŒM21M4Ñ4ji¡OÔž¡åI¡²ê3Mª MA4hL†”õG´§“Õ=F&F††#0#0#0#0#0#0#0H$B@šÑ”Å=‘*?ÔÑ“f%=¥?E4I§©§©¦ž ôš#0#04#0#0OT¤ˆ™#Fš#Wy#Hò#ÔÐÉ£# Ð&h4ah#0#0h#0#0I@ #04#0Œ&JŸ¦x6#0#0#0#0#0$ÔDh4#W§æˆÄCÕ4õ?TzŸ¤Êª=¢G¤€ #0#0ú?þÏõí«]¸‡û]ÝZëöv<“˜(¯ŽÕ®è’2"¬$Jˆ‚€*y+Q!ýGëýŸ•Ÿ–þ?”®n뙵¾ i4Þj捻wR<\Ö*Ç.êjêÖ.3þì_#þIU»@…^¸ !©AÕhhÛ#0Ö·Ó6Œj6M/¥Áú··¥*±OZ1MDNG˜ÃÜÓª|97TCÅ*“‡qTÞ%Jéð÷z3†fìåøýD?DÛiµKi–ÊåukKj+i*Õ&Ú³5¶“kV/Kmr–¢@PiA" µAj2tr¢Â$PSB±˜#0P$r"*"ƒb#W„ {`ÊŠ*”H,€È¸*”*Šµª·Â¶Z™™²Q™¨Óa‚%Š#W$šMLĄ̈MS,dcJQ¥ÛI,$&h’€š2¥£lj-£4–M,ŒFM£ŠŒFDÚR‚XÔ±¡LXÄÆ*P*LØÒE&ÉhÚˆ¡,²Ò˜ÊÑTD,´†ÍÁe&i”b±¨ÔVŒA¤ÙBT›Æ’0Ѧi–‘Œ¥+”£a-¦Õ³V´ RT3bM™&BhŠ²m¥Vi´ ©(ÙIS[j[flËI”ÒRFˆØ, Ù«04RJXÐRÈ¢Æ)˜Ú&TY¤V)0–*$±±I©#4¨Ä)ŒQ±B‚De(À´2#$©0²ÄA3BQ"„C)š4¬Ðš2 h#dQ²k%cFÄ0´‹Ée!#cYJHLQ%&F#WQ!“Bˆf$&"Q$¬€…F,I‰™M¦"™€lm’Š LfšfÂf…ˆÚ“`l¬!I)6+Z( ¤„±"hÐE1D °lDÃ0ÆIHF4&RJeb̨ØÖÒjH’b iJ$ ‘d¶ $‹,Ê)dš&̤FÆ"M‰‘£6DÁ2©¦2B4¥‚ÍEHli¤,b#R²Y#`ÙRDŠY$$P"$Zi©Æ‹P„’̦Ê2ƒŠfiD`ˆ"šX¢m©‚ÄHiA¤³Å’’È’b,LŠQFŒÂf¢š˜¨6e E&cI#FŒDH™a1A$Ê™š³*”ª%3!©bfÅJe4T‘¢ˆ1I$›$Q¨Ñ’¦È´Í6"šL¤Ì¦)defÓ"V"0D”I¦¦“LRÊ#Hjh´ÛFÍJ”É…11!²2FL’•€Ñc%b1”e$1lmŒI&‰4”e5&¢É$PÒ6ÄcËf“h4h©ƒ4Âi2Š"ƙّ$¦ÉL(&l™ k6V-a,DÄše’$±mÆ Ö"™l)@”cb‘ Â2IbѦLÓJ`Ù+)¥0RkI„“¤Æa#WdÙ”Á"›*4ËSJ‚„È™‘F‰¶ÔªÚ2†JÓ,e3šQI)˜ˆ¥-K4YŠM1”%–TÙdØ‚-L¶Í˜b›” 2Xi M¯Úë¶È„ÔÕ Œ1ŒÚ(ÅF¶ ’hÒj-d‘K)¢ŠT4kDÒÒ„T“Qµ‘¨Ôe“FH²˜ Rc‘™šSlʱ™¥BØÚŠBÑ“L"Xʤ“22£BÆÒ#W¬ÑL™2ÒmÆʱ¶)*l¡Y,Š¦5šŠaM›I™¶Ki¢cFÖ‹DkdÓ5’J™e6²kE6ÊRšcf5©HÉHÚ‹ÖX“IR&#Wj#hªÁUB£Em*"ÆÖ-¤ÖJ-¨Ú**J5¢h‰’*‹h‹hØe£k ÚM%#3–¨#Wa%‚m*&&BÓ$Ä5ƒXѴĶ5I`Ñm$kTÖÛ6iU„Êf¦¬š$Ù‰MB(²F¶¥¬Û¥6›ešTÆi³j™m¥RÈ™š¥HÅšh[C ÌÉ2ÙcBi”†’ɶVT…£Z%”c‚0‘QbCLƃ˜±`a`b$H†ÐXLb-¦j™ QC%´•’icdÉ¢‘eŠE6R‘š‘Dˆh”Ì’Â’‹1¦`bjZ6̈ÊÅ6“%dJB3L‰’B#2`,(Q”M¦ ²A‰…¤ ƃ%´X¢iLŠb̳JØLbPY1Ša)d™d„ÔZ2b£LµQ³)$£ …Œ…Qe+#L6Š“’Œ›#’-,$Q’±¤H@hÒŒšM&̦̤¥™YJJÅ0hh²F¢Ô’´U5£4Ea¢¥4›#31²Æ³(ÚFB2Å ÓbJ“i2…)¦*H))l”Ê ªeM’ÉRe Š‰4’"%#3(É´É-£["(„S3 e,ƒ #3›1DC&ÄÍ#b5šMÌDؘ‰©! ´hÑl%EF¢ŒjÆe°ÒaŠPÈR4²dlb”Al ¨+,Ù,†Bˆ,Z3%6D•$A¶Š€ŠÆ¨¬h“FÁ)Ti(6("2Fɪ-“ D0°ƒT¥#3Li†T¥Qª#Ji¥¢‹dÛ2KD‹Qc#)j¥¦Š¢ØÒ`ˆ²X’)¦R³#WÅ” $©±(‰•µV 1S5%¢Æ¢Í-FJŤÅ%f•HÍ´m@Ù%2”Ô–$‚ÅIFÉ¢HŒIT”†±I™bÖd#35R¦TÄSmIY-™!M$Ôh"ŒMšÒmÊIb¤Æ“0FŒX#WI©DhÚYh´mbKmhÊQ´•R …¨ÛF¤¢Å"š$¦F`Li©b53d™µŠ¤±j’¥•dքƤ(¨¢HÛb™¶imE±hÖ£bÛI™3e–[+mE™QµM•¥©E1©(22“S%cE‚$Ñ¢ŒM&m¶53bH±I[MJi¬µc4£&4Y,mm²¶±H™´MLIF1PÂÄQ±M$21’U±m‹LÕ´cZËISC-l¥“mMM²Ôi«FQDEŒÑY%#3f¤ÔmŒÍ›*"„ƒQ’L¤™‘Dd#W¤¶ŠelÇõ¿Öû«úoé¿ž=•½·+û¨?ßÑÖÁÿMN&Z›$Ñ8$Ý?ˆ.b®V#3MŸDâ¯ê÷cxæþ§õ*a&ƒ*–’¨ÿ·|qOõûÊчnJwÿ~q‰FƒJƒÃ£Œ#WX.”UPUU$ÁTMÿ6¦‰ÃðU#þÍ!ŸüþÛ?ê$”+ÎLdeòK ‹ìç³GûV-¹Áî¬MƒCÀáëˆk§àë-,“ºêrf[f[ºW'$3mIH¡²N(mÉÉcþzݶŸ.#0Ù?ÕH׿ Õjj$v—†DÚôïMtg³\òrhë²wÒjE¨Í]00aZ#mÉ#+…ú\39«‘6ý®b·»äÞíì¾Ìis,#WJB˜€¡"F)&×mÔÈ{Ò3ˆ`Wd@ÛL9j4˜Ù^›šKÉ(½yëÍz^» Öö¯üVX™ ná¦0Q`°wkz¢Ò[-¡J'T¦ŠïA)÷ÿ²´CµâôÅj&]›E0…6…"ÉßÇ%cûÜ’)+ÁXÁDîT™â$£Ç÷n\P?Ö4{4Q÷}0 ÆÍÌŸóÑ×·G+›í*+9P›M¿xcXß ÖVA#3´õ,…ÉŽ\º.â™\Û¥ÔUͺyÝ6*'ÛuôKol[õwWÍ¿a^¹®E3_"¶ñsxéúÍÛWŽ\Ü/Ï«]u‡„öžd¨ŒÛ1é§dCm´ÚÑçm{·§»UsI´guÊ<sçÛmN#Wª#WCLÕ‰hS4B˜,ZQ®¹]çv.QÒ¹s%ºî’G9¯ÊÛ^^vÿvþUÞ_Û|Eƒ¡EH‹ C¡‡vØ˦N#-X,ú¨¤OJ#3?{S•µ?7¦$2ð`‰_ðT¤6dç¦U€¨â¡þ”Õ1È0È O²¦.eü’Ò#WÄ[ß_L@ãš:¿Òùm‹þã%#3ÑMYl-Ö¨·TÈØÓNÖGMzmóØä‘A‘LÏZ ¢¢Œp.Úó‰óÉ÷êû(ùc“†«ÛÄÂ5áð×¹š08 ²Kç^Ë®Ë3ÅmçáïW“[WņqBÊAåU§Ã_¶ýó¾†rvnÖéɯª¡Lciœ„dŒ±•:%ÝnÍo%“ñq‹nY0™ÙêÂÉPïÆ…ã4<t,´ËSaiÑ¥@çãî~¬Tê®R.‰«,ª Je'Ù‹´8È1¤Þ2Iü™B¼&Ÿ.°²"w^ÝçŸç+ëo&-étÌþ“´3$PN4QåEŽ‡Ê‚Ä7Ím‰Z¾)u94õuº {¹î¯=tõŽ™Ë@Šù37¶•‰æÊ׺³ST(ˆ«Æå“Q¼rÔU¿¯ýŸo$X=í2B’#»¿ùì:fˆ°{«siBéøͯJ6DÆ1$š’“àæåw»¶ÒéÊRtNÜPL ªrºÊREÂR¨ÁJMÍE¶ó¿6õêò iI-¤¨{ÝËŠýô·^îÇß×Ûr£Mq-=ÂVµT-‚ȤD@ú_…^1h4–û4XøõùüØÃíÉôoš°àqdDh#Ž"TcýU>¥†QÄÒ¶³f~ &A„}OÑÖݘM²kô®I~í_C¾‡G0®¼,/#3A#”… I#3›H·AI˦:×Òå—ãuo·M_åÚôÐô£Í1ºQL¡—UiH¯fõk#:¹äè›ùVS“–[4ï«)"£N)³#3D-š´D•Qg:Ä®ÂãìŠ4xh-´´Õ¼ÛÞ^* ÆÏhEúœ+^7%±ZZ)uIÁž¬Âˆ%%HzE#0gÆFŸ™÷;õ@v„mŸT¼I¬PŽVk‡{¾%†*°ãŒ“7Ýþ{«ŸÊX„¶Vˆn›8@„»šQRA‡t_œ5]°ì{D#3цyàÃÁ¡C¨Â€%&aFeS?C§Ö¯Ë¾žïQÑ;Ž¡¨@Œ†xh‡üó}(¾ÝÀ3Úw©á†?GËUi‘¹Æ1üœ’ãT©µtª>Üìø_…œ*ŽC×%e+§Žoá’šˆJCVÊÜź|l-¸¬zùL$¹crFI¦‰g{»-ÙHŸÔî~)®ï2òçÁe2Uìõ.É‘Ñ Ê=ZÏÙÛJwÚWu¡Å‡WÇ1º#3ÓNmk(S½?g[ëñy:šŒà˜°¤á\`~Û2×£ç5åž+’ù§T-N ðÎ1'ËÞé.I¢Cµ?<YVŸ#0pBx°ÔNñ~)îf0Y´C{†²}êÝ*“6[ó®Û "õeÚ?®1˜ëLÑ“—£hÔ˜†t¾©Ò‡Î\_„Mz}ÜMïuXaãHÕČᗹçZ=û{Êeƒr~eHÄþéŽÈõßæh+öíqÆ¡Uaṳ̀gNúµÙPÝ(Ibâ¨FG÷ý—}©‘›ªß´çî„íÏ—lkŸPBC¡ÇDnuˆú¾ß%±qâ‰ÓÓÀÕÐùDE:´!`ÝÈ%ñs”Õ»Ü8:ú¦ëšÙInÆ%”Èi€á\NEþü¦4äVÃNƒÓ¢±#3åP‰Ÿ’l#äï×j™Àا.®Aù¬dsð2Þüh˜ª)V<uÛ׶ɣÓskÓÉÀ wMµ¶vÂcöŠ1¾ºæ¯ˆuµ[ÜpêÖ«ƒ¾*#3·©4ÑãÔâï¢ã_nèF6ÍÕ§_¼êáŸGÆÛüwO©†¹‡¯»·ëæùxì„’HeT×ßÖ,Ou)F:Óæ «úrhÞæ×5}Ïì8‡/Ũì"~|}(ëgÇ…>¹óëLã ¹?A bÇÂ>Ž¢7î©=š=º€·Ä6¸ú]20Ó›‘ò#38@âV¶ã[ÿEð³#W¡h<8fÑKÍÙÍMùýU>i™ë§7Çös5~TÜk¿Ý‹ìÈl†ªEé?DŒ·ÝÊ»}•ÂëW¯®æ#01KP aÍ»(Ti$©}âÞ~'DÓðw!¹]zë°ú›Œß‡«©«!+ö×Eé£<,·f¼šfYBÝRÕL_?û˜,Šˆ°ŠI§ÿU×âþ?ëu->¿ËÂñͧõUE~>ä¶)ôÝ·ìuÞ"ˆ,F¡ùúæý–ûb;ð{:ž‡_F[U#3lÉQW(Ä€¤w(sàxK=Ø ¾Ù»$Oõ5¶¯¬:ná%6â\0s²éPC¢«55Á)™ïÅÌxÔÒëêK°í¼å mk4µ“O»-nõ#33?RÃû‘¸ÒÊ.©P`/:…ºýIvS<f}©ìöÛkn'éG,e´§á)Wˆ¯‰ìrEÉÄÝaH-¦¦7?vfã±úÊ¡šU h#„zDÃGw|C›Û³Ÿ:Ϝ郪`q´¥Uýiœ{³qöQQx™ª°(QM‰´aú½!“»˜ââSèÜ4c?Ωù÷ðßÛD"Á…Peã¿|X1c‰ðõ}Õ~Â1ÕÙÓ{‡µ¶Fý‘xóܶ»°0¥(óï4ýn&ø—¹(ç9#3×°¤ ¼´Ÿ#3W²ZX”(8èduÆ×,t…#WC^Aƒðµà1žµï(â†EbU22GŒtÚN⺄CÆñªb‘HaÙ5zOî&‘‰áøèKíª“lt²ÔåÙªÍçiì¢ú°ÙÄJˆ ÷íVÀ]é®î5¼fb"Š‹)…#3½’‚Ûhí»¦)¾@¦#W:§~˜¶RŸT¦re)4®—³c½qºÅQŽu%Š¬Ïª$yôÚ#3tõdqÞj+6À…úéÝâkGÛ{ÝfŠ9¥‚t¨p/ÞѺRªtB„b5@R-¯³Ë•—¢q³ûõ£Ç…@SA¶R%§ƒ#W\²”‰»:2™1§œç¯–Âï¶P®6ÜL’aÊ!"‘I8¿½óòñ²2ƒ9â&¬_’?Û[ÙŸÒиév[›û‚+*hËäJœŒ†#W{ÊÕ5žVT|ú?”q¬¥±9<‰Íò»ìô¬ãÔ"±-)päÖ"Ä–º³±ÐOÿmؽ;®wyÑÌoÇ4f‡ak.-<¦4:LçÍeEÄãåN,ã¬Éy¤Ž}¢Þ%¶ÛÓ_a¥5 ËÚfG#¦mˆ C5¿ò|–‡,"Š¡Üj¡»"Án“…íYÐæk§Ø0˜Ca`ŠÅŠÞ%ZŠwÓDHr(Ô¼F|)¨<“®îu|²Jµ¯9A`"*…½U—Ã75ÎIo-áf±VJG/Í#3/ïºrÍá`#W,U.γ)\ëd²Šâ>¢æZËCy|ÞĺàÓo6ãFŽpžì}”~÷”¦Ð›+枟žã2@Z¾n9wiL„Ïàòšñj¨·|«3,eFÃÞÜ/=ç=h{Ñ+yq°-Ó~[¹ÑéaP¾(Ú< h׺%¸ï²ßjˆÇûáçZÖ^Þ¹pc|ÛvXïד…/„/-ø¸[ËTÔi70¸Š=G!°ÐÎÚžJ;,yó´íǤkÍõíÌÑbŸ'm‰îœwÂÁSƒCïPbˆ‹Ïûó)?ÁüºëìÿV8çYà‡Ñæóà~ï† ³sÖ¾.‰Çú¨íe‰ZÑI3uwRl˜Õ?´;o….œøæ1ÚwXM—v|gXžlEp‹«Z,ª£‡–.O¨u]*·àáè}¸öë|àoetÎÀϧbbÏÇʺˆ¤¦n?m=éÕì¤Ðm¦5P«—#Wst‚ ^xÝä¤:=Pë®tÂÓBª^®•x#ûÚŒ}E#3qUîqökWYK#W§Ñΰ|;kT!§_Ó™% ¨ƒóGŽ‚«íìî.«óLÙ—ZЫNØ£=´KHÃì~)ëŠuv-6«§d§ÁEð±ˆoYi~~µ{fö‹úΰ‡D_==×çt¢ÎêøbØÓ†ÄàÛ:=µDÏI#ÑaýµÞHÏLçk‹KÑ5ÄFžJÊkL{–QüÕ¢ßåÛšO-Äz¢>äê¯TÝëxÝÚ€äj{¦,ìübÆvó«Ç6û¯ÑVÑEAº¼«Å3IuÜÚlë»pLk›20W³7/oçûñ<Í(íf°Tb‚,ÿLÛ؇wdq3•„Ô$•ÄrçÖƒ*}«Ò3âÐ Q”ðiS’Ǿ·f§Ž´[>ä.ñÝ8ãÇŒ“/¾_óE¦ªgd$—íðùiPRl$‡øT-Vù'è;)·Ö/öxÖ_h´!Ñ£¨jQÿ^œã##W‘ä˜cú?’ðSSÀp!ˆÞQîIð¤ox$`{#3*sH€ÐÔÈsMÒ©›-þÄï©PÄÕ+ƒEàر?*_coêÚ“zÀ¢ë»Œa˜}ÚQ„À”Ú¤†¤.x…ÛLlck¦‘Œ_fÂxÜƳæâǯ׶OÉX6µÑ o.p×¼Ÿ:þNÒ¿ƒüèg4¸¥s‰ü‘Ý#œ–4•‹!Ç%`¨)zŠÉ°[Ÿá½¢Gîí#0|ýî>µ+÷M9¨£«“JÁ«Hꮬ¶sÚŽUû;vÛ¿]Ä:<A)†imëTŠ™P"1b±‡õ&0b¤±[Fêm´7Á·Q}LdºÁz>Ô¨?‹]ƒõ:vfÏ6u©‚Íêª'0¶ÚDâO24Ìp6ë3|ëTl\ûïg®JÑ=|ÑanýÜþvár»£ŒŽj8Ú…Ù<Ÿ…ô†º™\ö³¾]eû2QIáÒÊÏĸëÇò—1µñîE§ÔØåðÊò.v#3%DÍyâùbŽÚXhêè¯#Wò¼¶ÃP×#3ÜžÖ³ƒy=ðd?hJ?Í¿F5¥CŒÆØż%8ý%Êq€.åK#?|Bt²àÔ‰ÍHŽÎ6Ïf³YXÅlGÛg¤Ff¤¡J¢P±F^>˜¥Å’¢«ªR²¦¯»·7‰ÚäR•‘¢ˆ—ÒEV(Â}µìÎaBõ%¼]XD€!ÓÜîMØWÅöV¬ÖmÝÖÄ“‚t’RÚGÏ/}^ÓoÔVÑýÞ;ÿÚÍ€1e£ê#¿îëÚÈQ·Ø~ãö¨&¥êËáãËÞ`á»ævŸ•@½ÊUîéúÿÝïþ7grŒ”ðQ¤÷i[<r-ãÃwþ®+ã;ãã?qÌÚí¢ø$Ñ$ÑŸr]–E½¡ÌTãouDuñŒ©Ll›6ø'dЃ´ìꄆk0yìf#3rÞ§ldiÉ›¹ôVrº™¦’—6PÀígôå®TÑÝ"Œã»æâ—JÌ»˜^X¸Ï²ºÚ(ÝÞ±ü™&DÎædX(•ê,(Ä8¦$<‡®µª-ó9ÇúÛx®Ï`‚¤Ã¨ãðÜŽÂjƒƒÌKª€a÷óæ~lYFÝ6‡¡6#WÏP—¹/ŸÒÚ2„][`ãßšqäÔ²3̨‚6Ê./bÚbÏ*s½æ³™ÿà5Ên%µÞd^E~¥.Z>ß<S’,ïs20y°²YÖªk1åÜò\FßéË^ õûæw±ÀI ¹wÐçÑt‡nqñpÔÜo]*ÓgJ¢ðþºq²xÒ% TÐRïƒK5ÓÒâhÍ6¯£¨ˆy¥$QVRh… ¿ BÆq©£{U”8r<7Ù ->?ѼøXîíoó·H´ixâ#ãIå˜#3ù⵸™Ó#3³¾ýÜ9igkxý3\Í4VÙ”"R?X^þ)W7¡ÐRFŸðâ}¼6aÒXCV¿Æ!J,™£Ø”ßÏ5y{Þj™`ºxÖ>ÊŠiSº¹¥ ÐÒ=ü×Xa±ú+n0XÇvo#0#3 Ár¥NPvÛjª0àÒs¢k+¯'ÁÜþ#b—Ÿ”Dƒ¹â™å]3¢_û¾qhuâ9.TÄ™eïá.u>DCÂaFóT¡ïlRç.Ôm§Š\çh&°`Á©Œ³Ð™2K%œ Pa+F½8šÚ`µâìaæåÞSHž#Ôþ¸TI@¼3ŠÓ¥9ÂÄŸ ¸ûk…}ÑòÉÃMÍÑ•í‡Õz³Ö…×ï²:<0²/otj×h€*´—AD¨¸¥DLéuïÊC¸pÇT,Uã‹jÏuTµB‹'ƒèDôyëi<ӤǢݽ»¯_·MH3½DOÀˆ½J MFþÝ´ÿ‡RwnÏ£'(é™h•c@ð®2Š°p4ÿB§f÷k5fÙ¥á]Ü}ŒÏRaŽQŸÔ‹²lö^ö‰Ù«ü©ªr«³t0Üç…ƒ1Dï Kÿ>Œì©Î:$ÿŒPÛÊ‘.$&£˜°Ö¡!¿›‡výSßÉ*÷gþò3Ãíñ.‹7ƒ[¬¦kw Ò¶àŒÂ“J&—€êã«ÕPîƒ@Tñç¥PX …€Û|àn䛪XÊnYÝj DÝT:*¨×ŠÈ–u±úA™¾ ¸æx®T›—Â0u·•9äÂW*J0G(û„l°sã2¼_n*ÏÃ`—zó}¸#WÂv-ÓJ#ã¾kÊ£?~¹Gm_Tbv¡ø9èt{E»«ÄElâ %§ëæÌ_†¶ºYÌ.ýÎF%&6·86àÎÍœ.Õ^M3…o·[žåCG=ÈüÊ3#u(ñ{D-U§÷ºÈi’ Ô0#W!1A¥œ¥Ü¬ñO|ùâMc8h:S:égÃJ´ºzÍôP8Ü+g®Š#ohÈQƒ¶§Ü½Ü–×+4&/ÍZM¨ò™é':÷ªfÅJÈlµ#3ÍÅ–â+¬(E(‰hŸSÖ÷UW€:Þ;”3À!ÞµÐ#3ä:àöÞ »´fÅÉm£œŠUk(qç)Ã0Ô ìÜË[”“þ-k‹÷(Öé«ú2áŸÎC¼P€Ùî³£~=äTk űì>mo82<?í‰ÆXomáwŽßõÇoÙóßÅßL‰TE©G°À_9òÍÙûn‹Ž¿Dt¼Ø¼E6x-I<Pøïü#3fÂ6¥¢âªÿQ‰Ã{?7žÇGG=çßéPM»¿2‚nðÕ 0N/æ¸B€€‘ —úûj@’‚Õ¹ì¸Aæ+¯ÉM&`yaÓi»§*0íïIù|*ǧ+ò”zÈâc-¾×âxéø=F½5Í"Üs°ê9½|ÝúhÍêÈjDz‹#„¶žB§³Ó]ñ)NŸ±tê^jŒ® ÚªJ(¬!#W+MÐÁã¯Û5»YÖ ™•±q`€D$ø~¯åûZà‰„öù›3ë¿ü³>¬ø/#W [DÆѹòŒTì31ÒZÑßÓ§õ\Ú[ao뽶šŒ#3¹• Gš[Ç>šÿÃ×S@áìjæÈESÎú¡3d°û(âD#03øoW‡psEIzGN‚#®Ø ÛöúŒÎÓݯx®7Â~¿G/v½3Ëç‹vYÐg ²ÆG¸#3Z_æ±Ò%aaÍ¢¡”ûNÛ68Q4`Xè D.j›tW6"~¥³úÖËðÈסÂPÃczhl8GRPS»HÚ^í-¾ìñÍd¾µ¶}:õõÑGž#Wö;Ä„*¯G¢¨Œä½ÞšB©ÐTùcþÏxÿgûª:渿AÐ0-wÂ0#¾ÝAð÷ËÏVð!ªfj#3v•>_ªeŠËâžæä„Äû`>·ôq<‘D.?A¯þÁ+\-Ù$\|?CÐ#0é½²F™'Žˆ-]v¤¹fj¥ÏSàà¹vÊo{|6§ÛçxÇ…´¼Uü<ÖšÔÕY½ÎQÕ¤¬½ø”†‘ÛW蜑ö×U?‘óþìÀrϘËLݪ¸ôŒüÿ6ìlÙßuaþñùÅ÷ß¿Ð kÕ=ùDä1Hø?¦1‚I []é»`¥\rtEŸg³^¿w•\S@LRJ»J¬c—¢û—sZ¹#3È’î¶áô?‡ýOÇk7J"Š–ïÅv[é2›ŠBt¬*ˆÀB°¡»ÔsGÇ#0¸Q—L´SáȈ„ГÏú3œdÀ§þóüû°êÜá;{zߟ^zlpŽ·4ĈúYˆPâ™ÍL’ø‰9 ‘þRâ®ÎåˆÀ½ßž½9u+Œÿ‰ü¶9ÏiG¹UK¦%·HLÖÈwvÚ?Ž‹3¦kyEWøYÔ›þåí‘öûýŸwØlÁ¡Ao»kƒˆ¥á•ù‚3E⊵vˆà"#0(Ä"$ðò›[>‰^óÏ¿»×R¹ÐÎhZqù™G2Þ_î±™e¼s’V‰VÒJfñ^5P‘û¾¾s®þ1ìñ<lÅoÞÒMšñŸù^G–Å`à,ŒÛÑÄ»èþ7-#W!Î#WrfžöÙÂOßÛÉ[S0#WÐ’^ø!@ß26ÓÖJ4²‰–Õç÷èªïYƒy{Ý¢Rï‡Às‹vaˆô%¦wØ>ŠHÙõcš2Am†×¬U1«ÓN¾ÍŽ6 ÓåûxוºÿŠÑ$·)ÑñèÔÒÁ&ìÏ#W/Ó~‡üvÍ`taÝzÄ ÚsÝG6ÀXÈ-%N|襤`òÄMC<âOzm&Å„`ñUå×îÉÂÛÇ!½oD>7Ô3øw Ê|‘–R2ühï%…¹!b¹…+Ï8¤T‹X*®¦qš¨šª@RIlþ5Ô>k…®¨*º69æèÜŽä"õ€ß%Ã5‘ýuJÀŠ†!ærÜ›b~s¦-0(RF¬ËÈ÷_ú¨áK̼uWËâtx4º.ëÛ\ß‘©ƒÏ“'Þ¾\†ÚVç%€#0ñ>ȉì«U+?›¼]wÉ[ɳ–ð¶O¥÷u†è‹ZÅQPC6^}qd²@æ0m˜¤][WïíRzNša£Å¤]þµý–ÿ¢Æ„NÄÅ;Ç™¿lSvLõYjÑLì»âXi&»åCõsñü´p{ÿ¯Õ³±åk²;"H¢¥Œïª…‚XäQ‘ûouˆm<WÝ8öŸm9ãkÍž£#3ˆp™Ú7ý’KéƒÓ.íÙ¼¸«ÇBÓ®~+zŸëÖ·¢æúñ¥u·* ”k|½ÍBþåt–AÄìÙãK{ÑÚqe¥aÆî[õ™M&N„÷-,ŽŽ¨ðð«$¡äUq.h¤0bÉoÅõ¿˜x!¾¯¯ôÝl›ï3¡¬tჟ&^Þ4ÇT§Óûïe¼âB}4ŽOI˜©L„¨5z Èv¨}þ%jÚû7ÉÌáå&Hj]:fL{ŸÖoÓj“ÁV¶éÎxÉFÀì:õY+B¶;”ãKÐ:rà††u´yÆ/ðû;sÉÞ¤2!œP©È‰R˜h 6¨ÓH‰šh¥µxÎζ“k!šF=*cx8å¤cÚ#m+Ž˜"S:iGö5姘j¨mœÙÿΘ+¹ßd==xpîzèE^R£QDX(1JÒíÕŒòä!ÕT.yS@t¦7Z/ôý¨€&†ÜÅ «ŽóC }¤„‰#0¼ðìê°hCÙýž'‚šþWS@Ì|ÍD¶(Ç_(nªiXÓá„3 §hײ¨‘Ô#3°/@ç˜!#Wh$€B@>¨ˆ¤#0ô6NOç¥:4ˆDqmºZ«\v«ïJ)Iè]Àé›ëD¦ƒîm±P>~Õ±ZI)ne¡K3ÍóW‡Ú¨ö!gZ„|:çÍ*s‹:ü@ªá,’#0WË10ÅÙå™Ì«„Ü©’ÖÖ¼¸’åýðíÁ¹¾ùãMT“®˜8yénÝ}‡=#†õ”…ÂtgG—Yj±Í…ëäý«¯†Õ#0©˜-xTXPyˆk2©QAB3{²‡{&|®ÏN‡.Y&hŠO¡üÇ…:^TéߎÕÃ\Á厉¸áÛ°y§K·!Ã:Ùúèèc›|9hŸ„õëqœè×eãSÂ+ƒsºØ½ð4 Qž|®Š±nžO0#8°êòÂ#3¸ôߺu²–3.˜vÂ:š‰ÌÝeî˜se¡P”<ï§ÕÔ0sórÔ»r fßÁÃcÓc^þ¹K:¯KžŒpѤ$qJÇ.ãônÆÇ™ƒ¬—¹¿›G8“t†K\v|3qå×á£Éz`;¥·Kº`Êõäuº¨ÔóŠ²É·Õ”Y<øTz5b±Ó×&VÞRò‘àpLÇà=ÜÕ¡±#0~?·^ã)e3çdµQÛåÄñLàµþBa]3ìSÔíS¯¼m¶øEÎÛ{m‹‹fpÛ1ª<Ú#W¸r]ª#WqœŸBŽbço(kÆbÂØâî‹ =µ¬Ùl—T"(b^»µÝç™v¸´ñÙŒhÜÝ"áµÍbŠ(ð“h‹â4™0Q%[t£ß4gîØ.]ðyÕÒÞ&ÂR4À5"Bli#WIÐÕ1\)Œaõ…)‰éŒM}¼|pX¶Ì7S#1¸ã@6#0îCƒ#3îŠ)}ž¾›æN1.`cmÅÓF¤wH7á°‘^øÒ±dOiE§”" ÙS–;ÑñÆ< 2²~ënU“Ê\¦hJ &Ç÷4’Ô.´®L;;Û}Îâ¹ÏI8ã'#3Âõš¢Cø)K.Ç…ï最“g`¡L‹A¾p«ÆL…]PSÁ#3ÀŒ™Œ¨Á[hxÝcNΚ†‰-¸æwHXôÌXšt<Ûôõð‹ÊFXˆD2[A¼ú“D|»<1ÜÒþojœï[Gó~xñô¶v@¦ßtCñ¸@ætæk}öC#WEœ$üÖì€oŒzU¤‹g'Y´¬@?-ËÁgÂ*ÇcPD1_ìúfö3*5F‡mL Xç Tðá\S…:.uYÁ8cÃp´T7jb›C dâ™Jæ`b[uÇS]†-¤Î1¨†¢‡kL‰FŠ10úí?½#W˜ÌHÑZ\±b1‘ 3eÎ.‘R¡†Ùc@Ç=û“ĽG»ZìáR¾í¯—fýë¤-PÙ#0¦ô®š½7Ú¯¡^½ûUÈ"ØÓ#X&Œ´mì’×»om&Ѧ‚È—1˜ÅlõÜ+&nŒ&+fá@´”…±HaR„D$#0“0ØÝÛSÕñïnŸLfw #On-ÕyúÌÈQ¾Ñ¼§}»™YÌÁ®«Õ}³~{:Æð>C ÀYY2¥¼)s’«‡@Ao¦|#0•˜TØ×rj(-‹9`ˆ‘1dL9‡Z:ÆU2&îá^Ï#W~Y†zñ¯©é¿e`sõzéTÜ/t 6ê8%eðîͤ~oáÚæœÕ‰:ä~vJòˆÑ/¢#W<ñôPS®EÜF׳GK눯aO2uHLí³ô˜Å B̸.9Q-\Þ:ˆ¹›ÎâmÞv;§w›7©ƒ_NÃÖçŸOÊ¥ËkDáșڲ\S2ÐèieM ©Yk71/¨¦Â£í³8ÇÂîJÖuœ9OˆÝö-›óZ‚ÛHý?Òs4ø9áxò•y>¯Œç~RÄï#WöXt(ü/]#W8ë©I)!Ú>Kt‹A«ÑÃÄÉ¿{ŒÝl›x“É¥’0cøÛåWÄNÛ×õÌø¸ÄÆØþñÚçglqÞùÑüïâÌjüúÌøL¶Ûtñšôö»¤ô©km½OK°©Àã[¡ n}¼+L¼†·$Ö©(‚Š‘(00²Z²xN$mŸ%Û±ƒÎ?†v]0a[ÛõLhß>90õ}BpÕPiåRH…n?fQ‚0DOù¿?«»=†µ\R§—?Ë_÷æ×ßiÄsÀ¸$àÕ+“LP“þ{¶E¤míR4!K•~#3Áö}Ìiò/:'Di×íØÐ#3PSˆ”FÈ9ºåU–DVA¦Î])š˜è°«%$Ò(6²JdD:Ña#3$à:Èë€Ø£ÔP¤QŽ‘Eñ´µ›Ä0õßð]€šM¤V”#YkŒ†Š) PÞ]°J"íECF•Š¡fÝG2h‹bd+"Zq¶àëØÄ7PÂS*™PÔÄ̪TŠ@H$ÊÌï$¿—ž+¿Ž]¦™€ù<Ò“{ü#*)¯ã=$®¨„7.tòçÃÏžO>xñÔSMä=¢†•Â>›9Ò™áÃùÏXŸ$Ü7Uš|<T‘Ýü¡üºÃ9×DíÀçjø1ÃÜ{©óÝ9ÞR üÜ=ÀÐ&-h—ÐõïdGöÕ7¡húíWãEP¹Üb#Ž1’ˬ´®ç;a£bX-•GK˜²Š¬5îÏÛ\úË1Æn&ݱh†MŸF=®Dyuè£oÐå X7(Ir$»\³5¾.|Ÿx½Òxs™¥ØØs ̨̨PØf9x•”RöàŒ°¬Éi‘õ{TU–2¡ÝðEéª"S]Â|¡üÕ56¸}öQ²øK¥OÂNE§ ê©â¥JòñfxÌðÖÄr☮õ›Š‡‘µjÊw;çÂÞÿFÇïF‰NŒÅC´¹ìä~gWgEèÿÛöé2c¸ 4ó$„Á;>)Í™]¸‹,.%’?Ûœ)Ç~ö÷jAìk#e˜”¯³HÅNÔÃDTc£)^.–3ßÛמ)‹:*P,ܑ֬©•Nš5¸Ã}„73ê{êÀ\Ô8é¶T¦I ‡p¹ŒKÇÇõ^[{eøf?o†4¥JtÇ"Œ"dhgÒèÒÛ—&U¤ˆÅ+5PÅ1zõ«vÖ\Óüa7ÔÉ´e"žŽ©r–¢å¢5Ý¡ÇHîÓŽ6Ù+ØT(æÏ¡²•=Ø£žî*þzøè6Ÿû¶ƒˆƒ &á•g/D)šô¶:û»ªþ^±AÛXÀÿuwnÀŒÅ)‚{1}±‡$T&Ñ O[–¢ »†µy+Î;ô‹üªsYf;&ÄlB#0ëÔë‡ûáC^ÙKoŽžÿ#3íë‹hØɶŒ7vÆtØ\×$ÖŠÜÛnš³IH'§¢"ÌD#u+´¶CÍ‚Ì!-‚bopÔHM(dµ¬ý•Ñ®Ü¶Õ…¼ÇOˆ[;ꔧyçê¯÷Ói8Ôåî#WÃÞ{ßpOF‘A>àD¥YÍ· ï2ÚÉÃÁ˜!ʵ'ð>—í†í©‹ÐT"Àé*-<ÍO5MS£oõKÃÉXùc†‡2—f(pÒ=ÍX•3Ž‚•†¥^[ÿ#3ƒöþáÇ_é~=iœ„–õ#W÷‰ò9“¾#ºõZÇË+ÙGÕùxùù#—·xÌî¹&‰ùú¨]Ñ(?Õý•‰V•bˆÖ?¸¶N®êq8`”ˆ§Ó'ý3¶>Û¸ÞB}1uwØɾ»p¿¯#W,| É3ÆXÒöí‰br'žÉÉŒà7GN‹õÌPÍò)Ý-½ñõ¾‡‡zþÀF~ÕÌE¹ê˜½%‡l/ëì—/W³rSÅ~ðˆªˆÕäæVF)Õ?`Ý.=ÏÙ:ØTÅ'š«º9¬§ã>º#0ZoT‹p¶}:sz&‹žýr×å 2¢ÒÄ'Œ3~__‘Ü(o‡î§®ë*߶-çðCçѯh¨p<¬ÀwŸ#UJä†bâ¨èÉW–§¡H<ÒÜ9«ÜÜ9íž"NÑ.óŠ^–â=We<kßÑñÐ"&‰•‹’!ôa>§ëd‘eÂƊܶ´Q§j¢¾}ÕGð¨8a ˆ!$ÕóûóS“´FAïa]á2(]&8|;Á=}Ù}Ü|U|KÛ“2:Iå¨Ú¡§@§Kšf=œ§ñOŸö@Žñ(Óÿ@=ÏOk,<?#3×æøý>þK9‘#0AlîÁ‚U” R™º|Zn¾èŸc†`GÌ>b£Í‡‰<säè¹Tô³Å>ùfLOôLTŠ)PREõ;lü]nLÚ’É¢f 3eKEû§‰£MŠ¿Ž½ŽÛµ-¼ëyã®®–¹^ ‡+(¥ü@úX²Õù‹€û©%dR¤šeÃöþ|²áE2USIN0®„†‹n“×kœßÚyöy½”jñ\ª¨¨JŒi¤Š¤ôm´^Äקüqçù¹?SüTÂÀè±B‹ËãQºEX ˆ)öËÀvÊæuŸªQ…yéœ~Úå¡HÕ!Våªøîí£_sòB^ô¤ý©&¬¡&ueœ`RsC_ŸùÝ^Ê6‚·©WÛzIQ@±BíÊ¢¨¢(y¡óa?˜ûp~é1÷ÏZW!Í[³R4´ÎɪÿÅ&œ>¡ZºB˜,0ʨTž^…m/û1Úi¢öéÜû¥¬®7+ÌUË5ýöwâIlö¬>ÈË/õŸÏOájÅ‹çƒâ‹K¨ucBÖY/‹=Z•¨¬ýÈ;ÛKà4+‚7Ú¨:D|E¥%‹Ì¾3Qµ\Ãwîô×Dèˆ~s…¢†G6Qn¿]õ#3›ú¯ÿvªñCCe=Ô‡È2›¤ò×eˆ6ÑÈE&>Ù?ÝKQ#WÁúuÏ,5m¾}n›aë„ê‚|J"HD¹?—-•Méb/PF“a2NîAb©KPëušÚ¿ò†©œõsº_Ò¤3cIßö$º°èðiG§#W#ù²¤?{I8&î”Vñ~ËQ –(«dý©L“a›öVî„=,œÝ>doš=\}v¯ÓãÇày}öþ–üø@GÓØ%ðø|E~Žuïâ•d|ç–§¨·-Ço£W8Õ*_úŶC®ëõÃu‰©žƒÀz_÷.¯7³4ªÒãôK5ûÀ·å+õ^µœ¹Bê-l#ï®?£r¼>Ì{k`[_6’™¬›sHiìübà97ÔH£±r.»…Ý5Ù9òZµT9LÞò¥|9î©.{~Q÷ÇgO#3ib4Ûó/¸Oúç´ðýQà˼érÕs¿¦ïwGØÚôRV¿öè½ÏÐMvñrSC;»‘8Li“qU _'ªthðmÁ!û(#W‹ø˜Ó;èOuê^õ®Ó†öÝmçcQŽ^2€4ˆeA@rŸÅ^ëC=ëÐe*‚H¡¿&û«nÎç;—Dßíê»…j‹³Ÿv÷KÁ›Wš`ÍG›5Õeh:'¢Ý:ñ·ÂQ´!Uñ°p´V1Ô@zp—D]Ïʱ)aI¾µ‘ÔGÖ;Gœ/øâ!ü±ÿ/ÇgÛŒwõ¤ÿhŒROÓ#3<Tr” 0Üúï…ËO@€»Â(4V‹*#0(VR°rþuö0~Ù=)Åa¦Lž]GŒíãÓ´¬³³dÌÀÛL]KaµÐ¹Ä¨¬nŠ#0T(=–#0çcG£ác/{9ãôH28øp–5iiêUŸŸŸ>†uОWKö4‡ä”³4ѽ‡Óful³«”¾¬50mü£ŸOkþoËݶ¹¢b¾[lzø†¼èÆ/Ir,ã•AËɘ£VBÀ¨$¡1ÖÊÎïQ~µƒí¿àíôÉ\•&Peà! jÜ"ÎP=¤‘ ˆÑäL¼‚ªyäP3ñÿ/f6NËüÄ´<0K;}à{Ož~ Œ~ò©ùQµÔ“ДLYVgÓ:9MYi«#Wˆ¡ šiÂâËß©·¯Ä„üÒ©‹»çã?Wc@ŒsàjÂË€°Ž÷f`eJœÌˆbWÎð’ßÏ\„³áßé4õ"?ç¢õý{¤• á[ò7+;µ<åÚ.ÊÓSζñijd’Bt#3/õ<7=³Í|{z9þí£ý¨Ã£÷ØÙtiã#3`×ôôt¡ª.d•Î*‚Q›W†¥o/È¿&ÓÕ&Þ•š!¢¢èK}ùþ3LòNÂõÖÌj@Ž.ºDØCì釉˜±Õ³¯}ŸŸWÇÃõòO×õýn?!¯g7\P{}<ß!Ïêž:z¾²Ë+ß¿ÊGÝ_©²«ÏczëuÃ?–¡œ¥‹L×e5ÇM}Nõ2,ìíâîßIü7lºb·ãf~_g_Ü6&í¾Ùòo´Bç•ç‡“±Š‰j£Ûòþo¿›¨\<'ÉÚ¶Ÿ’ßàøe;hv819mÑç½Èv»„WÎ*Ÿá"ÁÜí~ë÷Ëóâ>]}ÿ«¨ré<ůFBóêý_ÐóÃSÒ,K@Íw«SçÙU%£W?—K†yØ=_]½/Í°e~qæ“W\’{Såþ2ù¢ÌŽB–I¨ø~«|°ÂK—v§·><Ó•[ÌE—ëâ>È'½þ/í³ðèRÓ×1Ô1ôÜäaæ¾ÖÙu”«}üëÂþ»¼õÔÞá²Îè5ð»>ŽGvW=^¸MP²¿#0ý0öóÍÁÑìöêûËXy³àœõtÿmVF0ÏFѵã£MG®œ»6º×Ô4¬zÌî>d-À-ÄqG:^”±»+ðUu†°Zúã¬wÊ.‰6ŽoÛ§ækÁ¶ºõÊÊõÄÙ]ÌÎX=†ŽùY*„¼–paõF±¿³õìñYÔ>‚h™a†±f¾"s‹™³…Ì‘¨½îz¹ªôü®q°¯>š¤î>ž^8m³ÅÖÿÝÞƒ³a6Ë#3®ò‘/“׺4Úú®Óuþ#»ƒe·]†¦,x×SáÌ!»„ý–®ï’ʵ?ŸK«(âC¼#0sî…„wÍ\R³ÙÈ¢²®Þç›·zü¿.·ÄrŸ,«×Ï>·ðo#3ce*ïÅãÖPËËæç^ÏÄÕìòx‡ÈìXqÀeªéÕÚa¼ú6Šj¬Ã«»l®ù#϶cµw£å¹j’ÜÐýŸé×M˜ÙÓ÷_¬[_Æòüh¯õ/Jš¯‹¼øîdúª¸0?w$»¨°Å°Þºûs}#1¯î8( 4½Ùž¿ÕSÅÙÛWøK|9'.-ëB9§÷mçû©‡›„=†ü¬QÑüTLAMûÖiס~σ~¼ùÿö™‡³p8O‘?G«ùü5Jyíý:µí:p„>WŸí7ö6çzìð]ÜÐÕó$»í2÷Éò.ƒ/_(ãðçüöå #ƒ·çwÇ:,@—¹˜÷}6~—b5èe÷kühú¶pwÕÍdmÙ”r;9Ùãô=DkhæWøîø·Àá(Þ<–üûüÑ«ýßCCOöe™:ƒÅóÎ磒þfxû‡QïÝÑâªÍX{ýŸÒ»môÙaæååÔôúÏØ~'êûÞ[÷=Ú6ï³H¦`áN×çñwÒ•þË Q£Îaøþ€Š§—ìçÈeá—‘±iuùùþ½Sòëþ öTjTb¨Z#W#&*ìôùý>xÂèý§ëóÉiÙœõð*m7ö\»Áí·T¨À¨F_Š,tªHG§—çh—À~?8‰Ùj‹@#W@Qiz,¾4õé3ÿõó…X®)Wêöz‚ñÑg›°OéÙöûîæ×Aú½Ùg¿³/yŸì³6®þ¡Ù~^jµ9úŽßÀŽË²ÖNºsêÕ¡uk÷é¿—ÆhBêΉÇð_¡>NäÅŽÁÐ:»ûpˆüù¹†Ñÿtû¸ö#3ì;²O±ÜÊ=£ï´w#3ÙK<ðØyž<UÃáV©ä¼ƒ¾oßúXù4:Ïíýƒ8ýØè¬_úÕÀþkö_}ÿ¡¿-Él½Chú°Nì:ôìí{ÿnš,ªížúipØœ;¹–þïuÝU‚±·åNkc¿íÊ[ ˜C^š»ÖáϯÌy£¿†„牢¡ã‚°¾?è?•Bà*Ô*P'â×êéæã̸{ÿIõ¥‰àý<ƒ—šŽUo|ƒ¤³OÅÜ#W~sÙû9`~ð„*ƒU‘bõß³ ›ÓD H¢m#3Èœ8ßÈñžì!__ÛÛ—jðëšÜ¼œ”2<ßgšS÷–+÷·dŠþÎEÑÊïÝVúTL—ä=ÕY?#3H£àïþ_ÂøVf ‹Œ'íUg¿¾¡u¿N¤6Ý=ý?7"KL7Hj`•òË#WÄýUg¨Ö+ Bî*‘‚Tó_Z…Ù£šK`¹’Âj$k¦òV»À#W<æ‹ÁÐ%vŠÄ$ò¡Hç+®U³‚˜>iy°ßžªK \>Hqùm/vYNœ÷rïuÃxÔ1Ö¤Dt³¡ô×ĤG§Û¾¤²LxMÄSjíuäZ=5 I,tÄì6ÀæDô!Šlý{ô×ä«GÏg¤&ñšèzw`¿äë¤8¬™ŽÍæp€-¾ÇU³ÞE:—{ªÁÕ歹҈®§‰“\ÙpuϬP‰ó>®+#R´(fª#SLT61‹z£Åö™±gËw-._)clb^jPŠÓ›ô:øJŽ9Nõ®#3–ëÜÂPÐfp"„'yBäÈi°‰LèÔÙ-–ñ¬Kª:7×êÚ($#0;,²°Òm¤Ÿ·Ðñ£-‰*Yž9JûË‚äR -lCĩ!›@»hªÃ+&3JÛóU¹épÏãÂœ4{Þ>'V+ä†ça{íö‡¦aA$’“°‰” Z_ñÚSQçÖ}Ò6°ck™T€£Ÿ¯„„â5,f¯×@@hà=¿ÃtWw«öÙgÃ}ÝOùG$ÆÁÛòç~¡Dõ¸!ðÝ©kŠîowP±†AˆªÐÚ‘è#WŸTD&éºLõäs¹MZagJUž}®„†¢ŽÍ#0¥ÚkÏÏš¾ã-?ˆÆ×_oõ~=ÝÐÁëðúÚªK?ÙêÝA«^øN…¸ž}$ÔÌüKß)³a¸ôêD„žü=¿Gãèö²càô#3€Ž ‹óY€Áç´#3öúdÚ˜yOxók]n3y_ÝÃÔ×CçQðÅ3Ûžížk¼±‰è¡LRIÉóÉ‚=Òel„Ua=q‹ÝJÝeCBK*†þOÂ0/_FÉÝ¿žPÑå Ïw fñx#0aÄЃ}¢Ly˜’P¦a›aü=#Wçù2Q'˘dGÞn`A²U,i/ÇÎ5¾ö”q#0Ç–Z¯iEñÒ*7;Á£<ÿÃâƒè@Q¸2§™QÈÊ2#0̃3.4ò6ŠÛ‘¹Ya šn0QaRy÷`š„¥m±R8‚9‚ŠÑR‰4íˆÑ#3d##Ð<JªÒƒÀ¢¢-©¶Àþ½ÚžEVjîå5fYš¨è”"%ÙK¬ [Š:jˆ9¬1¨ÃL’˜ƒV(š¡#WKU‘ ÕŠØ#Ê"«MR#@âŠìôɦW† „Y‡dÒÁ±IÛöi]꺻n—B\l„#n¢Ð•ü"-ƒ‘dÚu•ÆŸÙûï…2"¹ÃŒäŒ6ÿw_æêѰѢמ&\¬ é‰ëý¡šƒ’»Ù0Ìþ½kžãDîBÂàܦB4š±FÌdýÞþͧßùoÓ—·óßÓ=;M¡#3ÁOsâPbG#3f¨::ªÍf/“ÔpÅÒÂ]’ñvI²âë¯o<Õça;.'`ª¬ŠªôU@qPJºLÛ©ò_f¤ÍTmøfùü8ÚµX¢œþäÏXŸÝ‡dºM÷²7óéÆwC¨ˆ®g‹F™õ›,Ó,`*ª«U©MCM•øýÞ*iñt{¨|–òúß³gïïôîóUz|Xg_Õöz#Ú§/ßÕÕÑ»“ì' «$ð~?Ê÷Ù•Eÿ³ÏÏ í|tâ+lwÛ~oÑV…”%õÊó¼EÓ¢Á®…ͪÛ/ùé®9†íô»ëÍÍ‹º·ºèÅEÂíÇX]¾Ã1O†¬¼)™ ’$ƒ®÷xK´Ö>±çîöþÏÁ~¾?.>¾ò‰bAå+Àsðp ˜ÑTÙäÜà,°èôú—wX¯/žVðßÏÝšø¯,uz—“2ë'oï¹ß‹‘ÅÎs=ÊëÕë#Íüõæ˜Tئ_ǯÃöëj¼3×âmŸºù5UÈM˜"m¶ÏÛVÜýµ$² ¿¶/°-ݾ@‡Ø{¾Ú±‡ò§¬.6åŠ-yAú鵜ßËL¡Ñ5èb·7SX©REn1?äf©1ŠS¾KîvÄ“ZPÑ`Ç Ãx¢õ£Ï<¹UÜÞy“Ç5™M‘&6#3ŒdSª–(cm$v`ëXUá©51•TQ®ô˜£ËX A¢6$E…vBÂ428Ò®@¬¥e³úþZ0Dp{RVÞµhUùxÛ/-4V ÆÂÕ#WÊHpm‚Œd§y8Î.vfŒkÇ!*RHþÌ4+Œ8DÜÐÈ4¸0 ˜"žmêivÙ.›âpcª$Ú1¤«L*&)Å©Övª-!ñi«Œ®!ñI‚Õ-®kD´8ož) ¢#3”i¯èÂ3³K@0ÇFÑ:of²(ÐÙÞ%ÚL¡©þî¨qñcÒ‰¿5±½(DÍtY¥@¯‰ÉGUh"ƒ<ÔGAºÑWŠeF7²ºÉHÄ!!üÑ:]Y 3‚y*°ÄRJ†sVÌ"U–ÚÊ++`”fõ²aêAšNȇ§—õoà&(i°Õú†|ÔŸèÿ{rsÂ7ãÄÚVln>#3ä#WZÁЦ?j¿ßëNâÃݶ_E'§NuÏۗѶ´õGÛñvUç6ùîòO0$K5ì’¡ây¿¢‡å~„[=°ø\®Ê¿˜zµtkŠ#W‚®NN‚A5¯K&㘢•"699Ý×S¹¢„ðöxÊ×eHõìµ}åú%Ã<dl9<+ÓV©ðaŽÄô®»úµ\u‡Ç£ä„kßýÍXÆ~ؼ8;K¬mo_Þ¸×FÛ#WÉh‡T]½¦à9Âí# \Œ¯æ)ØA†ÃÏíÅÁÚ—Ö}{ÞÊG>îYŠ†${6øäÞ&Î+ºaE©ÁËòy‹™w(=,Ä’\åUs9sàæ)?™A™É_0ÌHô¢Bäó3“D‰xAÉÇúU!Ðùƒ©ýlÿ¼H>ñ£óm±G#3#WåU®wU•òAµfgŽÅm #“a$’#WµOäÂÖE_ QFG#32`åví#\æš#!H0kA>Ž°snFy»:æœ÷¾O¶¡¯;ø.G‹¬&9¨b«sžI0i‰~ðáÓ¯WPÎ.÷^Ã$Ýjé¡‚8vO(Ü"³êêÞïr…ÒIE(ÈÊ,GPª˜(«+ú*Ø¥ÐÅe”¢HG’"Ä5þ†+`ú²é¼ÛD¼:** –TVVUDdZëóy¥ý¸|ä|Þ*ý½C£ïð´‚×^Ó·¿ïþ?^On´Äôìb‘@D‹”ÔõÛÄÉ\Ç2ù`ǺŠuõ Ü12LK±&j°¸Ä̬\†¦¨”"㇆XÂ"Ô†<sW1çÊŠ°ÃBA‘¦Â¼‘ƒX<Á™†P(ÙAÔ`ÄÌ0YˆÈ_SÐõÍ¥â^üŒM0¯Ã¶Ý7ˆÌyZtuÓ¿æ´bŽ¹A³`ØÙ7cUPß‘³ÑÕ‚Äû–¦Yˆ©]Ûœ ªØ´B„l‹³ÀeÈåAÒ9«CãUÌ›yÚ#3a…Õup$QÅJÇ"„LêÄÊ5 )*(˜-†Ó‘2ņ `Ñ,;cñ`ѱhð]ƒ]šS¨nÈ¢á1¶“¡×l°lôÝä2‹f]°SqÚ7ü4.ßgXoÚº„m´¬0Ö¥J*…NÙ‹ŠµwR\¡[{dU·š˜yÜá4.1™ãž#ÈÂBÒƒ§“~î¿Gâ~=ÇûÈ»÷!B#WƒoEÛ8ñ|\w›¨vª…mã¼}µU¢‚Hóþ48‰(B–™Lã"12ßÎï&y0€q`ð ½AP®g~înZs‚6Ѿ|M—å'I<Ü6ŒKüžOœüÂXiÍɟ͇:s¡<àõ‚nW*ÍÌå#0kXñ#,²Û1bÂÒª§åJª `ÓŒç»ÃìYøðhÀâG¢.n0ºpÒŒi©ÓðŸN–#W¬‘ê,Ž¤G#"D•0‡ƒ¿mVô‚vŠƒ[Í;"VúrŠü.2üC¡¬,<6šAˆÔ©¦ã¡CŠ‚;B+àôz5¤’Ñ+oKD ¨'GUÝÀ¹€nsºº¥Uhª#WŸ=þï?—ßùlwðãlXoq!å- X¸¼9˃_q†á¯‡ô“ÏìÊí¾'GïåìåˆðùqÅÞνì´øIób:?—|gÙ60E<Š“ø#W¥3ï‚£Ãô]„š -+A‘¸·B¢67eÔcµÂeÌ°M¤‹A6ÿƒ„¦'Ñ#]©„˜<j›V•árIJ¸Ñm“‚P¡SqÂF⎎6‰ˆ¶<£C¨¤j#3´â+£qÈÇ/GF7Ž%dFãYÏS3¼\Qlà†fæRªl‘A‘XP„E“F„)”@pH¾$”,B7Õ2•(¦%éPꨘ–òôd1sd©£„MpKGi#3ì7*éÍð\Úµ´»Òn î]d“"£¤‡Û)¶³B¬Úw«Lc9iî¯#3Â'Ãq""x½Ú#3-CN0!¦>ÇœFµ©?|![^ƒîdIÅÆàVÆíŠÀÔ `éC,‘e²ÉÂ2™`^Ìx…{Ýô´‹u¥>I*f„-™|¬>ì3€ÖþŽ1ñ¦j9=€‡Qð”¤çŽ*AD@±’+LÂlá*.H*)P 9¨¨[HךæÕ³Æepä¥;„Ò¤˜´ÇÜù´ÛCÕøCèp|™’È®úKøŸÞ±öX»~ÕÖÃãÕ¦¬3$^';˜àÈz— iZNA)ä‘V(—£ˆnµl<ælR/Ìî#3qs4Õuë¿ÞͶöáOH¥ÆÏ[á™rnHmuKqŒz±µZÆ[H·0dîÉ%TBw}Ìø-x@kb!öyŠ5ð^-»è½ôµÖ§®2‹ÙE;)’bQ@¦67dØ…2Q’m›ÌRúëjœxÎTfø§•*¾(H?a<cýŒa µahèI6·.2‚DFWÑáw™D[ðm±šs%µfÖ&ÙØà·0¶×B‡æc‡I‹NV"sî~òI^8ÔA"ú¸Ð¬8qŒJÉ}oèm«$o€¾/oJp$³«Oµ'º3zÖ#3Æpß3,\7½Áú6pâÛöX4¶jmG”í†}èãaCÛœM:›à±¾S‘¼Ñ’nÈwi;uÉ‘ùõÎ×Ú1Ö5ßDµäHÐ$›¾¼—PÙnoÃãNoÖë#½ª„’K,Ô’@·}Î_-ìÎd|¶|.ÁO#W8;iŽW¾6ÖÜŽlýM—z3ªv,‡jÐO»¬ŽØÝÜ»œñ°k–Ö»W*#ò’êÑ™#3n4·B…Þ|&KYÍ%,ÜxtÚw¡×qFÛQØuÑÝVMæ%’±Ü7‚Óœlsylîš·ÇTZëœF‚°ð Û¶Ö·»‡D{.‘Z±Ú:1M×WGóžN{.^gÄ‚Òß ¸ñ¥¡Ó[±ÆÍcQdáÑXe˜':Ö…M‡hÍörLí„Jé™Ù÷Æ0À²©:†2nræû™9iòMI¦O3f`˾ÏqºÆÄLJAªÃKa4I¤ëaÎÀyÌ[6äª@á€%ÈFWç÷yÉ^nÅ&P§]TÐ¥âCˆROy#3wÇl¶§,lú£z: É—º;"1Œ!ļ™.ã;¥MVÙèi+ÿq‘'‘Šö‰\àÙ?Se7Ž¸âsq°ª§ß—ê ÄMc–ã)äEÌþÔÏõqVº:ˆÂÎb#Wrk…[¢n:ˆtfŒ˜¡àS©¹‡^ð-ee,‹Í=ÉEž=mšhã0…§2¨ùos.ù§¶Ä)p:qÛIœ™âàSe¿ÞØ£7‡7EÉémÓ£ºJ÷ÏI¦L&L5ïPâ†ïÔ1I¶Ê”ª¨Ž|L+É_|j¦´ƒE$ÅŽ.÷›ËâÇS—iÑÕim;'Ây€Ô= ¦åœ•²9Î2Æ%$õ¶M‚3¼¢!ôûÌM*KNó:Á·‰yÁËm-xÿ_¤Å7žÛéâ<{Ëk®o¯@ý9tòeG8M+YÔ^0ðÉÝ°±OK{t© 7¢˜Ú#W&aXùFSE‘#W‡Ã¥yÙÒ͵^ùünßNànF¹~•~it…»3ç®uÚ«Oµí‹¡4|lMšli¼Ü+Uú,(Æjüö,×·ŒŽ.¹9¤Ý6i±)aLU(:ã–Ó4quŒÕ¦™J0í÷Öù;ºãÉ·‹ œ?¿ ¾RüØo8Ãç™á{½Ù+h•àÑ%åcZ¤‡jmµ¬5á‚SPÙ×{üFLQÔ~?ÑØ:´ÄÝ6Ø^쟃``#KÈbEW³n4S°§GgÓxyþž:×\é#PùžüîW…G8Zq(¨ðò˜J'YB’R±Úçȉ—q¨Ï½WŸø?ì?‡œÏ C§¹Èôƒ–NJx|ßHt¾ã?X©"BŠ*y³oCÕÈ#I]…ºÿ-qÀxyÜÚ bÇm¼Ñ†àoî–ô USÐñ:º<vÕî@Éæû0” —EaꬾÏK¡/‡§°ÿ7#W®Q·']z&ÈøO~×f^Ÿ[tÀZè5Wyn‡ˆáU•Íx’gQ½ðXEˆ×òå˜H?OÃ-™·üÞwJœŽó2Ï&àƒÑÅà²{L¡¦m4‹=LÆ-µ¤Þ8m¯Óø~Žö˜ŠÊþÜ%ïÃ.ágm• ÌQn‚ÌB ¼¼;1%<?ï¾ãÜ2þÝ)zYn‘ßÑÁ3n€xŠŒãÛž ÷_å’Ö?w.é¿°¯|ñÎkòç\·6YÁ•¬9m(3ƒ=Ps®° cpåæçn›I¢‰až±ûë‹úÈ"H™ùÊÜ·@Dd‡K#0ì<AEî±5¡LñÐ Ò-Å@#º°F#W= ヂºâ=ÒG¼ŸŽšq+[ÎLÔÔJlÀíŒMÛ`d*JÔ‹øc*þ7îþélg\'&WoøæÍò YÈ°4¿jZXŠ3M.…Õ…¤ÌU>S¯žPü7Žx"•íqÃ%¤àæœÄ…(Þ`•aý¤·Fä`ã˜ÃR;F‹Ì´ßÛýúuÏ''‘rá(¶CØÖ–³Eö÷ngcÍõ§Ûpvá¥à÷7Ÿ14ë#WN ˆ—ƒ8ÅÌ#0úlxóò–Ñ‚! ¦$Ú·SÁQj±ð[jáݯ=d¤!Róˆ"Ù™l‰| õlp)ðëZ—Âa\øœ8,fÿŸÿ[‚šßpߣ>ߧ•ÝcFœ÷j¶.Ÿ0øiŸFB§uø”{Åmꎻùí3¢:Ï#W–54ùFQbFËÑföÿ6#Wƒ÷ÃïÝÍûŸïBêÞõ»»·VpûUØæ¸É»Ë÷;O³Ò¿Ÿ³ç®#F2H9Ì…å2]ݼ²—=SQ'#ÙÂw“^€ôÉI=u^ˆ,Ø£»8°¶ì˜Ž®Ã¡&aÃ94o¯Jo]zNÜ?|57N´7r:j²z þ„ê6BéêFWV,x¿Þ.³Ìxk¡Æ6«ûY½²<Šs4òìÌÔ”£ï¨žœ¿¹7›b顳ü~^”êÓØxi?[K\Ÿ<ìŽ1zôfÍå×ä"Ö¥ÒL¶Ó° 1õ›ù¥îçÉŒ×AÙÑí†!Û»_Ršdâ#Wñ6:<¹ÎÚÜßÂ{øº!—ÙˆÚô°0}z¯F(nƒ#3YqÚÄ:$ô®T‚eÈnkˆ3–í:Äpç,š~[ii1#¶ØPRi„Çš‚Œb‰ êÂôÖùàxf¤-0è´%ûõ¹W°x“tçá–öÛ-צÇE‹rÉ%50ŸšàÕ[ÿˆS9lõipö¬/<Fd Œo9R+žâ0ÄhÔ—ü•Šä„„Ä[Åäs#ž#3}ü.q…LûûQü;x{.zk>øbú4t–¢ÚðÏ\÷xìnŽyš'¨tBr•[mÇNºO^oµóÂÜ5æ…›?ÞÖµ'n[´÷]ZÁ¾Ì»´È<•~Pµ~Èo|íìqÔò¯™ƒBÏtwKqËé1¨HF‹š–<jJ¥ºwŠ›âV Ç%@+™¡#WË&a8ú·ÙÉËõ=¼ÄŽÙH¸x‹º‹)kÃÀýkÊÏs5b¿qÆû²“§GwL\ 語mAû"ðY^o"&â¾™Ìí#0ÊUÉ»g¤CÖæ\>æO¶_k ÞïñÜw6ìî«ÃÎg^U¯>}öÐ$N™ú¸Jê&‡vëN„[´÷¨& jç#L×ñ¾c½éþ$>ŸË¦ŒøÆÚ²:U—›ø.2+×ç(ת0¾&Œ˜ÙѯÀÑ(úÇ»Ñ`trrôX¡I@ŠA!™Å]tqGæ–Ù:´ôB؇«U)¹JÉ\ä,í-ñwÖuÖšÖ†¥:´íJ|cíºOá:²_D혗šÜíÏ1Eáv}SQµ¼2Mü//{¾ a3åú>VÐ÷k9šî:®ÑL¾WÒÖè‡ôrPU;,¾WEjC÷ãiÇ˼Ï>Ÿ=«{ãÊzë7'.ùÔ&‘ÖõÖ#Wë›K£Ø£…iž›Y'Ë¿»·a#0 €f2 ôfT‡'&’2P~A‘ëKâÇ#WY˜Îx3°h!kɺª¬©n6cJ¨¼4&ÂO¦ò¯U‰IënÐÅm¥ãÓœù½M|ûlDÙºÞE«—z‡y˜vïØÈá.šÑqf2{“T_DlyëA6â}Çß¿=äñdC¸L¿éãúÏŸ”yÕ®Dç|ø©¤8ÕàðG“êߣõ¸xY{;ˆÁ‰ ã6)4jO)×%žs6#c2m·}ù’Ý3âöŽ."[km³æu¸}½³µ®«Þì¼[îF¬wg¢â¶o6Åó72a¶Ù'ØÂ)KïY>{é;t['6Œ&17ZSòwfÞ¨¼ñ8Â+(%´ó!ÓQ¢ûEØ.zÁìÇû…1]º8ÿw¬W–ý<j»ö¢#»”é-ö÷½#W¹Ü£7’î5•W‹/[?÷-Tùᱚ´¾\p¤z7÷NumÐHâŽcœ9#0»ÔµöÍQ<<§ç°}ÄkøLìlm˜Î¿Žqiâ¶ÍŸð#3c>¿^qà”Fõr ÷CÎäÎÔ´Z“äŘòrïú¸²¹\íˆw²ª0lß|y*Áê¤sb)ÄüÃÌÇÚH'=¤mìPæK¢Ósƒ;V™¶m;q[RÃ/zÆ3DA.ìnÄ]¤¦È§Ü‹4ñ1ˆ¸¨¡£ÙÎé§Á½>¤’^ˆþÿÀçÆ87#\,3rˆNC{ï´œ(«6V#3R€Uk)(BZ»ó!Ke4xùy›¶7ÚdM,±}¬‰PÓÍ'£ÀŠE#Wâçc'¦/~5ßHÜkšH,æòæx §J–Ñ,ÈÙgƒk|m*¼°Ð“zXë·tÂ! j†_ f²Ëicà– ˜P¶-M@uÇÀ°Ý¸p}ÿ–äºø· Ï·RØé_Þò`Î’ZW¿³±EŒìí³L愦æ4M ª\'£³¡¹œªÍ|E–ÏÝkT¢mX¤ú<ËÞ£ÅAï§ùDØÎ,Ãë;ñŸ.¹ÑÞ= ¼öÆô!"AÑÝåKSÛ4X,Ä£a8r=%܉ß_éåXÖiáµï°´T•÷-s‰‚¸±Z‰*A@\ö/ÆM#W´®k)žÊê¤æ9ã«ö‰Mï!%a¦R©üÕNòå[\¶Pn¢µo§Iœâ9ÀÛSËK&¿ÌЖ6š_#WëêË#W’صŽ5ð¦vb¹Km³†<—³ìãžêÐUkP»f¼øçŠÄ–»÷ÍÆ!ap«¹_˜E¢ìœµ¥õ¡h+P±dë[GZ*¸(¨jNkèèŠWž¦ð=a¡sKŒö=@e?ýÛUê—a,|µÃ“Fq K,©nJœ`¸_;_%Úbx?%a|à§}ÓÑq[cÉ÷Ñ÷æ…k`jDÒ5ã‹IŽÓ'a“•Æ·×Ujú_HèªÌÕ,`œRWsÇÀŠÇ_¦ºóŸ÷{·™Fç¼wëÌí1ÏÖþ|îz÷ñ׊»®¼°lÕ|É8Á9äÏ„–ò[–èÚú]K_$¹#3ŽÚ5wÑ×®¶¾c,1Œ1R/ÆÞå5~ï7¯?ÍŸÈÀê“Ô¿jÏÛ’íÕõ¯ãµS`íÁô¾Þ:Ào+ƒƒÏ›WÎÄ(ÇÆßsœ¸ÑÇKyn|¢{!?¤á U”yUFÞ¶‡Ýœev³ÑUP“}ýdJßVéM%j¨¤8Š\ñ%zßúñô×ã㓇ng™øv‹•’&ûÖx¿#3TùX?ÎîÄʃñןušŠê«ŽžŠ{„D†žJê¢/啯Ãî§^¬«ä"Ëäæ¯G7Px|•,›šë5_}¶`ÙðÕh&»l+fŠî¾Æ\,›Ä]vjÄ”XؾO‚´9q`î‡Ï‡ƒTp¥A†7§8Mh¾¿FVV^¸ìª×–6ⵑüäšÀž¥]͆Î9ì´lsí}¥Ä9<*<MHõ¹#3˧4Û6#9i½žèNÃÏ/T¾Ó¡µF2Õ'@¯U×»"èâ*«5 ˆœVÒ@ļÑa]lóÛžº±Ó@êºC%ÿî¼,ëI—øǾx#c¤ÁŒÁ§q+–¢Ò5E-’Ô…]H<•W³#3!Üe;%Λ\›¶Å‡¾ïuÄÍ,Þw=oåSÙp¹›Þ<©¾u¹áë{°ðc§¶Ø‰ÐN"˜cÎü;Yõ8™c+‚ö^]û”ÓjNt}ëÈ~#|m#3Çî²»ÎðT³òUÎgrʺê2.^º…³dcja…`aš"îéXíâZ«eÚøœíëˆhšlm쮽՗óΑ&r‹©Xn¾»ãßÓ:“;íÅIhº N¼¯‰Âù£)stÁö"Ì•_kxì[Ç°®0xÞ¢oÓÁÂévÛŒ%Ñcñ‡zµwÇ»w@U‘Hò®KdYî³ö£H,§#WC>¶¦Ï’ÂGß=q7¾>ù¯tãóëpžßyPq4˜äD8[*âè@<‹+b»)+£6KHj¬Òº¶AœÈ¥Ñá#WFâ·BË óa{žk·4*ÙºmÍjË7†§K)ÖZÛ¨ýp¨¼L!e=”9ƒíœ£œs‘)ã'Mú³¸DAëÊWuÂtoŽM¹ñýpW²-7–‹ªÏ#W߇C«i¼TöªvËã\Ôgˆ£O,ÙŸƒD¾æGAËK¨ÅôP¢&AüŠ”Í|qÈíÍ)=÷†#W2ãW<¦·ÂÔ8Ÿ)9/©/Ó¦)A:’Kxb²Õ^<±ªµ^XmÓMIm%‘AÉ#ƒï9RS®0ŒV¨8¥Í;9wFò”زÁ¨‹ˆK$6I6ÜTAGå*Eâö¤È{`fõå].®½ˆ£kŽI¦ëÅ7UTi[6}Û…RÄV/)IÒ5ÀY&’®#WÀç„9–”…ŒÐa.A5¨R"G„n˜ªÇôÒÙÓS€“ä²R´“˜vÕG[AŒh^.šÐïµmj5D+¿-Ûš©NNa b_,ô#3鸉ÔKì²ÙÖÁÔÝÕž÷@H½P¤£ûcÛ}ýeýüT¯M‡×{g¼ûÒ?#3V³‰RòkZ«£ç½rnóí/ÙrO ºú©¤š*ý|bÃm´Á0Š1¯Ê³‚`n„¦NP`oW›ÓùÆ:EŸŠÆœKE;&àTx¦Na’ÄåÏhÔ»Úö~÷nÃ}°-†+ضyn|jî6ð†Øƒ;b÷ì¾××RÍ|^åw€¨g)Pvæ…Nç8»#Wè6W=UhŠ¿,ÁÎ} âk…Ö±…T±ü‡,Õh-m½rª¼M¾=ìÓö¢bMþÑÇŽ1À¸Ñ¯Î'‡êº+]x×;S§Ù'ÌúŸ³59¢¯O•HnõsŠ„AçRhëñÇÊË8s¬g¾7Åzt=28¹ßýªŒNHM+ýªväû‡)YËóáêþ·¶|»NÂv÷ÌÆ8œû{NN¼Xçè¢Ç<èïËì²£eˆ„…¥\^»íöËî6¼Vf»óD~+6s_«nNÍ9c1a¨¹&ªX=ejMDGñpœ´†#3þãÛçü:szYww/.äsÓ¯Zœa_ëÚ*£Æ^/Z®”Aé©”VyLí+k-‡\ì0HÛ_e•€$Õc…VˆÑÝSƒW Ž{<E;¿‹¦òR$‘Òy4ûÑפ²©¹Nšòí»æºæÚUõ‡:§3ùâã~ä4ótüœ—öÓágy˜µGÑêuQw¼ž2»5¶üÞA{ç\MŽ¾uÂŽP𬵨Zº‘c'èÿkmOÓË/¯Jp§j˜Û¯¹æhÕø8á³ïŒ-I˜Ã½&%rx°j#3ñâpËK[>*¦cä“(.~]#QwÎB%þÜ8²ë^ƒÞ§ÆËdÖ_^á=eÁÏt£]\RT¢ºFަ颚ã,çLèGâïË™Ðiü2xªíqzÏiÞê(òR:b˜mC¤BiBû}ÛYöÛ#3ŸZ,4{þO…\xFª_¬Z^¢iJáÝvQaè[›9òˆòóx1û^ç5ßÈÌàìü÷m‹ãT”¯S%àÎü¤ç[ÔèNµŒšÕŠ¡7FG;à.Š¤üu³£5]ѹøñyŠMÌgLf äÞEõƒÞ§wrùÇH7òÅJXIgŒÓ[¶ß‚vaŽ¿…CúÎ0T&^î8ìÕ&vÎ¥©(›bæú4SH³i{ËàÛÊbGÙMV³Û>‘¶î¯/ín«~¸,t²šª—A_ÄW^£©§TFQþˆ›dx.Ѩcûj0˜+ú"ýÊCî«íÕü£S>=žµíõ¼SbêZW#3«0½Ts>ÓÈCçó¬^áæœqÁ€{¯QΗ€ˆì÷ôõÚéô_ïæ± åŒÊ¿ÕO¥Ð›#WqÔÉÓ¥âÇh%³´AAqNë2<¯º—i†b´Y#Wˆ#WòéëS•¹¬Ù:3à©ìgP¸#3öñ,AJ5¥s±X¿ƒ:ìãCìŸ"×&àEZöžh4^LÙûòõ(lÕ#3€V%+v¿ºBê*§rùb9Ê™¢êìÛ˜`TNVjk0*ŽwîPø4ˆŒAydYq#W莞iiôT/ÊÍ2 ª¯³ÈK¯½Òèü½šyËñº’4j5RþÃæpÁi¤%nM‹,×Xôqά.¨uéš%nÙËs#0á´á%š®¨1èW áN^W#Wä›Á7T/g®W«™ðwÀó°®xah}\µV³ÑMð[Ç´fTèá¶ó¯Ä^Nkmqs¯N“¥ŽNço¿í]99/3݇];ñç|ëd%<ÚžüX¡Ÿñ¾’uŽûçT×ÆÅéåxÌðª#Wz~Ît³5üÿ£ûsðÑ÷s³3©q½ÑÖû¡™ã#3b[å2öªžóRð*üŒN«ZY0Á}yë”–D¥Ð…ŽJAL39TˆýÊÂ’`ÓØÕ£¾Hm{ÞqfòUòFBžÏ;ì¬@{!\ØA"÷7^ªvŸòWÒ¤qÇ”a»ŒuÒz=}qe 7Ë+ÜÕ`ÔªŒÙ9D 6Ã’×ç”D©GEÑrÕ•ì÷z–µ2€×´æ_¹Yve{êµOv8:ˆ–,zUH7Zц^9¹ò?,\Ë…RŸŠ}@+éS‚‘Ê^=Gm\ý½'[;o‡=ÊÔ-žq#WOÞ)¶tç¹LŠ UU!O.±Àiº£*‘4*Åû[Ïm>‡yÑ€ñY$¨¡B1‹1æwW}Ýw{†nsÝhšRÆ+9+=Ì9›“²\^žê빦 ZÊ‚+ôd|*"XB#0þ˜µY͵¥ÕŠ¤ˆÅúH¯Mð€ê0Ÿ—©Ú‹È1#0…ª¡%îÜÓãÒåÅ×wº;UŽn:]i›¡á°=¬.í£#Wíé‡RçÌ(=fÉ¡#W#W¡0)™~j8ª—Q–ÉbòÔp¨’nMz=ï¡#3˜ZB(`”ÌtæÙÄŸ7²1Û™l„wT4Œ×K£’÷9,!‚€(@q°½èžáà!¹žŠEÓPU<ɸ‚QÄe6X‹êF”9Å '>;•Ø5úçæ…âæÊûçT*Z_#W#éå^Y¶™ç0,÷âU•“PvñsZ\;?ÂbZS9¿|×.þ[+®ÑÜã·;×R6D$Óí|QUáõUw—ò¼ýúñÛYäÔÂ<ןŸuÚ÷¾Èáw^"mµá\Õj5FÊmÊ8’F#xEqˆíNx—Q²èztÊå¶ùôËl»y?9؈ïÞaq@êÓ2.ÚöŸñûœÇl†‰äèß\¡_Êhøúš@µ• ®¨¿CÞCn¤š$‡Ÿ ð¿u)£*P\n‹‹¢.Œ"øèt„ŒB-"/Y#3ÍlŽ÷ ¦aþ¿'¦Ÿ}<¨÷Mÿ5×taÝà©F<ÓvæêÝWI¬øMÛµÃÓ¬S¦>·¿”·§Ç]÷ômУ]#ç˜&‘ÐÈLõyyªã×âùìtS–r=1ÆÅXuxÞS:w5DñpñZO.}|ß«Žgß9ÖŠußßýô}p·èѯéÔiA'WXp_M¬?S±e ;|¿9.û+_BZÏ-Åc˜ #Õ6(¿ž&1¹Û;λ)ÒfÂùúåšÍŸ1ž€(ÅòXø^æê¸k$&'JÍïÎØÆ'nw¬r.xÛÅΚÕKùv2Û¦pü¨U{˜ëjŸ%΋yÊ6É„D ¤Ú·*ºd$þ9ˆlnô@φ{˜ÛïgBLöÒŒ"üXAÚ"Ç–øYIgÌ¡d—´JJ—àÂkõôÌþ'M iqφîq>â¡áÛ„bØø;z6z;J9ÇÞüóÇ8ÁêCŠ†û»ˆûO¯ËÏx0|ôŸ·æ¤ùJ€4gW=˜õ«¤‘Má®_Üjñs#WÐèÐÿ_oiljÁžv÷aÅ(ï!åÏvÏ,–]®ÇJPŸ«ûOdÏ+‡)œ25¡÷wâl/¥ŒÜSò©bßõô¿KŽgíålÈ^þYãa‘=øir÷8xÂÞ‚0Þ.‘†!š3LJ`c:* çO9Fä_óÖDä>>hcÆžM+œcó¬>ç^#3†N#dÃï–óZf²ètˆFýojŽ—ˆ¸¥é¨¼”Ø”üL_Ïi ìzò}Ù:âðf'ª/Ϧ˜h¤ì@¶ªÌ2¹¹Ô(Êù±TV>6ÖÌRÓ"!ªf#3.Tç^¬g6íîMp;,båþ%4n÷s19y©Sè~~澚¼Qð_M•ög:±`áÑͲp2ùwñÞ½1s嫤åHb‡7HÎ;t§cͯ^kÂ1ž£oZó§ÀÈePŠ@#04 u4£×ÕhVÂ}ˆ6ÿ-šyí@íú»kù´Ú¯“]Ö]jgêT#0Bvd£"4¥ìŠHgéu…4û|/—ìww(x ™ÁôÕduT÷yh¨èzžNq;|ʧ‘Sªb¤©´çÝ»møÈkÒžàÖ:žƒípK#3¬¡ª9éxW‡!HF³Ù»ßpO_JïZ3Çõ¸ñ¬8uÒ[NfÄràÌGMª:“òéŸa*?R˜ôÌRp-kÛ;JþK ‹O»œ©ÊÊö:QÞ]eøAßhÏ4ŲÂQ0ð3ˆüׂŸÑú›LaÞfÍ*ûCÙÊ.[/ (Ê:ã¦í©žÿÄòï_’î^]% B8}å }.‰¥ZKHtE¹ú?Ê|P/\aøb(ÌÅÂ÷.m™+ÌÕ¥*iÑöJuC<céƒØ€IBšÈaG•©P6hâÂ÷Ð T¡jôùXp_Õ‘ñ¶ï>ø?˜€ðÿÔÙ{ü—×úû| ÝÓǢʸJY×·€<êŸ:€3þÔû“õý@xz¢„‚Z{nµî¯}õBTagO²#Wìô;³xÈé³ý¿Â¸[`b—Í ›t‰ü¿o;¢/³Öð`Ñ,.™/ñ:îâ#3T?óŸ•Àø"ÑT’(Œ)…?Œ.J®¢Ä¤Z“l›ÖÑPp3JûxN•v©éwÍGaäMšè5k5þ¿EŽQ#7¸O]÷e§MÍ<îÁÛ üÝkƒ)ä~·.ã©#W©HȤ+XÜ&©Ã‡©z½:œnÛ°YIQT?¦Äâ8¸ œ3ÉRì󣡈‰"®mŽÃfÁôr’·&¹ù véüÛëY€Ìÿ0ÿ6uäÇ ‘´(bˆÙsGÉBp(é%ôØ÷‡æ@~g÷Lç=Õ’ËI{üÿjÿþ_qJ6²zDFØSL5T 7*hl÷ÿ›™ñÏ_kÊÉT=1WþÜ@öÜÖÀ†?MtG¦#]s‚°3÷#0gdø*›ÞÑ3[•¹‚x€¥Ó/—ÕMß9}A†¼ÌD#ÝÁË;™í!s˜A…H…E—#3Ú·HgMV©PHÓu~ðnÞŒåâ6ÜÂÏ¿ÆÉú>#0hÖèV§¢¶ÞLrÈk1ÑB*µÓáêuö×u´=fëàÄmT߸K}wtÏôgL»£èlÏÎvÄsàüõ]¢$A£‘¡ƒè„×)þ¼–ra‡?WêTÐþJÚÃjtrÙ7Û™²äê^?‰AèÃ5ŸÝ¿/zzšNn•š½@ *«0[„ÂfÎ~ŸŠýš=_y*æx.%ZnjÒ²5UK“—‘ÊËô»¢="ã#3aBÿjgrX&¨ÐdÖŒEÏ5rñ¬H<µD,ÍÐÌ«ü_sý<fÂÁÆúSVp±ÃdÞ<,ßRmúv§B¤D 9'{47J.Š#WdXZÿJ^Æ(eûµÁ É2”ÀçtCZ ¤›¡Óñÿzø°)€aµÜÉ%!'T‡Ûî©jŠ¢ø«ù¯%E,lF¿{õõ¶´}XD¥pš°*!ï`)–j¼ìǽC$¬ùµ™RZtøÓÝm0|#WÀÄ4/‘ñ#07ÇEJ.¡ö:½"‡ë‹²ŒEõÄ2€lý|µìñÎÖS§øRÚ±NôxÿVW9·Ù˜˜ÂÍ´eÝ;,U¦=ÔUÎÚhÊÏh$cwèÝ7ôP¿£Ž#WþîšÃt Ï«Û¾\ŒZ"i®eòHãû,f´ZDš÷¸Kz¹ÖÙð?cýŸ.e¯³»¡¾§¥kÍÎäqæÝÍR,VkwÜ&&õE˜°C«Ñ.iT1™B)¤š|kžef4Î¥…ÉñU¾g»ë%ï;)û+Ý¢e•uë1Ã;þµˆ“ãŒû=RÁâœÜqÐ#aå<Ûâ-ùÏd_é¾»52xi]÷‡+n.X¬<òð#®õ+œ®ƒÉ«0æàô\JÈkiâð"Ú"7sáßžQ˯ù5Ìw¢Šé×|f5µæè:µ’‡ßMQ=¥KºdcU6/Õ¹êðŒ×ˆ£rr)èÞôLDçè¶Ö0^î…‡b#0ùª¦cXR@—=¼/ªÄŸ2äIVº«dî8NÛýÓ(k6Ä;Ú4Ò(Ië¬Åð'wP6ÏQj¬e§X›LO˜F4ôd ¤t„ŠdàEñNYçÊûµŸÀW'\Cã¡yŸïˆ "”u®ö#09Ò³H§d_¥f{#¢WnSFl‘˜€Ò#0§L+qqŒ¤BmBÀ…©QYAÝ>^ÎÑ»íƒÝîë{<րוÚç~ LŒ¢spì·âî™80·ƒ‚›ž¯v ‚@¾Çm‹Ó‡épÑœPí{¿™Zªðõ¾¸©’„(„B—㙲ˆ1(KPÁ1§§œÕQ±ºsà–Fš²ŽïnœõÒÊ蚸çž?¦¤À‚“3#WRnÐ͇ ~Þýã£ß{‹a„“/À©0C>vMRúÐMRATš¯Z¥Adà&¨Š.[*s 8ÕúF-ðÓÀ]v¹óà”ÑÌOÂÎ÷ÛÅœüÛɈqÞ…ï…ÏW_¿‡T®Þ#3U t6é§<ì^Þ9láFïÉs; uëmàý¦‚Nâcxü¥NÉÇ£þrqõ&$xÄ,€ÅáàŠ^SC–¸\ön™ÆÀšõF——ÅL‡/QÅ<¿‘˃2}cСºoñÚ~o"wì¼qé; |ßÞ|ûÁÓÙ´àäCžmP”¹¦ñÇyËïy3Ñáx<žYfèXVåøþóǸR<–$þPä]v°ï KÌïDäÍè»d¿>wTA±»‘:Ø.¡¯6Ç-·õ÷ÕõŠD®™³¶Ðoà[ø\þ%”ºS¦«AXtå šÛ;>#WÿLñúnzitºk¢š"þ1”’(¢(–˜¿k^‹^7©nÂç‡ÕýÂݹ-õ'ª1Í-v7Cåì}•1½\$HCL© åÛ‡hlÍ{æw6E}¿fL^DöF‰[©‘:œŠXµ#3e0ý¨i±äê7àŒè‰·¯™0©]ÑxPC ¥öÒ\š°Œ´VO§¿_[؇˜-Î|ÇÕ¼C÷p7($ðûXŒñ`ð?G#˜”Ä»6Æ(N¢‰&QÞUŒ68AÁzmrî†ÈÉ…ô#W*¿ì)¨¼¡k~é–[žä uV°¼99MÕ8n»Q¬Jš3¸#WoYƒË#3Ò~#3´¿U`ÉŠ¨À›¡@Ÿu=k_:îØSeI°”YW‹ÚìÛalrËlõ+Œ¬Ü*[Zâ?=QýVPBõ.Ýæ¼â‡÷/*>vCvX<ôû3§!Ã~vôçŸ=w›ü¶ ²lÊUa§«ÔYR+ÐŒYòƒdå‚ aóòƈ—,6×`à#3ÌR!#3`™€¡1Ó²/ᙚs˜pa<ÌDÈ€É÷$Î$u8ÉóEö"¢±håáÅ@ŽeÄ]n!u”tç¥ú`¾®a¾++Àó6têÙVyáPþë³_(1"ø4ó±e2iè’’Ñú"¼°¼Sɼ?¹uqêäwaÍqã|aÎýö5oGDø3†Ž½µ×—Kš¨Òp}œhâš0̉ɮ„Ög¦Yf§áŒK5Q†÷"Ê”Â#0„À#W¡ˆ½¼°CdG —¶k,w}†¬”„±Ï«Zå×)U2¬³Xˆ£EÈë5rê[2ìK’KIÌôBän#“™}C³†+㱈§K³zúÀ™˜¯È®@6ñßLO^ÐsOJÉ#´Óä¥ ÑJ&{¨çˆpŸÌ|,ÖSËÙðÞh¢ñí£/‡)P«HayH:«Ä^GZ«¢½;öt#I4”#3ê!rÊ[BœT3ô]íÑ=ä=PtÚù¶N*Ÿ¦·!M.á¬r5*ºì¹÷%BIkhfä^ÝRPY®0!L²p#" âÃx×5(JÕºvL!×ìÕSH«àäHÒpñLŠ¦7/'¤à=Àì\;lFU¯ÑÜñ·AêƒäŒiÙëÎøï—tø~Îx‡¿êlaoa-.â̺ŸG „&Û\°”*¦Óiîœ9Àî›ÍY–) 07£ƒ(djjÛ“Šª 2¿Ä$š£:Á´óN‚-ŒmDqUÀ‘j2"숑‰^äéŽfmŽÏæ/>XË¥LämäÆJá/[dJgǤ®TÝ\˜‹#yŸÍ1ÖŸ#Wûƪ®›Sç³ZŽùœìsËšüÖs7÷2›Pšå¯g88¡Òoe•afpÜP:wäoë†T©5nebšû(<x³:O)³Šwܾßæz#WkSèÉ:Hq¯/MѤ!²Y<':ït ”´`Rx¼ŽØSú˜ARê `gÉ{Ù/–KLªŠgóbÐ~D#0#À Í› ©Õ™:P˜Ú r/D€÷]zõCx@é Ûøddÿ#3‡Ú{s:\åÓR&¾¾×ßã–¶òOY#WΑÔtìÈ¡Ã=…%b¦Í,ZHŸ:·aÑ(ëá–Æâ¾YÓM׃\™ÜE§‚ôHžÉ}…e;µ±Àà²Äöøèi3¥ü½Ý1©#+i@tÔ@dÉQ#Wè"ÐB]”4ÏQe””ƼŒ'ó®d}o#3ÇW\~Ë]1¼ÐîµÕå[tá{ížzøYüŸêAªª%T;;íF¯SûVꊓ}#3^Îÿ=þÍý~õå·>ÉVáåœ##WbÙUÊåÛºžtíÔ.ì, …2JU`—û?ÞúŸ`yU4ÏrPú;q3½½IûÒû6’@Œ„b%z õÊ“ñÛÍYg Ÿ•U>:('ü"Ïjýéóñ<Ú½{áëí!ýé=U>yõÀ‹T„„#3ð-#ÔE\0ýpT2ýçöcá½v‰'Ëçó3Ä7øàÉxg=³G€"žz‚„A[àB<€÷¯žË[å"}#0[èêÙÄ&éØç‡Ã˜àQÐÕÑGVÉNØ=S4úbR«›.öÀKà·MMѨ’Œ®¼†¬]7-Ó0ü:9¸cÏ£S§:ýyXó¯Q8Âóáóë–/æ•ÐìueÊ8R¾<çqW¨#?˜pø¸Å7"¤ÄažUð³’ÈN–V£FÙº^ÏE6Ì÷oeÇw&êV2T”'ñr—F=Ixå:x·Â?œüÝÛ¿#—“´õÃNì–XNkÃ`M£Y ¢#3ErrêÌâs\ˆhÅ-#WúÏ{#3œše¥#W»õ±Úƒ3?#0ÿ&$å€Ïd±ãñƒ=‚ÁÎ q{…öi¶OϾ‚lx¹|_¶¸Âi/-즳l+½Ð!oè¹h<·ÒÏ®ºµ#3Ù¢°ÑP`«ù‡Ö\ÃóÌù‹ûÒ–¶ã«íÃo·«·dà€„=e0¢ÜC܃Í?Ç2´ ðòþ¸LþìI<on[+óÿ$Ú0<;}‘âñRb_Äí.y”cÐ#3óhЋQ#0r#W½À…ˆsˆY‡¥ˆ)ªvDro±CšP«"•ÐZ\,u7@ç¬ f !c`l2Ë^‰@…ÒÓaBf—!$àÃ]Ë#©Fí”,,Qt)~vÀ6¡4\,/Òà0`DC[dÖ”i¼^éc6±˜#º bXp‘,¢c#WEšD£"J„ØúôD3Š¿Ø…løyÉ.ôøOõÛ¯GxóæT³ˆC“>aq(ç!^¼ÿÏP9é팿§J¿áÁ¡Æ%¾I˜;òbƒo÷¨\¬›;4ÿ^ %‡¼T(,A¸€9$,ÄøÄôøÚ=¡¨tÖïüoÊŠâX_kFÊC¿a»MxÖ<.µGXdG§Ñ4=üuœ×–ÑáI’»ÔŸ›©{AÃßòslçY%iÈþ¾È&÷4Š÷YÒ¹ïÙQèqm26VN%úŒ ¼!¤›Dj‘FÈ8¢! HÈ4Ò‚’ŒM–äaß…a7}»]É« ¤-ÊjœwÅ*;JíÍkü”÷{»x¸®:ã*ÿœ/’)ãzó:ÃÞx]›¥uK8¨à.jÔGŒáE61Qšhû/bŠvpøœEŽO/u¨ÈÉ&ž]uRpdïfŒÕ¬°õªÿuDz©òiŠj"Ä¢|êÁº¿CÖa°x6/•[¾ùyiuÍÕ6Þ©ê蔈@žkÅøÐ[=ïJ;‹IR‰â_3ü&´×#0ç-¨êÃøƒaò²e›B÷÷&ÃbWO˜„„2ÑõåË‹q‰mù¯*ûÿG¦ÛËßw ±©¢Èh¦iRÍi™)‹kϲü£04UU#LPZbUCÙÛ¯·Y˜L²†g¬öt;“wg˜óBŽSYÄ%Í,NPèÞÂÝ6öèã}‰!rÕhŠA‰#3S¬šXÊ#W+¯ê¥èyX€´‰úOÛÜykÁHÉé¤„í¸™¡!æl7Q`Lúk¦&c]a· x±¬}„x1ÓCmœÅÉÒÍ“Ÿ·¹¡x¦‡Jd†¡âdäÈzR£4 3GžÔL<Á@ûÓ+&˜ºbÁ´;+5°J”m³–šìqÖQ@MòÜο68jÏ#W]³ûzž'dÏ!@œºi:¢÷\7Ö¸‘ÔqŸ%ÁБ=Hy…軑ìˆg ²FTC‹±h:Ì…ÎDËîÐ<adZmxwéïxd¨±<ŠÓ³PM¤ ZÎiàž^4Us+;Œ6À:£ r…1¤"B"ÞëxÖÉVÆã¬Õ’Ú¼•¼Uv[ƵãmW-dÖ®jºm’"#W¢¼ÛªÆ!XbÀÊݹ¢7^»yÛ¹¶¾©6JM+Û:)6VP\×"mpĹ«5wXåÆ‚’†é‡5/z€ÔÍž—`Ã’Ÿ–+çÉfýØG)¶ñdÁ¹ÜgÐáÍÝм@6Ä‹¡¯Ÿ=ò-üë‰é_eÊï)|zùÖL)ô•Ÿ*šå¿gîóH¿ÛêZM dÚ5°YˆE yÂlÃÁ³yß%fŽÎˆõñØ2k|#WðºÌY<nážb©WâšA•qÄ.]˜£ƒÕÊàÕmŠ#3„f#0Õ}²F(Õo˜ˆô/M97>ýuÁØO²¿#3Š“Æ0ŽÌØùæösX” ?.ضn¾V²Õò¯néUÍmÔÖѨMŠÛ[Û{Ä´j‹^¿x,m„4„ UT‰ëÜÿ³Âµº‰Ç:ßÅ;'oP2x¢UKxv`#V&Õ=@¶uÚ¤ÈÃÅÑá‚‹ƒ“M‡åÃ3†œíê{€Åøî„c@íQ”;#3 ºX‚!¸Ñ'Þ¿b}{ö§ýy×TùùÏeÂWŽó± S¯.ųɋ39ó”ƒdV1´r#0§ùGÓêÛŽ[º5 wœù·z‚a…M÷-5Æ$:»^šî(5ÁéH™Ó{Å¥LUŠºåyfƒ<y^}ÔPt©#W‘‹L) Z@D `ëÇÖ†°e8JV*MPB0ÛÏ–—NÞŠÌχ~é–X¬CpÄ6°xpGÃ_HÒucnŒ)@s&+îk'ýTßr¨í>Ôë‡ØÛæ9ï„‘j¯?$‡Æ–EˆñB‚¬¿aEa¡º\®¥Ã=yy®w.ÍWwJl– º1³ÅŠíF¥ô|/Ç´$ìè;AQS¶e~TpO•g‹Ìú\´6”ød›Y4(™Wzäî¹9øìKÜØzÜÇ|Á*7ãʧ*Jˆ{Çq `ÎŽžo£5I)€,Ò,‰Œé¥oAdRw=FN<ÑÖ&ƒŽŠ45œŽ6Fª€©Dò…òÌߪž†-¸ìy°kÌã±Q|çwÂho<›MüOZ-xÊjµëf4÷5BI0œ€~T÷±°„‘‘“ֿ͌ùMxéçÞÝ8o#3IÃaÓˆO_#3yÕ)u¢šÝK7âo(ýþ]ØÉãFÆ$`¹‘°1šQuðÍÉ.÷Ç}‡¥ðç†Þÿ¿cO¶¡Ý¿JvÉ,ã®'ˆ¢½îC[òŠQöíWTñ·¤JdÅ-õΞ0œ>«âpzt¸ò6óŽ™2õmD‘!·[C~ QA;:të}Ú´ä®ò¦ö<#W!ѺsgòŸ^žž‘z+ÙË'[¤ÈäŽ2FÇ¡‹Ù*û²¼Ù¢£SK)Ý®)+ÅöùÙ.‚—Ѧ"Þn›0^%ëAÀô?çxγ=»(¤ÓÜq3Óд’}÷{C[Iõ}|\Û)éTì‹Ñé Æ'ùÉÅ,ªìšw_·ní0¨Íšg#W¬ÕD¤iE¡q-SCaA%¥"ÉO3Và'[89…DɥౖÙ_¼t2Ë8]À®eðD"C®ª¶;b…UK»hw(½sîµ#3)µ«e9®Âäù®ûY#3 't¢t—Ì=¸kéÝyLÍÔgxC³dH–‘€é%Ä´81¢R ˆ‡„é`´ÕŒ—>»m}…Ÿ”ó£çZÑÐevô{㸾N¯#3ðuXÿdá^Ím<Y°s>çÔ÷ÜX×—†QuÛøO±ìŽåhÛÔö ˜9:ó–Y‰1-H¡÷ FÆ$ªu!dæ‡c}h_ƒ!ž•ÁÝ 2:¸ßƒiŽ`ôø¿Îãª>?íª'sèè‡6K¤Á,‡ñðœÌºwûæòyìÅò¡]èJì.Àç¨N¼Ñ¾A—H’FE†ƒK fq:•8 ’=õ*…jS²ÐüK»(c!íÓ’‚Š,™Ôœ+u˜e°p±R@âB ª/ow…’{dÙW@+*TaÝG` ±VCÞÔ†©ñÖ‡ÄÇ~™sG âÈž"ªróšH¿µägC€Å÷\90_Æ3·‰²Ê,h¾¬G¿s>µÉÔ=”ûÜtJ#3Ý·È›„w~2l¹"Z‹s‡ë»Íld@öã—9©Þ&ñX¤ê)TÎ#3”“÷!UXÒÍÓ¯P·GBªÚ±C]qÏN+ѯ†ÓagCoo!Ð3µß5¢ðš¶·$Â¡Ø AÈQ°†6oÉj#0Òi¹Ï‘ÙõyL.J×z‰óôg`òL«'&ì„'è=gEÐÚrœ¨xs‚õD$-ìÅåüxˆ–$à}ÑœNÎÛÛ¶…Ñ܈:lx˜bqÈ»írUÐÀbä’wh*°%EI麨Òk‹MUŠBÐ7aÁì4—V*T*”ft5»K<=³ŒúÊê*C®Gæ @¼S¼8”¥€ÙŒôéø=v·î°\¶În«&³MúâžGïNöÑöX(øÔ„äÈ,×…K-<*x,ƒØÐ=¸8>_ˆúã¡¥ßÖ"š{:4%äÈ0ø²-žÁEÆ'ÀÙá=UÇa‘Ž>[êàn<jûŠ@Ía[¤•*M ºÐñæW€hÖ&…Œ™Äê4câ;Ûìá°Úóã¶èÙ èK¸<<Xþ¿¥û‡Fûùð•»î»ÓÙƒDúÙìa««¥a(y’Q³]nÎ\·÷îƉÜA7ï¡¿ÕÆáO”F¯g¢ºÏf¿…{“VŸÑpt¨ÞÃö8j者€P´€û/JdJŒPDòÅìªùÉeãü_ŸAÌÊŽŠó\“ìá³ï©êPN¿ÓžŠ Áí²÷ÇTMÑ0<è2ÿ-ù~”]ßzUu'G0ðÑÇh»ì4VÏ[žñ¿êÌsxnXø¿SE;AúÀðÂ}ꎪ´íp¦#0å„]GÕ¡Kdµ¹Žg8’þËQRÿSUÓ¯e£ )1ræa×Ö½€ AœÞP–ªL·zÏ=lõ¤Ö“"ˆÉF¥çù;#W?/¿ûè¯Óz—»@€ëþdÕ#3"HAYÍ%HÊ V¿ß¿ý#3íÌ›o îÞˆçÓ_)„ÿîJ»ÂÐråJ¯%ÂòJ‡×Vj8^²¿‡»ÃP÷îÖMÁÿSdQº7ñÇbC°ÿ0ÿ•¯NË3p›Þ¥6A@@#0Rè!€#0Ëúñþ>_ßò?‡ì÷ Ȧþÿñc#WàúPÔùk,_Z³Mi&sáûçþ¾{5Ëep«ÖÉ}w¶*OïõtmÓûÕ`Aÿ¸ ‘EL\˼·D5B¦&¯5ÿn±×êuâÙÍèˆC/v™‰z¬±Ò¶nµ(ø½aZ #WôoŠ…+Jè;úÁ ”CäR¡UTR\@ÈÏèùü×Ìü´sŠ üNŒ·Ëãù¸N)#EL”¼¨V~Ó³o)·‡]ZCº«Ìl?|]±*ð"ô°lmG¥½yÛüÇ“dstüŸÈñ±Å>€Îo—Í'1(I*ŒƒâïW¬J·˜.µ%ô0öцüЬbgô@÷Xpë¬÷eÙ•Í°Dlö“9ƺ‚¡U×ZâbQÐNY€“ˈûù†‘VµJ ¼¨výÜynÇFΙèyÒGêÿ%áÿ¬6áFéœÔ/ žSÙ<:+©Í˜ÝtØësDO#0@HÒé¥dj¦#0Ô’ÅÙ›g—݆Øm¡Gj#05RÛ"~Ûã¶$`©÷ü•_õrgiwæÞ÷³ï¬ãý1 Ï]¿¹}‡×Þô<ü¬X„…5_¦Ø.Æ`¬%McÙu}·¡=HMS© ¢Ï ‹>ïÇ—Ûu.ˆýER–ãŸä'^H }ÉLŒ8² 0„b2%/ôf/òm_ïÏ óóóyONžšhÄ@B".“2÷º9&\ÅB)E$€“˜°MfÔø0ØdÓÜt9oû#3?½ÇÝe¿ÓôÚƒš}‘×/$cáEddùÎ<9{2úÛr•UÝ-Ã&‰9i}ì Ô]³S#WêØ*“ƒólå|»|¼,îü„¯dŠ9&n\ÃJ' ór÷ë#0 (µ"®uäxêêëÓŹհtw:þ¯^LNQt„.QÜ×#Te(Lxî0©ÒjËÕÊ&Ó3bŸrµÝo÷rkVøîû÷ÿ”m•ŒáUmƒ©‰‰í£2•ý¬ÌâÚÜÍ©)O*#0uõ?”ΫÃIr‘#0®uApðͧãû}¡È—ö“ßáP¡ ’bš°¼ó_Ã.äÝîᤠõ!M(Œîž¡Í1×·¨‚¥ÅÁï^QŸw—^áâ&ƒš‡L…O”¹ËŸø7.ÏŽ™œ{Ã^ð<{võôŸjånîV+ßH#02=Þº6Ð;ï_¤¡#˜{_ûø¼‚×4W«vùù)d|2xé=•ÑŒß–œwm“;ŽQÀ‡4áÓø GäˆD﫼|È•ñåçáÌ€Å/YA©@:òÕ¾hü¨Ž¡câŒ(%ò$UhˆÂÉ”#0R¿W¯#0;1ólà$ƒG㥼ƒÅäð¥#3H©Ï.‡#38éÒÅB#0‚ ÏWgÕc§B(P#3^…GZÂÐóUïókÙÍ!òŒ+O*†}jçé®(‰ºÕ-6#3ÁXøžU¬‡¥q¨ÊÆ#3èA-ÙøñéÊœø¾p- âwò•N¤d()*¬ùfžH^á´WÃ>|bÞÍýú;²#3y¹¿HÙÛÍMWŠš©`”SÆ1©zýïq_Q‡±Pþ,«•ù$üìÔ¯ºï$)g»šÛúw_!JÈL¼Ókzs£¬ƒÛ¡£@¬åg’Ñæ;šü7ÚíI,Â×Ûß¿(l¶S,š‰Ñ¦í&˲ʽ+§Ìó}ûþ?#>“ÒÁÄÌ&z²fí¡6Ý3'Gyªç¤'ŒTÇã?O‡ëàÌÓ¦s"o×óã¦Æù÷={ü1 €RÁÄ*[ð÷wŠº$ºÞ=xA*})æ?HäŒDC“Ñ:“L± ~›½h›Ä¿¤oò§.<ù‡Oé;;{ŽíõÓ1Ýy%Tœ#W‰oÏFíC‡ÑƒmnocÎôÌÛÛLÑo^É°cïjüG*ô-,pª‡q•,Ý«t•V#3.òèI#0#0>".î1GÔ€#0!LÏpß¼$ )žnwÍ5€¬xtâ}ùðçóPLµ”‰=¼aõdxþ¯zxgý~=gÞð|Ýú…9µQ[:O¯0Ú‰×e·yB‹senítvK-D¶"ñ:íÀqþÉM‘<ê‚$w݆ÕÂü¶åš @†Ãô‡¥EÎÚß a幑Ý#0ôù#3ÉAï)#å…dØ¡Ï=4Ñò>Ž’I‚+·BUýl-œª½g5pó\<¢ÐóJ´KÕÎÛt†¸vÿ¥]jÞu^x!Ê2*&¡Y{NxEò)ÌÚÕrFê™ÆFÏ~¿¢Ç¿ÌÈëîÉïÊ>mzݲKVþG5î#W#3ê0fH‡_ïzIÂÎ…Ç÷:bâaáG³ï§úoq…¸‡1¼Qq;ã¾jª¼V§ƒå¬fãÃüŽkš´¦R^[A±Šío_ãßu’ÙuCúrûË¢ÒGš:Þל]R~þ÷§Eo{{«9~©ñ¼Fe_ihîƒS…½ ™‡ˆ›@NJ’2#Wñ+Ÿ§épD¤ÓP¦õS5 am2¾J¦îm3F¯Â=°âT‡ªxz¤<=Èé)Pf#WKDuÈk_óýøŠ7܇^ÿŸÒÊMGù:ÜðÐ/˜ƒïß{nòç³xucb³zªD#(ž†é̲ғJ«œè÷ñX¦ßRäD§VeWB±#0ù¨Á©¯+ÐU0($ߊŽ3yayº(ëÇ„+e…AQ™@nßÊ>{ìŽY^3rÙóoóâìolP5*ø<6꾘~΋¤§’?ÏeVÇž#3odžµçŠ†vñ¦i€ÿ7{j3sòu†ÍþÅX(ûÍ+ȱ!–#3az/(N\GRÀªì/lzìå§þ‘)–-/‚–œ¾Wå‹ŠLâG*æ~õY¹r+´;l'§GdB›42»_‹kÂAV©«•–«†¹ZáW•Z&Ë_UÆ‘¶2{„“;ëA#Nª˜Ë÷IÇ*¬,êK”¶Ø«#ÃØŽ"“wÑ´Ž@è¥wåvÐʽ6Ž¹qAŒbT>äA#W¡_4Þ=GwÛ:3'so1Î5’³“¢ÏØäÄîø‡¯Æ³Q~*p‰ßÆ(ëÓ\w¬ëµÎ¨zƒø?tf³5`‚¾ç¨¯~ÞY€Ñˆ¸ñ¯C¬×U·wÖ¿žØ2ƒ¶0ºÕ7ÓÚH!.°øL&øLûõîÎwí$’Il»¨ÔÛÔºg{óÄË-„ÿ¹:‰vû¼þ‡bƒŸHŸm¶÷#âó7›9ÄÊŒHYmhdû¨(ô·å8TVõh]/pÒª¢ÑkÙ¤#WÚÂñ“;É7#”f«”¿)$w2-d ŒØá±ê¦J¯gðÿ{WK]¼nCL±§ü§l`±¬1#WaÓ÷(ãØí˜ößÖ¶q“ÜåÎ-×òš9DÑÿ_hi”S›DŠ_¹oÚG}Íj“¿ž‡ß;—¶sÕã)lÎg¯½;C¥[Kâˆvuü û¢sÅ3‡É!ϯÒ‡IWM;¶ïåÇi=`Ý»ÌØÉ©1¿‹çíf_^¹ˆ§ÒY[Pî„6ÕûSo„.xã[ñ¶{΋c:ñ‹¦:I;½¥á¢‘Œ¿ÁÜSmÝfr]éÝ;¢œð‡BW8iñ6Î)ÙÐpî,NWsùíãþ½¶_u<¶ÏÊc„3J`„ΉãÎo]þ”éÌ,:ýjJIN<åÏë´CÎ2øl̪]‘!ɇÛyŽ¥tÚ#Wr£'õk5^Oœç¾Z‡¦¸Që¯Rç)c³#0¯¿~P#WfN¥€#vuÂЦՄ-zÀ‰á¢ÇUÀàɳmTx›ÂŸ¿å÷GZàÓšUµ»B#Ò&¦°ŠdÖjÞ鋨¡îü¨:§lh×z_Úç}Õ‘u@)%í»—[·×çYbix°Ñž ¢®TR¢%üÝ?Á§·ãpDŽ'-Ë£ïÇ‘Yé÷ÃÆnIÎ_ÆSd|ptùF,ÓÈ'l7.IæJ ¬Ç@*˱fu¹ÐÔeñ+ЫKsiÂÃX@-ÃuPtb›¢Ï“€™“‘_[=ÆüQÃì¤à™²¯o¥5þpDÁÄK«Ú)vÍ÷;”Šò'drm~V×n?Öî&´J„¯ÞñåëyÏ»jƒïfÅ˦`×p”ðœh,Õ”'?³¾¼»¬{¾«š1÷ÐÃA<‹ÆÒB¾^ÝN}Ú–xÏ_6`óÖ«Ã×úyÝMm+c™zß™œ9ð¾Ñ*¢‹áX@P?ÁÕ$©gSœ¢™k£ ‚WR ‹£c2Ñ£2xY!ik€âSûC§woa‡Ò—$úŽÄÐÿ |eÛÕ#31øÿlÞžDBj[ºIáÿ8´G¦`÷¦öIN?Ò?&Õ52;%oò²|C&Øœýª½Ÿo«ÈÂ…Äg5!üy¾eþã˜A+%n¥M†5âˆN½W°D ‚Â6èàÜû¨Ôy°””ÀÑÜ~·µ4tŬSvÂRì´ˆ^6ð"Îs§^µ³Êßéûæ\:dNz–ߧ§]ªo$äaÌÊk»ß®_1vÇg#0U¤¹~#ãùÏLèèKBíû~èòĸç?œ‹#W|§§å>=§ìÈÿd é#31F/ ÛìÔ܆¾¿Ë;þ?]ØvæË?>hÆ>¬º}Ÿò|®×‹½ˆöø± ”¸t¡áÉw™éôãÃÛ&¸ð‹—²Ÿä‘Â×MéY´Ú…‚zf:'C~Nç.Ï”ÉúMê#3q›úd€õõíÄOó:¿DåضÛlÁ|yé#3ñË”U8‹º^ô¿(î‡~)jw^Hm/#3œ‡‡ü§]礇Év50‰´@ˆ»‡s¢vƒ!¢ÎãývgÜÈÓ‚àeÌì’ƒˆè³™Üü¾^2L.XªåUÎBdFeÙDV%Ùîk«&~Hë÷ÎsªšhŒŒSïÉøßw#W´©Í÷°´áñ¬kE°óeêØ£Cˆ4®*DOVažl<¦Œ…¤õBñE&¸¬R”›|zYúñÃ\aߧíçàÇÜËZÃûÜû¨#ÙæE…ÁÇšwåŽÃ…ܤ}þu‰‚@/N#W(†(`Å¡ÀP#W÷R [HH’W P¨¨xÃeUÕ¬!†HL!‡ð@#WŠ.ª¬ELC¦©:à–¥F¼ºëtö–Nï>#3U’#WG†P3}||Ö7¾Èû(´ JÐèT@awÛ¼˜šÃÇ鯺<8ÎQ»ÎއŽŽa<ËA„xÌ·Š¦rZ#3Ìö¢yô~3^wÉÒ_xÒÞ‚bÑh¹|c›,áB#0Gãö2xEõéWq:ß³?e±Ô~–u-ê-ðåôÅß·]$Ñ„Ê÷s?aƒoßëý̆&F³Îô$}œÁ¨ƒø5F‡„ÅpþÒÎçÅ8dR,xŸ¸€Y(‚%¯Ÿª®<~fê”V>e_è$¦½ôÖ/VZ Ö¬wŸèa?¤{AtaÌxu(ˆ*ŽnðQ·(¾dÔ!pã@ »·éÐêÿÔ+ißìd@7"l6™Î_ú|Cô|>eD¯ÂÑiP-õªÁ=ãÊ^Î#3µ–áÛ˜Ž#ÅâŸä•*Ù:û–¿šë¾Go±y9>¸ Ééñ#)aÐ6õ+ŸgÕú=¹iÌE$ᔎnpÁpÏœxbúŽ‡ÑϪ<¥Ÿ¹O1vËØS¤m¡ÒQÇèOwæOOóûKg Rp Ì‹ÊÇ0ßÁÕÛb(Û ¸ù øD&@’HOõ¬`ȈÅ#0¿‡?ÕÕ÷„¬}¨Zù(¼»»FÜðŠ°Gº‹âë¦eO†U‰ê}4zç7=B^àrN¤” ï\€ÀkX‹èrYyq,É)âa„…ÐÕòÁYßbô¯êÑa¤?q«;ô»Hû|y›"Ÿ1Š¦7ÚŸ0Hr£- Z;ž÷³¡ÙÏ•oŸqÏÐñI4îŠ=Á#0¨‚“«)N9‹†#¢uÛ,l”bX²Ä½mf¬: Àœ(¹=8ûç<Ä|‚¨Kí½[©¹’QS}ÚÈá¡PkÄ8*·ƒ*9ê#WeTé=S†ÑÖßsŽ˜û}Èq¼Ï3ó:ÕñÓ°ka$lsîÚ˜ùæ¾”¶ÀÑAó~_-/¸ÀN[&1â Ar#0J#0 @0íeËbT;;>x]s#0«”‡¦\u6~¤ÏžäÔ5wÉž*˪T!ð΢öTô– Œø×–RZ«éR|GÇv#3È4³Q¬ µP‚#0G$Ò$è˜Äc0ôò˜’îTÛ]ðbçXf&i´›\ÑŸ<VîZ *]‰?9#0ÖÂq7Æx–×ç£I¶¶=&‚3Òɯs¹ LÏ¿¨•Õk¾ï:rÃOødyÚ¾øÕ¡óŸêÍ#0.pQ².µ,¨Ž+!fÓÉR‰¬@rþãŸ(*ìî›ìÅ0³eV¶9 ‚+¬^ŠÅBDYf¥„¾§bï/kPK–ÁÃ9X§w€äGNGÝûyÉ€Â>Ë.8´ã¿-Ës4ͲÊâk•'€Ùß»0Ï[ŽœvW ~·Ôìs-í·RûtA†tcË£Ú5•Ñ,p†“ðºE8D©©UñN‡•Vß7šÛ]nŠè0‚3ƒ¹9 ý¸Ûm À– wo½/Î6æ\ñ%·×Nç?Êá^Cdß^>¸¹-*#3ÖZ…o[Pµ>Ì:;Í`>½]‡x¶)´F(Ž[ôÀ?=ìoJl殡é[,à:÷Šñ΂²E¼~Nj¸gñÚ&Ä:èE²jóˆ˜HŠª€tÇt'SèͼԂ7UŸÑ\óWwéò6š ˜õ¯µS #0Rb<ÅR¡>¾‹5ìîŸÛßG|KG* !ãCŽ4±Ä¹ØõM…Ž¸0I‰|0x^r7£”rúaGlY ¢è¨pôq#3œdµ F ë]8Rþ»¢Ì¿Ó©:lÏ—¶m¶4>ïìaãræ ò.|Þ™¡ëó{G¯_ógSqrµìUýt<ÐŒô‰BÄÕïÅ‘I°½T~óÑ“MÈ}0 ¬ßƒ#WÌ&IŸöu—vÝÏè! ÌëŒh]ù>¶Q¢õMüÕJüÝjÏ©%a1ÄCŹý’~ùÐ,fÎG;ó'V×X®Žy!å/scEVYšvúH²r chy•V‘|¤Å(¤‘Œçϵ„é&Lî•!_}Í©6dçM¦[£bÊSN™ÂGô+U.Іå þ{k¦zÓ;¿òó¶ÿmýqË‘i^ï?`ò¢Ú2ÈÉ¡ŸX7@H±Ð¶p²òpÈå6ùJÇK4ãš*ªw×à´´[Ÿwʾ6–Û¿ Õ]}5Ľ›v}ÎÑÜyÓJZˆßS{\\âŒúÕËê¤ANíYr§_Ù∗ÅQiuPÖMµÝ¦Ù4¶Çú‡0Ä3à-Š´Ã~\í=Pi襂‰kgg{9m:jˆ0:³Á ÑäžmWÍÕÅ»æÎUÚÛŽeÜk.›ï|²#W³¬@†uY¦]T¤üëu¢Ðé-A8ˆÓö7ÏFžþæ$Ðj½smÍap{ú_í‹Ç¢f¬§ ýœêM¯kVy¹ç3µë¾±®ÄíºFï¶5–²ÒXNÈe†{£å¤!Ï_^dªÅtMéòŽh¾ÞËyððƒsVUã93X¶—ëUîXÏ*êM[iHâm#0ä²u6g²ÂÊŠ‰·Ü ™äŽÙg£=Ö!>úòˆw)—bܳ-ѾùÑ ön–Ï"“)›@¨®62”tx#P°#¤¼]p/F<–ß_Zñ;nš7ä Kxˆ}Ãø¨‰GtZm<Sn±…€áC€‡€l|vm¿ÔÃFõ>þß1s]üQøèÐÈ,BÇ#0®ãÈúædŒÕèðˆbÀ2#zzÕ80÷zÚê«#W>K}eƒŸjr箃¶#3[;p¢Bºé¿äÒœ:yËÙ/k{œ]Vˆ>Ìëüÿ;—ï–å¯CçÆôþ«[ê.îö‘›DÒ†ŸeNâÚÕ6g—ÕZ<݇#W¦0&Òèä©ž°q—]¹‘ÿ#3\¡·¥#WÑÎÙO{–MÃÓˆ·á9íô#Áï‚ðœv?ÎBbáë&Eê힆þl;>ÏÌŠö¢Ãç#W@I»ëߢ-•?ภ×Þ}æºgL{ˆmZº‚÷éœfç‡ZœLÊy\í–V ¢ókˆÎô=t{KE^Zö7Ûó‡UV¨Á×Ø€A°AÖñÚ)˜h°5M¹©ç¥Ã϶†÷k4EŽË >_o—7>Îx("Ø-˜ñ"ûlȱç“èEhýhmŹ8"å§G—sd@T0Š&aWÎÛ'æY„¬Œ)º×‚SˆÐÌž:Òžzv§‡Çªí]-\ÒÈ,Pu¿„5Æ¢ýzŠBûMy½=ÝõŽñ% ˜”p#WƒÈxù9BŠTª9o˜€fëpÅQÄJI<•Ü#0UNú„„p…£¶iâéðlÛ‡;íÁÌçh!Q¬nÉÌá:>ý7ŠÜµDh›Á#0’« ݈íÔçv#¹[ElW‘qŠ´¢´Ð%Êge¾íúZ„ °s奜õº'1(#0岇”OCÕº š{¬`FÉÜ®xM’>¸_ºs|¤—Š³ñÖîï2¨TªŽ#W²:ÙðÓOǃ'Ñ÷í`ghò\„gaÉVkÑíµ“ª^ %Le۞ݘp´E÷5N ïu¿±Ãt((j_kP‘$Ëo3ë¢×":MP–‹Â³ Ö[<Ñ3àãúvÁ檸ã4’ûrñÞ¾¿ ¯ÑŒ²Û‹ïË ±”u×U‡1K¡T§Ûu¹ó[ܽ•žÀD±F¥£»ƒ%±íQªÁÒW®(lÒÌ,ÌPÔ/”¡É…wZeÚ¬Xê¸#3]Øa—±³Êì³íi,Eù`$êò2‰Y+ç’‰Ìc!{òÀ{3”jr‰P."É0l!A‚â¾Fªfƒ³DÔ±õŽO~¥\ªÊ«ÓÜTÚ䌺±ðšRŒ˜úõ¦^i“ÎG·;-§æ䈔Îb1ÝÖ'¼nȲšwT`¶û„¤×>CÂç#W¸x1ªx‚ uef³¬dð&€Ø§T•%OÕ+ùåI ß‹:£e‹Lu°¶#±Ò‚u²~òzß¾¥uÙŽûO³‰º ôŽaŽ¾¯&tï÷#iê[»o[‘•+´0€¶C8åéƒA SZçࢷÑó >õßÔß+÷’kÌÅ«!ž©ÝëÍuˆkõQ1È–ú¬´±•k±Ò¡~s¶+5Ƽ‘ñMõû«ÞÂez$§†Q{Ní¼¾xRj›AæTPº¯ {ãÎÙä,½×ªøÁŒmä´ßõûýŠo6ˆÄ)!Í6R`,Ð(+B<llÍMˆª˜¯Na-Š Õó²àê†÷GRÁoÐóu;?8®Ê©Jœ¼ªJvÆ“5{Ö]¬m.¯#WNÊ–@œ-±ª¶W!ëzî3Âù#ÖÎW4ˆuÈ–*ÌΠʾáfz¦*×Ô;ë„:W*Òà9œHaiV¢Œúð”ð}‚ùh£’Ûh&)u\½•Í5·¾Nsó”V½,f´#34@|P3èžÛ³#W^œÍL¡®NA$Þ2zÖ£|Ë|ÎÂ\ŽlVÝD4mŠ§3r !ϳ³33¨G(åØwq8BÉO\#3ÉX⡕ćÙ.gfò^uÅ«6Z¶s€d Ålˆèè}hÚ–•Ê"³H¢žœ5ç{V£²hˆøŽ$<A\—F(Éε}iäVƒZ#3m~üã5ì÷âýÈóìèç«"wBè³ð¿ÃUCBóÃ’$þ#0e2Úþð:Øh2P½S~(ìY–!Ðå#WÞ;`ê8N)”ÂÁàJËÆDi”¬r#3X¨” w"ؤoÕKó\ÛqÂÌ… _” Mu^QêÁG>ù³m¿M³¾mGlçhÇ”;[{ž)¬æŸh³®H²2‘nqóiü)™=Ø‹q#W„:]ehš|0è´t/§6WE‘CŸ¿^ÓÃ^•û—Zóªó;wèjŸ·w‹“•Ô·ãß,â3sP^™Òlƒ]âÖÓcX´÷ìZ vZ€r\níps‘&5ÕŒ0µâZ»Uœ-JÓ+‘nÙqQ\f7w[DÔ"¢íz`í÷hF¦:^ý#0é@ÂÑ4taa͘é(-ª5Æ °hëzZòî>_#W.ïWœ¿«¹99ÖêâuA—ŠtHxåä`¨]yùvÀÑÝå¿Û°Qˆ=#WŽd¸à¬LBw´ˆP—’ØôBö·y¾»E’° ÔG06ôÈOtî¤c”óW·žíÕݧԬyr{¹æ›Ðhzeyòæo4D çüiÐwß¼JoÛÙDÜ ¼Î÷½ÑÒ\•qsZ@RÅ¿çýšù?W³Ò=ž!ëú3‹HÚƒ™¾_ˆwdþpB(ú3¨ #þ+VwÞvt*øTM%ev3Ø¿\‚r!P9V¥ð°ëéë#00y*ÄÇ–(Òtíöõû»IÛÈú÷N ÄÇãb²öu|†n3°Ycë;‡#0 ?¯ÝK½Îp;uüŒ#0|!ï««Š.í!j3´e`ÏóULUÙ~åèí³«§ß¬|‹WëéçIã6càÛÌÞmªó-Ž–W³ÐLV‰øŒÈ~&ûåzè¼A 4˜ dr~Á‡S;ù{þ#0Þ«~Ü·ÿj?G®öû`6P*eR(N‚…8ã cûPô/§ú þ7Ùñ ê#0õüM?µ#0ü#0PžÄ#0²‚#0\¾;ÄoPÎA•ùòHŠ“ùšª…Oî,“ù JYUC·ùÍjù¢<?ƒA#FÑx„v€\”Y2b¡t°´!†ì)[?ÜÇú‹m<´ØööXÁóˆ]»Û tI/þùÂÉFµÿ5[¹°\ü¡›àšÞŽƒgÓ±ßÕÞp5#W&`퓵.Aâu¬¹·q#WÏÃqª¡Ëª—3ÖõëªíHc«š7¾²¨…uEø#0Q‚¡°Û¶Â·`KáD]âaÿXfc:3%ÝÖ¯¬‡1±íxuƒ‹õ9û°öõÓ}¿æ}.zúÙöÔGY²ö#ºh þ0?Ý#0[‚÷…¨.äùÉÚ&ºe²90½ËŸËwXf¦³4ô£XXý¯ì5”†¸2 Ösü»¹'aê^CH!µù,*&_wê^ͧuu8%7ü$ Õà3ü벫ôKlæôÈ"Å5BßôCÏøHsûÁïÂÐ]¸5ô~Š<'ë¥M–GÍmèïOPÇ\”«öx>Êñ€‘áY*%qN hû?›#0ÿw§áó•ýVZAÇÒÁ§c ·;…ƒ¤‘£·ÑÕæøËu~ì_±«õZ´à|C‚ÒúC!ü“Ÿ0‘4•d²UV¸,]ø!pú ÍçÜ[S/-AõÚñþZ»I;J¿¬#WKp/r¡Ô6!q§ˆAø¡¶¿(oêý¥ÁÛÒõÐBEã@ªÐ.Bì—)ÑÜp³e#wNbЯän%@Ôé`ìÔ–ÈFr!ËžäÁ§xZŠÅbðÓ #W2ê’Éý#3ÊÓ\cJwtIã,rî+í!QÍϯJj·Y´9rO_˸;@Ù¬ÁÖ-r·´¨ö@ax]íºÁ7-ŸßÞÊšƒ³j6¨ßÁÏJ:ÃCÒk¾m‚цHÔÙIdc',@R%Ø'Rs×å#0åsÔÈ-žJQ.¤_Ù²"œƒÂK왾›.ž#3“nFl‚Ðßa-ˆÐZ|¤?DŸ_ìÚû'–@4"¬€TÅy¯^¡²É>ï°/rxÿˆí˜¬*¬&ÝÖðÔ;ÿi˜d•xðzhº·"sÿZ#0øÉ}`#0XÙWXáP$С›ñ¿'_òkÍ=†Ùd]ÑŠm©³ƒùp»ž¡øèvÖ]dÕƒ*=Š2:,y§¯õì÷{¸áaŸ¿÷f»&$Lj˜ˆ$™ä¿oÇÕ3¾’•†_·Æ€ôʯm½Ð=¸¦ ¼#3oì£nÊ;<_½úà=©]Xœ™Q8ÖnлêꆈŸ"Uò°bgHœ!rƒ\"r±åù{qt™#0w˜™¥€_è2$O~´;“ÉX4IÇŒV Aˆùï5ðÓyG¸Þ (ßÝ¡{R@ÚÞKH2OpÒ{|N=.£˜„COWí*©ø"Ò9~1âS¼¨$ªa¢%Ðè¹ÒmovNÀ‡`:DÐ’œ?é4Àjt̳aÙ?bærîÎý'ã^¬¬ÔFnÌ ‚©*öÉLBÈaN^ìr–\tˆbÝ㬂†I,–…wTî’I$,^b0ôÿ³×vja¼uUרn#0¨À¦ðáÌ´º‘@SøŠ¸‰Ü™A¦TJÛFòô¡±éç¢2 v<´#0ëÈ7æg'`õªC¿Ù«[0íK6ÉÌ¢ÍÊ)ÇD:Ÿ¸Ý®{@|NW2?±«}…Ô-ýˆÿ–do˜Š ïÿ—«éÖô¾Ó‘ÖB¤bþx¶ñ¿°¸^‘µ?¥×'èk1i‚‘šö‰’†sFß3ï#Z´ûxª½AãÙþ_H¥ ë÷³Žú‹÷ÆR~'c ÃaX¸fþïŸÅõ}`T%GqKÊ‚÷#3l„ %/ÂK#W1›ø&ü†KÅîuá“É^h:…¦á`Éóþ†Ä¢“î}¸œ#3+,)J"ÈÁ$h£Œâêñ¶250±7ßxÜrrÂ`á"ƒcA£ôïÙ´?p¤Lînò—dš#3u ¬!çàÅd ÁÏWÙöÃ46ýŽ¿·«YƒE#32+Ï©MÆô×øsêšÔêì!ˆ\ëukr€]”@D—T<ƒHJƒ©èàÒd ŒÈ–'áK{Ì&Å’7n‘Úa~(t‘ïédIÇ\H~1usN*šx‰.†F,ä…4ÔœDmÀÜ,8¤Ž›#WhÔ¦ó fflT3,ÝÖFwt’wwõŒ;/ï#3FŒfø‡Àz›Åa¸ÔB‘&ºÜþÞ*¾¡¬Üྒྷ¸.YÓÞsì]*@#©²CÔXsd Îæ›Ð"õ,á&ˆaÐÖÉÁ¸’L75–h&¢ý]_¥ü?aØ?å‚ߊ‚#0mCÞ™ùÕÞ´Ç2ù`4d´·íºXZ -'éë×T›Ñ’“/]¿æÝ"#3ƒ`ý¸RÍßö¿]ÌFç†KK½Fá2™¢WYY£Y§2'¨6C´k9 Z]`Þ:Ë2¹JD1Y7k¯$’I3Uk˜©—Zš’ÒÖÛO3W5e¯YL¬ÀÂ:ñ—PÇ^°ƒ£Ä÷¼&·¤jÄ45Û rÂñ†G¬ÇV^,Fƒ¬±eA)4V3úÙ+Ÿ_·í¨Å€¢Œ#0¨ ò€¿fJR<Hå‡öØfVi?´˜¢ÏzPlÀsðà܃š¨„í;äO:òâÓùQâ!ÒaR!û' ¬p…v—J¿"‹KX«0x'Ì m #0‡gwZ}Bú¾´;ªvv²IÔ´ý‚wzÚ7¡çÙS¹>Vª«’n#0DPBgÚôgMq.¿Cô¿wï44®`}ÁrßyJúZTÊFšp’Ql‰Ð©$®õešÁ‚H‚‹¯å$¨m> ÃÂwt’†ØùA±dìûmƱœšJÚâá$ò¼ÕÞ|®ñä\²’šÎÝ!¤cÑ^nëM·¨pÛ«<8™~%<ÏÄèØz"™Õ)úµåÛ“®/¡X=òàeç{²Ss•#3#3O8Sú‚Í‹68ê|èè]9î K»ønf)$‰"Æ)O:ýW뮜fëJW(Š4¸Ñ0"’$’¨¿Ç,_ð“£Å=ÞÿŽ£À"#0‘…< ¤R)ÄQ#WL_çôˆ@\³Q@ƒ—_½œa;ûÿyû§w AH’œ, ýá‹)Hø2¹V'¬œ=:÷ñwdËæ™\#W¾ÃWP÷-ò:f9;.vûŠ¦ŠZ¨HBCfìÍjh†—Õ52ÝI®ê6Í#0w@½Ù,¨^(JP>igJLÍ°£‘ZsJè#3í+»PÑüY¸ßÁ}±,POÊIêÐî'kÞÉ=D…±E†Éq#0bd/Ai!ýLRÑNð(o@dî! Y„bxð—ñã5¤¡y Ôéqm¸;ϦàÉ@ºèˆ`Gì .,ý;7áMœ>Ë-Ý#W‚#0 Kü|}ÿY.ø8ú]â%Bømà7È|lÕuà‚Оǰ¡D|MD’}X¼õ$·¥ì=ô#W´OtTSÈžrÖüÄ©Razˆ}·nL~ñ=0}?süÝÎç=ò{ÅÞtÏéC“gØ6²Â?#3¾s7fÈ¡`1#0yΤ‡ÔÐgØètn|N÷AÏ„:¡1§ ;û;ô‡¨ ym-gH»R$ Çnßü'iâñNÒ*PDÑÞ×Ë´Q&&iF±üå`ŠE=[ùt×w‹¼Áæ.挱è;Í]BV·X;3¨Þ¯³B¥?è9vÀ›”!ÂÞÞâbƒî4ª(…&la5a>óúBεQÌ.Ü#W>³"Mž:®w'ÃîÐÜìßÄ#3ôñ”ù/8‹t $Ô¦{tŒPìõ+7“Æ#0bxðv€|ò—PrôòÑ÷…òˆþ(n>â¾áö¾Ù6ü+&$ K¶Æ‡È?&W|Lµ{þØ/Ÿë‡«_U¬@ŽA…5¥@ö)uìBCÕˆÉ$“Ütôí}h›7~ÍjrŒÄu#3…µ€"@O›Å_Ä‘I>¾±®9lj5vô´÷–sßûÝ>ƒó¨0G½Løôu"Ü5µ±–§¿ÓÅÌ{Ê×4±³J˜£ï°è€›-ãöznŽÀÈÂUöžª’°8#3AËæo0vÀ±ÖÚÏ¡Uüì{`Æ*þ•'ш¶J'Y:……}‡ :óMÎ,@‰¨5–#0æZ‡>FH8ö;aݲXç—âð2fKóMã’težì<!FJ°‰hêDFŸT}$ÍœŠ8…_ƆPgRGß¿'–pdeÿ#WTN[;Ô*2`‚Ò«ï÷ṏžŠ¢1ID©8êõÍóæíˆhÑ“1k¬[‘¶a£Íæö%ÈïzÊéMO«‡©6óès"™„8Øxê™ã‰ ²+ }þDÙ)¦_×Ù=éâz%Ø–4Hm±ƒ?NÓ¼ÄO®#UÊ‚ìQ²©X5Ë¿yÓ½ÑótúÏH§xÓÄÐ>y½a؃³ Ì5‘Pñí6š÷ÞË0ïüImn#3 E#Wx–2ªQ{ž·¿àòS쯒~Xéîì#W˜Ð/å†LʨD.•ú-ýýŠ€~-$¨Áª¯öè±e÷ܪ²ÑÔÔ‚†À…·†”c…$Š»@¾¹#q›R¥2C½ÌÏËÏÕúUö?¼ŸÀü+ô|‚hM˜vÀ9D[2i}'à„Oj‰oìùÉ/Hkl50Ñÿ‚gîw!¼·VxÍîbvl÷Ú⧴#W«bBv Ç™ËñüŽ“ðyH#0§°EƒT‹#W€”ŨϚž)Äû#0ËíÍPÌ .ëÔuRGÄ™§ž7夜ÊðÕ{bŸ–GmÞ&5û.ý»3ùe+ÌïE<²0)ñÔnã7ôô`˜=á‡ç0¡JP‡°P¨Z6úyƒù¥hGY–KùæYBsäõðßùà£ù9ë¬ì-%ÖyY™I‰g½XÙHû«:`-ÓÔWræd‚ÃsUoX£I~´bTÎe@b¬L™À´¸„LÂ<ÀûTCÞQÝ, ÷û\À=c #pµg( ¨ûeœ£&\PP¢‘jˆ¥ì ‡¢"öP’ˆ£‘Š¤.¥à(š€ü¹¡ÒAÑ6#03¨ŸCãM¸~§çõ3ú09M¾´çü%õÁX(š*¦°²©ÞSJRœè#W€#WAÚÂ’E:o¹#3»S¼?Jwï5)™¬(ü¸šÕ…€ˆÇ΂ĉÿòÙøOÏÐiõ#WÞïÌj¿MR$R+ÕEÜ?oÙD†<-_:6’`0†˜†rÙj)T6B™„•òï6ß&sUé®n‘µnÈl¨“k-KnëË̺¬òvñ«H¬ SwøŽ)ÒvGÕëóýÚKý@QÅ982ï»Ó=¾÷>ÍåÑh(w¯¢ë§É˜…ûI#0–½Xò!¢0pè=>gš0Øæ‘È4À÷Ó¬Øýæ°-f!ú@´":9!bz¥$c$@„F€(È&8.p©‡#0Cœ#3¦ˆQ/¥â#3Žd=Ò_{@yW¯låÓÐùT8DQ#3¼N¸{s§¤^ÂýJ…!KÌw kzœ:ó+³pþ‰î¢’rqYø31ÕCÊ#0ÊCGa˜Ù´£iÝø96N|…Úé¨~¹Ûmvûí°Ïüèâ†`ÐÍLIq¡»i#W¨aïóî2íwd£Ô{?žÛ_.û'›u~«l¿‰=!ààô¸öë‘DÊ¢”Dääû€þ>óì`/ÄZT¯9ìö)ñ÷>»§ÓÖ¢B¶Mžë}Z;ÛÑë©PûÈ_mŸÐ‰³ï…ZZxa¦j³CÓ¼,)$ÊÔ³Ô²J²I#íç˜>˜Ù¡—¥h¶ð44µ€jbA Ì©hxÆÙEûHçû½Ï;¶š¤¦,øU"©uOí+™òçE/ÐEœB=”ud‘ÜQò2rBùö?ʽ‘!Ù±HŽT„‡4Õ!‹ôÓÙeó@uu|¾Áë*KMÅÀ6_~Áya½Ð ´˜êãË«\S½%¡:ÒÊ=QÒøò#3ü{ÔQWøSyËJn_#3êúº¿º<¥Æ„<ÈFÖ<OtÀ‘“¨tˆ$VF1AIgƒ;ÆW’E„^ÞÛ£f†ã#3œô‘àc)ô4OÃúþÉýjíûµh¹‚úsÄ>ü¬#0tb¡-ˆ„L}†¼}¿Œ¬iªSô§ûzOgî3ƒŒ=ršä@Å”QP}…*G´sÎ þÖåGÒý»E‘~³þÆÚß«¿Uz»WzïCiM•;½…!’¯‚qXe!Å”áÇ jÛ˜¬¤ŒÈ"Ÿ‘"¡‡æG ”¾Ç/ ªqŸÒßÜùuil¹k täˆR)K‘BâÙ08 ü@î¾79†ÿ#3EŠ@ÈnréQÔš‘MCøþÀ±ÙpÙ?L.œ·ö›8#ÑMð†{ÕìÀòS!R"دèû¬—#0Š©Dž€&£ÍÖt«Ò>º‡©Né Þó³$ãi#$BÊtýAÁx‰¬ÝO#0óX¥é¹ºÀrÀb*6â0dËÝÊÄ3Ö]DQVL‚TE®„x‘§ˆõo÷$îe8y£/_,|–¯Œpgq̃6`#WªŠ¨4ôâ^YÂû"¨°©Ý‚v1d‡NJ‘®¤nb=fÒ/Åv Ç´4³dÙºJ@€Ø¯WÏ·k±$Ûbiú®Ã¹!B\LÉÀÛlNWvʼnx×»D¾iÙ™«%K²wlqȇǯj7p|OæÖOæ9Òq–qFœgÓwpÅãh²EC•J¤æMÓsEnŸ0õ6´ö楇Jf¥¬(A©|»„o*8¹hÅFÁßÔ>ïbXý:¦>Àß@xz<{pv›Ìdö{ÒGsx5Ö휉r(…M ·\›£¡ìïâ#4›Rêy#W!_AÞ]zrç.À˜¬™À]‰€ÐúutØð·Ié§øûŒ±ÀtÞá°â#3ëöì׫:15#Ù·0·¤T5ÐýDÚ£½9œ[#ЄcÐA«ÀFÑ,1@)X'#…Êé·Añ„ó‡“ÉQ ÝßïAWÀ>{UhÐ؉6e´xÚUl=Ÿ^®%qã9fC·iÖ]E†â2t?®úêƒ^“Å#0=Ž¤(å¥xé]»Õ3ÝGóÑÚ1#3þ$÷Y¡A|(ŸhÔ<&JçðF¶A`¢–ÛKˆIßvî£yžè £ìO§òör¯ì#0(DTâ¢<•ôÿp˜D›Žßä=¯AÉ,@hôô<mÛ¤+ï E4“TÓ!êù\æp‡…;;Ÿ³‰NROA_8SöSIìøÑlbZD‘?Ò‹Þx/ôÑñ=÷N~ÃöÐs9ªÞ1FryÈÇá“wgYÜ~ÍõŒà9#3qÃõWlïó÷ã÷^÷¦¼v#3ÚGoïa“7<)Ï…8©ª)" vH—ËS ƒv…ÑáØä:«}ŒÑ]%ûƒ¶Òxúûù{ôÞß/²UI$“ÍûM¢XìJ‰¼© Òì6ÉÚàÖ$:n¥ÀŽÀòPy—º=<ŒÊ½ÅÉîfòô!©8¬†|bˆH$`²~îáGÈ;À¸ )´z¥èñL”$€dH˜ÑW´H#0Ø5z^“ƒ·–®£»¿óoó„ÞëC1ý¶Â]}‹ŽÅÊV/å^ibtÅënB±Óƒ2ÆÈnP/ûr2'åG‰^mÊ…jô;챈pª›²B„³‘‚8Ãgcq¿@QŠŽÿhu2lj¬7´7V*ªHÈÈ1#3a^Ï·Õùþ³Cßîù@„ÈþZ¶¯J—‹«½z©)j÷Òî»yÛ´<Jµ#0Oò?#3G¦oHdÿ\5©ÄQˆÿ™Ïág*°HæúÀI®G™vEÏ<pj¹&‚ïi)‹Ò|çÈ1Ð)Ù¶PS(c¬?;%gIÌS?帘¨IS®»ËW´«¢¡–1o‡×®}s¢Ji‘0Æì,X0R~LÙüBjG¡d𚺹z:ç+©˜'#”'4»=LAAÜć:CìG@0‚B\"_$ò–=掜N(Ê#06Î2f¿.ÿuÑàyŽ)ÄÅÝÙÈ?“ÕG>ý¥BBMÌ#WI#Wéð¾Ãˆì:ȳpÓmº·_Úê ¢„…¶ý6ë?Ôå°œ—¢$|+÷¶D$„B2Çàvºîè˜~Ȧì#0dýÚ1V1ÂØòÿˆÐ-cpü±JöÆæ§EˆbZ ѣú5ˆÛODJG6#3…±TÛcÆÕ“ö–àÌ”Œ`Eƒd ¬’c4øœºó+ür±~j¼ùùªšÐBêzF$~ir~'·Qã#W“ø=¹úhjO4é“VáM}(As!¬ÜîXØÜ{_i§žrx}¹bO¦R´£&ñm>àQr‚ÖžãTˆ6R¢?#0ò‡HY¬€WÕV=Õ}Í ‰‘ÕýÝ;6CÅ }zÌ|KƒßÛóÑÝJîóþŽÇàø#8Ò¯ÃÜ@}ί߯¸Ù‘–¬³Y•-{¡ïM¥ç`ÄbM¶oì+0øÀ°Y¥öÙ0^¨¨± ¦TiûÓìÕÛÑTæµÛÜWi’½ ƒ±'¸¢ØŠH*€0À‰±/²Ô\aÃ:0æ^>…ýñ/Ëù¡âlnWdKy}{ï™#9žßÆý?Mÿž~œæº2¥èDuŽ÷ïázàƒ^…‹-€‚£É‰2}#0wäûÂC¨Z$HØEÏm÷Y #3Él®l°K¯_Kï:“‘_hƒÊã˜?ìCêQýÝ=¸úGóW.1•Ê…ºÐü$žÅHÎ?‡÷?—ûÔ#õÜÑS!»VRYBlÙ+Ʀo9×LËdòž3¼ë$¶äTY(´üŠ¼P“EKd0 hbliŠƒ´ÈFà]På¦ãö0W2ËÄâN8Ë1`E¥Š``TLŒ ÃCT‘i5XÅ´¤PÍ?éj—ìg!ô#W°†ýáæOPà©3fI^ ¥¨Ë8ó†Ä@Þ÷µ Æ€ÿ“ðXŠ/Ùþ̬9Ö›zæ-CÎÝ(bÚô¸áŽãe¶(i68P»˜–öðA¬…$P0MŒå-h\0›˜Gb?Àf!‚Æ#ƒ{+>4‡¾ê$m˜Òm>hNXTò…Cñ€ýI·´~Ï‹×VL3ï"ƒ™a!Yy¬ø§ëÐÚ´:ÏÇS0Åpg2*AS/á#0¯êŸ™X˜‚Šâ’uB<Ÿ„ê°¬Wéiý¬ÎgÎÏÖðšÕO]^ñfué׳¦L¤‡òŸáT–‹h#0 µ²>ðØcŒÀD.‘j´8§/ì$¢ç¸ôñ/ñúúPzv×;ÓoyŒ<ü¤Ð:ÁPÛÛµ5-“b_6¿Sašv£UJÓ".z‡¹žÌCNÁs…ñ¯´ íP;\‹ß[±ÑŽòtÖö—Ìã/J4T°îÏØv“×Ô¹¿ä_+o·jŸ3ágí ¥FðDÊh@ãR’íõqI2͘@¶@µbŒý¼`À7Uîý_¡ ¿õí}±ý0Pü}VÔbŸu6de¼Ÿ„ï‹P5>E`‡ÓKŸ9û ¦ãÿͪ‰ =EöÃ1½æ¸uâÕ±ç×èŒíuzNžyá˜Ìиs7ïûéCO1X³ç,Ÿw#3oßQ‰£7DË%„~Èé†Ó¾|qèU2o«FÉ»GJÝsJ4ëBmã÷‡®ÇĬã£kFŸÀ_’lÝg “{Eœ`¶€{gùtÉÃS¡{ͱ]QÐÓ,òΚñpSz%[ØõLE33Nù ’Á>œcá¨4ƒ˜“#3õM/ây•ƒxÛ3‡§X¢Ì“Éêæ)h/U`Ì5À9f,f?ºPï+˜æ"Üö¤óEÆ„m‹•qw=¹$¬2‹²ˆíoækwöÖùÁŒ¨Drö}†ÿm¿gÓÞ‡úØŒõÜÕØÌU”@Óý"#3ò›{|Rï©ýó,%0!PöB§—÷ºü#WB5&‘¯øì"/ìü¡£=¤?!9@gëûmÝöw¶OðóU_§¢y#TwŒ#3´þK‡{ë|ߎ›Ê/€4s£Ãõªô>ûµá“Ún>¿–óAÞJî$îç'c>i˯ `ÎÛÉÒ4¢÷i@¢¦ÖåPªËÖü¹¤#í¼le!ëìûÿÄ”HÖ¡^G…ÚEOzëÑ÷c#3 ªêØö<ͪã!^÷jC”?x8®?½6þÌã:W°©P0¶ˆ/ähf9¡Põ¸8ÞQGO4ƒ~Ø(ÿMƒsûß‘c–6c^Ì:íÏ€ÎTÐçí{ª#0ÅU=¹qX á]µ¢#3ÌïH£œ¸#3î˜íö÷{ÚÅ÷;ÌvÂE7Qdy«ßjƈ ftþ‡Š¶:'¶¯‹~難Ê$*¹ENû#Wq}¿‚ùø‹ ®lm#3X¸B#0Ø”? "è‹óÛ="÷·Ó$i+Þ¨qrѳ+®5ˆ¾KF+Õzju-ŪºPÏGÝC@hçÄtAö(Ìk’«ÖûuÌõ@RI~[ÜËšÑj!Ô bbBl®lÇ)èbå•¿5Ò\åm;VU1ñŒn¨çÇo=˜“n—àUƇ3ˆ¤ð»?µoŽ¦#3z¾Y/)çOn:RÊnë„Q”îìÄ ^çmeû™‹åWu·,á¡p‡—©pÞìÐGûêl#Û—Ò÷tÆ%c§y¦H4–½MƒªjÄ6ù,€¡\dí‹ÖÃqÀ¥Ðµ}ê©lTÔeb€HÁõM±²6E%žK•¶0²*#Wd >tîb#3.SJêîK¶ËÊñ—Ôw:eúg)–i³/-F‡”mÆß Ì§²ÛÙ)C‰„IRŒM3F‚×ÑÕÓlÀD'*Öy'-yo¶«Ù[¦Ø’˜UÝœ®#<T-jƒÒl¨("p–/â¨2ÖC#3Ùµ8ï#¦Ò`RËFœäà9ÿÂ#0X#0¬€E8›ññ{Ô]÷KÎij:ŒE{2ÛØH„#½úö¯0a:[¤vØzÁçŽåæÕEøwåÞë"¼#ò®õ¿MxMÆÅ~ݪˆ<©;ñ:–ÛZŽ´¾Û9xwUøo…%"ÿËÛ0eü4>âûºMç÷^':áÊm#WŒöš3KÁJûaû[²_ì_Ozrút—p´_]ö©·w•ç”HfMù$adv0Œìýз«˜FýñGõô鸺1ñ˜ÜVè5º&Á¼ Rˆ„êc%ˆ-8ŸŽ;›áG#NoLv¢îÊöoå½Oå©WPY¢!FS§k0æUÌÁ¡û6€>NÏ›»CðO§ìN®è[oz£¿/ ›¢.åÝmÃZØq—y9®º#0¸…;»ÃB§6ÎežÌ OàÎóó¨]%'{_mìiˆ‡d/×P\Ðj€³þ£¢2„ƒ+6öš·Òø¥8'êÔ£Ñ0‰Ú‡)Sg˧ðÄ}¨û‰Í|ž4²‚ìJÕ˜SDƒ Dqâà""#פ.{‰xÔÁO$þß…ú{¿äíŠõÒñ;StR|&½©ºúÓ‘”ߪgD®âÂ4˜ÇGî–ît¬=Œ’˜@Ïøéç›á°ÄÉ¡{ŒTɦ’‹6d¥{еCÁÃÉ tˆÆÌwézûýb}]øK`Ôš)ÑëÒî¼4ÝÐÚ|ÄÀÞ{Q,#3ouõÊÌJ)}ƒ™ß“h–3—o$E»U"a€äLÆÌ#àá'oV|^r´;ánïm·úú»÷[{ý<!é¬M£Ì}È®$¨N.•…$6rÄu™’+U"²5·KhÉ<¢1Bõææ¨QÈ›t¬…îÁ¬%òÐëØ4Ç_ÊhѪL…«ø¹Ñúg·#3ŒQȱE˜wDBFE>†óùÿÛ¾|–Wöjâ„aPU+ãþŽÔ#3è±o`ûÇÃJI#0ks½8”ròÑH ðˆuàþ¿ÅýYû;Ž«juÏöYd¨ã/?Ê»œe#0‘C&RDó–(þ-x?ov¿ßà>ƒü³÷¾#qÞx& ÒÞ„füÿäË2ÞŠª«÷ž†#u0`?ܳA‡ûN 6ð[hFç“@æW‚f:Oè¯fÑæo&öƒ`tÝé6ë#W3n7È”q“Qr4ÒR$b„F?äòOì²ôísÔo*UÙÚg¥‰$ÆGlF¤àv™´ƒ@ðÌ7Û±Úݸ*ñMiܦݡ 5‘hîyŽûgÄ–.-ƾ‡0ã¾”G¥ÕŽ%ŽÁänæ÷ŽÖ€ñ«ÈžÃxàW=zKƒøLl#G™$1=Š!½S"SDk3Ì,̆¦VÌÿ!±ÞqûM€½"%#3¥RµTª4ã¨Ìاj#3Cx™É $ƒ3ß_F±ƒ›ºB¬'Éäö"WÛö[º¿rr2Áb¦I¢Y”G˜xž¯>±Qˆyõ<æBŽË}9ÍœtОe¤ñô(A žÁèü»¾‡åa®O±bk¾*=S!´Þ¼œèG½ÚUš©Å©gOÝËôr 0Á‡1†ƒPÖƒbhF# .£Pf–€‹Â<8ò(í]ŒßTnÂ>ÐÎ!b+Q™a]KqÌÂnàà‚ÅŠ,QDT‡NrÍhR·ˆÇãóÛs²ºk3ŠÕB-!Ø—ÚÓšLK²dúOJn< ¡_¡Y#0<©õ#z6ù|\wéû #WvhíåjêsýþΛ-éû¡zxëˆÔ9>1ÚîÏEyf¯«Ô|Ž�ñíhà (¨~Á=M=…ŒvAÅÁËJ-dƒ0ÿ]Æ…š2[R³wi"ª#WDí%ØbðÇí 7, þ)õ *’Rˆ„¿–Û“"KÊþOô¥íoi-™£F±¬oáo{¬/5‹lþ?#37#3¥*‰d`üÿØmOxR¾´9B>ˆB0Z»¶ìÉé€h7¿Åþˆª"#WCÛLáƒ'ºöø5€I¶j‘#W<z˜…þÝÔzõ5^%TV"#Æù;GøÎÎÒÏpvaRKC5ë.…ÍCöØàÌÖj#3,#rH…\EaÊÈHâÑN‡Á|bËjäàÂŒ‡©3¾GðvöK—ª ‘9C4†–#3å ©c|¨e{B†ç '29ˆÃ‰¨dÙ=´T¸/Mgx}É°÷0à<ŽmaZ«ÐT÷öŽDaø#3#CL`lj#3‘97î6‚háŒAŽlCn^WßmÞáéäìC~¾e—À—šÉLÀ·äØÝÛnÁÓ¬Í0I˜…s™M79É.Eé#W앬CÄÜ0k™ùgRùwG¥èÚJÚá½û¸ãý/K#W«_£#W[ÈBòúÅ7³Y4!¡‡ÔY#3K˜ÌW@8˜5‘èKô¥àä°Cr½}Ô,œ ÄÖÏ'¿z›:0ªÆ7ÕÒ×c /Í0aÞîz‹ÍgB^䃽SWE¾§lŸ)d=™§žÙnäò—§t¡HY§¦-5~gw×|;Éß»„28Ï<G“OM]Ì,„ˆá‡{0›wŒë0’7c€ä®»5mÝ"$Óø¶2#3µ‡ÄÝÃBf‰M²Ý¹o‹`ë:ÉÅÅ@è3r–‡jå“€oâÇ‹ pxvõW»œ‡M÷=ÑîÅEÍE)ìÅ·g©ëÔ1™‚ÚÎ´Ï ˜!¬ðn¯3s @ÖMs`äê>©¢¶÷z½—5³{:„³;ë3KO–W2=2C_OÙ÷ý\ýž{zßzÁHê=ÕŒîI¡£#3WGŽì(¹¨è[[#3ë#0p?çr·:s}±N÷Ž;tPk* Q4 Æge8*Ômˆ£#3hשx6°¾õŒv:†HbnŠóU4,Êê‡AÄÞ÷"ã‰Ï§«}ÛPR™I…âÄ°,9PCH\œ»YÄc‚^;¨ˆ¦÷Sž¨GP߈Z×ͪÆÚ-çD&ÖX§:ãÊÒ§(÷ù ê(Ñ=N†÷ui!:Ç2ˆaÕD½•s5³ÙןˆX“A5š(%€Š=¦À7B8xƒÂ99Ö/ÊÃŒgÎU"á#WTѤ&§n«LûKß-l9†”‰#!îtšó·]¢erå^^-Ÿ%íë·%îÄ3ëï÷:èŽ ´¯épù#D>íðC=âÈïTœAÀôr8î'An{2á¹ÊÛ+9ËuBˆ˜v”].±á¿‚™Z›‘Sl׃HS’‰$è×û+kjF•r Të{MðÉJ&ó†¼É¸¥=é%QW¡à8€ò 8f—”`ãçZöO “*ë({ù`z"¦ÐÉA–mî,Å43iˈ±áï52g™c°”lXòõà5\¦Ìrïî„8#3K7ša³bç$¨,ˆ¤¦Bpd#3=™Ô½o•¡TcxUš¦–Þ©»K½Ò'‘˜ÄQ.“ÏxÄ„·p]¤Ú#s’$V#W ©gŠ¡T=ɼS>D ¤‚L´ÙgLm”»2îx'%õ#3˜‚ìÚE‡<Ê'±‰Æx䣑’Tèð„YL#Wjr±’šJI'/©®ð,œIÑÔs¡ïks·q„´hèrç1À_—Óγ)Œ¶*ó2ãÌÌ™s33,m˜ÖU™fd’¬vÌõCþÙ¶ó߯d6wa'¬"Úï§wk1E«Þšæ•+íî#o^Å:aáÄÒÔ½zßw¨xSgp;`/p†É姞YnIÁûÄJ¸ßk£šgBa Bgr.Ýà6¼ƒT|Ÿ>fµO»<qu‰Gï~IâdQŒÃP6Ø3’¸»FƒuaL)2Õ¤@KkN>pbu°ú½A¶GL„Ý™†=Ü–p4ÉÓ”eÞD$DàŒë3aeHˆ#à5§×Ø,—q‹‚1*Š]RËŠ‚"^%œ´˜{{—;ÑëµP3ŠÈ£á·KÚ†Æ`!26ƒ7³f§d–侜:kŽ».ýç=¹‘óÅŽÃo©ñgº¨U$Õ²&XÝx==a¬šrHxˆå4R‚ˆ.Õ :xZÙ_Ý Bˆt›÷¢¢ô+°†ÜÀ]S¾-D'ʇϰ߃ýjÆ Ì4@9j/%CÂ79ïâUC‘œ8#TðK jB'Ÿª\ËTšë2Tñß·a§4õm’1ÏÑ@ÍÝ#Wxò6YaãNø½ ˆñÃûÔt7m@‘Ê5fø¦8œÞ’ɆBÅb[l·C]gzÐmƒ¡™HHæy‹!ê#12ž®”¨ì±€yìÒh£lhíÚÇ6Ç–\¹X³°þ|í„Ù`©¹{Ì!2qQ®ØU**Új^£ Ã"hi2s¸Õ]"yøí#3½è¶6à#0™Q®w䱎 0šfÖ#W%œÂ„,ëüŸŽä~#3ÄCclaQQØ—5(x&BRr$õPd†z&ZOQ‚ç«œ"yJÐÄ'K*L‡‰¹^#܈dh°’‰˜:„`ÝNµØºhUÉl>5ÒP8aCSw¼}Q¸Hãnˆ…µ¶Ì¤ÜBJÇo+„Ò9tä:¯éСˆ¿2iHÁŽŠÔŠ¢Q¯[¦pI&™;Àf3ì9|½ƒfêí¬žõîC”0ŠTžÈjØfõ&ÞgÓç w4ÜAvQ6æìK&ïTB¡&ÂòÁ°5ÇÒ0¢@⃠½DRŽÊJà;;h¡‚)Žµ6ш$®IN ©«Ü§gÁ„îe4¦#0”aÙpT11Q¤Ç¦ˆäÁš3I!Ü´Öª7#Wl±k+N“¬õ^КX·OhÝ7ûœÁéðÈù΋ÖLÜ"¢ÞŠ|‰Û^<c’^íðEt∈‚õÓ˜<I\Ê•;$|¯?µê|hÊRÉchšl°1f š”#%!‰ØbÄî&ÆC£›Ý@f€x<Cbl=Åbtß‘MŸ«ÕYZ 'Ó·1Y<›Ðì0êaÏO€I±fÅ-‹ÔDhòuà³c©àïu)Ä¡pa[ƒyÎÅrU(LE?D¾ŠËª÷ÔÖÌ·“9Ðól©~PóðôìNéîLûr÷è¼bÑÅ‚BÁ#<¼¼{½þwCfni„‘¾æñ¾– 2$Õ)×¾~³<¡üã1îoœ¸Ó0FA‚Až¦ƒ C^ï@Ü%»íÁ¼€“t²¬’K#0–ḠwMNI˜G0Èú8ß36’ÁEïÿN±©\0½8WªÔúp/Üz”“0œŒÊLAm*vÿºŒ®jÊî“^(uÅÌs¿¤ö¢È™ÃƒSr~Ãô·=9ûâÍwÚ¿‡m’ëX™æ«ÑÐ#W`UÂðÀR9òŒ{v˜û§¢¶þÒ?2‚i*K…ì $¯…±ú“Pø”OñçýÖÔj¸P¸)JÔªgßðåœè.={ñòñÏb†°ƒÖ´‘l,Dl–·ÇÕ¹\ƒeç;‡Èë2½ï{ÚjyìD»¸ÁwÌÒ¥ÈÄBíÔôR7ìÚž‘(‰FÓ·gõN‚<¥QD¤©Í p ±$ÁI@@=ÿ#×뜷ÃÃt¹WátR<|\ÁÏOô‘òŠòöÔY‡ù¶¨iZ#WImçò#0mešK/ÞÂ@•·Ý~Ÿ¾ý‹V®ýu=åÔ‰E³6IFµ1•”wwz4ñê d6úðë³ùX¹ÓÚDrñá>‡„Ú#‘1S[Ûº‰5ü¦Û¬Ì¥)t#3?;s Hàç("– ’xùŠ®ˆÌ#3ˆ#3ÎU÷aŒ@›½ÄV1ýÝKµQ¶+=Rà|WHAXJ61~ü™ñïæu$„ð`=ª(Š-@+Ø/Ç-1IGžÊŸ—ü¿qùÑÿ "äÕ(¦„)ZÈ#0yÍôŠÛ°:ž¤ÿ£¦Ëò£ÊàM<ð“ióÉZ;HMÖÕQ7àuÊ‘*-@7²*#˜{¼úz-ùÊ¥¡¡†p°å†3D’0cˆ†²V&ÒÙG@U~¼i #0„!ã?ª$H)aÕJÃìžùrš#3"yâÆ#WMhíR*!ÅC :Ì€|è‰ð¤7#0ª@:‡Š°>îàÀÉ?Ušwí¶×n·TVñ$’QkB1Jv°”±PE(PF+»JÁ3¥bb\†#W€`)X‰Ú`²J•“T™ Ì*Á@fü[¢-×ÙÞp‡”¬œ¨ñËácò@æl„„¾'.ª/qÌyUC¸|n¤ÀÃæÄlF?•U'g˜.¶ È„/H¤=F‡XK*?u>¸I›*|ÈTpˆ›ÉBŽ #3ÀÕ½5Þ÷‰Åóƒ_òY#Wå>Í«Ú#3´`;së·.ŽqëE|òDUóà#0|bE †Þ™“È«êV6®Z*–’Í¥I6ŸHâ8È€ôÅdPÄA9jÖªK4¶òo{Õ®²#3Ò•b§./Ì0‰çèI=Våc}’Ë0Tk/£Ft,¿•BÄpÒ5BIñ…emÅ [@`ÖÅM#°†#0Ü ÝU#3‡Ub¤ö“ée_Ñd«ÓŠô[—RIõwo•\¯Srõ7v&…$Cm¦2€Ú2ÁWþ+¬Aƒ\º;¶æåIY]Ú9/ZëèòQ£È´R„‚º”ÏñgÆž¯›m+7ò÷Ó3dÙ)Wàˆ¡Á#0žÈ¹!pÓû%-„3,¾g„‰â"ª#WÇ°=¯s'·óú§ôdíûÉáÛJÒ\ﲈ_& ã,…}~œä{7î—çaÚ<Œöþº¶(±¡âŒa#à[¤g{ k0HÁä°+Àvg.Ï¢¬±ÌsƒÍ#WÝXÄA>SH?$FÛ3‰…ÙÒ¹®ßïý%Í#òô}xÿr/™Á¼Í‚*j‚©AQ”#0HªÈ’ëЖתÃ!$c"$B&ôÙôo²H†ê^“]‡]Ÿô‹Ò=ŸjÝê 먧û9»»OvŠgÍ/¹ÿ‰‚~×Ég>«êã6LQÆf…’e‚‚Êp!Ly×H’“fC&º„n´™" #0å"Q*KM7PŽöæ„)¥!al—tÂçúXˆ ±êƒq¸§foJn×x9ýåI <*At™'ÑÊl¾×«ÑëhTH#0‘@A•öòq¶t÷ŒOxXmØÆà#É#0÷Õª¬8â÷ˆItEà:É×¾ÒBYèímaú[¢§ÈŠŒIÔ<º›¾ÌÅ4ýyïx0Ãø½<Á æ%˪©¦Lÿáþ2tI![7GÙÒÌ<ðÔyy.zd²U’5£lì¿]ßlgÙc%££í¢Ç*‰U3a*ìÃ#0¯ÙÒ¢ˆJ©U(¤îíúC³‚užMPJ;#3‰ê¥ÕKÙÊyÜ“Q„T:à#0Ô&ÍV4ÀµyR»U_´Moª±³5 F‘ ‹éä"G)%|Š‡{,ÁTRÓ*(œÊãS¨‡›!;h‹1<„M©ïDãùilDm‰°gßUfì®7¼/U5ÓÎ}Æ«˜`g\4áZ†ß«î=µTÐPJH-#31.•¢®˜BSLˆÃõUïB„€—áAU#0òìZR›oá¯Uû^ÕˆFšÆÚU%j6ÉÍXÒM”I¨¤Ú5–•[J¤´“V+LÕf4¨¥3*ßÅʶí²Ac¤â’qŸDè·Õ*NPá´òC„T"yâŠÔA‘F@¦ÖôÌ:¤)JÁÞøöXÇa6þ^vDnÆZ ×6Ú'ðË™#W#3#3Ž¸ª9¨ƒPÅ¨Ö 7¡h!î#0Y¹¶ &ƒ›%:dšg]›V{´D4÷ÖQúñB+Lj"²å¥³DÛÄ1°Sn•ò¿Á‡fâJ²ªC"8a`‚¯¡Yh]†#0–2‘rZ#0:9νëqà€=pP’E#0@ÙµR•K@]¨¯GŸ0<&z3~ç5a‚ñkhbn¸¦LÇ]+«)1p€¤!R¿àféŸçâÀ=žCϦPvJ‘ŠUR›ÕM›øÔHЈ(&½@,$;3Â:°„X¨Í#3–k~ÕÍcM¢±\Û–ÜÍ2¡KÍÌÁ¨©6ت9È¥‚Ï¿3òžM`ÐC³díKÌÛSy˜ˆ°#0‰!rTNðT6r/¯‹çäoë™çï?§éQÝÍ@Á„Bi:(HɆ©Ç?¹•s ž£‘^s¼Ogrø`:ª† '¼€(#0€ð#W*{ÒGPõìõúüŸ§Ï¿úÿ…*˜`$ƒŸ®”"I'Æ0Îïež x¢(ô˜7vצ¯¡ÒÛx-¿G¡"Dý&à#W…aH]”D{jªžóm¯/¸ì:zyXÉuDÕ…:4¡x ²Â*.¼érùÉñÞŒ¼ùk̨"ŸÓ‹í y›×L‰h¯Ñdã†_ÄÙ1Wp4rˆÌf x ç¨v÷>|Ïë ÃJAþŠvÀ„M †šQ#WŠ9ïõž#WAéãŽíÙ‚xÉÒÛ¨…:Aò¼$¼S·½þLžÓµ}6†ÅFo{¾9¸\0¯@ðRL#W¿¾véžÜì')‡pÙ#0Pd!Ó .ØÂoéÿ¯”ë>‡W×Gp]»ó$°»`<Er@‡HÎIü¶#39žþ{t¾Ë¿^”CPå3³Ö¿'S×2;CÝÁüP: ‡htÇ÷ºµ88¾ÊŶ¼<¢®Q*vKι¯]qÖ÷ü²{DCì‡ÜX:©68V4~·{-Œ>'âëCÏDušCLûù~–—O•q Ïç!±·RƒìÛæ/F2åý—6Œ1ëQЂ®Î³¨"&ytu#3®§L #0»t=¶ÞaÊFŒP¥3tB®e¥pÊgÚ…ŠÌ}ãÂŒ2þ‹%³ƒÛ1·•¼V†NàXRÀX7†Ø4Å•C\áËY,±¢åkŸóÍÝœ,ë8ºm ‡ËŒé‡S"ˆ@$™O6…ú`DÕÅÇ33ŠÑ»Þí'`‡˜€"‚0(úÚMV¥CÖ>`óQâ ›#3ǧÁ÷¨;¼á¿hÂ?#WRоœNŠÌ·oìþŠ!Ý"B¤þ®ü@ʈ²š*ŠƒJ5ƒîK>þ‡˜üÀ©6?5]Ý®å(±Ín•"óôþ†ˆàLÁ¢°=Y NýCð|½tìíÕY®Åˆbö¼ßÂAÇX7Gðp67$†¤÷:XÁÃÒDwH04Oæ¥n;¿L4Z”’XbêYÇÓîKõ¹k£^rŒcYúuŒäÈÕÔàh¥@:O̶NÅ„Tt"@bÍ ãî±Ê40ÓλK/r㓘€Ãv·:Üñˆ€ÙîÏw´Õ²/Êÿ-CÆ@;-’öHUó†\{l-±NÜ®¢º–íëþY>Xôú·}“f·9œ¨±p¡Ùß×d+Œó°tLM‰“B|©®©L-QXÑÙ›Hê$˜îc®sN:ѹ>(#3̬£¯*ÛŽ«²éãÇXí®y\9ÇQŽÚ³7ÂÌ:Ù¨mÕfJl Kšg;pûkŽ‘ÈNûO9º:mðf‡£¢åF#3†Ãté}E×·<{ñ•„&ç™æH9h²NDÒÞuÉÉ‹Üëg^Mgj#3¸rá¾{z»‚›7~˜¶56ïŸXµ;Q‰šcµnÊ$[¢8ê)Ž“ÒÄpJ¯Mc<ÂH#äDQ€´Mü&"<ïVÍMÖê<M‚ùX††‡Ã›½7Ÿ^8ì]𙣳yÑ,˜Øع=ïÁÎÞþüúk!ÓÖTÕçãEwozËÑö"`óŽÕ …Oa…Â`—NhÏœDÊT]OEÂWX!™+Ë»GØÚVî{s!MgÀ¯\úÈb/À>ÓÞç}úÀ.îÈüëÓ¦¾Ø›h=#W´á”;„$"¯F;¸Ï8`Ŷ~x!ùÁS–‰ÊøŽF¶®²#3u;ˆøuÈÖÍ]6#3¢çñã·ÑŠåÝIÖe@_”"Íþ~€Ç Œ„¬ï«¡èµ#3W#§aïôÖePÝzÍíž4ï°4wNqµŽðÛ“Å#0ì5É°‰`7àÞl—¹Ÿ$µ³ô];8ק‚µÞaÕ»^¢¡sZtò×i°Áw9ì å±ÏsŽ‡¨âH;³…8HëÀËUR¥ˆ"Ã!ñíêvøO³¯™ÄÌÁµŠ}w^K"0F;4ÑY,ªn¨O…cãñÜÇ°õEJ=¼7¹3‘C§dÌ$`ô«+çóø®ý;ÞC&C¹$`Eø1$“~ÿ@Ø‚Ä4‡œ¿Ç3Ò•Ž¾àçÀÏ^Ì͸>Æ%Ø`»#W:gŸÑÝIýnΞ¥‘`™¤øô;¯HüO†ÚG&jkë$’/fwb‘g¸rˆr–êëSFäÔÔ³hýþ>¬ˆò5Ô‘'"wÛ¥ð’§êGN&=¥d+³6;m–NôS8°&ÃaÅ?ŸÕ³h„FAmêéÖ¼¶Ô/ˆtÀ“§Ù“][TZZtl´A9ʶ{šUœ,,º ƒ „€),Ù5®—6D‡uu.–îEÉbÕ”¶»~_<xæRB ÈÂ#3´ÂÁQ±XHUR`…IM i·kw"#…ÈT ¹çßecE#0«cÒLÀ:7Q×Áµ$BH„F@ŒdŒD @„TI#0 ÚMû¹~u£dpÈv†³>l^Ÿ0Rü·8oߘØs,¿Óí«~ÕøÓu¸J~?¾ëÅ"¦´—+¥“k¥Ûo¾ë«ïW]Ýù<¦È'}…à "¥˜xÈ‚/E{íÙŒü†eQñ§Úõ¦nåu£#0èGñU ©Ñ‰Ž4:(,”À1g}`ê^óC&¿!”Îê±pöXs[)ŒpT5\¢`ÛQ„k¸>{¨pÈ0Û࢟Û:½HM›q¼#WöŠ´Ð¦Ýˆyã¤oØk©’Ù¥e€1PM!´©‰n5›ÚV:]§³M5tm<¶‰Þ„S’èÁÒ¯£¥ôAÌ© ŠÞÏðÝÖkšë;ÚêݤH•)l!B…#NFía$&„¥ˆcJAR±ë5ʆù% B@ÐÑN'¶€–:›…ïÓS–¸mk]|kÕöÊj1js¦Ú£PRâA2ŠI8fü›Ð5Iy*Ÿ~M~<²zçrîãÊÀ6Ío"h4Agŧ÷üŽÞfg‰×yÜæÀ½ùÂr(SÏg(ŠšTô#«ðq³5è·&ZZcþ3mâ;÷ã3sÆ/°ìì"’,XªöãˆW#3D—ßÏð¬ŽkÉ¡žsÜ“±–Óè1Ãw+j%Š3ÚT=`‰hÀñƒ¹7Í®×u˃†·Ú¼Ô#3àØ!"í46:™³0RÑ3#3¿Ã@εÛ2U6;x|='gkâü¢OSb(Ej£˜x5iL¡ˆUà#0·0´¤Ö?2ö#WŸOo³†‰½‹E,6®Û(FÓ^§ÔkÚ#36ù~²Ç<Å’F½²©Öý»õéb¨”s(œ38íÂaãðã59²}CBe‡NC¹.~ÓaÑÓ~‡nºß ›#0¥Ý÷³ñ³µ:†yûo{^!i»#Wd¨R3¶›Q½w;^{>¦ÛxEðærî\ᔃñGÓn3N c0 ¥dã±Yªï?w¶´“?É®0ËYÔÚÆRtöpbe$›i}Áùû\}³`VdBã\» ‚Öá „Øƒgž4)!!hl5çRÕ²Â@Èt>) ŠF'PA¡ra`¤Ê¸pK5Qz 5â:ç/#W€¢Ã ƒÀ%2'ø9½Ù^ƒáهƈ‚ÌðCP&$ºÎåvŽe”z€V@@‹¯¤¬ÄAÖmä+«¯«uטìÊÎk¯ZÀd(Œ`¦Z‡£±¦‰†…Jç±´G·ðËœp›—vT7*¡/¸xÚ1ž6ŽG†h0J"AÈM´Dï9kKç#W°X·SKD2–çVß+«¶Þzâ‹(ÜaK©K;î·dy~îs‡ž-ÑñêDû}•yéÉE¶%)ŸI#Ww¬é”AË5C2áÆ°3rWŽ!ûŠOÞ†m:±Ç³l)(‘›oJ…A-¥9>“H®–6™#0?@çÇLšòdðLpså>Ö(!÷ž“@˜5mí»¨ÚãvüÉ/¡%’þ”ЃÚ–7qÙ‘¸ù9&ð$z`í±ºÌ-"K\b7ð?dµÛAMogê?wo‚½™ª›u2µZ–:u!!ïóc)ìðGÖ.KåÒì5^gò$0ÀgN‘sÛ^W÷v‚(Ó—SbT‡-ËÏžÈdíâ(DÁi6( XŠUT`É·Ùà}é‘YDc5/IB¤F¶OvÇ}ý¶Ü™»õ^Røµ¦•ivó”û¥þk#WãÃÔ߈4{úfåJª¯d²'6—·˜L‘|#0ÑIÖo«ÚòzS6SÁõÔÜ;ü9ÇìjU0D)„ëe•M-¢×ÚÞëã7•ã¹Û#‰ŸÄÈ”JFÛEŒˆ·R˜ÐÌ!æ!fkÍt&R+1b(¢‰%! ƒV‡Æ¶ö´„´—Ÿª…>DQU¨)?ß´1F3¯ç$I4@¸©®HŒX‘#Áæt¹ô#0vDLºZµ$â[ØF˜[¯í4÷+a ,»›Ð&jƒ¼Y7#Wp¾Ô€÷ÅZFtHFØÑ~`ÒÓö£ÜCLKy5;ˆõÑ1Yehxa5(ï¤Õ <LÌ(,ðíjªn(šAa~µ#'^rq›Y Å5%dejècß,c¿²!FˆbS:úB“#3¤Ýcξ‘ÀÚØm¬£N2K>9ð¶oófÑLlUÈ’¨RŽÂáÝÊz SY2h#3P’a'±ç]ß¾aøAlùD»´Y¶¯7‹Ëhúÿ=‰c §÷dO‡}pnfÒ™3báZÍ&–Åѧ#¸b5‰‘ŠJÖ?-&#CVÑH©Á¨5c¡÷ùÒØú×FpÂÀûœW¨Of¶ÃË3XRŽ ¯nÅ f £T=ëÑ–ÉFNÔ'PÚÇWç\Ý×Z{9Þy¯SJ±¨–6B˜’¥I[׿^Ôó×Æ3}óE=‘FSËÆ6mî‘rl§v6šbOo»Fäsy@=˜c)Ú#3ZOðËçYß7¶9hc‰-Étb÷‡’Uz8Uݺǃ©‰§U/O§$ÅXh×úÇùNkìÜo© 5uëqJ•\xò<réEį¢1W¶Lk}¯G]á‘Îâcz’x•œsnÒëG™‹ášôÏBv·q« Ù´`@\ n"†´0Jw\—5-²ì·Æë«JùO;¾}-)ˆ©¾™rWFÊ9-Ex( Ð*Ⱥ•«#3#3Ëóò•<j$8&Œ8c#W¶òC¦*oµ9›fšŽŒ#3VÍRMD¡D7a¢´’j2 `ÒåÐ|½âÌO»xtÎ6Òå»Å'3¶ŒàÎZÙ’´›„""lˆ|,jÐÈ…2K3IU-#3ÐÑàÃA“F,ˆ*Aa¡»Vì::D#§Ûvœ}ÔwñÖåQí‹ç«©RfyR‡§é§),¹W¾¢ÍF0Å#¹‹Ýê਑ÑÍ£à³ÔÄqžNm¬G]Ñz€v®JN äV°àô~L1®²qùDJ¦ A!² ¹°¤5.p\ú1v9Ù³œ5éAÂÊS¸LåòÛ·Ãœ[9•CÄSBŽÌ‘$¦†„QÖD$EÝã@†FßÁb/Æа»"tD¼o3›=5™9^‚m6º‹¢k#Òe³›ÝØåHupý«ð,pF6‰œBN“a=®ôò“"k•8É•nÎDt&]Ns•™1sº§/ÁAEDØcÊÙ#W†#WâpßR‡rª6Ì •ñUV`¢éúlدA1HE¹~RÛ‹–gÓ¢O¾2ç07mpñ—©9æãAêß“ïµ,9¥½p›ØwpD34Ël›×Ù³&ww(1T£G7¶LTa씳Bc¬önìs`Ç!ê›ûæ*œØ†:~}™ò/zø 6€)ÅŽÓg»Ë¨œñÔl5݉äJˆ±’`—c‘#W“!¡õEÊÙ/•€LgçøÒ“IP>mÕ#WæÜÖÚÍÌ8)Qºa#W:ˆë$‘ÌI¦„4#04*zõQ§Á¿’¦ƒ1¤„Ï<Óa³®Sz×BuëyµsYi5K×K6>5¥Ã4L#0-¹Å#3û99iõ‚ðË`[LÌÇP¶C×kõ½6J¢G‰:½Q±|[@¶V,ù»qמEý#nšfö£9ÄL‹4>øáöö¬·ƒ313†ªÙ¨þ(ÜÙ7hJkV\}fš9×sP45}\ÝËâyª#‹ó¸¼Í)šK׸Èc¬‹óKšYN~†K3.++ÞszcÑØü¸ÄâÂ0!$˜=+#0S£OüX""#W€íkó®x¶è‚ÀÚ‹VµãQµÊÕQkkFÕ®[nm² (Ê(HP”®Ã瑨-vÂŒð@ÓÖVhe÷éFR“Åpt¾°“D#0ºL²|Ï»Àò#3ákc‡í&÷íÏ5ÓV·Ý&Q’b¥&Jf3T¥™’²”Ó›Q´™i?iÔ”![H@˜¨šj`dÙ¢%#W-¥{öæ#jLY%”šÉ‰šf@ÊcF„UbiDÒú{¨¨²4()$¤²Ä¨3&A5Q‚e0Š0Æ1“ZQ²¨© L’ Ó)¨2•ˆ,ÐS+‹&l‰HÞ÷»èŽpu:NTì!´#Wðʛ׊–*#™š` ö~pT>ÕÔÅ¢^Mßfcûw_/ëÖVâ?3AnàÇZbôtHL%ZÙ÷Œ’øÎh‰†û:CRjuœ0boÖ}¼ƒ€Ú˜±CF6É°©p&Â:³9uóx—y´Q$j3ðÑèb_·hCi‹‡u¦ê%Ë@3˶·Œù»Ëv1Z´%3ß?¬¥£fq™ë]†LV+…Í‘±„áÇ7“ùDè1éózÒOt([ðÄ\Ç’i6Øc)[À‘ªÙƒÃƒt–ýÇë-›*>†ÄŒi¶‚È©¬›bÑ3Y–`I,¿GÏáö}}öïWHupâ‹xU.2aDr›å4ûšk#NÒÞbÊ©¼Üã=z0…ñ0è]YÚa"Y#35´×«»nf_Mp;rö;W›ÓñÚÓ*F»SØQ@Œ°˜ŠíØyº“ãµ~„‡Tñâô{†0‰¼R÷ÈP,¾ö4^ ó#3ªLËfþo}›©¹4Ñ:c…énX¡±ƒÑnGž±ztuÑuÕU›@tÚ(QL(¨R<á¤g‡IÏÇôÀ(÷¬¦•î_Óù¿ÊÅtó›:EKŽŒ¼}Ͼª«Ç‚"b8ܶ⟋wåÞzˆ»Veðû-kT¥žû“#3qȺL˜¸z©ÄqƒDÆaG–e´Ûµ8¿{ØÌ|Yݬjß²„\Ê‹bðZ9\œ‹Ù¯zìB8f{RMMv,o«žœ"——äo¶X~Mö’ý,7ÕÆé‡m ‹ “ic³4X¸<÷œëo§Áùœ>í‡81j d…uý¹Þ<ί·¿Ùý™z†ç±r^Ù'=8ySeRÜÓNâ{Í+s^)thdÚÂîk’k ¶Â~BXsœº}σΩËxIH³YQI×¹ÏN‰’ÑOdxO£{3©èC“á‰lî6ˆ""#WTÂyŒN¹—uAA¥Ý5™¬äïÅP‚l‰ÔÈÛüâ}Ý© (˜‹Z9É‚eð†8a½›Øõ`;ú&y#WŸ›Õ5Ù#0ý=;5ÏFîÝž<€9) ![¾ë*ßF§PžÊîî7‡W]£Ô#WX-ð#3Þ/#WÞç{FLNd)&±œ ªWŠõªI‘I½Fp@8y(•|\±ƒP¶ß»Ô t‚ìÈž¨q@üCÃK~à„¾æ¦€£Ft D4¢¹]·Qe÷x>°8$ÎË}¦œ>0ÚW/‰K€ììò¿ª.¡…½tOŽT–áQ–¨MtØ8Ý{¨ÝC?J“áéÛUMUŒ2Ÿ×«Xè$EMnüFP–tÓZfDÔiš“y©ÀÖü ™™#W<+€›<²Ý &G½‚@" œ 8½Rµi¥#mÿ/˺ÇG#3woÖuÉ!ÕU…ïsÆjþ¨ÕŒ¬•”)fOµ¢:5£Æµ#o+.f^#3F6Ìš¨ƒÔ `ò¸Q‘s§F÷5˜#3§ïã3ò¹÷FÌ1¶æ£tæcæi5#3~øMdwt‹O§&thE‘/iŠã·–Û1“–R<8s1Ú²io5¬DâdZ.Œ7™3#9Öþ&ð¦ÕÕ“P¼ímó§k¼o'‡Ó”·‰û³Z0—9¼èз֦ÕÕ8ðyzÅ®V`|¸¨j‰6Ã00ãì×D¢0A$Îx·§¨>œÛÕãy˜ÆŸOTµ¬4c#W¶ ¨)swaÄf"Q¡sx¨-lŠœA…ÇÎœq¹Òt‡—¹räÓÆŒ—yï;^Ök“¿s´O“ éœH-±$쎴Hó©Ž`?‹GjÉž¹¹®œ'—#vØFŽ®ì$1øÞÎdÆQ£Y<#3#—Òñ{Ç¿XªÙõÌØ@!píµøs€AÇHz7hFág^®±§7Q$i‘Ýé‚uŒttnÌ*?s:eÝ*ÇVI|¾#3†#35uxªiû›ÛF&NÙØÅù!Ö‡Ô4ä¨ùc¾óv°¹—Îýdƒãvœç;r·U\Æ6Ô@<RiS+OÓ1–·cI™¥>Zn6(±kw/d&]-›ñœSaê_ópæ&4#W ‡áØï˜)2**\ƒíBm–M”§RŒVÔ5RÅd¨Âa&²|{(<üͺ£õë¼X¢bNQ”^qð£L¹ÞÌ=+WxßWªßc[mÍ_IÖ³¿m‡‰IQŒ‹·£ãP»ºñ~áo¬ŽyôÏ#WóâEÓ¡‰¾·žNmÁD'ïˆt“¤øД˜XáÌÖðj§;ãyÎ6ÃŽ $G~›^«{æ¹;6Öà%3Bâ_{ª–¾ËŽEo—ƹΥº<ã È^¯P•ÙÉ:}‡í¹¹œì6NöÆojŒ'ÌDòªwO{5D•…%B—QIÁ¿ãLìƈHëyŽ°…`ó†¸%æe6“H„#R¡ã)dÕä7;,›#3ÄF„=#0Œgv„ÚfÒ¦Õ"hq±-kŒ“$ñËDF¹iu†ŒAA•?ybEaÓP"HuÅ&qT¹‡ªHÂIÔ#utþ•NL#3+".íó‰#WÛWVK™ls¡öw/hmLI¤À„É–e`¹¬‘±°T(ߌ'uƬ´'’ñ372W„æ.–Moy ÊúÕ9R¦\Á`NÁÔ°˜Ø`8CA“wÙI®èy¢JMÄÁ[=`ëDL™7<DJßU[<–:E²MjŠÑ§ƒ#3Â!lî"ÌrûFËtÅ.#3úÁhÙhéÅ9,aJk×:7»9í3@ke#WMwÎ/KI{Šô:n P}-Nž9ëTtÍÚ‰+DZ!"Í2«k*’kˆ]Œ×X¦Ó’ËhŒW#W Îîx³Mëwœy³´‚õ&2õ3ËL²¦Ú¡±L,¶iQ"ËC°¢òE<íÃOXr²ùsd(ÓŠUHEí#WƒD:¦ÄRŽ¤=Ô9#W÷‰”ÙSµ5Ï#3Muù!|Cº¥'ÖšæaÆg@pÂÕ³•¶Ã¶6ؼ@òâÀó+ut[\ß1¶Îu&þú7£|p÷¡Ú^7i ¤Ë§?— nú̺5#mÖ¸ynš“T¯ôñ£rl‹y_¹²\îâ>Æåœ{Xkpk”çºO·W5mÖi¾ÜÞFos;:Ö`6qap›2v“FÉ|7¬®ÍaÆkFf14›yõ1õ©Ç±”°@Öõ3:5…{~˜Vc™ˆqß4¶l¶m䔕<±EU!'BY°¸Pî &êâi=“0ŠI TÃÊ%Ú „[Ô ÞyÅÊ«ºzøgˆÍçJbÌ¿Rh·:TLÔ]USÈM*‡‰1Å_‡[¢¸ˆÙ òU0–W<¿–úí„ÅMv†vʵE¢¨X‹M mMGd‚ÒaAÁͳVM\FJÎ!7åÖ¦{ÌÖƒ˜™4ÍÚ4qqÓSt0xÈÇ$|8-<8— 5A°¬Mé–k33S£I&ó[sn³2nØᩘ̺uê3lvj˜1<“d¢tÑ.iÓ]µœ²•1C[$-·G„¬ðR)#3PÓÆêÊN-Œ'#3wÑœ¬ìÕOΩFz6-¬¡§ø9#VÎ÷(³²ƒ¡ÍÈ,¥5s„McEJ%6CnoaÂÍáLÝ8„#31Îû…m„c†.qe0àå:sÁ"Ü#3š;Õƒø“®p£G0(Bs(ú#33§œ[“¹$Ibh(”Ñ°R«HŒjø†QYŒðUÒc¥aÉ»†á7¶35+ñjXÕh¤éT#Ëâ{¦VŒ’ a£¨tزÕVipèhð4cq)a¿ò1{×0ìÛs•öd¤–kÆXs@’¦`È#3ºc†#0r9ÒùU#0ÒK¦ ¾ˆ/e–&¨ülã·E̶šÝb… ¶’¤•Y^³Ÿ•ð âuËf˜!ÜèõcrÖaØØÈäž”ªì]šO@¸<¸ àƃl=ª]÷}šF†¶‡².©†zèP\òl£±ˆ„7sNb‘Î#31ì}ŠnÕ]S§öV~®ìQ?¹\M4'û‰c ÚÀä3ÄdíCÉ’ó£al’û*‚Õ…'±†ºÔ¦™£&ŒÑ†,rå†éXóÎ!X¶ÐVƒ¦ƒt™9h85Õ¢êvk•ª“>#3»8É¢l&P¶dåEI Ój l”ÎJEÑ3ÝZ&p$¡BÄsRÝ÷£ICÂnêéš›2S!…ŠƒÇ[Ýœ’ZEµhaÃJ›¦¯ÐfDš²sfÜjqL$š²Ø¹dt 2ÀÑÍ0•Ê¸3všf×kèœìçÒlïXŒrý8Dt“JmÊR”sB—•š9–N&Õ5d₈Î(ct]wÆÞª2“‚¨$teo=9lË—‡®ºT¡ÐB„FME….+4WÃavå;RéÃÖíy"6ÇBs”<>ë¼ë¡m¡íøÛ”,ÛpP´3qµ^q³1(ÜsÎËt9¹Á96xó²óΊ™À ú¥KÅ(üBv:;bîµ£Nòчé`˜%í F¶ƒg1Æ5“#3—áq×kž«fÀò†ä †ºƒ›ÒhCníBl·#3Ë~)Tâ°ò˜™2ræR: Ÿ¡#35_Ç&39qŠD‘A²´‚è¨yOjï|"ÇŒïÔäÎÆ™1^¥ØEò¡áà7<4Á’šH(r¸=Fo;sJ×™P›¼`-ì›!L"†RP¤bÌ(£Œb±#[x½c‹b ôhMx<CŒj>»3±ÑO TlïädË#ÛjHÀcg xñxýšæ¹luã¬kÚmד‰þ•HÆx…»PÐ1ìu ¬`Ás=Ø#ÆÅ m:é`,ÍA†N õœCs<Ö#3úª6†`CCu.)l[劉8‚ jr×y2C‰¨ÅfLÊÒ3‰±¸@Ù3L÷èÔ±•À•J"‡ ç(°5b$feeh²NTqœÍBÈ€ïÝy*8'³¢4_hðG!•\7…/i±@T¢ˆ(ÀDÈ ´Þlt£;ñêÎ((¤É.fZCHÝÒØeÄ1|c©6lJÚ‰¨‹#0\´Þ]0›Ñ²Œì(vgF—d3>é¬rB#3õA‰(ØØ l#3‚û%C²²Øœ2Ò`mŠ6®FÖ:™ “‘¨äfæÂé©)a‚vÊG-lq„¸0Åš¼Žð0Í»RHfûQZÙÁÜØi2¥¡#£O#W%V”\6¨'Æ´%ÇiTƒ!hÉe´ÌÚÕüé´›ª·ñ¾«±ý‘HEDÙM@ðfw€Ð.àMeå<éH£¯ó5ôý<áÉÂ[Zò¯¿ïÛ.×EXÝÜÎîB#0‘To>‘ýÆA˜Å'!U`#ˆ‰¼‡FéýÈò ’‚; µ]B‰p*,Uc"0Î2#0d#0Ê»¤Á…pZ|rÞ`üëäi`4Ø;²Ú·Æn7Uu¸ñ¢r¡;H>ýí|®H}^¹Áûgu¶>¤ê¼ì÷2ðâÚg7³)LÖŽ1“wóÒ"Ò•aÌ8ÖŸm-¸¡V«ÆÑÏ-áG9´àÙ³X`ø;UoÛ Hø¼ÌÃÉ&âm›ÔÇ©Ü:M.‹HG!‡ACsNFFYãiÄ/èz›^œ²ËY匄47aÜ]úù¤ß³#0׉Ȩî[`YtRZD§Dnñ/P‚$‚;à„aÿ7#3²mÀÚÖ¼mªÙ·IÓ‹ÄþO«ÇrU^ÁQ)‚=ñÀ„Kt…KE@(U.jT#W+"ÈB'(&Û#3ÕE¾+ëÍ#Wx™Òuò|¸£G9à<¯—eûD#WÔ•F?6|KåJ„K—†u›u¼=¾Ý^ hhx#0Q×1ÓóÉzÕ~ ‚ˆ ~{T‰$=J濈ÔåÕÝÕvc[®ìË›®›µ®l•~¼Ô¼¿‰»²5oŠ*Þk:»è;Ä^=cÈ–EJÔs_î#ºSj‚ð@w‚p‰ú0#0!ÈgÈâ‡.=ÄW”NPëƒ!È€U©##0¸"žãÝ@Ÿ©]Åh>«É”™ý¦bt®OÔÙPö`Ùÿ3 ”î\Ëùë§9%ª5®š´W Ú¥Rb`0(Ô`eŠ#Wc´M¤ˆÞ'ÐneÀ¸=¾PǸ-ÿÍ”‰§4v#BšbP1oQðCÃ`£ÑªÔYQ£ ÌG‹™qÆ2‚Rç\râì)*Æ•ì>ö!˜‡ò#3aÒüù¾rå‹Ë†áD¸heÉ,dM@í=G¦_#WNÈ#M¡%&¶-‹FÖ‹Q‰1¨Ä¡´f™–B‚ÂE"ÂÐØSƒbè„)ïþ£«PhäPRÝjmÛ~®õ¤È2^ÑÖvÀSa«´Cг¨¨ÓFR’|¯kK×|–¼É)“Ë®½5UÛtÔ‰P*Q4HÓ”Q¨1ÄgMd¹'‹¬ÈîIPèNò‚6-½ðÄ)ª?F(µCô?L"[Ç%ÝÖWE)è Cc'¶zQk«´Ÿâ»ÌÀƒ<Ý¥"rLU”´EúíE £òáe¼“Ø YêÅx”gƒÓžB²3k¬"!pƒYV•Ü$yÔ}Œ>#W´˜ÒÇZH²‘ÍB-&@¦B„“vL † i‚%vL`lÊRÈ#3‚i ŒU¥Ã3D"ÜŒ˜Ž|LÊDÍŒpgܨEbæøÂ$|NÔ´esåÝfm2É°¾ÛyÝ[„W<f#3Ô|Ã1äü2fÛ¼ód´&o]s’3Ó4¢«H’ň¨Áñß®Úع¦A¨9!R8fµJ\Z!Zê‘´ŽÃXÊft>Õ#W%Ãá‹&õ‡)²N•'D…‰?À›Œ4\fÂnŽDgqˆïAŒË#0mÛ¶%Ý¢Ž@»$f¬¯9âÙxþ/0w IãÒ1îò)¹M¶$›ÊÐJATÔˆÞ1,}aªK®¡J}ä$BÖK6iBðøX#3°P3@‚@CC’Ê5ÿ$̶>nðž±^¤™_&.°˜q#k-ý¹m¡£Lý»ˆ8QÄßUr0«s{8+hŒŒ&EÇçÂŒáë¶Lü ¤îàò;‚Ä‹gØÒÝð6lÊB«Êw;Ô^yÙÝÙ:î‘}ÛèîŠcL1õÇËFÁnȧŽ–•µšŠ°Í6á$á‡3¬Í#W¡Q3¶Í")|,Xí;ÙÆVêë3#WõÆ1ЯcnîSyšÀÉô)8r39òøÛ‹cûàÂ#3-‹| Ê#WëÒpÍ0~}lŒå$Ç#·c¥ìç1¹ÃäÆe²g3Œ,$BNžÆ·®–JSÞÄtöjטWÛ•"™F×E…mC§8˂˵p›Í4ßWÑo\3‘¥ç£¼\Cµd"ju¼Ò,ã,ë]ôŽ†0~Ȩça ÐsrÏT42U,±ˆSFQ»uv0Ì4GDSÍ»[ì·ª‰2ì»´—½Õ.©gBŒ¨430íÅdÕ½`%¤€¨*‚— N¹ÄÍâ€8ë0m8#WQ†§P"å.ï#0P¹È$G†ÍÁ`1èºð‰T#3¾½_ÒÚö1)‘E‰ì `ÃQþ#WÝ(˜5÷Œ? @«"~¼èlWÂŽ–®ìkc/¶Ô9ÿP#3± (ôÈ4Cï€o&3\õLM$¢2ÈÞøF©ï°P0´¢UUFªS;Á-ü%´ÔlmE¬›FbÛ`ÕImµ~›RÓQ¤_¯~}¯†ÜÌ>z”=-•jˆ3ðs2퟽ôŸj©7±8£û ti]è©ãéÌMTož–R„Rû·ŠÊÿn“X+qm•‰5qCU¤•È¡²o}tá¬ï”ÏB(Ò¹˜rIÔ¸¥–Ú”ël½æÛƒ#3’n†eo‹±©kÙJ1E])DM#3›K#{0ÒbÑvʯN qPFÞX\Íä˜g<25”Èõ0pæâ\;‰¡õ¬¬m¾ØÛ‘2±M9&yž³!{`ኙ3«!ÅI®‡vF8|æ"ƒD{tbÛ;ÜÅ1¾Ç8ÝSAê'®t¯…q99ðë6pî;áIÅ's=ä‡!<':ÛÅ›”äcP^åP…ª¤Ænš¬I/]vôÅÏcV®Ð붬ÖjwÞ`ó¼#WGŒy%¡fN°èVøt;FYŸQ™Y¬NírI7Ùžo^^;œNõo"Ó$šŠµ•ÒµÁ´r-!]¡µd“e$ƒ¸¡'Ê¡î‡Ú™l›XØjÃú™üYÊ`Ô†DÏ#WÜ3`Èš”Ú_`…ò%¨º±¬mµÙTªmÍ»)›jiG)5ŸS·×QªU”´ÂÆE7.kçbš¸zÞvì§Õݺ/ –ǃI#W#3ƒhÊbHmQ‚„‘$Elj9” 0`€I(ÙP[d·¥TOÒ©‘2ÜêX9ª#0…è¥4‰wu®›SAÝÚ.ͺT.1Œqå++q–JØÉ„d«–¨11¢¡*é`0al”+Ajiµ‰cL“$a"9™´í<Ýw™«Ì×7%#WÝJÜ<òìoÙ¨Ú4Ê›Ìíüÿî¤HáÂ4ShÙ¨@ÂÔŸ(¢Ü‚I”2©’¥f±¶-eI”¥—•ÖºjÅ6™5¥†lÛj}zúvùúôe¢Õ˜”[i-ª6Õ Dˆ’D¬!rÞ¿Gœ“šŽ¥‘šñ(×â^ÈlU’ÙÈ&A¨*Ðg‹K) Hv%(P뀈È;S¸Z@áóÄw¤CÒwžË«<O9ÂàkÓó° aÛñã!u4#3#3}Æߧ4OŠ(w#WÁvÙÜHÄ¡)õú-˜A`-9Yœ¼ ‡v†½4±°5•¬[t$"F#o짰ÇNeˆr«-2ÙŠŒ’o,jvÆàêaz“aŠTâŸ[÷Ø:˜·½*UPHÐR#W¿Ž #WÓ`Š³(£-#WŠëÖm¢¶l*b]åcmñ³qCª,ŒtJäìu×T¿¿…ÈžbýÌÝn¶ê#WÒbfé†ï†øýÆG‹6¤•µL¦c㜠…s|ÍR@‰.m!»†¿Wò½×¿Ãv8n¬…ïºâ#0Ça»ëä fß,¶ÆÓ𱓙ôIñÃ{ìÁQ7ÓD¼)Ð@¦ÐAjðÒàq•lý¼ò¥œÜ¡0¦3p0…bAîhBÚ`“;Ch¸Çb6Âæ¹,†ÈJÎØÁ®ìû”%ÍÁ‡L8\VGËds9œ‰r¸(…ŒHj¶ÿ«éÂ.3³Ô‚xûß#W~íIKÆ<q•©ÚOÊî‰V›¹„×¼¼>ÔÕ"(@ë|Þ¯¤…vãíé67†8A¨-ª¼×¸þšD¯ŒÊ5÷þƒ"áD#3?I$ˆ„óíö«méKÓ²m£ZCü>ꊲI¼ë¦š3i/nµÅQ“xÜÖÓ"ÊÒKd˜žR¶¹,Ö[Ki6ØÒi'ŽSLÒ<íÒÍ2i¶˜¥•¡ˆËïk¬)¬¦a²Ù¦4ijh¢Ö¦ªš'§m#U)réµ }[\×n5š½ºìh©ˆJ LÚ¬¢m^§R&¶"-,ceWî6Û¶¾nÈ›½wl“dQkD¶+UR$šm›ZÜ»ih²e·²ºMM¶õë¼)±6›M”S#W–°6¨µ-›I½;mªV£´xÛ¢³fÞuuçre5iÐÕý'y´Õ&ô+¶jñk¬e(•XHÛxMrb-M¦ôôW·àýÅêÍ9ïÒåŒé*ÙLxîè–Dý¿—oòÛŽÈ|:'åù}3ô}{vb|ɤÀ˜Æþ7·ª«”Ñ«ÏXƒŠL0¢þþOª [åIå-4ÃV‚à€{ÙI2‹²u–¥ÝÖŒXˆŽT–’h›ó¢@2’Ý 7ÅKm OtÍKM¥LÒ¨Œ€ Œ#0B„÷ñß3=Œ#0¦#0ˆÛf•³M[Ý«WK[râhMª"0iÞA¨ª»eWwTlQi,[P¨"˜ŠÉ„ˆ—Ì:„SJv˜Q׸¥ÑH*l+PP m–͘–b‰c"“ÚûUÑBQ©IªSZÚm›2ÅY*ÐÒš)R›}+r†-EeQ-”™FšRB#3¦ÙJYS2Ll31–6#WˆÂ”Jkf¢”˜“I‘-#BÆÉQ¬¢š,ÒÍ,Z”ª"¢¥4™R”ÌM›´Ed²i*ILRU!‹$Z”M¨Úͦ•!BJLX)2“ ¦I¦J–Z¦ØƨY¢–&mI’E•¶¥šÉ“F”ÉiM¶Y©6Õ¯k]ÆÚ¤ÚVjÓY,¶CIlȈÚ*Ø‹ T#0#W°ˆ¢T* eºÈäȲ…AL1SÖ¾mnj”¶#3j-m""‘`)ª*Q"Hi¯¼žpÍ`zg}ÓÅ`F½v6½´F“ù÷-ò¾uA‰žÙ3vïÏCMVÇŽ¤üí`0ôIó`Ïõj&Ö`}ÞÈè?Ô9#0Òݹï-T8v¦—SÛ8ïP»$îÅ2éƒ)o*yÕb˜HCãeã~ñU™n@CÝïòÆÊ#²=HH¨nÀd–F+óðnìk«g]wbÙ 3mfƒ2ü²3ÙtªoÉÉJ7i(xr©Jí‹®·ÁqÖõìÌ,†a”O,[õÎ<0@ÞTùš#3Œl¸Óha!¼†/P)ÌækîfJ_áêãµ×`”¦ºõwÞЬ:=)+ô”ó€T·*à÷ª>FÍW7QÎk#3‘NŽ€¥@ê@T¯ÑÝÛ>«Š1ðãHc”6QûK¨Ó™GÕH¡ó¨rIlV~¢ý®<›2+Šòf™„P„‹Ç_]b˜ó8žlqñÜÍ&•Ce¶v7)ídiK(Ѐ¢†“cë2Ü"¦[#0Þ\›P„E#‹†8ºñX”–,¥@ «p·”¦ü°ˆ <|hþ5à=úG9Rô™ÈTšePT]jEKfL-Õ¡Òuù¶Ñ+![8›öjræY$gfõZÅ8KSаƒ pîí†!£Þ¡’mŸ:6õÖŽ&EŠgk˜}|FÆÞí”q„s»PÕµEi!º}atgYùÌòÍ‘¥¨¨ffZÔ>‡JÙ]ÄÀ¦E«óòúžþ¶Þݧ5�B—p#0#0¶6ض¨Á¶ùÍ]×mÈzœ=[êÚ¬òQnú$ E *H¢VCåaßa6Y({(xcF_iÜïÖ²Qp‚Ï\ᅢ"°"Œ‹Âj5µ$„Þ”1Z†MîÆŽ#W¸‡è¾’lèÌé§)ª¥)®Ú÷jA«àhJ~<¶mÇ´PdbE<ú ¿Åî{YëÌó—±å][{éLcØšÃRô‘P••¦`}ž¾W¾è3¶ôμ°I.wõ‚Ør® v7((/4nbýãדû·^{<Îuå,hƒ° KE¶BÆ#W_ñ«ve°Þåe±F6ÐÊÊH‘-s@ÓZxÂ’L¶mXxˆjI.m‰Q`£sX¹0ËH(‰b,h”Ë#3u£™ÃRØnU% ¢Î`UXÙÃA‰´åv©g6¨(ˆÉin1eŽ)b£-Ña¨U‘D®Û¤“KV‡šEÎèʦqQJC\R7=•&ˆ#»#0#3#3ö˪‚Ž‡9ÞR@‘ø †MðgV!àšíN¤¹È6ÇÁ#3ûªƒA’„Ë[Ž•*˜fÊ´kf§®OD!Ƀ„ZÕÛ¦IR»=[Œô18žˆu79Vyˆ¨¦‰ÜjƒhÇ'*>›êâyO9Ï°ÅÃX…X¼[FË"6€‹PV76Å·-úðÖ½e!Û®«Ñy"CÞߦ|ÃØ,v#0-ÒÅÍý›·8¿? :C÷韺Ɠ¬Ò÷ÈÔ¸„5!©#3ˆÄx$œé¬+É[qiñuª±läß$!›Ã]WÉ©o¹µÉ®2M¦!G ðfœ!µ@¨dÅ#3 ›„Á õ%›!é%eL¨Œ¥Ûž0öHR5³µ_ZÃbŠ Îo· ¼€^öÞ_®O´œ¯¯*Åë´àâ~5#¦ŠãL"J£ÆÔŽ å¯í²™À%éP¹õQr!d)®Úþ:cÖfq®Ø Ú‚…$-tƒ¨¥ÿ‹PÛ£ô±×Šbw_}Ovº«*Ô8¨7Ö0¸d!¡ 4Ð#3ƒMJJOVž¢+!¡¸M@(Xä#W4É”Ñó™ÀmÁö³¹ÁÛ¦®êÖ®ƒ3§\UØNVZ x…áX.Š¡ÊDÏDð£‘kÆñ0Å›±Å*dnƒ=ß`æµH2ÉGu;>t]P’ðË®!q¨Ž&3ñë6\Žt¢Ï;ëøAT;Ogó*T½B @œ^-LJJFœñ»í…L|¤h:€ 2@HpØÔ²u,Fñ(¹Šm(št˜¢•i0*ƒ©6ÙˆxÕ¯HnËÞò7’Ê +£\#3fâðÙˆÄ@V3SÁs”!”|Qµ" IH¦\[T÷ëS³‡J+l]ö¦ƒ¦¨>¼uY‘šŒ|)¹¢`Û}h‰Ú#§#0Dlú²áÔ‰0îŸ×Åô!èº1áh(ª;¦#kd/ºÅˆ9ŒK¡}ùæ0Êqì–shê!¬vY²El ¦Ç7L³[01¡c@¤Ÿ„Ðêþ5$ãÑUˆ«ì¢•yz—¨°³»™¯¦n¦´) Y•5<Ç”»ÝF]ïÃ}zúåDY~*1vÚå!«ßª$‰‚%î¾ãÃÉ‹>jzR¦‡Ý;˜—'»PõŒG#0‰ð× ÔZ(ª…üóG¨ß¹I‰ÁKC‚±9›JABÄRGJ#W„l#WU^€ˆ”]ø,Tda$dpP`’Êï„AlM‹¬´ 4”¸ ÕBF™’&ÓŽ7°‘¦›VB=Y¤5[CœÆóTú﹩1¼Ø(EUQTUPÕ‹Q=šZ“„%RpªvÔ›¡ƒM#3µÚX(Ÿ-qQ1Ç!$-‰”¯ç7&ë»—G›§bP‰%7tAVѵ»¸×Å•è£J뺸ÝmÝÝ/ƒ{zŽ½kš‰geRíTf©DeULšCye§É‹>žÎãPõí¤¯xxÉÄõáÇ Ü·FÉÐ’RºÕuÝÆ{„ìí¶ØnêLJN¾>®¶p¾s€I›aÓnšø—H%…¸^!Ë©Šjµ†Æa7‚¢2Wß4›C¶:ÝÂ\ý;³±2‚>ŒôtYñÇDéèãéÕÚ%T ß õÞuz²ÄõbW‹Ú)´NÞ#3:e!ŽÐ{ð‰ÁÛŒã•Iñ‡Ê÷H€ €°#$÷öÒ.ZJ›;FeÒŠD¶ðÎ,›„FER]SY~¦Žg{#Wãf¿_ÃÙHÐX²0,øÕJ32ƒp^`òHˆ¼È’MÂAÙ!£Ie%ZP[@Ó'‹,L¦Ha;Ï+ÏV¦®¤•áTÆÊ©%#0fPØH#0HªÚä´€¢ïnêñ×;S»q%Ë']Ž½’¼é©«y¦óÍ6å®é²Ê‘ŠÇ‡q13!:0ênïÓ#04yË›ëÛÄtŠ0/#Ú@àpÀ¾ÿ×µê—nøÀí;ík¶ˆÄŽ,FÖ&Ø«'ÞVÀfÍ3múO’åô|}Uì¯CÝXêÑ«&õY‡ûP…‚ ëG;¦M`|ÕW¢·º¦š6‰”~Çv«W›fšM+Jë¢æúä<SYF¨Ö¿BÕÌcb*Æú–¯(Ú¯e콤©h`Åþ¯áVb‰Š‘X#Wà GïÅŽ¡Š9à\5p&jC àúb£še>XÒä2ëµ™ÀL¤-’,% RTB„–õ10m Ô„[@í†ëRXÎÞe–‰_5¼1Y‚‰™dÈ…& âÂ7Á5ߟ«3_i8ê5Âôïd°@lãC{²õÅ=ËE&ð!ýÉ ƒ4á#0©#W&Ä߶¥ |ÅoMÍmõ_Щ1óøÂþqR3*©í¢¡WK#07{%G›±#r!·kÓÇÑM¦¼Ðþ=µ#WD*WwI£i‘¨Òm£h¤UŠ °7{wÊóŽífC#3”Íûql!õÀÃ0BñF·Ø¹†c@jcá%¥VÖ&2:gÞ'^±\È£"!"Hv$dR£û!]þSêäû{ÀœpX[EFuëQd#3d£°€y›©¸½Þ Í=›º3…4ÏÍ»þ8Ap¢ît‰¶l‚!h`@IÏÝò6ã#W#3#0ZLK* sÕÀã²Ù«‘òϼ%—@Ϩ—éoU#2x§ïE’j’Ca Iúsì×±Q;áR§¿UÆ"&†É#3à—¹‘± ¿ª#ˆ`{äZWjóÿ¨)W€'s:aˆ*F‰sŠ^ˆn©Tß{Bzn‹ ê;—¬ÄhÀ;Ó_æ&ÓRxù/óþßågé Eç~g—Iöö0fB¿+3`÷¨=šõ‹¤<aAVkÐ3ØÈ·éåì&þUÄ:4#ý:a ³p9ñ…5E6 øž@tž#W?8z/ôëßB'œm}~N¶ÚðkkóJ«6¹Uk¥UE’¹sj¬ZÐÐ^س#0ðZ€’ H,Aš„#0©Iõ¾â£zÆ1xtUî,uƒ5+£b‚ÛIbÁNQ²g˜ÆÕ³˜DÈí9§3ßcÁëSñþ#3£Ñ°.é>‘äbmO2zÒŽâð4Gœ„þ#0ïäî"vª E!*FªPÕ‡[ñ,ˆò©Üh½’Õ"…¤¤WA¥æP{Ò=¿=ß#êÒx¥Q‰ŠÏËŸ«ç·i'×€ ¸k>wÍXZÍÅ ŽOIê:‹´·ÆõïØ)á·Î$OyÒZÚúï-ò˜çD`ØiËOM:ž´|Éü$zžjň‚„†0ˆ à9ö>þhhŸ”(H£¬BLU5üTb'Œq,‰øç)†$ªÂ#Wî®jåíü_<zç]oeÐØHŒ¢FcAZ ¨Îš_¾—¨ÆkAé Æ#3ˆl|Ãè‹:þŠ¥•#W@))$FÞêNX ˜Å½Â(ë–tÕ}¤Ii¶›GyÎu ÆvÁ#†"1,L´6L€Vu!šËk€cF`›ìÐcB}@0¦6”#ŠLLtƒcAmVo*Z3N4IzñwŠÜ«¹ºïnôã|›‚KPAI2D#3"Ö”DE‹ s› i³%8ÅÄ`»%™*;Ðh“) L)’•†YhÂRKH²hÂ@´¦S#3³m]:! `MYi4r¢M+S-hn‘µZ@ø:¥Ñ5,oO 6È«#WÃrÑZ,Mê¡ë*À›ºÈ9m_/]z÷ëW‚#òwtîŽk–¹r¬m+º4TZ1®[–æ$ÅyÝâãCË®ªø°[™^qÛ4›ánÓ©Ã1«NÐf1GÆ‘ZNŒìEßýÌ4Ë o‡á0±‡,P³(2Æ…#3Q`hÕTÂ\q‰º5@€ák[Sú²0òÕ7O\ZÅM¨„¦À5f3J×Ó^7í#3;O@†Œ'R@æFª…#/¼ÛCäΖÛ$óT˜¹¢kOÉå.Â>Àò<¢ÑõÊí©”ËT•˜Þ:ªï:ëɹa(bĉCX%щñÓÂ’˜@Ž”†AÛ1/NVàqãìyüûGDÈô¹’ó`Uõ‡#3çê¢}°h…˜E Tí+òÛájÌØÖÊY5¶–•©ˆ»ÎȨ’¨‘ ¡em¦jZlÌXÒµåj1:®NvK–é-Œ6+;©ÝtÕµÔ¬V£Eªå›6å•RIˆÅÂÑ!#XDuÆâÀH+–¥IIBð–Nԇ퀧"ôPEHY1hú7œµçÈ°“ôEF Aý=v9x}~=¾ï=c¢Æ#3î_gœÑrCCϨD1#0¨R0IBÇýQP0÷Q@N´ÒR(ÉÒ‹\#gÙ¹³&†N8þ<vÎzi=ïËÑÞy¼æ‚þºà}SsêŽqoõUX(aŸƒmA ²RJMÙzñºÉm$m&ÜéL©Ú¹·6¯¾¶ÚòËÓ¢(¶KVFÒ¨é¬XÛªîÔZÆ‘¢Lcc2#W&‚4ƒ³@*Ò*qÑ£bRIVZ’i!Œ¨@Ê `Š“ Y¦”çE##Î:§wï64i¦ˆœ‹[{†„™q¾ÂˤüdBE#¨¦Ø„«¤eJ ™$EôÍ#0"%àB,˜Ž"â#3ˆ@†P±'&0¾Zˆ²ˆI$ŸpÖ ¼6´$#¤eO£Ü£ØDH`Fâz@ÿ,Ô8ÃtuÄLwžru»!,r\ÊHn#žÅöÉw†ÕK#WìáPámÐÞ/#W@^6Lôñ$ЀèMeUH¢T«÷sîíÖ.Ϭ=p„ƒàùwÉÕà‘iÜ)#W`#3¦ÛlST²’Á›ðÃú1ûÓ7¬T¨‚i_ÅÅE–ÊNAò>¾ˆ›Ü#3\Èz”)-a»—NÅ]€v"t!ßo1}×~‰kÉήÙvHÓY”¦5¨Ô[D›f½åuQ¦Õšm—êÒïi]FÐFQŒ"±FIQ}˜;#¡9žœ»ì˜€—F«¶lomÚ>MREʳ˜ÀÆýçï_.4‡Ê«ŒÞÆ”{¾âü2f'ê=-£Ø“æ[æ>—ð‘;¬£%ƒ1¢\øìêÓI“ ªDâá?ïˆn^c9ÅEäl3„Qî)Ãa×[̬#3·p¼¤˜˜ÐÊFÑ% 0Æ#½ÅPb±±4Äo%vtÎêÕ; FÌ’2œù½»…Îæ¼y4’Uæk{ÛôúX<<‘É<ïg|H„’I"BMHæ(ˆ#0OqG àÍÆÌ%o3ªÁ:s3‚c½·'-´¶£ä±dMw…Jr‚Z-TÝ–0µ†¡ lÉþO·‘·9*lèè sû¡ž#0Σ¤«“g4Ä~û#W(jGv$Ú>¿l8×V¹ÂH…é:Äb5)¢&ó®äÒ×MµàÛ4iˆþ¨ÅM’l†‚,É’€b(0hƒœLŒª.7Ö±©}Iù„¤#3[œ¿[>õWîî8?-‰’"o4‡!5ЋÅ4úeÚãTkÿ¦ ˜ny\£Ù¦~ŽÏ¨´–[# 8l²`óLIæÊa`ï¬dü1O¦™:ú„úSШ—Øš´¢¸k”>_#0Õ8$j<#0¶Æ\<’YD‘EF5”„ƒja‹3f™Š£j6–Û-M6$Ö#W3¥5úev! ±™´†ÒÏÛmnU+ÌʦŠ‘h¡e5I5©ES#6Ù¶ÙVZ6³S-²¥TЖ±RˆªV™DÙªÍMRÑkj5 #-!#36{Õ}÷[«÷ýI¯é¿Có²tdï£6ÄŽÚ¶z’0*àðÍTëˆt~´¢ ¬‚£A*(©"m®¥k\¶ìÏ8«Ì*0äB%⤆æÂAœEƒØ=GW5¯³UR¯¥|ÚÜ«œ‹[›Jx r€À ×"A? ÙüEèq_VÚÍã”T\ÉPc*TK•Gµ#WD³å(ÁóÆ„Èþ°.è™F) ïóñ:nŽè.E›Ë¶ˆÞ$(ÙÔ¨‡Ž5ø§÷† #0N0@J"5#0OgÊ€;”#3G±9+´?[2=¥áö¢)"UÓ‰oÈßÜ‹Iã[j,m·‹´Co%ÓW«º/2«–5^KdãJž´&Obí¯bÁ„Þt?_Ƕ³Ónƒó&÷b%‹rY‰ü¦Žëƒ; H}Ô–ƒ²´¥4—ÖKà„MP¦ñ.eÚ\ÊizÌÌÁäpŽ_å¬ ¦‘Ì+#WXXPµÈ…p>w·¬øÀDàD爫›9=¹!=<èçr½;™ ú‘pt¬Pd;‡¼íe¶¶4¹hÃÊ?ØÒsDC¤;yÊÃG‰y#0 äY?45t9w¦6A¦oO#0“ÝèaÁÄД‘KY1¤rP<Š:`pÏçhÕ„õcj"°Bhb’6±&z6ãó•7Ú9›%QEn,M¥Ì^gåñ6¾«©õ0,÷@þÄÄš2"M6~;èg!IªÕ_ŸÏý~~‹ ô‡è2ÚŠiîA Dê?ÇX«hÈ"H†˜¶1êàè’% ¢"¡" ñÇã·Yåvþ·Íz4ô©! ð<æ²·äwoÉÑóÌö¨&UœX³M¿ËÕÕ;›ã@S&©#W§Yb²’¥% hQP©çâiŽ»mnÁûÅßCzð²î¯b3)f±ÔbÈ°ðJ/#34Xü5Îp$ü6³Z8¹Ó*±Ûó¿ôî“K6kœZƒ’ $ÁŒM4¾P€ØŽ!n[$)ŒBãtC,ªV ø×—·í¸bªP¦3åƒLoˆ£§°¶ ´›ÉJZrá´¶@$…Ö¨x&H i]ÅÆ„Ö>Wòv>8†"6^¯8g`ˆ»|›%‰(S"PÞˆCªZý¥:£ˆ ¶±£3)¢D‰ŠL#04ÁB‡B‚vçZ¤v@«ÒTy÷g?~…ÏËÕ%‚8Mœvα6u¼Þ46¹2ˆIµƒDp`ä0n^1$ 4“©Õ¡/\7on†lyo9i&s#WbZ~Ñ… w<O>”$"ÐE2cÀ.P¥•cDV0…KµÕäªM¯-5/-]šª-Û‹`³‚‘•OÎ͛˾—¢\¯b£¨v#0`û¼#W3ëéGÎîíð"wÕQ H‚è…Êb"’Š)’‹gWzÍ'“ê‡ãLw ì²¥öê#3xFM´Óž–[©NAô*=ÈÃF;wM}¦¡Sž†²ôfì…(_!É\Z#3á"¥I„dÊŠ#WɲUé®^]Ú̯æ\§#l£^“ËäCš¡:2À¦#3Õ3Ñ”2K+à(Yd<l.8š¤¹ã¤¯.S%!wžy†ß…Ò#3‰ä‰¶Êâ&ŒàÕ»”Y>†¸‘ÃæU#WA@dC¬èc.í5,Mò]˜qK³™“T#Wˆ Ø£4/ë0Áèˆâš(mÒ6Å8Ѥçk¤™MxʨöùsLìe v6ûÅȱÅÜ¢¾ÙàÐYˆ8•>4#WT¶œæ!ËR”Ì9ßW–-`á2ç½,œfõ«–(%w(U)Úe`ÊH—öÚ¿L8Bóa ŠDÐ6¸> ⵧÛK*¾‹4<&Ü[- ‚Ix$ª0×Àé™ I"°J×T¼IE”RZ˜ÑT:Y]F=ät’#RÊá#°¬ Ï©“S^b=™û$úôD—fy‘-|0í¿ ^€ÐMyêUL¬à膩ÕCæv/'«‘F‹›-âü8xw%µ›&°5†£¡Å ™dµjZù_næ¢ 4¹Ãº¦Š£7Å?YWtPRh¸l&#W“Bá#3i°”äŒqò‚Å”`Ž2²SBÎÏìæ±$ªa§I¿è~öSh“ßçßiŒé*p5Áó¡EV21*©è`°B“«R‘FÕ^³s.¦—kÆÞmMérÉdR:Œ`#WÜ.òƒ¯U2#WzÈ{¹sÀu †î=³¯éÛ̸Cš™MüJ_CÐeýÍ.ïÍ#W#¿YN4¥#W×իמ“7w#™ö#3ζ*#3}ž“ çAFA:£FŽ±;8"C9h^œ|Ÿ©É0 7,£¡Ùe¼Ð@: ö€M¯Æ)«¯¯Ï±\ön;´Ìš¶ƒØvÛœM„Eô>ÏC«\ÜÀ£qb‡G»nÄò@ê‰ÃÊS¼‚Φ*%¥=°,‰m2“x¯<éºóͼ^s¶ºÄ¬ªUIZKVZGs¨ÛN¶Û˵דEÒR ?–ãq¼¢‚Ù¬ÿ¾â:(xö…hb÷1-¤#W¤€‚€ü’’AX/ƪÿÇç1QŒ“Í Ï™V†[Ha¤”Ì2‘dDfj†&tÉ’|Óý=¡MÉw¶ó´ÖA¼¥ÂÓ.c+ˆZ1öh#CTÊ™ZÀ#J•Äbå˜#i‘•SeË Ö·Iš£(jÉNÓÇzFÎÜ–gÂÒ:—WW®ºõQu‚XÓΞu6©7ºbÀ¬é&qB5¢#’e1œÙÓáÖ›M›dvïx†Þ<bÇŒÛ#k (HFÍKÀ0‡#3`—÷&Ó H‘ƒÃH[ÆK#3‡ºbÂ0-ð±ÀÆ4ƒk²(üÌCFÈÙˆá‰fåî_\@?mR'`qW?;©d&&Ë)Dv•HÈ#W?°Žå<9<K#3r¢?0pE]žIØXºµo(€b= köÎ^=2ô#3ÄM“ü»ÝýÖoˆ(3N©LJéwtõ‚ª„¥: ’"šVßsk̬ÒYQ¬•±2ѱi5µ¥-oÔµùØÚ’å,°€ûðØ[ ¤´P#Wd¤)X‰èúòô‚ÂéÇr~}M0,y®XJ.oêmSGз*#0€CÑÛk5d*>é¨8”§GY³—ç#§,d#00+ÔO‡} éþ) Ã{’liƒº•Œ‰ó©¶µ´]ו╬ͶګØLSá¡¿½tiv¶hxÌpË#39IxµãbjÝ&@îö¬M“‚`ßõ[ãÀW<|ÎgMòß{²Ã)€t€„Å"&¢F䃫'Ž)1{m.8׺œ¸9",Œc³M/×ÃXQžy‘#=U›ñܼéØL;²f3#¶2¹œÉ6S^æ0&?–¹íMü^{#0Å´S-É`(#ÆüÊ_)w-õÓ#3—§Äò*7 H_ÃË^ÛmØÎÔp9åàKUØdéo#mþ2É™‘ßèðdæ‰:wÍ×Ê“§v±ô¹ãô¬Ñ>ÚìYõ°»Î#,iEZšÓyßåì|DvlA|Ljˆ¾ÜiŽv2nã×íçõá=ÑyA£T|AÔ͵¢m9ŠšXCé@&âë屨÷ƒ#3;kï‹òMG牦¾’sdm|³7¶Å5«¡àÊÎDÑÐ;>Ô‹'×B³šÍ7ø¼=ý”Z1LG,·Œ¾½ï+’Æéη°Ž'{'æ°ø…À¦¡††ˆ2á(O¶#W[eÐÜÿ»^šÆwLÂ÷wk½ÃNš“õ6h>÷ëuv0û\Â%ÃNw©¬–¨óÏ<5‹Euݽ*ÜÑ&ל²\3TOLP«€²"ÀÂM†R-¼å-Hi#3þ:^ýE·Vý@¨o¨ÞÕÍ'¬‰þØ7Y^‚Þ-#0µRX„4Æ>Ãnñb)•i0P’IŽ²d”eˆ¨±$6° ©EÀ²SH@¦ ·€%E>$Œ$/@„FÄn2‰¨2¥m5Ÿ;×"ç‰××ÐrË™éNðâêÑæ @îüßÑt¼îªf-bÖÚs—N(x¿æÇÝÅ2óW‚ë3fx‰N..ÆÅkBzÁØT0*YªLÛZ‹AÈG’™j`È+ÑÓëÃt0õàZÇ¢š:iÂ7rl8¢ªî]Ô/ßüÈua¸Ï#WŸá®ÚŠ.hõ£jÇhG#`,_Ö#3¡"#WKת%Gh‘ÌÐÙÌ›Ã×8´ŒD(¥ÓUQ ·'2¢´$›hÄ‚®íÍvë5M-¥6´íÚ®¤Ø›j¦šÕu[+™—‹——v•¼Ö·Êl£di©µT¥A:=—5¡¬':©·¬¬D0‡»·Ï#WVšö” ¦6™ºLÔÈáÀ¬-´kï¡`RMñî¾#)79Ôú4ŸFn Úëô»)À©uKoÂP˜üòr“lj¦Ìîxæ%×—Ø9sýlØèíý•§Þ#£+Dá&È0³€÷rç¹›ÉÜÒE,…” Æ4úi»Ùù÷¨*7ŠÀL¶<y[y6æQ‘·@r½'zƳ¯ƒân@É•’{¤)¿ß]†Ýcàª1_â¯;ƒç¹_[O±—cÐý›RÞmr¶uõO>Œ„qgjƒÑ?lâpJã]nþÑ¿§;ðÿVÛšªÄH¬ËPr§n¢#?|ט¹“·ðål8MZà2"&ѯKÖQ™0}Ùïü?o÷_É¿}€Z?’YÎ|ê_\_šbûn“½5íñÍà›ãkXl wE>Ô–©#WSzŽ¼öÕÕ»¡¤”$5)#0Ìm‰…ç ©ÄÖV#3¬í$̯Yû.Z…Â会#3Ö’ŠÁB“b|ÞÞEÄ#¦Ô\]ý¸q±<9FA…[3îvn…5‰2U"ÙÁ(â„RH?‹äœxô3ÐÎæ–t»‘“ºlPØå²Qø÷Oú7!ƒÆ:ÛsxH`w”þ¿Ã½Vî~¶’Iy³h,x<sA³2ßê2 Ä÷Êüq#EÛ\&¶dkuCˆ¢ÜaƒÌ0÷…3Qö`‚i}TC§“ÆÎ;@M‹Ûhoòb0ˆÃÀ< ÎåéÒÛo³¦2‰,™+¤a~zŽÆX#3íÞ²«.’LÂðì1#WÃõž6|tÍ€ˆå¿¸?dÐpbÏ]™7¡F#0,ƒÅ22ÈðJ¡ä€uÏWîk¥"STƒô0xŒ#3Œ PØY1»8ÈP÷~ÚÕt4Õ#3p´0ß<kq¬éÙ¿ÀÌ mßUmMpj9Ý!´±½M7Ûoxuž–Ýk;ŸXk»F #3¡h8Ex¬b'•±a2«`ìÔ‰Ù‘œbÐc¯)èáIHÖ3Ë#3Õ{CÄŠ{€í€\ŸÃ¡ÅÎüþ¯—="†>’#€k€‰P„2x“ƒÃ‚™#0!|úµÜ#WtE#cb:Ý•±mÓðª/Ek2ÑZˆ«lj±j6¢¶ÔhÚÉQk%¶6ɨŠÛ^-UËQmsm[¥òÃFÄ<#3Ê'aìýwÝŒÚ027eĤD‰÷ÁèFŒ!,*CØFàÅËça¼ìmºá~Ü3Nn €à»—lµ7ž“7NÊö™‚`#WMˆÏ6 áö8)†7Û¿•]¡Xëü'·ÖáÕL{#0uÓWCÈ"2·ŽØ-6Ø Á$4VƘÒë WýÅd¹EæQÉO`{Ž´M&Óº5ê?9@D )'á$½~vB¤4 ‡¸-¨òòÁ!ä‡G£·Ó¬´ðçô±L:ýâÒlGfÂ!æm÷¹ÌNd²AûŽÇ.üôRùb'PôÐö9®ãÑBN*B#3ÌË®slƒíÕ½Ø6–„Õ5kf•oõmzë¦ B`Ùš•Ã qà XÀÿ–Ëju33(>˜TM±JÀÕ~™*ªJ<œ¡ß${ûªõà΋¹—¤Ló¡(ÕM0Ù&P¶tI?D´i¬8&$²‚\ô]Â#3*(Åu9’6Š ŸòªE#W6°Ö”‰Ÿ-5’cÓW¾c¤&dI#3Pc€ÈÎ!¼1#$BölPijk4Y=÷øôËs‡…믻Yûm漘bÄÁ@€ãa#‘`¹Ô± ªI'µ=•îÎ0_,]Ž*Ùj‡?‘ï²»*d–Gqa9_3TF±µnç*#+|•±1é=d(ᣴ3<”;*¢ÐëÛ¸„ìØÎñ¹Ë^E‹@Åð~«±rÁb1AUG<]Ô™çÓ¶‚ú§ù[WMuë<>¯b?W¼/}ƒÜ¢ð<9M¼9…Ò{àìôì.§/9ÔéZ-Lþ#37^Œ¶KªQY¥YªCWWDÑ L…$*¨Q2>ôØ5Í…¦Ïª4-<Øǥ܄$“Â*i°q†o@‹¨@4šŠŠIÈÐÇ›[ºí)%YFm#3²@lZhC¹úGÆe°±*0iƒ€PšIŒ“0ˆŒdAÆCC‚¬zh& âPÁ@`ÄÚn#3¶Ž=B€œ$£Z-M#W¦Dˆ%…±1ƒÔH–A6¿L ÀQ Î4ÒTÓÇJ 1]‘2WæãC6#WOÙŠÒD ªˆ¨Œ‚À Àj5⊹_9]2,Ù6Ô›\é&3Í×i7N©#WRm5Ø»‹´£ÁDŠ¡ˆÂ£[( LÐK•œÔ-$bTm1“¬Æ©™]D±´D†!ˆi§l–©MCB™FÆ5I!¥+,„–U3"¶SRõÕÛËr[¹Üá·$&ú[·—\Ö.hì½æå¼Þw‘4hTµË|ýw»Ù™Œ`1€˜RÊ5©¤¼À¸¢‚h`JM± €ØÚAªÖéq*B,`ð›5jÃ@ˆŠÅPÁ€Üv;ŠjÆ>2iŒmæEN.©½I³ZŠ´ØÁÇõó¬æ!ƒ#3‡uU¬a"ÁnL¶6ÔÚd+ˉ'Ã Û ŸEG6›D8âÎ)~0\4°È1œeÛp^9”£(Ú÷ŠY‹`…BµzÚé¨Ñ#_âpˆ%nQ á…f1é=8õIt˜U‹†ó…²¬”ÐŒe#ŠÍ”I†#WˆÆ"Öɉ2D¼„x\5d3²XX3vBã%ÔbŠÅa¡±v¢v¥l3VU…IˆcÈÜb-¥Œ„˜Ùhí‘•‚ÑU¢òîz]íîÉwY4—¯]ÖÖ˜Ã7Û.qÂ#Q§‘âÄ2·fRBK.f"W‰Zã.“X˜:ãÊV¥„&&‚´ŒM̘7Ÿ+FÆÖ¤2@üÌLü,F™&„ºx¾.%§Yº´Ý“sJXz@ç•3[©«Àæ¶æ÷=#0ñ…>%cOYŒY=hAàwâ|¬K$¤ÜÚÚ0qÃ3{B4¸i#v#30W#3€k1‹lÆöõBJ¦Ã×ÓHW"4!ùŒ †}¦ºÞ #O‚Z ¶úýæ¨gù±”ŸW©b¥Ø§@ˆàÑŸºyDDÔNÓëè5¹–¾Ú£<Ä‘«\63mn°¿6<Éò{tôøã¥t{ÑÎ$ÞH¨Ìm»9a]ÈWÓq˜ö³F4Ãæ0n¥2`p˜c#0³6É.,´Ó\^gÒœqr›âñ½H¶$öpÂ-6°Ò¡¬ÆETrØ(CÈÞ¦Ž7Ð63ƒ£,Ò²µÕÛ䆻fhÚ"b†ÈoµÓöãÊÄIÖ¨HDC 0ØÚƒ'2©"‰´žà*#3–ác(3*ÛlÒÔܪQúi¾uÃ#3¡M‰¢* œÄf"1EF’[C²8‚2#W„@²‚•°e°¸–#0>Â. Ñt…€D&,CÐ…QµÑˆƒtD[F?{@sD5r:ûSÙçÙA#3ü²==SK´K½3û¢K(…Åßå öà.WÇðv†Jãf‡ãD±ršÁ_?²^™Ñ–kß^¬QíwÖâh¾+ø+ÊóŒ—1WJ*ùx•ÙDT¦Ë¨Ê¨ýNýhê¹Ä¢çÆQâ×ÃkAcÖ6›ñ5¹©IJ÷d{º2%̧]rñ+8Æ^ŒÍ§KRÉ#WaªjÒ&¶w<<4ËÆiҬ͞U¡è"qï_ê/‰Â1æ´WuÂ*×xÖø^ cbI]7bI#W(6£›ÑZ§æ-¨¯E~Þz›çÂa[ÀÎã‹<…ÑãD:9FSÅç í'yº>Ý#3ª°*°ƒ&IgbÜîÖÜh„kx΄é¶F+!yêv¼÷=æù7l@aü-Û[›èÔïñ0D9ߟtPh±F¼VÈ!“»{ÿ<è8K¤ÐËüd2òd*hüNáºx>Jc_@®~QÑ”/Wi^i¹ã„¢rá‚à=ù¨BÆ星}‡§’ìÞʺ."h…e-–î“A!Äð+ZuË~ë熥×W¾ÅýïÇÞû`t¸±³˜({ƒKpÛŒSbìÏfÔÊ}(ž°ô„XHDd=?dÇšµd>q8Äã@"DR !÷eVåp±inS.›ËnµèBÈXCåADØ–Tƒó¡UÐKÕ£hFáÉuJ9"‘´†‡žo'–׎•ÅWeªéZ¼tµxóº,Vů:æ髵WM»·2—jñZ)á–KR+#*mÎ;¶îí´Z“Tµ5ñmW¯6´››Z¤¢î[ABIl ¤˜!ðpá±õeÕ!ë©¥ù ¶’ÄsK#WjªWåvµWeÊÅéÉ‚(Z ‘…0`EB¢F¤B˜¢KDlWœ„‚B£t)¼#e†‚S]îZ¼B]"µÚöÚð™eD0ÀBÂè‡Å羓,5Š„¥].µšë^Ëj"£Z”’Q¬¢ÿWº[ŠlÑZŠLÒ•±ª6-™EEEh¶-±&²4cLÖ“dŠf†dÄÚš,Ei¦Ó@͘ʵ5yØ#W$z*A)a ñ¡#3ç÷$ÿFeËFBdMæ»ç[mzò#3&ÕD"’$‚–_yÏeýÓíó_»Éæñ[×›×bÇø^ýØ(æú%êN&Èß+«»äè(h[çG˜ª*A‘OŠH„H¢%2Fü®o§zëóÊâÍÕÒ«ò¶ÛpÖÑdýWWbQ¦õ™‹cI§w[¥®í®–Ö#QkhÖCIZ5Y(" EDˆ,ÓLFH„‰ ©JU,húlÔÛ!¸ÑwbœˆÀ™f!ò(ð'‘U()A&"ò@C ’#WŒc¾CBÈB¥Û»$=šÚöFÚÔ¾ŒÅºß^»’æywlŽ0²Jex9Má’°„50,ócÜXaŠp’Äv~0ùƒün}¹•=ã±óòkÑẠ+͂ƹ0{0Ï•wQL7#WºÑ!•°ŸLÓp$?ÄF͇¥TFpÖŒhЂé,Å`ÂR×ÛutÒˆ‰n™’i¨LcL$.RR·Gša™&AðË“o§l8Õ-®ñ‡ÝkŠKÄþ¸¼ž^«"“m§„ÆÀòš!ÐÉŠÉÓgÎA+Ixs`”îë¤ò ѲÉø•Ò]Œ¯£½ãxÑá4)‹%æÃ2bKÈždqsß“kMÃíh„ËZí9k·±Jë‡k¢XŠ€ýú\ц0™Nó&N82Ë“#3"]Þï{Å4#W¿á“º…c,ždêÀY:Ãon;·+3/ÞíW„ì@QÞ¦éÆËC÷ãiüܼ¬®«>QLœØ¨Å!™e:¥iU#/7´yëÄÕEt~µ“ƒœö°²DçXDHDE‘A¤¶ùd_kk]1´™)–þnoçb ¢KP$±4$DQd$T¨#0–#W-»ÊØÀ¶vÞ®VŸz¿îèqé¢ã>ª‘1-~Voò*?(^Þ¹{òˆ˜!"ˆ,"¤J(Ž•¥Q)˜Ä#0±ð@2Ã#0ƒêøNŸ-NHQ¤V©"\CÑ‚Fš§pÁ^ºÈ”2‚‘s-·¬VÛàÖúÖØJÛlÉ1„.yˆ€]P"ƒ”«@¢"ˆˆ„Jae”S'–)ÂUŒ,1ˆ$NLç^GcahÛ&AÔ!i˜6À–Öµh¥# à*Ò"¬*Ó ’£cbbaB¤(€ª!B¡B±Xň/îžâŽ˜XafK7>ò€¤µ`THG"ê¨Â´ÂcÌuíu•#0„‚ÈÆR0øyR@3Íþn.¾ÙP¼æð…K>ˆÔýòO³ÖÐa`ªsð>¼BUxYóf‹²6`íÛ¬Û=pAÊ1ŒÆ²Z»tøoÏvˆŽ³Az* ˆ…¤$UTE0!$9ÿ?‘á¦aó#˜UzGðѨ2<Ú¯ñ„-‘xxhÕìµ8Òš>¶QÞÀÐʉì?¹A$#&ÓÊÑüm#}BFÉ>©,dâžïѾçêɧÇVŒÇá Dy>|OëWZÏ“³~jYÉx¾ÈÎVv:m†èù™weËÆBm¾ç1ÁÑúBAp<b«ÝÛYÚÁhõÏ\Ý÷Å8d9pI#3t„*$–‡Øu5¸¢zÙ“‘¾ìåí$ÕÌó¨K8áÖáƒvZçeÌ*’bÕ[Ã’ƒk8(äR‹Ø‹Ú!“)ˆER@#W‘$xÕ##3IBd˜]MƒƒQãñL@7@þp„Ñ\د‡G×Óéæ„[alzÄ@õp[}ô=SáË{šì{•áEÊ“zž]¢Þàcè{íœ3¾M'“ I uÍjíJ1jœÖ[«»£šPÁ›‰gûî÷ÒP…E¨!‘ƆˆvEÏnt\#J UKR*1ÔB~ ¹d'Cù¬âO›ci¯g¾ï<óNó¸Ò)sjâiG]Þ/!±)F^W%,,‰¦dl&Ê•£ÉW54ªM))60^7Á„¹«•ËnWqÝ7›Ë·uÚ˜çD¹]ݺEwmºñny6Ç)·“Én®]ÍfXÐë£Ê¬ÖÑ‘ç2Õ5+;·6ºUÓm®›l¥®±M»¶í%Iâî“tæ§3:[’)©Ýºjë.vÊƱ£•‚Š¡)´@…+h#DU£ü,ÙeŠU]í#0¥€ô§¢³·$Ø#WEAìÇ£€4®"zðôD,Cʀ䯸;áxŠ°b5A@Jµˆ–FÄE‡Ø‰â¨Žôç×¢ãûgìUNÁÁ÷ÐY#W‡| ‚Å^L{¼!=A¬5üêÙå’¦˜2CÕ´CÏ·¼R}´(P~_wüÝçKÙ¸‡_r]⤈³U¨ÌÌÓ}:öåªöš¬’•$Q3RÖ¿êSî€#0F šN¯g©ˆ€H‹ƒ ’@‚I#¬"<To¬¡‘:ÔkãË´c%sk·:½-]ã¹s”³h±C.VòZño$ß“Íæm4Ði¡D3äÝÉ aĈ+@¿ÚÛ_z[nmÒžu·ÀbA»¥%$͈¨Š÷mmì_:(A¤fFª8‘¤ñƒhc$(×éZדkuÚݶlù6ò§´ºjêÙm»2$[`r ¤¸j:gón\ RŸT¬ëË}CþK ËlP€8ÃB¤"Â`[@±]¢½Çò~¹¨õÖRî‘C³¤6¦‘ß]¶«ò«llªš¶lÐÊ@DU# #0 Á @•Ï?¨Ôƒ¨ë"*‚9ªÄD„X¬$H¤a "¢ÉCíªC«Ìy0(kPHî>]8"TI#W%A#oóZ´FkCfklÔ½·µ{å¼@ ˆ¤*QjÚïLeLwé‚’4KAM·(#0Ô®‡ ?Å~'ˆ\jíU*«pl,m÷q¾°v='O¦ç|=ðÙ¶ÿš#WzþKø'B3<‹Uêüò¯Ç~’rÄët´]&Lò(`ˆ%5¹0v³Ž h5HOàµ.ÍLGO–66’’‘¹'âpõ㣓]t.T²FOÅVsIËLÅ]1nþ¡I¨`‡d›÷z#0ZIç‚ì?ÆÒSä…·C@R€™‘#0AIÆ«$•Š1ƒ‰ÇêêínìnÚ¯~êŠò»1×$©ÍQœ²ÂH‰ñ¶—#W”À¡ÄFÝ`#3‰ëtJÖµ!*!4…XAÆc1•[¶PcCo1йhY%¢–M>ئ) Ô2ª–€+b+ B°ÙJ>å•"I$¶ˆ!ê\œB#WÕ_{}¯;µG9Ôæ ûÃI¢‚— T’™#0¤#3\‡…Þ ‰é˜Y%zþZ5‚ÃFiwÞÂg#0)Ê)…X39«LܹbZA‘¨5#P$rðiª ê5›sM2ë@6›÷ï#3pƒ4M ¶à=0ü‰¿¤¨’MÖBó~YšÚù¶…4#06‚þ¨‚…b´Ý 1QÊÐD„-P¥Ð‘/ h^åwyèØHÃæ°SöCFÝaAâHÆ@ÚA9+}ßRÝsl*¶inœ»ªüJªtU?@r€$¢DŒH\IKj"ÙƒdjðmdŠtË»‘ˆ#5%Ρã=Q7†ið‡„ɘAbŸYOùÅ6¼z@>ŸÈ–n°Z‡6AÊR±1m€ÖéeÄã?Šl7öÃÜקýîîk?>\_ÉÁ,†‡5i½m]ÑÚæKjd×|.íí9eɶì%“P˜à¡ $jmWGQ—+mÙ!+¤ª±‹ýY8fÙµ°"ì¼ùŽHÔ|²ðlà5P«/#W¹IÓ+X¹Eª}¦ãšóxÐêÓL#Ö!äŒ>À‰äCϼ6šÁæAÏA?N€ýqØÑò•6m£òÁ*:ˆ€Þ›ŽYˆ)¼GòEAE1¡`ˆ#WO]#31'²Z@±£¸"L<)Ì¡òwqƒhos¹ûà!•¯÷/HÁtÖ;&ñîÿU¿®rÈÚëáÜ—ô>Íü:ž÷çß~¿…JI£Ø ŒI|Q>PP!6¶zàƒ˜‚ºƒæŠ~ˆ ¤`ÂßÄíãÎByçÙ—Tµ‹í+èÝ9]*PWñ*"¨Ù=»ËØßòÉf¦á,¹Û¥Ó>ùšÑ¡1±‚††t¹ü5z0Ç#0ËHh•TwØ)‘he»‡-?ÜÎi-¼Ø”j_E;DLÓDñ#0N±8c)¡ 1ÂüÓ8G¸îhX»¹‚®žÝ™BVôÁƒ†ø0˜±Z)œ]å›j}V\6ŸCœK\$DtÐÍÄ#W0#0×AQ®”6D´…¾Yƒù;D{Ó½D8#0òˆð:?C™cÀ5uz*UoÁ¢ÞÆ#0Ú)p|èÑœ™%Ž(7@ǽ¢l*I±GõA¸yy|Mv«'\m$~OOx£Þ¯Öñ"GßwÇÞeóÞk/âc‹N[†ŠªX>û-#Wåû¸#…U7c™òw)ð[¡§gæß(¤#úÞÖ[×ô„;7ä"É6¹JÚ–è´tÈö4=Ký“G÷ø3}{ûT±M¦›è›»³] Zc›c½8E#—V‘ã1µ)”\@,W50Ff`©“aØcÔnM© î~ÆE“šAÙsôÜ÷d»ÝúqWN¡Þoˆ£+¥â£9Æ¢2ØdÌÖŠQszÏ-;YÒži{ìzÒy°0y:sRõžl†;ìÅcÔÁ`‰âÚ‰áƒG?„³^]¶NÔ-îëšõéaNP4qOj½qwï+¹Ï&þMÞ^Tcßõ šØ—#3† ‚0iivké$÷RÝ´;ø¼)‚õ#WŠb_£élÅå!³Í^O\žý×¥ðþ=”ë$w¶NÌù?@µuãXëDhå¸t°!ÄŒht(!ø*X`8OkräB]”æ¦d£p&}¼ë=‡ÀOÕ½!ÄïùUûm?àª3¼^Ÿ¿‹3.8zT–5T ßϧ§v™#WŸ„³¶]h\³‚œò·G«G—â5àt'iƒ_MÏ#ra#3Ðj¤€ÅàbW¼#0Q}7šZ61*#3|2„Ž!òÙþï§%ÍÆ#W"°cçÇäÝ0§Ÿº§‚µŠTJŒ ƒREC-h8<‡:útvæ&Á6t&+yɲòvÉ£½bÚþFþeÔú0ñx¼ÝfµB>HÁ›ùVã*ªZ•UYµÛÝZLé,÷¤”“èÛµpÔm·’ɶÉsW;Õß|<æ+ler¸Í]½]â2¿—¸„õŸEdÛºD³0ðÕö#3×ðœ>zœq4µÞ[Õ#p–?íTïrÈÇ¥†1ឌ²¸æ3<á°¸k±°'š¨Pv/ ¾—®þ×,àPƒÚg‚±÷ù£¸Ç³žCa3ŠÅZH„EoAµ©ÄB õ˜hZ#W±LH!s úvûó;[`Xª–™éŠ¬9Ÿ cdÑJ4¼“à¥(Ò€;›:Áóå{iXÖª2‰Œ%yëÚ9N#W¨LɹŽ”Zd»“8’ì¶G>ýÒ2wë–›ºy[5½!`XÞ·#0ö>½bŠ’I¬ëo#3G‡F,œèåËnOÖ pœçg£àù›]2…Î%連'ˆçÈîKæ¾l†a‡?ÛÙòHy¢ž’ -çä#3ØH¤€|ˆ"^"22=ýä ‘!ñ@êS§ø|…郇ÀˆÑchÑ«ioÁø/]êhX*G²·‚¼ç2™º€Šp:â&Ú81 åï§]Ó£¢z|¬Ø2Ùõôî—Ø@Wäj90¸[˜xÄÈ·ne¹Š(èzNÞ±_Ž„ wMÃ9MfÑôŠ#0áUŠyñRý@2Š•Eg¸#0{þåݤw©+åó°TÇ<5m[|àš1g4ÑEy#W,:É9(|#0;äªTõ‰ `‡yB¢ªM³ükíêB«.GŒ&:z#0Dõr#W:6#Wu‹#ï"¦f·ÕÎŽP赚(ø¼eŽDÁòé10e•iô«9ÀvÞOŒò˼LA‡A@QÂB!HÁ5—3Uš›ªÓfy…ù<3èj;„fýP6}’‚ÅU¸ï>fï;ohÍ(4À°¬bp‡Ñ³Gu8ZãôxþÇú>ÄóûqnQBD}tØéÂv‚°RÀ*Aö¥²Ýî”#3"}Üøòš1d¨D’ªDM! ¥”5$]ð¦óßB`Ù@yvÁ<†'GñÌl6ÛéÄ#É%úGÆ#W*f~#Wf#WNbƒßOê—iI‘Oe ©Dÿ¯6Ɔ¢PnÉ#ükÄ׬=ñnÜ.ñèC®ñv× »¨ä€þÄHéxùÝÿ…ýÝõÃã )Ÿìù^¾£rÖw±h®i~®›Çd„Þ:v>¼8L²~áXI•Q4€íóM£ÎƒÎp2$^J1‹‡«¶ú°•é1¤†¸ ¡‡¼ù_÷ý[«rÇ_Ë^Áʾíxm¬;õŽ£ßøgåw¡…èª*‚§ì¦Æw«ô耵 ~]bSÉë¿QÝ7¬eU8àÁbß¿\#0·<¹õ‡)žoTSU™½øñ°¸4L ôï0Íýg{;z®ÿÝJH;<?™ñU°Æ§˜ÝŽA?…Š#37ó!Ë"$$J„B!g„:rðÀmÝFH"6¢B¸]ÂĪº¨È#Wn˜<1rH(§éŠë+Œ;S‡MY~§%FA*5ÄJxÏi¨m?#WБl(’JƒcLZBš»§ðu֨ƱUKfÛ4µ‰) ¡Û¿m…ÞòI$`Þ‡5ÍbåÛÝ>AZšñ#W•i"€¤PAŸ‡ãÎ^ü>·TJÆe&hÉ£0…–(&–‚b„Ä$ÅŒ£SddØšF‚ÉSdØP‘”únÉé<ÜL÷&\O•—d[HHj‹ëA7û®¸?dR’M›vÌ ÜÆ0Øh4L˜2ÊiEHÂ4—ͼnélôÐK‰pDÄ#0å@|S|L[öUÌrú‘ŽoB˜G^#34óC‡KSd9EE¢^º8ЪԢni°b]d<¸ÌoGTa“"P•³!èbcm¼3‰˜Þ—ôÃ`-®šMUNB7TRzk9>àfì!’w\rÞÄÖ6š—hzªƒa5zlÏC·@eð^ƱËpû·×”ÍÑœ·ŽA/ο`bQù©Ã”ªJÄ&KªÚC8¨Ü_Ö‡™Éþ\ñ‡{·Ä…AÞG©ËH™ûÑ@Ñk-‰TÆJU[i¶8ÒŽ¬0MQ¤Û6˜†Ð"fMé$ÒeIëÅÕósUãguÉJæÕv»¦æê’í]#½ï;ÈVÌÛk‰×·ˆŒß6Íæ-S#WEa„ ÐÁc0 ‚Ó`)å¢C1CˆTCé¨Û¢G…2D”h¿ºcVÁ¢Ø´šÓ6ñ·®Pdbr#W3LH#0¨×_±ìO“–²£-1gÉâ4D„V—,A|KC#39AÇ"~DÔÕeÄÑs4Ô°‹ýœ0¯âþEìp³ÄªØf“b°.šÀé30!‰V@ƒQ¥4˜g€æ:’þF¸ÛXoE+Šš03JÂ\T#Ñèwkf“iäU =ÍlÙ¢¢ÙÄj!%$H°‰&ÄÈÐ6"Œ«€³5A”¢éªb(Š+iW¶îZDN–ñ[ìœÝ®êÝåö{†#3ƒ˜Z$˜mªV pô}lÃlÞ"™EA1³iÆA8äƒ!Ë-µ4Úšq4Ìiâ ,ÒÐVÆ1†#3¸h–…jŒŽ c*ƒ¥Pj¨¬MÊ«C¥%Ê;0²ºÞ LÛ«‹Bh‚QÕ"È„¨W#¬–Œc Ø›+Œ‰”¡²¢o!)FP²˜àÞX1SP46#3-CW1/`”éL:{‰R J±EÜ`åE\ïZÞÛ¦#3A‹c£ƒOOy‚ÛX·4˜Ó4ù"%"˜ËwµÙ#3œ‚¢&Þ\|’º:7A;e)ZEiÙ¬FYB²Hì2"ReXÔcp$¼õ†Öž–Ao¹ö¦qØ`GZ¬?EbÈ—#"CM#3®Í+bOƒ´-p‘¡è½CTÓºY‹"’@‘¤c#3ªµ=.ÛAš6<ŒË#WÐx“5 A¸1ÈR£Oˆæ%¤Â8GÖA¥^&”ôU‚±Ü%KŒ#WÄ wìŒ+(ß)ÁÑ£ÌkIÆ`hdCIÑnÃDPÙcCQ Á¶„Ðd¬Õ$¤ÈΑ3CHè%¤ #ƒŸ@öØsȱ™v‚èƤcQœÊÀMˆ£M¡Ž4Hk%`FF¨”®u¨LÆL+"†‘†™Í„‚¬XcFˆÄÓFX„Q¡2A5b#Wƒ@äA(4ŒhÞàƒÆŠ¢£Ÿžiè8-Š¾Pƒ6ðÁ6ù“Nr’&4ªh"LJDLb`c±Ë7ŠQ«””Mèö°ÆÙ©C›Ò0Ý•J.¨ºN0ým¸±š#Wc+ëY³F°@a£ÎLDÓ4R("¸÷2ŠÈôò··Ht`L‚‰=¤kl¯I_3Üaµœ¨ÄÙ.ù¾çZoq,(WÖâpc|Ã#0i+o–L†¥.€1´6•Qc4ƒ;X%žÃ´ª-ÙÉc¬Æ41x4”–Ä."&˜ãõjcˆ£§Ä`Ñ°EQ$D¢º4DÎÅ)¢" KA±Bb\Àc2—¾¯*ñXÛ%“YeëmêÝ#EIƒBFÄÅŒÔ 1¤‘€MbP®¤¡K#ø8>€‰wš5Þ;œ7w^ ü_;˦5FM®õÕÒßÜuÄõr;³¹-ô_|µøö’‚ª(Z›E##ƒ¬¨#úÙV …ÔCïÔ)OÒ‡ùd¼l‡ Ë0C4)ØŽ¨TòŸ›†:Ñ/Z‰J#WP{ó€yH.wd ê?—<[í½%E~(G\'¢,lúdªÐÀ¯ÓoÔ5©6ªJÆÖšf²´#W”j#0Á.[f—²r’'¶¥%D8žÚøƒ ¡9ºé÷ÙÆT#WIP’/·ã³ws—ù&™Ç(òk\Ì‘‰4J_«ø‘$GeŠÎÅ™#¹wvÄEµÕÕÊÛ{k®št#W3£CEPl2˜ØqEZ‚iT$5ª•6bÊb$þ#W¶\;¦æ»³G,Œ6o’‚š`1†gÚ‡(lô_ÏæÖ#3€ô™²ƒ—g˜l[ü53·e½|w*Üî‹:#pÀmñyî¦i¹8,2lô5Xí@)#0ßÓnâƒk%ïR;tÅôŒGÏ›nGÝ«ÃD¯J"VÑô¨¾¯ÛD[Idí~x™*†\‚†¢„<ˆ˜šM…zžÀx“£·ñu]6½º=ðmPï,/T1%ÊnU^7¥¡uÚm›é®³¢§ÑÕû{#3°OÜ}'ÒYÓš½Ý¸zó$‰GÏzwéâ#W<èþ?“ zÝÌüuI'›g“¶ó¾ÀßGV$4#Wíú@Ð/&'i¢Uìïëóéîøc;Ã0ÒØöä“oUgx|OI1Dw\Ïš¸é’#3E¨AB¸}iئNz55‡„…Á]¸¦!ƨ‡imHª°5ØK‘1=s©Õ,I¯´¥ç#WŠæº¶m#3u–Û#W'GÒXÜJ§^v×# ïKEƒÄy¥†²Œ¡=‚Vä+RDUOcíH†²Ó¶”ªQ#3ª¦vüæaØçczphI#3ýx5¦#‡©# K¶¢Â~Ö³ÈßçHœŽ°LÜص"ìÀ#8`6ßøcŽÈÃQ"+.—‹WH»µqIE¼s[%¬[Æ5nÄ·7wkW5Š£Thås[¦ÖÝ5d5Š‹]-®VMsrÑ\©Ýì×#FÔš×íwF¨¯¥’!6ÿ`b¯6ø{ݱv‡agx…Î+¬1ˆ®ÒŒŠÓ†œ1“3im zN:Š<ÉžšzÀÔN]É€ÞD8r1Hä‚ELA_¨€¶"!˜ â„}/ÆŒ2H¹#W"RS#0ÛïwDCðèøºèy<#W¨Š}PhìZ>"®@>’#Wj`¯ #3Ë’Xø)…†ÀÉ4œÐFí#32KºÈØòöÕëL)›[³K]ÆÛ®î;]%g]ÝWæð.ó¤RÒêB1@ðA°ÔÛ6«FÚ’µ,Ú¶Ò[hصڨšÒT–ɯÇóÛz‘.Ý‚Rä%ÛÝED7Å‚ªóFÖ–j6&²FŠÕJˆFî8xŸ0Q†¥AÖGþ‚z”_ßêé ê·2ŒZ¡‹C#0ùÉ/L4„>TTa§ãiN.û*ÃÇ©½€£GÄAÖÛ›<s~³¹ìŠ,™›I¨i°–ÆÖ×çýwà×{×àŒŽê:²+詪‡¨õëÝ#WÓÖR€uEVAM SæA(Pþ@#0#:JBY¡‚Æ-,e£I±f†ÔÍ£e)2JI´hSmh¶£mc[Q[SiZeR™SlE$Yd€Â_ÈDzÖÖªˆ(rJÃ2#W‘i6äÆ‚#3•Â$9Äà ÆÓm°*ÊBH1¸ÃAßÁã£z`Dî ÒT݈ÛãQåB° 4D£c1†2ÆB`#3DV&ÄÐÈÆ2#yHñŠ´Æd¥MŒ$ƃFlA°"h( b¢"Ðʈ¥ •T!@Šv˜_ ‘TjR ?§øu°ÄLûjBÑ´‰B’‡SwtîÌ\;S÷jí7®·W®ìÚÀ($Œ$±™m2ÓЋŠ†Øâ.ÓÆåÂ!>V~áÓºIe??·×>ÚÒç–Š)TR½€}!*H$$=¡{pþ‘Í{Tý#WM†`ã'.Î7Ôå·wO¦õ.Xb<µKÚ.Ú—4ª‘ˆØeå#0Ä0#@ðŒóÆ%R¸9P`ýzqS•bf=1)eu§a#W¨h¯1ÑF#0õþm?µ¯³_~¯ÍVëí:B‰Ü×S;°wž]ç]»£´·–]J•)™#0_±×EµsQ¶±h´[[F«rÖË Õº;·Rõ[¶à<½ó0³À4I³üíVIf*{1TÏÝD2È#WÂ’§é¦Õ“0]œ3®)œ§Ëh.ØM Ö£íþ%·íÍi§NÍ•tékké®f}²é¶prÌ`è½ìäÈ¥/®¶‹ýïͦWŠêŠT:ÙûùÕ´»Jí˜ßTk.ýÞaù ¡o¿[ííä-Ožv„Xå’ÏQæuúõkîkÛB~:ñëo¹¬ôBßD¬¿Zš×)(IÙ´/ÛcïÃñ–Äñ‡m“Ó8°í|q#3†>Ó±?³:x@ÊbüøD $ð%èj‰<‘:—ŽªT9>}g¥¹Y÷;R'nîgqßT®ü¾°þ½[=¢î^*HUëÆÝ"òšÑ4mé¯aƒ¦m¶÷GY*ÌCÔ³f%ñ¤¸ÖÛ³3AiÃyw;æŒõ|k>º«|b—0ºA‚úÌy'ë‘™û¼áŸ“›œÒÊ’G±ÑÛB'=‚ëmŸ˜çßï8+l\MšU»•é˜žp@$t–it¥aÅ5ˆI²¯ljS#WÉÚTTõH¸k\¶Z„$LJ•O¹p uÓÌG;²·ž[…hÕdí×ÑÏ{M-³I·à{jì#3'_]él›]JJ²…SÊÔT*)´œ#0tˆ-E¸ŽÍÊPÁ..éý?ÙY9#WG)¸¶¼9+©àí®=<ûnmw^ñ™uÚµ-G {•Ÿñì"i4ûäà0ÜÂT]•+Ù½]²(/6+ÁуA£Ïp„«)±Ø¥³˜”Nf¬¼´¤a’áÂH/Ól¶Ù팅ˤé8‡]â2ÝG¸n”ƒ¢ˆK7Øð’¥ám86‘úm™=ÕÎ¥›)ž\ê"óԋÊſpÃk´\#Äæü;vÞ릳Çht„ö3å3¥€ FBeªjçŒt’Øê-VÖpÔ˜Ö@ÚÇò¥¡”’Ì^Ëçßlðx¬ï.m§"7ÍJâþ›í¶-šÆL$ƒ¢fb6߈f®8kÃÓ[é‘þ»ˆž u]‘éXxæâ6¥´®qj#Y«EU*¨Eð ó<ÎåUfÞtYÍxo…£2„Y±!ð“8‚ü#“j¦%ô6X;noCf̯’ѧ™ Â%4ª¸'Šd† jÑÌܬ¹w±â½3ºørïθ Ú(2zÁëG†—6ÎaEf–aƒj}²µã!±‡u´'vÚxáÌó¿Ãj=vÂÞxëÁÞS”SÉ$ôx34ÑÄsDœiñ/I^Ù¯bäC‰u,†uµ¤ç^û¢µŒ¨!m‘ÜÌ9SÍ”Ï5ì(-K˜¬×xzб2(=¸„MÕ¶;FSRÚZrÿ·šðy¿LÅuïã½t}ýEX,»sx¸1f9’ŽªMVo’£jèi1Ìç8*¨+©pl°Ñ¡r•ãJ¢g ûטÝÓ10/(¬Ç87ƸÞ8ׂ—„rgšÝV³®ÔptË>Ù&´ñ¯9&ÞŸÆe²v})!ÝÝxzêòª»¬_7åß[7DëK§MÞŽRc»s«"“µÌ#<=˾ßéƒB;&ÝwM&^0ì»5åé~jUûßË“³¨÷XÈ:]zLcÂ/Ó×¾™Z5áàãl"YÆa݈àšèù‹ï'xó•×K·yÈž3·Ã.Ec‰bww*Ÿ3=5㉾¸Üôƒb·‡óÎEÖ3BdpbŽú7±ªoQi7]Þ%Þ˜zðèO“]´6ã·#WÒf–ß[óX¿~/rs¤ 3%·R\»Õ2OŽ›í±¬æã뢣p|çw™P¾xÛÒ…q£²ÙbúAÆtT(¨ÏPC¡ÆQaŠB¯—tiŒÛC¤.ÝnèÜf3ƒw#t7‹îAq,b¥CËP¤Ný,ØÁ)QŠMFoaCÃ…6Jhzg! Ièx+ŽÌ2)…š[.Æ{°[݆¹D²v†o‚‘¹’Pm®Ú€«ÃÂñÈ;lÂ1E.=~½ª#3€JàØA”0Ü;:wt£›]ÕŬW óëo'^;’Œ¤ž)âK‘Dšš@n¬·Sõ°cÏ·¼=ç•ŽuæÝÏ!àøy®‹Õ¶×OB‚=ðè¸âºÜðz#W#W^I`³J‰LÜl#0€#WF©‹Ø^ÇsÈØ«˜_žêìߦYnÒÅ#»™5K7H+#WÐmGNY›¸Ê”WN‰~³Zšš<òD¸Èl»e‰1¶yL°jÙŽµ&VQ…hétæf^ÔÅ&{²3lòži&ÞtQøjLˆH¦ÙªdêΔ/#Wƒ²#´7wl{Ž´® 7÷o<8C˜ÛÛÚbLCžggÊ%eØÔ÷űeîžéˆ&N¦D¾T©œ"W²Ñ¯?m´8E¨˜g%f÷V¶Ré¤î›QÊ 27TÎí.²#Wá#8éQwçžñÕ]>Ê{”ì.?héQB(ª›J"ªjiÈãŽ|3q:í9™®9Ä®¸oM9@Øm6;Èå%¹nY5™EôxEa$ÍX|…’ÔE)^<#W1#³uXÂY<l®hq«ŽŸL6]Î:kÓž*ÓÓÉx`/FŒ\"ÒÊlçSàx…ª,ÇÌÎ:ã}/§…6#£àvئ«3ÅZ}¡tËh¦ïxwu®¤xt¸Ãìa?‡.pn1YVY”ŒuñÔ¥!À9d'Ý÷•Xß°¸w—±T¤„yßÅäO—`½ßO[×WÂøò6-Ìq:8Š±e#WV5õvÁô`|EvRçL´tÖ·5ß®!Wfå¤8íéÔôÌã”6X®wã{~< *Ÿ.v§¯è¦²ðuÌ°šºë¬·²SSîm¢p.½f驸ó9òðÁáŽP胹¿ðÔÕ£ÞvG¥önÆL¦ïãnyËïàçb_¦j6©½Í¹¯)t¸ƒ-—Ͷ4±Nóæ_‹íÇt.:(ê§ÉÃD.!/ëlxbKrS˜Cžô”V4µI#WVÛS "A†2ÊÁ¼A+B¶Õla*ˆ¬Œ÷™¦ÍGØ{rh@äJj]4µ`bJ δžÎ.ØkEX¾<Ý›Õ0aH¥ùóèFNŒ;âÆVU³¸>²¨Y‹’"5#3 ¬,[ô7,Èh¨·tÞ”e½<ěá£àç'æwÀ—x¤ö6t«¼°ÈõwbÑi•£*¯Þ1ýé-¡!¬*ÃÏYÉbèÐ(Cµ{ˆéÀ¹`C;–l\¹àl.ï6…3²ÆeÁÃØÙ6n0+šõ@ÔQc“sn×–åoÖ–Œá4G²Š§ dz¹lªFªê#3M¸¨ô-§naÈÓX^FåO}ªÒY¶çWxÔ M‚S&n(3pR;@©zTW¬”ñ˜²NUÈÁÐè¯Nðì<03‰v)È"º×9Ð̯‘‚6_zËïŽ55N¥Œ$È%h€>†€OÆžð)ÓÔnÔVfìŠ\SÚ¶‘<,GHAnÛÊ#3¢ù”‹eú<¼¸ oaÍÂëã‘:„ðA'“ð^wƒ´C4q[‘1êUT4úŒëÁ-˜)1(ñëöÙ'™y;`kàüƒêsººU§—_ò´5sé«ë#3v8º="tu§5O4MáÀÖ;º%ïŸÇ~ÍßÌƦ¿e±Qµ~ì F‚OÂaï»Ín°v•=™4=6uIÆ´8À•6[ˆfƒT!!aDÚ2HÚ@•a3ͽ5¹¢±©)6‹skÆòNë’d´ËQxÝzók·¬×9ƒ#0–€P)$,HE 2#W7‚’"µZŠ8(,I#WnÑÚ™ck"(Ò°XÚk¨1ÁaAT2*"#W¢Œ…J$T‰})ÃÌæzÎoàQ§çqÖï†Õ/$ÀŒì›¿òåÑ8˶’ø'Y ¨ˆÆt›#W*{)ªVôâ·\ŽMimk_mU[W6µª¯Mʯ§ËÝ]RöIÝ5OÜáµ8ÎS&‚%ƒïÁ2+>qrõ5fYjtªÿ´UŒcCK+udÊéõÂŒƒM·msŒ3p5££4ÿÔÙ‹DŠì(Á•ÄÈ/Âh*|Y¼›b§ZÛ,Yˆgœ®®)KÇ{^CA,ŒT„)!H1WA”‡¥PDiòãO‰½Ãµ«ªoÌ"D°L€³(S“7CR@mÇ6C#W&£´ÚXÔ‘K»¨¬lM4ƈa#0•ËEG¹(2³É·÷#O’HU‚òûe°>ã…¦ÓTm´E9Z’T™T€²bC,›Âfœ‚˜nö«ÀÅ3R·X›€Q§cuìeí¨Êi‘£®9͆²o#¹Î³FíуMÜUªT“UTâN9)&˜•.ú‡*¢£e¬Ío,ã&ÛIl°T\X&óÎ$EÛJ#3©¥Ã‹FÓièÍ“Mµ¬à[l2LêZýwƵ:ç'víi€°¬†g)Þ%gnH#†#3‚cB÷V¢T[ª±(@¢#M#†.œKmf䵪 ÐÑIi•B$94³Þ\Êøf«™+XI›ÖÇŒn.¥3¨nÀot†Ú4ë;âµÍ¥"NÁ8Â7N8í¨ã×wÇ*Lá‘Šr£$ŽÎŠ„ÿ’dÖ©U2BgÃŒG*9ËRê¢Ò44ÎÉZnD£E´nJD©B¬2#3‚š4†“MŒMBãV¼Ü[y2Xë®Þ¹ØfÖÂn]>4ȵ#3‘R¢µÙàÆÕÂUÂÚMFe¡œÙ`öÝÐTcô|fªQŒfšÆ ´çú&ÛíÛˆLwN¹@üuXPˆÚhŽ¦©ý9Yb’?Y/*•>n:u©…L Vi±³´Ã¦c»Cõòfô¿s뜃U¡’H NGŘӎI5¦àÔ(È@6¥„`y,u£¤µ¢lf5É„ñ¢fsmQ73O1……0Í®% JPHô\Ó#WFÑM¶Û"h‰EmÿTÿ½{AZªóaÓ*Qôˆb,`cJ½È©„QÖÝtÁEψiõ`nÀ|’WŽZhxÒÔæ‰"pôÛÅ…³)L8…Ù 4Ö¬žoLq'ÙŠXÁ–B1ØÓ<»„Œ‡?³gCÝ5#WêpmŽ5I€EBÌœ«s2±5»Å¢¨si#W“XÃclÍ,HŠ§Í3”’ÓÆbLd#3F¤çYˆk,à.D(@`“dYr •&»=Ͳٷô`$ôžÀÓâ}‰ƒòj¥20DˆÁJ;K.ê&ÚñÉž¡Ÿn¿ä¯…Jˆ15–MÓõÚ¿e&û#µÖuJFU#å&ß‘Á(æ‡kÔ€yö>·H$?i#3ä¦wÀ°ïUÛ Ø6Æ*[›Ç”l-óõ«ó}÷bÒÛ—Rðì.OàÿÇs{z¦êjh‡mœœ¤›8‚Mõ'ºî $÷¡^ñN¹óAPÕ>Îàú·Ôð ‘TrqRR#WC8}~Ññ Ãx¾ð8ü8§šÆkdãÝßåUŽŽÉÝ…#B¥á›n“t¬Ú\AÁíö_ñ•Ï«CUDTÍË-¡Ë”Ã>Íæ÷=¶àcxHÆÐÔƒ’1¦DDNNíÙ³wsÛé¯mùÙ-žÆy|«Ãœíí'y>M0;O@@èlÃÖÚËò„HààçH¤C?·ˆï>_gÏf3£¬z·Ï*Y £ÜszPÀœ…ènF׋W6ó*ñY+®ºÝ*óTÊm·²cj‹®º¹Xªwj»®µjs«%xmåÁ@U"¾"êÀ_²†³OƒD¤|?Óß~ËÞ½}—ð¡£DcU$Û#Weµ°ØÆŒ‘ˆ™(Ò2¢i(Z¦jÆ„Ö6É°Ul””UVJ0£6T[ŒÚ³dšRl”¢Ì¢*#CZ#W‰LKFh¥4YM"•J0ÖI†Ø~UÆÌ"iBI¶×uÐ[IûŽ³¼¯<Àˆõ`öΣzì‰Ê·¦w3–ãï›…öl¹ >Ýà¯Ìèuÿ tBAœ<8àW³ÂÚõâ« Ûë¹I<òêÚi^‚à{qPP6¡‡Ú0/Z£†Y.#WÛ» |›oÌå^Öl‚ÓJÓ‰#0µ ©ì¡Sî}°~ȼéBaíEÖs$#W$‰„×·røqS´êÛØÝDrŒ! f'Ÿˆm¼’H]‘®Öê=>,ÀGß«¨w<ûcõQªj@ÛiNñÓÿ#W(]Í™3zMLab³#0£úú«[îZ‹TIUb©DmIµ¢µk•—ï»Êº…&£F6ØÖ3íº®cÄ¡\¤DªGÖ–-$X Ì8 Ûx¸‰P"1)¢*x6>^9XÐÁe'“+J²ó®žÓhLz°¶&"<…KjوÉúrèÕm(râéãPꢎ•¥5)U±²#3˜9Ý„¯<ç²×{vøȯ)TU¦’#WÄ#3¤xžHÆ‚±`0¯¹¼c7ºòîÝgg+ÛÎ×—;F2•Ñ–ö•å%ãrÉ6¸Š 7¡2þlZ¦àœ.‹ƒÃ@,“(ûÁ§œEi¤¤Ek”B†#WmJTG¡ÔuQ³!3™æôžz#3 Iõ8uaLlù8du1p+j¥V\ë´U·à½muä=iܦѷÉQQDcB’¢!çR™0]~ûçDeá©Áû¦Äg£»©¥R'GX;ÊÊ“»ç¸ÍI†B(v°‡{¬"{PÔ›Ì#0\:ˆ¹’R#W²ê¨Y¢T#W©214ÀÌ5w RÙ‘Èrqæ2ó²H’I$NWébª®‹ï“#0óPM4LiJã›tKMk #0½=”ô?ƒqß‘ÚB°_Ý#0âwwt8Æ~´×jO4¡»:oCÒD„d1ü3Ù‘LÅݤg”׿¼K3¸›åÁmÙIì÷r~G0IÈ&ô,’ª`ï®åÂëF¨‘O¤V¤¨Y0Lm ºAopk¯ÀAîÁÒuéÄ«†æ£$dæÑh3Ç実¯pû˜¡M˼c&+??,eQFûõ³S%U©¶ºx-ÐSÖ}ADvߘÒb4†–Ð^'m'°XsèËÁ€hLÚ‰¢#ï£ôŠ(zêIAð4‹l¬6·m(”®n„ÐÆ#3´É•#3ƒ)@Ê#W5*-بû¢¹s]uÁVœƒ-V„_W¤õØ5DÜj3ê±âoÛA÷}²úŸV`f¡Úlz“¥4µÍ•cWœ÷ÕD‘R‚ïV]Ò[‹à23u ´¤D$¤Q‹äsÔXwiÇi<eº»äÐú}D›×hE5fæwNŸT††’xB‘F4Ÿª–ÓrÊ>ÝÀÿˆÕG±æc”7*ZX!2ó(²ƒhñ1À4€†élÑ Tw|>¿$ÇÕïšX“€ð«x 'p‹B•ÿ#é1#3݃T3&?#3ßZ7Ot#0¢xs0?€tÅ$" ¡@DDb`ÓìB÷øíEä̾×ðùoƒ Ê™åtxâ„lÁÿÃú”Ù†öVf“#3“â œÑÒ+šÌ0ªé¢Ä`C$0Ç[²ƒ^ª#0÷æzJÇ8<ŒÒ"™–ËWÀö†ðý3ÆÏŸÀ¿x Ÿ?šMõü _®B#öů¬ª¤ÂA.f%~á^-.}uºšwröx—|‘D¹âˆRnTÝxžT¨X¨õ#:d,#[ܱèÞJD6Ù,ð{¡jđûô˜ÒàUF„JS»rb¥@ý3<->³ª®ÐÎY#3#3³›35… …RÄ#0£"dE!²ã{ÓRÁ#04@Ÿ¸ÝU¤°dBÿ‚ TTZ'Ya¾GÊhð]ɬѾ°8æi44êi¨„24yf»“"‹I#0Ä»‰ÝÀ`ôæ*0l|(C<Ô^ñC\lß9¡¤çQ œÍ´çTIÞ{EÏŽý`K7 Ú<,f6@d§#0N€§CÛ%ß×Òž§u{L%;²3Û;c#WÖ:“Þ«^¼°`¤äÀÝ$°MyQã$EaµJ‘A±ì?À1@Á¤3Rh;#3uÉB6h"ЫÊDúâðml›3€ˆW nÃmi´ë#_xÌj#31¨RESp¤8\赧:¦!Òcb°xñÔÏv»òqBëµJ¶˜hi!±Ì¼°€õ¢Êf¤bA#W„èðFÕ%3ÚšCÔyíë{8g3¾êu·0hÁÆ9;PÏ\;I–ÀR•¼\fÓgÏ'T3YÕ9qaus8aÄ«²Îؼ~?î²Þù‰mmS2ºã‚e39Ðs«ŽŒ²Ã‚L¸ÖÝ…&8Y3^}Ã~I`ä&ñ#0»\‰PnÍlJVQ ``7ŽYHW6ÁAµ½Á¢”i#3#3‰úò9©GÈÀ#ÊH^„¬%dÁ$ÌbJ#3ê$-1mz[wFP-ÒyiRã$Óù*T QÔjä•usGn+/Ó„À—1¼ec³©QËIõªŽÒ=J–27Û1çVÊÐÅÝ]Wnpü€§{ž–¨5xe®w|°ó(‘1„8qfÓ½³ƒkfC³#3¬çX>,‡}"KiÒa´©a•…3°>‡ªKK{;¤ÌI©0¶Ûm,ÅÉR=O:Š1–”•¾wieQ£3ˆ0BÏ–Ù2d™Úò8tü™j^cˆñ›íÐÖ9¿TÈö«¦Ä6È&ôð-ÛmŒYM.å0ŒR‡f-bìóQçÙ"ɳ#›ëfõÚq_nØSn<[7q‘š“]äkùºÂí1úÅÇyˆÎϺ'8aJ¾{DŠ–~xxÚÜãL*¢7xȇ‘èˆd’ÙFiÒqQ‹|uvh–N»fŒâ[6b.= Òk»ï™#.Œ¡bŸÒOjë;ËP³]aåÐ󉮼±8k¶ðO®âæMÄÒ'¤éÜ•J×jj"Ê FP×/Vås‡*ª¶¼`¬`0Ôç´Þ¬ !rT¨âÂÄ-Óü3Qä[ò-láy’³LÛMFUÁE>ÀKKK#WKGEÝܸðŠëœ¢÷è"2pk“*Ð"ª}*eÇÄÂfÁÐ7§mÌtàÏÀpüvÇ~s¿9B.ñ!ÁÉHøõ€zîšôñ—” wèe;˜kŦ"B6gé…Ü"8×Ý«‡»¥MlzÔ¦ÙuŠqIµH&y˜F‘1–Yb›K`+aB0ÕY¦Ø épKº#3°`ö$Ä-Y6 ³H$œ¹_#W:ÚγCex-ÃnC{0skYŒÐ¼Q9¡›àà^S¶ ׇ‡ä¹çiÍžuÈÂik#ž³Eu³¼¡Ù¦¤f†ÕLu±¬V]2L]®%¶KãLC¥ï²8ks ºŽ™´Ž'é×ùþ>NiÁÓhRä6pþY«i?OÎ}™ãÅÐ>B Ä€¡&4æÊñ£»ºŽÈ¬y,<uÓÈ‚›‚Ç)ñŸ{„ªjmCôŒsÅË3"穆)½NÏ—!Ü“U7ô—yl°´™©•+>ÎÃFò`±2:!&ZÔT&(>m³³ù^šSéµOªå䥊ÅÄØüñ«õuº4#3‡MDèЫH ÆÐoª,y#3Žš¡BPt®Æ\bäù1)’:ø½›º6é}°°¢Þæ[«´õ{P‚Ì%…NxØæ©úá;4NMùæäùàÎsFSÅÃL#34ŠyŠ¡¡QΘ¤ÁµñÆ4g°š-9’‡hôZbmÙ4ÐÖPå½1¼¶ãu ^& w0»>xÔEë735Y.óºfÚ`ùâ#Wî׆ã$ÈéÈ•ÑËÍÌ70ñSHšºöœvÕõØ6š–Ô³°â ëÉÂfH"™uvy0s½»ÞÝ´ßBÃG=eÐË®ÑM£º®ïθ¦û¢6w·Æ#3òæwL|ñ‹ŠQAÕJVöã¾äu‰! Á(]ñriî%É«³jhÄiƒS¸ÖšH©‰™úäŽ->° ÛQB$}4óUmˆ¼(5,Ñ´‚+š¥mT(ïå¤Á%ð³¼SZ4ŒYÚ`¢É|ö‚GoX#ºà=TжaØX¢–2‚23sBU†¢ìÕ% ¯a…ÊtÈä1#39‘hÑM'P5–¶Œ[)E*$¡‹-¨V®ÐКK2j²T8›†¬pfeŠ¨”`ã¦JÂ…Ž-³3a°`d Í¥»kvâã2Kzùõòõ¿#Wiknˆg„’â#3`ŽQCÂ#3“Ã#0ì&„Ô¥†Ò%À³‰¡œHŒ]‚€Á%C~<ªÇH£V„Œ¹²©±€Š@âÁ‚pâ#„Œ#AÙtˆÀîѤÁ50#W’éÄ0b:3Š6“1(#Wh‹Æ56Û^†šLDbÃM#0ÎF’¦èƒ·x™J7Av $EZÂ>YÜÚ¨À«…¶&cHŠ†ºFÛci;ÇJ ¡…?@dß/;Ùæ9ö¸PtEB4QL! •Ûœûuè©weªþ~¹=CpÐs÷rX ì-#W‰±a#0Z,¬Ð@NdÝ»ôžm–‚Nį(Px}ÏçsgΪ HŸ_›ë/nÖ!@ß¿ÙžJÒ[Kð;c}d)TrD3ºü‰Ð¶i<噫dED2÷›#3«¼ ÌøæfyÓEIŠn±àäq™ê?ÚÎìýM €°äŽŒs«MzkÒ㘓\!®yï#†&ÆFC9È×óK5òíéU]yë«’ªŸU¨‹ù`›£ g.e+ÙJ¬ˆ½ßR×ë¬%ê™((¤a¤¤'#WMáÆœ4±” óÓœ2Á¬}œ¹J˜¯¾ªQôª‰3·Ššmw¡Æ6Û‘~4¢[ÏzöщZEö‘4Ç—ã&ñh\Æa©1ÓJ7£ú“ÛõÂ)£QqOí…LHðªl¾Ú©Ø·@/aªV¸kƒF²|4®·\n³˜Š,‹›[,^,·:±ˆBëSÕ_ÕJ ¤TOm2áÁø59ôHe¾AË‘Vеµí‰Ú'º #3‘ŸdÛîºós‰ÌøjɘEÜ¡6¯‹|“»l_‘y¼:ÊNι;³†D˜vøžTiø˜µÆdk·W#0©ÚOõ6Üd¯[[µêyÎ]Õ¼“!2Je¼KNñ¹5æîÑš‘]çG¯<ºW.»uÞ»¥ Èã¸Q$, A¦šTbäµÚ*0„SZ4(Ðûé,#0@|دfI¶IA§q.pb¦%ÊÆñ'—K3whÑ«–.˜ làᤔu"È̲†DŠEOÀ¨M gWåçé;Ÿo˜!ñ†<O#3Z÷#WЀôÁS’s[GÖÀc8?h³PŒ’,œ”Éõím¢ª£#P¢#3’<w0¶ÄBà«CEi+²DÒ)…x|Ãó°å#3lIAÍh¢Ÿ(7§Mbtxƒ¹ˆìGƒ-;ÞÈî tO‡ÆpϒŠ‡DŒg?LVêîûê\|ýìOF\èé#?,œ@þÙØð4ø#3fÉ¡@üB‡Ìjp›^2½–UŠó“SŽ45PŒ#3;û±0&˜/´À~ž¤iÉá¤TÐFié¯e§}™Áù2¦dÛ¬dÝË#3ÃÄ.>hŠ'(ŽêPPvÅEzÆÛzèëM]XÕƒÅx$„ º`\ÐÚr(š8f>6…ÜZË–‘˜ÇéãäÆAy þ;¿+4ÚL’C&I2ìàè_c#3¼¦éAŽ¥¹EXå#3öž€~L¡A~ˆJ °ø4²×í·U2Ú$£XÛ6ÚÒ˜Ék&´ßgRk%µì®œ¨¢ä@E˜PD¹´¨©{,«QdE²A[„G"@X!EWª";Ô{&Zò¢…¥HXrÿ#0ìTCg†U·¸ÔÀáñ|}_Uókšl›£D”:¯‹äûÔ¡h*Pß…ÜfúƒVEF@QP5ãàvü÷:·ÖŽ½3´ð裎Ï+ëŽ7n±ÒÅÑ£›CX¬w®ã+©ã¤¯¶‘|+oss1†â4TŒÈ=R@hÃFŠ!H£„2R#WÔ¹HÕhˆ#0£#eq6A¢DÉdu4ÄèZ1*Æ«6ËL¹P‚2^¶Éµ]•Ò®ÚܼݮÔmžn"ÛªM¶´Qþ׾ʾã èÞtpߦÈ6›x[`H#W°JæªñRÂЙ`9šZÓíàêxžž/BXˆ"æ¬úw}>ÚŽ³I©œÃpÒµ Ma½iÁ–˜÷g‰dÆZ×®Œò7ö7²6NáÜî"£rÄ€bù «EÝ8lÌ7¶š„»æQø´YóTY¾†pÂê‹"Žµ$¤%EË$(fœj[%öèÝá¿Ä@MÝí<&ËÀ‹aª–BŠIñüº&ú‰1ÇPásÀ~0cߟ1º«Â¡ÌQ-xÊ»ÌÕtÕhQbI&&±¥j4T§Mw"‰I– ø…„»Ó–05µeãœLKj!Û#32 Ì®¿*±¡Ô}^‘>ˆzá"ˆ›Ï®IaŠH$ SILª¦ƒvÈÊ~ýÂ;tÏ\ Ê‚‘Õ"OÕ³÷±ú1O:÷ƒÀ÷DK%ÕQËÆåä‘"3m¢™´š’•K*™1¶%(emlVÌ5„Ð+1ªÒUûÕÆs]0’#)Ož+„tó'¦8±UÏ#@6ylFÃÁD8}3žjsCD¼æ|@†£v\ú´N¥#0'hC`›z0îÉVF@$0ÁËÝFŒù‘É>-á§HG³p•$åÊÑT>¢zÏ}Á!TæJŽÖ/Ã=}0’C™éÏǽ˜ÆÏ–õ×®JðÏ·Y„e.¦5J¦€ª<ß›×ÛÅzêrÂh§E=3£vwwHƒraß… jnšTQÅܲ•Ë¹,: 6Pjàe-.:‹:Æ(‘9%|ì";3ë[4=$—CY€â*°¢¶™ÛŽ6„6aÒÓ…&„”µoŠ6±Å¸AƒPוˆ²—!$ÐÕ;^pÜçl©g<ÇÃK³;ÁèÚŽ#…¹CyBUËnCMdÍ5¡lˆ˜.æÚîôENzé¥,ŒHe‹F”Aö#3µ‘dåTkHßã.Ø z&fs4´0lk'Ö]~ôÎ…a.ªnÊBÍij#0¸äEÈyz|§ˆK…„^⹬îæŒiUï·Ð¢˜Qýˆ)$#$Ñ #®ãö§·¸Ò!ÜIÓÓÓ–2:X˜Lï#W"t†ä<‚ „Œ,uÔA¨›žçt±¿àxyÓáç)#0#Wós8÷œÄ¼-’Q$Cšhr#Áò@¡¬š„j#W}þ¯ÝóïÁÐÄBiJ¢ö(–Y««–]–½žþ£¶¼ßOFNª¤Îð#W×y¨û4³Øë‚£àËGÀ›ŸxM’ÔB‚ äN2dE3ìÍx¬!û¼¤áïÝôxÝÕFe6¬¹÷ú~ŽMW Ž¯#0_wÎþþŸï¡Óôj“¿×é/Ç#0qã>Nà'¼…m%›5ï?¾†º¬s^ï0\éû‰…ÁÀÒ§Ê#6ŸËk#0#3C¶(«ÁG¦Ÿ<Ù*Í}ßWËm¬«ËéÅ1™žuÔI±4›w\‘‰…‘¦}]y…ûÎêÞž-xÜçžo6ó×qm8II£iM”a¤-Qòn¹«–#Wò6M¡+^uÚåº!vëÆé%ç[¶VÌæÖ¼–š(4!£!Åô¤S`D1¨m-·gÑí;ü%U|Jø{,x˜/fzƒ¼’³Ây>wÒŠy¨ºÃŸLd$AI5J³JYmKµõ[~{ê·éûjø~CQ*lHTjR66!K2U6ý¯×úQ¿«ê¿s#3"(щ±°ª1Y$P{P==Ý7ÂFEØ4aù#0-„8~´jJìèýbAÌÂÈyÉO3¸¦pGBE·¼ü+|%zÓw\Ù65‘[+b‹cfF-Œ¶*iE²Á‰é¼xÛi6Ô‹5¨Aíádð‹ÀBþ89_¥U6óÿN·gPz>¨»¯¸’x€y^°âd÷¯¤øþ\iÏç©NøÈ_¯A1 > šD—‡b!á"F=¡«Á¯B¹#W:i2ëÐJ‹âz…<ú!‡ÝMW¿-ï`±J‚²’v4‡ÐªèQ`3̬¡=‰0&ä#WÙ#W‹ƒ¸~ä:Cϳ!h‰ˆµÇ¬`$Í’vDÉTéOrÔ$š"k²ˆº€¦ÉE5©5stkTKZK(°1 dCIRÁ‹ H`¦Nlòœ-šˆ#ýZH(l¡ #WÀlk ÞTiñ $D$Fh-–^Ýf]Zùýt ö´•îÎÄa-YÛtj÷3ƒå¬ÌÜLf‡`%gvq-)AÏ/7©ú¶¿Õ°MOÎ&€ç‡U¬µ³%j‹|ÌÐ?Ó£Ù\·³:Ë׌lcü”‘Ç6#ë&°~ö4:#0GæüôP§÷¨%rN¸yQ»%±Ji¥I’‘QKcˆÚ¤ÛaKF¿Kmû¦¯hQZ6¤Ó «"ˆ"3üéëÛgl³@ÕADH°¬0P ±‚%رœ‰±#ð›b,a•HЂÄÉ…4SDÐÙ´£XYA)wë#0ó¾êŽ{×nËÐî¸êE›VþeJÆÚÕæ¬U£h+J¢ÂjÍ–"¶6ÚŒAˆO™(ó8c§³©¿ÔÎíí¥VY) ~s?þ P÷@±a© ý²³Ê>ˆÆð)Éï’6¤§k¯¦Yæ#W#3o²$~çqýyi¥-¿©áè–FîÙ–!´£‚ÂÈÄéÑ1‰žwk0rE4YéТpQ#0ã-,ŠP(e´o’Ýf“SE*[Öl+¶Ùk»²¼kÍåkªmKoM\•ówY¦ì̪뛶¢®v¡–É–Z¼»±¦™6»º»»[&Ê’¦D¦Æ·Õ¼Ó^ur°‘F.JE„†" Û0<‰¼–Ù„^›×—U)¥²jL©W¥kµ¼êëÏ;V¼[e&S*¾×[)k×w-ÚîÝXÍlºk¤®mvç½òõ{ÏPì˜HhÛˆ#W´#hÀ]8!ŸÄkƪ³JºBÅÜ>…ð£!lŠŒUÌ‘ŽT€ BŸ,•#3¨þ4#3Ã7Úý¬[i¦#3¶Š¯mfùã S4ß„É·…ZçÜíuéÏUÍ‘j®ßårýw#0;ð˜I†Û'QO§bz°žT.(XÙÆ0)8Iêo¬äÀÔpáœBÝQ¬lz7‰q!níxç“åûH‚]ÈTîɆª¥N.¯™Ó;E¼8[Þð4Æš+û>ƒK\…;>í8ƒ$QGÊ)‰Ä¼.#3õêLqá)Ñ®b¦xŒ$œb#WÁ'TÁ¤ÄÚR7ˆ8£;ÈÚ3Óów˽l·{ßÀ°ÿ6RKüÄp @íÊþkWÐé÷IYw]*OMøèCïmÒr¿(<èPÃ#Wjš2‰éc6ú:¿EV-™¿VÊ(t‘K@´Xt4ÓFÎëͽæ\cõã<-[!d+‘~‚É–f Þ0IËé—dTß*d>W›ÃÍà”)Ý©ôJ'`O8)¹A°.Y«IÔ’Ø*)ˆî92Ó3Ø‚l#3L";6ëf#W cÅ4;d³>žð+Ù<…ð«),‚,Ô›E^-Å)Mºšé—Jœ‡ a~¿â»ú~ìbÿÙaív˜ÑÑÉ#±U¬mœáÖA.îqµÿ/›˜m‘^HòP˜NÛ1ÈÑÐB»{œ')—[ýÔ!D¹kœ çñšêQì¹.ïrý¯âÈe0ƒš¤hr"Jb"+3ÝP'líWήê¨z6¡cÚ(4¿ÅÖ5EUy›Q‘hö+lâqUÛÍ8twÚ…ãƒ[à˜NT}™;’™÷™åÑt¸á‡H„hˆ×lñÂÜ;Q߃7½4ƒBÈ“i™E¢‹‘ÌHHtž)FS‰Ÿ;™ˆfZƒÌiaÑ#0#3"k=¦£vÞ+mgÞzyŒc½'E‚FG¸Ô_hó¤â½$ Š`öÁ’#PßÓ!#0@µ"!kBÖ:YÃz¶5ùºZ¬šÄtŸeÇŒR—E'ٜЀZ§ßWì<Ž@aT6Ídmé¥t…ŠCÜ#0kåÀ}Gð–FáO\—Åe !\QG±ì_³01ø]ößpGa·s?L›#0¢ ¯™‘Š!ÿwü?§Ñ§þíÿ¯§þÿùóÃþýø~×ÿÃÁòzåþ|ÿúÇÅåÿ–ŸùsÿÇšïýyÿgý?ñÿ®ùþ]ŸGüóòÔÿå·Oü?Ï?ÿ§îÿýÿŸþK?óÿ§þŸçÿOóäïù}Ÿeÿõÿ?×ôÏÀ ô¨þÂÈ`wLGOÀxÐ €@í; ™&e\?³ûd#0aä*â%G#3S½ýà†"V¬‹‰ýÃ-þ˜!芊„41¨÷K¢Ïøú€þÚ¦ªDDUDcó$â#0Éä¨#3jÈlœ2Ùžæ¤ »´Ú!®=NHTȧˆ²Áý6xq½•À°"uü›÷íL‡M¥ÍPþÁmÍâÄG«ÉLžè¿æŒr€Š÷£ÁåÌÇmv÷½ÇöÖæì$'ü5²b‘Ÿn_…¦5ŸÊvÐ/€Â+ƒQf(gý)Ü5#W8L/òeýà†*Ù‘ß©¾µŒ#ƒÌ£W¦#‘u‰ìÖ¼«jÏî†ë”¥0=ˆˆBC ‰Z›ü±Ë5CÔè„t_éê˜?>>Oÿ~ü>Ãñ•¦9Ž•µY䙟æâí,{R¦¤ˆ_‚S<ô¸™E¥ÖB©ñ½ªÜP)œ”‡§ªTÃp£’åÁ£dO{Ň›Fø•ŽôÒ&^A}Ä&òP€NÎèá4+Úˉ2ËT¶[!uD°r‡ÖÓÑË7ªŠ<Åï”ñðãÛ{šj€Í¸ÖÇ*‰šŠ ºÞ¦0hTÆan“9`¢ÈP÷äöâMVK¹"ÌunÉ’c²2©¸¶÷Ä"¬oÁÞéä‹Ó¢•mœwÌlŒ|3¼½íd´cVê4}uÌe‰› ³ÔàîîÃe#Wâ)LY ò1ä‰#‹\¼í‹ÈÙ†2áý—ÉÜLÏÇ-q,«?B¶¬yë³x®‚3:FÆp¶cEÜ<cHîH¥ù̾yMÜÞ³üÕŠ¾ªƒ8yâð#P1'‘ÇwÑ…ŽÝZÍTÅw¤²`±HŽ¸“Á0õú0—p£ŒàYr¼|ĹfQ#3K AVB!ïx$q´#W7êÏ–º°¹Áq¹Š³‰Ÿ=‚Æv¢" ª³1aP¾›yp¸¦cÝ·Yt̃™”$d^×t¸lsSO°iVË#0|P$Š—;»½c×Ø&f®ë65ÉBDŒT"C^¡”‡KÉ(oË5e‹]D4„‰¦Nâí&%‰™ÃÿÞB{ed€Ÿ8m¹1Þ[žf#0‰Ë–Û œŽ£^£inþšØ'ãËssIm´¤DÆÅÆâj)¡µØÛSI›mÉXÉH‘aD‰&â”(#0ó¡œTO#3>r¤ù‘J¾ÒäÝ$Àþ–§•StU§H("„…£"ð<JíJTŠÁö =[æ œ>“ÏÒi,÷vÑx/Õ/åo…®Ôì…êd>OûS‚¨ù™·£á{n;º KîëÁ]{:;Ô9“—Ë%hº&zQ0‡fÙ9.ê!ö˜FUÿèŸå!”|„Oÿt<8èÒ»rq¾*‰\C°‘Áë¬#0ÙÙ™«§=$®Î)Ycj!”FvtµqŒ4<¢`PGé㶧Èg€Ò/ôf|z¥Ü—íËÛN®jX±4{k~^&è#3³QÄVa~ê-€Ÿ…›w…6}y¸!+tVBË_wJúƒ i7µœ3ºå0ÂMø-¾mÙc[Ť#Wßk\Ô•¡ªéhµÞ-·¦^ËVõ4[Rlmµ{æµËUìÔìõðº H¥}ÙÙÒU½ÐtAÕaŽTQ6”ØÍ>ZéÖÜÿÀPÇ•õ¢r’ÝDwîX¹â8.öawA 3&Åøôè.$Ç é®ð¼Ìr¿RùÈÈÏ]-B‚ä»þÙÒ‹÷D]@tPk’,€HÍã¼thB([þ®wø}l£§SÒ®õ#3O´9å©þŒ‹qPó©òš‘ÿÏVTQV’’Óeµ|÷Ï[ÞM§v±i’•*,A1‰RAi@¦k‹ EˆB&' „"’(@ŠH÷žÎÏ«ähk!ÿê|ÔÕ@DÝv\àA;6•|æÄ=¶G¬Þ”#08!x&‡DuÍd‘OTOŒ±Aðû~/ý‚¤åä¦/þ×?äBÞ+AáxŸRI€²#ÿd#Wƒ¯ø¸y®ŸÏþ²©?ùvÿäÐQ©ûÄ´&=]_øÕ_ù6oû‚â?Ù¿c~¡_xLcÿ¿…þÿü›O•~?gÒëóòfNjuGBs»²<Ïxï¿®d|žwp1ì<¥…¾žß/ºOÎ~š¿÷m>¯1×ÿ¬Ë“óÇí"^9E!ñ¾—|Oº^?ÍÒ,†¸šGä7E‡Ã³G?ÿ5¤ÔÅ–g1r…tÓfùúuûÃ*€÷ø™‰õé5¥K‰!þa(þ5(ÿçE¬àt®Þ'DS8&új¸\eœÁ©‡|Ž!ñõÚtí¢tYXþ\äi¼#pX›Ëa(߇„hœTK^–Ó˜H0»ž¯F`êk¼X¢<øÙWó{n»²X¥ë&¸ ºãíò,Ïø‘ϲ‹é¹¿S¸žíŽŸgþzÈñ)èˆÎ,˜ª†ÇX~ÔbI(@ žP/M§5=ÿüD¬OHøû…B^¿‹Â#0§ÿñw$S… '±p +#<== diff --git a/.gitignore b/waflib/.gitignore index 8d35cb3..8d35cb3 100644 --- a/.gitignore +++ b/waflib/.gitignore diff --git a/Build.py b/waflib/Build.py index 1afcba6..1afcba6 100644 --- a/Build.py +++ b/waflib/Build.py diff --git a/waflib/COPYING b/waflib/COPYING new file mode 100644 index 0000000..a4147d2 --- /dev/null +++ b/waflib/COPYING @@ -0,0 +1,25 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. 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. + +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR "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 AUTHOR 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/ConfigSet.py b/waflib/ConfigSet.py index b300bb5..b300bb5 100644 --- a/ConfigSet.py +++ b/waflib/ConfigSet.py diff --git a/Configure.py b/waflib/Configure.py index d0a4793..d0a4793 100644 --- a/Configure.py +++ b/waflib/Configure.py diff --git a/Context.py b/waflib/Context.py index bb47c92..bb47c92 100644 --- a/Context.py +++ b/waflib/Context.py diff --git a/Errors.py b/waflib/Errors.py index bf75c1b..bf75c1b 100644 --- a/Errors.py +++ b/waflib/Errors.py diff --git a/Options.py b/waflib/Options.py index ad802d4..ad802d4 100644 --- a/Options.py +++ b/waflib/Options.py diff --git a/README.md b/waflib/README.md index c5361b9..c5361b9 100644 --- a/README.md +++ b/waflib/README.md diff --git a/Runner.py b/waflib/Runner.py index 261084d..261084d 100644 --- a/Runner.py +++ b/waflib/Runner.py diff --git a/Scripting.py b/waflib/Scripting.py index 749d4f2..749d4f2 100644 --- a/Scripting.py +++ b/waflib/Scripting.py diff --git a/TaskGen.py b/waflib/TaskGen.py index a74e643..a74e643 100644 --- a/TaskGen.py +++ b/waflib/TaskGen.py diff --git a/Tools/__init__.py b/waflib/Tools/__init__.py index 079df35..079df35 100644 --- a/Tools/__init__.py +++ b/waflib/Tools/__init__.py diff --git a/Tools/ar.py b/waflib/Tools/ar.py index b39b645..b39b645 100644 --- a/Tools/ar.py +++ b/waflib/Tools/ar.py diff --git a/Tools/asm.py b/waflib/Tools/asm.py index b6f26fb..b6f26fb 100644 --- a/Tools/asm.py +++ b/waflib/Tools/asm.py diff --git a/Tools/bison.py b/waflib/Tools/bison.py index eef56dc..eef56dc 100644 --- a/Tools/bison.py +++ b/waflib/Tools/bison.py diff --git a/Tools/c.py b/waflib/Tools/c.py index effd6b6..effd6b6 100644 --- a/Tools/c.py +++ b/waflib/Tools/c.py diff --git a/Tools/c_aliases.py b/waflib/Tools/c_aliases.py index c9d5369..c9d5369 100644 --- a/Tools/c_aliases.py +++ b/waflib/Tools/c_aliases.py diff --git a/Tools/c_config.py b/waflib/Tools/c_config.py index d2b3c0d..d2b3c0d 100644 --- a/Tools/c_config.py +++ b/waflib/Tools/c_config.py diff --git a/Tools/c_osx.py b/waflib/Tools/c_osx.py index f70b128..f70b128 100644 --- a/Tools/c_osx.py +++ b/waflib/Tools/c_osx.py diff --git a/Tools/c_preproc.py b/waflib/Tools/c_preproc.py index 7e04b4a..7e04b4a 100644 --- a/Tools/c_preproc.py +++ b/waflib/Tools/c_preproc.py diff --git a/Tools/c_tests.py b/waflib/Tools/c_tests.py index f858df5..f858df5 100644 --- a/Tools/c_tests.py +++ b/waflib/Tools/c_tests.py diff --git a/Tools/ccroot.py b/waflib/Tools/ccroot.py index cfef8bf..cfef8bf 100644 --- a/Tools/ccroot.py +++ b/waflib/Tools/ccroot.py diff --git a/Tools/clang.py b/waflib/Tools/clang.py index 3828e39..3828e39 100644 --- a/Tools/clang.py +++ b/waflib/Tools/clang.py diff --git a/Tools/clangxx.py b/waflib/Tools/clangxx.py index 152013c..152013c 100644 --- a/Tools/clangxx.py +++ b/waflib/Tools/clangxx.py diff --git a/Tools/compiler_c.py b/waflib/Tools/compiler_c.py index 2dba3f8..2dba3f8 100644 --- a/Tools/compiler_c.py +++ b/waflib/Tools/compiler_c.py diff --git a/Tools/compiler_cxx.py b/waflib/Tools/compiler_cxx.py index 1af65a2..1af65a2 100644 --- a/Tools/compiler_cxx.py +++ b/waflib/Tools/compiler_cxx.py diff --git a/Tools/compiler_d.py b/waflib/Tools/compiler_d.py index 43bb1f6..43bb1f6 100644 --- a/Tools/compiler_d.py +++ b/waflib/Tools/compiler_d.py diff --git a/Tools/compiler_fc.py b/waflib/Tools/compiler_fc.py index 96b58e7..96b58e7 100644 --- a/Tools/compiler_fc.py +++ b/waflib/Tools/compiler_fc.py diff --git a/Tools/cs.py b/waflib/Tools/cs.py index aecca6d..aecca6d 100644 --- a/Tools/cs.py +++ b/waflib/Tools/cs.py diff --git a/Tools/cxx.py b/waflib/Tools/cxx.py index 194fad7..194fad7 100644 --- a/Tools/cxx.py +++ b/waflib/Tools/cxx.py diff --git a/Tools/d.py b/waflib/Tools/d.py index e4cf73b..e4cf73b 100644 --- a/Tools/d.py +++ b/waflib/Tools/d.py diff --git a/Tools/d_config.py b/waflib/Tools/d_config.py index 6637556..6637556 100644 --- a/Tools/d_config.py +++ b/waflib/Tools/d_config.py diff --git a/Tools/d_scan.py b/waflib/Tools/d_scan.py index 14c6c31..14c6c31 100644 --- a/Tools/d_scan.py +++ b/waflib/Tools/d_scan.py diff --git a/Tools/dbus.py b/waflib/Tools/dbus.py index d520f1c..d520f1c 100644 --- a/Tools/dbus.py +++ b/waflib/Tools/dbus.py diff --git a/Tools/dmd.py b/waflib/Tools/dmd.py index 8917ca1..8917ca1 100644 --- a/Tools/dmd.py +++ b/waflib/Tools/dmd.py diff --git a/Tools/errcheck.py b/waflib/Tools/errcheck.py index de8d75a..de8d75a 100644 --- a/Tools/errcheck.py +++ b/waflib/Tools/errcheck.py diff --git a/Tools/fc.py b/waflib/Tools/fc.py index d9e8d8c..d9e8d8c 100644 --- a/Tools/fc.py +++ b/waflib/Tools/fc.py diff --git a/Tools/fc_config.py b/waflib/Tools/fc_config.py index 222f3a5..222f3a5 100644 --- a/Tools/fc_config.py +++ b/waflib/Tools/fc_config.py diff --git a/Tools/fc_scan.py b/waflib/Tools/fc_scan.py index 12cb0fc..12cb0fc 100644 --- a/Tools/fc_scan.py +++ b/waflib/Tools/fc_scan.py diff --git a/Tools/flex.py b/waflib/Tools/flex.py index 2256657..2256657 100644 --- a/Tools/flex.py +++ b/waflib/Tools/flex.py diff --git a/Tools/g95.py b/waflib/Tools/g95.py index f69ba4f..f69ba4f 100644 --- a/Tools/g95.py +++ b/waflib/Tools/g95.py diff --git a/Tools/gas.py b/waflib/Tools/gas.py index 77afed7..77afed7 100644 --- a/Tools/gas.py +++ b/waflib/Tools/gas.py diff --git a/Tools/gcc.py b/waflib/Tools/gcc.py index acdd473..acdd473 100644 --- a/Tools/gcc.py +++ b/waflib/Tools/gcc.py diff --git a/Tools/gdc.py b/waflib/Tools/gdc.py index d89a66d..d89a66d 100644 --- a/Tools/gdc.py +++ b/waflib/Tools/gdc.py diff --git a/Tools/gfortran.py b/waflib/Tools/gfortran.py index 1050667..1050667 100644 --- a/Tools/gfortran.py +++ b/waflib/Tools/gfortran.py diff --git a/Tools/glib2.py b/waflib/Tools/glib2.py index 949fe37..949fe37 100644 --- a/Tools/glib2.py +++ b/waflib/Tools/glib2.py diff --git a/Tools/gnu_dirs.py b/waflib/Tools/gnu_dirs.py index 2847071..2847071 100644 --- a/Tools/gnu_dirs.py +++ b/waflib/Tools/gnu_dirs.py diff --git a/Tools/gxx.py b/waflib/Tools/gxx.py index 22c5d26..22c5d26 100644 --- a/Tools/gxx.py +++ b/waflib/Tools/gxx.py diff --git a/Tools/icc.py b/waflib/Tools/icc.py index b6492c8..b6492c8 100644 --- a/Tools/icc.py +++ b/waflib/Tools/icc.py diff --git a/Tools/icpc.py b/waflib/Tools/icpc.py index 8a6cc6c..8a6cc6c 100644 --- a/Tools/icpc.py +++ b/waflib/Tools/icpc.py diff --git a/Tools/ifort.py b/waflib/Tools/ifort.py index 74934f3..74934f3 100644 --- a/Tools/ifort.py +++ b/waflib/Tools/ifort.py diff --git a/Tools/intltool.py b/waflib/Tools/intltool.py index af95ba8..af95ba8 100644 --- a/Tools/intltool.py +++ b/waflib/Tools/intltool.py diff --git a/Tools/irixcc.py b/waflib/Tools/irixcc.py index c3ae1ac..c3ae1ac 100644 --- a/Tools/irixcc.py +++ b/waflib/Tools/irixcc.py diff --git a/Tools/javaw.py b/waflib/Tools/javaw.py index f6fd20c..f6fd20c 100644 --- a/Tools/javaw.py +++ b/waflib/Tools/javaw.py diff --git a/Tools/ldc2.py b/waflib/Tools/ldc2.py index a51c344..a51c344 100644 --- a/Tools/ldc2.py +++ b/waflib/Tools/ldc2.py diff --git a/Tools/lua.py b/waflib/Tools/lua.py index 15a333a..15a333a 100644 --- a/Tools/lua.py +++ b/waflib/Tools/lua.py diff --git a/Tools/md5_tstamp.py b/waflib/Tools/md5_tstamp.py index 6428e46..6428e46 100644 --- a/Tools/md5_tstamp.py +++ b/waflib/Tools/md5_tstamp.py diff --git a/Tools/msvc.py b/waflib/Tools/msvc.py index 17b347d..17b347d 100644 --- a/Tools/msvc.py +++ b/waflib/Tools/msvc.py diff --git a/Tools/nasm.py b/waflib/Tools/nasm.py index 411d582..411d582 100644 --- a/Tools/nasm.py +++ b/waflib/Tools/nasm.py diff --git a/Tools/nobuild.py b/waflib/Tools/nobuild.py index 2e4b055..2e4b055 100644 --- a/Tools/nobuild.py +++ b/waflib/Tools/nobuild.py diff --git a/Tools/perl.py b/waflib/Tools/perl.py index 32b03fb..32b03fb 100644 --- a/Tools/perl.py +++ b/waflib/Tools/perl.py diff --git a/Tools/python.py b/waflib/Tools/python.py index 25841d0..25841d0 100644 --- a/Tools/python.py +++ b/waflib/Tools/python.py diff --git a/Tools/qt5.py b/waflib/Tools/qt5.py index 4f9c690..4f9c690 100644 --- a/Tools/qt5.py +++ b/waflib/Tools/qt5.py diff --git a/Tools/ruby.py b/waflib/Tools/ruby.py index 8d92a79..8d92a79 100644 --- a/Tools/ruby.py +++ b/waflib/Tools/ruby.py diff --git a/Tools/suncc.py b/waflib/Tools/suncc.py index 33d34fc..33d34fc 100644 --- a/Tools/suncc.py +++ b/waflib/Tools/suncc.py diff --git a/Tools/suncxx.py b/waflib/Tools/suncxx.py index 3b384f6..3b384f6 100644 --- a/Tools/suncxx.py +++ b/waflib/Tools/suncxx.py diff --git a/Tools/tex.py b/waflib/Tools/tex.py index eaf9fdb..eaf9fdb 100644 --- a/Tools/tex.py +++ b/waflib/Tools/tex.py diff --git a/Tools/vala.py b/waflib/Tools/vala.py index 822ec50..822ec50 100644 --- a/Tools/vala.py +++ b/waflib/Tools/vala.py diff --git a/Tools/waf_unit_test.py b/waflib/Tools/waf_unit_test.py index a71ed1c..a71ed1c 100644 --- a/Tools/waf_unit_test.py +++ b/waflib/Tools/waf_unit_test.py diff --git a/Tools/winres.py b/waflib/Tools/winres.py index 586c596..586c596 100644 --- a/Tools/winres.py +++ b/waflib/Tools/winres.py diff --git a/Tools/xlc.py b/waflib/Tools/xlc.py index 134dd41..134dd41 100644 --- a/Tools/xlc.py +++ b/waflib/Tools/xlc.py diff --git a/Tools/xlcxx.py b/waflib/Tools/xlcxx.py index 76aa59b..76aa59b 100644 --- a/Tools/xlcxx.py +++ b/waflib/Tools/xlcxx.py diff --git a/Utils.py b/waflib/Utils.py index b4665c4..b4665c4 100644 --- a/Utils.py +++ b/waflib/Utils.py diff --git a/__init__.py b/waflib/__init__.py index 079df35..079df35 100644 --- a/__init__.py +++ b/waflib/__init__.py diff --git a/ansiterm.py b/waflib/ansiterm.py index 0d20c63..0d20c63 100644 --- a/ansiterm.py +++ b/waflib/ansiterm.py diff --git a/extras/__init__.py b/waflib/extras/__init__.py index c8a3c34..c8a3c34 100644 --- a/extras/__init__.py +++ b/waflib/extras/__init__.py diff --git a/extras/autowaf.py b/waflib/extras/autowaf.py index a4a06ba..a4a06ba 100644 --- a/extras/autowaf.py +++ b/waflib/extras/autowaf.py diff --git a/extras/batched_cc.py b/waflib/extras/batched_cc.py index aad2872..aad2872 100644 --- a/extras/batched_cc.py +++ b/waflib/extras/batched_cc.py diff --git a/extras/biber.py b/waflib/extras/biber.py index fd9db4e..fd9db4e 100644 --- a/extras/biber.py +++ b/waflib/extras/biber.py diff --git a/extras/bjam.py b/waflib/extras/bjam.py index 8e04d3a..8e04d3a 100644 --- a/extras/bjam.py +++ b/waflib/extras/bjam.py diff --git a/extras/blender.py b/waflib/extras/blender.py index e5efc28..e5efc28 100644 --- a/extras/blender.py +++ b/waflib/extras/blender.py diff --git a/extras/boo.py b/waflib/extras/boo.py index 06623d4..06623d4 100644 --- a/extras/boo.py +++ b/waflib/extras/boo.py diff --git a/extras/boost.py b/waflib/extras/boost.py index c2aaaa9..c2aaaa9 100644 --- a/extras/boost.py +++ b/waflib/extras/boost.py diff --git a/extras/build_file_tracker.py b/waflib/extras/build_file_tracker.py index c4f26fd..c4f26fd 100644 --- a/extras/build_file_tracker.py +++ b/waflib/extras/build_file_tracker.py diff --git a/extras/build_logs.py b/waflib/extras/build_logs.py index cdf8ed0..cdf8ed0 100644 --- a/extras/build_logs.py +++ b/waflib/extras/build_logs.py diff --git a/extras/buildcopy.py b/waflib/extras/buildcopy.py index a6d9ac8..a6d9ac8 100644 --- a/extras/buildcopy.py +++ b/waflib/extras/buildcopy.py diff --git a/extras/c_bgxlc.py b/waflib/extras/c_bgxlc.py index 6e3eaf7..6e3eaf7 100644 --- a/extras/c_bgxlc.py +++ b/waflib/extras/c_bgxlc.py diff --git a/extras/c_dumbpreproc.py b/waflib/extras/c_dumbpreproc.py index ce9e1a4..ce9e1a4 100644 --- a/extras/c_dumbpreproc.py +++ b/waflib/extras/c_dumbpreproc.py diff --git a/extras/c_emscripten.py b/waflib/extras/c_emscripten.py index e1ac494..e1ac494 100644 --- a/extras/c_emscripten.py +++ b/waflib/extras/c_emscripten.py diff --git a/extras/c_nec.py b/waflib/extras/c_nec.py index 96bfae4..96bfae4 100644 --- a/extras/c_nec.py +++ b/waflib/extras/c_nec.py diff --git a/extras/cabal.py b/waflib/extras/cabal.py index e10a0d1..e10a0d1 100644 --- a/extras/cabal.py +++ b/waflib/extras/cabal.py diff --git a/extras/cfg_altoptions.py b/waflib/extras/cfg_altoptions.py index 47b1189..47b1189 100644 --- a/extras/cfg_altoptions.py +++ b/waflib/extras/cfg_altoptions.py diff --git a/extras/clang_compilation_database.py b/waflib/extras/clang_compilation_database.py index 4d9b5e2..4d9b5e2 100644 --- a/extras/clang_compilation_database.py +++ b/waflib/extras/clang_compilation_database.py diff --git a/extras/codelite.py b/waflib/extras/codelite.py index 523302c..523302c 100644 --- a/extras/codelite.py +++ b/waflib/extras/codelite.py diff --git a/extras/color_gcc.py b/waflib/extras/color_gcc.py index b68c5eb..b68c5eb 100644 --- a/extras/color_gcc.py +++ b/waflib/extras/color_gcc.py diff --git a/extras/color_rvct.py b/waflib/extras/color_rvct.py index f89ccbd..f89ccbd 100644 --- a/extras/color_rvct.py +++ b/waflib/extras/color_rvct.py diff --git a/extras/compat15.py b/waflib/extras/compat15.py index 0e74df8..0e74df8 100644 --- a/extras/compat15.py +++ b/waflib/extras/compat15.py diff --git a/extras/cppcheck.py b/waflib/extras/cppcheck.py index 13ff424..13ff424 100644 --- a/extras/cppcheck.py +++ b/waflib/extras/cppcheck.py diff --git a/extras/cpplint.py b/waflib/extras/cpplint.py index e3302e5..e3302e5 100644 --- a/extras/cpplint.py +++ b/waflib/extras/cpplint.py diff --git a/extras/cross_gnu.py b/waflib/extras/cross_gnu.py index 309f53b..309f53b 100644 --- a/extras/cross_gnu.py +++ b/waflib/extras/cross_gnu.py diff --git a/extras/cython.py b/waflib/extras/cython.py index 481d6f4..481d6f4 100644 --- a/extras/cython.py +++ b/waflib/extras/cython.py diff --git a/extras/dcc.py b/waflib/extras/dcc.py index c1a57c0..c1a57c0 100644 --- a/extras/dcc.py +++ b/waflib/extras/dcc.py diff --git a/extras/distnet.py b/waflib/extras/distnet.py index 09a31a6..09a31a6 100644 --- a/extras/distnet.py +++ b/waflib/extras/distnet.py diff --git a/extras/doxygen.py b/waflib/extras/doxygen.py index 28f56e9..28f56e9 100644 --- a/extras/doxygen.py +++ b/waflib/extras/doxygen.py diff --git a/extras/dpapi.py b/waflib/extras/dpapi.py index b94d482..b94d482 100644 --- a/extras/dpapi.py +++ b/waflib/extras/dpapi.py diff --git a/extras/eclipse.py b/waflib/extras/eclipse.py index bb78741..bb78741 100644 --- a/extras/eclipse.py +++ b/waflib/extras/eclipse.py diff --git a/extras/erlang.py b/waflib/extras/erlang.py index 49f6d5b..49f6d5b 100644 --- a/extras/erlang.py +++ b/waflib/extras/erlang.py diff --git a/extras/fast_partial.py b/waflib/extras/fast_partial.py index b3af513..b3af513 100644 --- a/extras/fast_partial.py +++ b/waflib/extras/fast_partial.py diff --git a/extras/fc_bgxlf.py b/waflib/extras/fc_bgxlf.py index cca1810..cca1810 100644 --- a/extras/fc_bgxlf.py +++ b/waflib/extras/fc_bgxlf.py diff --git a/extras/fc_cray.py b/waflib/extras/fc_cray.py index da733fa..da733fa 100644 --- a/extras/fc_cray.py +++ b/waflib/extras/fc_cray.py diff --git a/extras/fc_nag.py b/waflib/extras/fc_nag.py index edcb218..edcb218 100644 --- a/extras/fc_nag.py +++ b/waflib/extras/fc_nag.py diff --git a/extras/fc_nec.py b/waflib/extras/fc_nec.py index 67c8680..67c8680 100644 --- a/extras/fc_nec.py +++ b/waflib/extras/fc_nec.py diff --git a/extras/fc_open64.py b/waflib/extras/fc_open64.py index 413719f..413719f 100644 --- a/extras/fc_open64.py +++ b/waflib/extras/fc_open64.py diff --git a/extras/fc_pgfortran.py b/waflib/extras/fc_pgfortran.py index afb2817..afb2817 100644 --- a/extras/fc_pgfortran.py +++ b/waflib/extras/fc_pgfortran.py diff --git a/extras/fc_solstudio.py b/waflib/extras/fc_solstudio.py index 53766df..53766df 100644 --- a/extras/fc_solstudio.py +++ b/waflib/extras/fc_solstudio.py diff --git a/extras/fc_xlf.py b/waflib/extras/fc_xlf.py index 5a3da03..5a3da03 100644 --- a/extras/fc_xlf.py +++ b/waflib/extras/fc_xlf.py diff --git a/extras/file_to_object.py b/waflib/extras/file_to_object.py index 1393b51..1393b51 100644 --- a/extras/file_to_object.py +++ b/waflib/extras/file_to_object.py diff --git a/extras/fluid.py b/waflib/extras/fluid.py index 4814a35..4814a35 100644 --- a/extras/fluid.py +++ b/waflib/extras/fluid.py diff --git a/extras/freeimage.py b/waflib/extras/freeimage.py index f27e525..f27e525 100644 --- a/extras/freeimage.py +++ b/waflib/extras/freeimage.py diff --git a/extras/fsb.py b/waflib/extras/fsb.py index 1b8f398..1b8f398 100644 --- a/extras/fsb.py +++ b/waflib/extras/fsb.py diff --git a/extras/fsc.py b/waflib/extras/fsc.py index c67e70b..c67e70b 100644 --- a/extras/fsc.py +++ b/waflib/extras/fsc.py diff --git a/extras/gccdeps.py b/waflib/extras/gccdeps.py index d9758ab..d9758ab 100644 --- a/extras/gccdeps.py +++ b/waflib/extras/gccdeps.py diff --git a/extras/gdbus.py b/waflib/extras/gdbus.py index 0e0476e..0e0476e 100644 --- a/extras/gdbus.py +++ b/waflib/extras/gdbus.py diff --git a/extras/gob2.py b/waflib/extras/gob2.py index b4fa3b9..b4fa3b9 100644 --- a/extras/gob2.py +++ b/waflib/extras/gob2.py diff --git a/extras/halide.py b/waflib/extras/halide.py index 6078e38..6078e38 100644 --- a/extras/halide.py +++ b/waflib/extras/halide.py diff --git a/extras/javatest.py b/waflib/extras/javatest.py index 979b8d8..979b8d8 100755 --- a/extras/javatest.py +++ b/waflib/extras/javatest.py diff --git a/extras/kde4.py b/waflib/extras/kde4.py index e49a9ec..e49a9ec 100644 --- a/extras/kde4.py +++ b/waflib/extras/kde4.py diff --git a/extras/local_rpath.py b/waflib/extras/local_rpath.py index b2507e1..b2507e1 100644 --- a/extras/local_rpath.py +++ b/waflib/extras/local_rpath.py diff --git a/extras/lv2.py b/waflib/extras/lv2.py index 815987f..815987f 100644 --- a/extras/lv2.py +++ b/waflib/extras/lv2.py diff --git a/extras/make.py b/waflib/extras/make.py index 933d9ca..933d9ca 100644 --- a/extras/make.py +++ b/waflib/extras/make.py diff --git a/extras/midl.py b/waflib/extras/midl.py index 43e6cf9..43e6cf9 100644 --- a/extras/midl.py +++ b/waflib/extras/midl.py diff --git a/extras/msvcdeps.py b/waflib/extras/msvcdeps.py index fc1ecd4..fc1ecd4 100644 --- a/extras/msvcdeps.py +++ b/waflib/extras/msvcdeps.py diff --git a/extras/msvs.py b/waflib/extras/msvs.py index 8aa2db0..8aa2db0 100644 --- a/extras/msvs.py +++ b/waflib/extras/msvs.py diff --git a/extras/netcache_client.py b/waflib/extras/netcache_client.py index dc49048..dc49048 100644 --- a/extras/netcache_client.py +++ b/waflib/extras/netcache_client.py diff --git a/extras/objcopy.py b/waflib/extras/objcopy.py index 82d8359..82d8359 100644 --- a/extras/objcopy.py +++ b/waflib/extras/objcopy.py diff --git a/extras/ocaml.py b/waflib/extras/ocaml.py index afe73c0..afe73c0 100644 --- a/extras/ocaml.py +++ b/waflib/extras/ocaml.py diff --git a/extras/package.py b/waflib/extras/package.py index c06498e..c06498e 100644 --- a/extras/package.py +++ b/waflib/extras/package.py diff --git a/extras/parallel_debug.py b/waflib/extras/parallel_debug.py index 35883a3..35883a3 100644 --- a/extras/parallel_debug.py +++ b/waflib/extras/parallel_debug.py diff --git a/extras/pch.py b/waflib/extras/pch.py index 103e752..103e752 100644 --- a/extras/pch.py +++ b/waflib/extras/pch.py diff --git a/extras/pep8.py b/waflib/extras/pep8.py index 676beed..676beed 100644 --- a/extras/pep8.py +++ b/waflib/extras/pep8.py diff --git a/extras/pgicc.py b/waflib/extras/pgicc.py index 9790b9c..9790b9c 100644 --- a/extras/pgicc.py +++ b/waflib/extras/pgicc.py diff --git a/extras/pgicxx.py b/waflib/extras/pgicxx.py index eae121c..eae121c 100644 --- a/extras/pgicxx.py +++ b/waflib/extras/pgicxx.py diff --git a/extras/proc.py b/waflib/extras/proc.py index 764abec..764abec 100644 --- a/extras/proc.py +++ b/waflib/extras/proc.py diff --git a/extras/protoc.py b/waflib/extras/protoc.py index f3cb4d8..f3cb4d8 100644 --- a/extras/protoc.py +++ b/waflib/extras/protoc.py diff --git a/extras/pyqt5.py b/waflib/extras/pyqt5.py index c21dfa7..c21dfa7 100644 --- a/extras/pyqt5.py +++ b/waflib/extras/pyqt5.py diff --git a/extras/pytest.py b/waflib/extras/pytest.py index 7dd5a1a..7dd5a1a 100644 --- a/extras/pytest.py +++ b/waflib/extras/pytest.py diff --git a/extras/qnxnto.py b/waflib/extras/qnxnto.py index 1158124..1158124 100644 --- a/extras/qnxnto.py +++ b/waflib/extras/qnxnto.py diff --git a/extras/qt4.py b/waflib/extras/qt4.py index 90cae7e..90cae7e 100644 --- a/extras/qt4.py +++ b/waflib/extras/qt4.py diff --git a/extras/relocation.py b/waflib/extras/relocation.py index 7e821f4..7e821f4 100644 --- a/extras/relocation.py +++ b/waflib/extras/relocation.py diff --git a/extras/remote.py b/waflib/extras/remote.py index 3b038f7..3b038f7 100644 --- a/extras/remote.py +++ b/waflib/extras/remote.py diff --git a/extras/resx.py b/waflib/extras/resx.py index caf4d31..caf4d31 100644 --- a/extras/resx.py +++ b/waflib/extras/resx.py diff --git a/extras/review.py b/waflib/extras/review.py index 561e062..561e062 100644 --- a/extras/review.py +++ b/waflib/extras/review.py diff --git a/extras/rst.py b/waflib/extras/rst.py index f3c3a5e..f3c3a5e 100644 --- a/extras/rst.py +++ b/waflib/extras/rst.py diff --git a/extras/run_do_script.py b/waflib/extras/run_do_script.py index f3c5812..f3c5812 100644 --- a/extras/run_do_script.py +++ b/waflib/extras/run_do_script.py diff --git a/extras/run_m_script.py b/waflib/extras/run_m_script.py index b5f27eb..b5f27eb 100644 --- a/extras/run_m_script.py +++ b/waflib/extras/run_m_script.py diff --git a/extras/run_py_script.py b/waflib/extras/run_py_script.py index 3670381..3670381 100644 --- a/extras/run_py_script.py +++ b/waflib/extras/run_py_script.py diff --git a/extras/run_r_script.py b/waflib/extras/run_r_script.py index b0d8f2b..b0d8f2b 100644 --- a/extras/run_r_script.py +++ b/waflib/extras/run_r_script.py diff --git a/extras/sas.py b/waflib/extras/sas.py index 754c614..754c614 100644 --- a/extras/sas.py +++ b/waflib/extras/sas.py diff --git a/extras/satellite_assembly.py b/waflib/extras/satellite_assembly.py index 005eb07..005eb07 100644 --- a/extras/satellite_assembly.py +++ b/waflib/extras/satellite_assembly.py diff --git a/extras/scala.py b/waflib/extras/scala.py index a9880f0..a9880f0 100644 --- a/extras/scala.py +++ b/waflib/extras/scala.py diff --git a/extras/slow_qt4.py b/waflib/extras/slow_qt4.py index ec7880b..ec7880b 100644 --- a/extras/slow_qt4.py +++ b/waflib/extras/slow_qt4.py diff --git a/extras/softlink_libs.py b/waflib/extras/softlink_libs.py index 50c777f..50c777f 100644 --- a/extras/softlink_libs.py +++ b/waflib/extras/softlink_libs.py diff --git a/extras/stale.py b/waflib/extras/stale.py index cac3f46..cac3f46 100644 --- a/extras/stale.py +++ b/waflib/extras/stale.py diff --git a/extras/stracedeps.py b/waflib/extras/stracedeps.py index 37d82cb..37d82cb 100644 --- a/extras/stracedeps.py +++ b/waflib/extras/stracedeps.py diff --git a/extras/swig.py b/waflib/extras/swig.py index fd3d6d2..fd3d6d2 100644 --- a/extras/swig.py +++ b/waflib/extras/swig.py diff --git a/extras/syms.py b/waflib/extras/syms.py index dfa0059..dfa0059 100644 --- a/extras/syms.py +++ b/waflib/extras/syms.py diff --git a/extras/ticgt.py b/waflib/extras/ticgt.py index f43a7ea..f43a7ea 100644 --- a/extras/ticgt.py +++ b/waflib/extras/ticgt.py diff --git a/extras/unity.py b/waflib/extras/unity.py index 78128ed..78128ed 100644 --- a/extras/unity.py +++ b/waflib/extras/unity.py diff --git a/extras/use_config.py b/waflib/extras/use_config.py index ef5129f..ef5129f 100644 --- a/extras/use_config.py +++ b/waflib/extras/use_config.py diff --git a/extras/valadoc.py b/waflib/extras/valadoc.py index c50f69e..c50f69e 100644 --- a/extras/valadoc.py +++ b/waflib/extras/valadoc.py diff --git a/extras/waf_xattr.py b/waflib/extras/waf_xattr.py index 351dd63..351dd63 100644 --- a/extras/waf_xattr.py +++ b/waflib/extras/waf_xattr.py diff --git a/extras/why.py b/waflib/extras/why.py index 1bb941f..1bb941f 100644 --- a/extras/why.py +++ b/waflib/extras/why.py diff --git a/extras/win32_opts.py b/waflib/extras/win32_opts.py index 9f7443c..9f7443c 100644 --- a/extras/win32_opts.py +++ b/waflib/extras/win32_opts.py diff --git a/extras/wix.py b/waflib/extras/wix.py index d87bfbb..d87bfbb 100644 --- a/extras/wix.py +++ b/waflib/extras/wix.py diff --git a/extras/xcode6.py b/waflib/extras/xcode6.py index 91bbff1..91bbff1 100644 --- a/extras/xcode6.py +++ b/waflib/extras/xcode6.py diff --git a/fixpy2.py b/waflib/fixpy2.py index 24176e0..24176e0 100644 --- a/fixpy2.py +++ b/waflib/fixpy2.py diff --git a/processor.py b/waflib/processor.py index 2eecf3b..2eecf3b 100755 --- a/processor.py +++ b/waflib/processor.py diff --git a/waflib/waf b/waflib/waf new file mode 100755 index 0000000..e22930a --- /dev/null +++ b/waflib/waf @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +# Minimal waf script for projects that include waflib directly + +from waflib import Context, Scripting + +import inspect +import os + +def main(): + script_path = os.path.abspath(inspect.getfile(inspect.getmodule(main))) + project_path = os.path.dirname(script_path) + Scripting.waf_entry_point(os.getcwd(), Context.WAFVERSION, project_path) + +if __name__ == '__main__': + main() @@ -0,0 +1,506 @@ +#!/usr/bin/env python +import os +import shutil +import subprocess +import sys +import waflib.Options as Options +import waflib.extras.autowaf as autowaf +import waflib.Build as Build +import waflib.Logs as Logs + +# Library and package version (UNIX style major, minor, micro) +# major increment <=> incompatible changes +# minor increment <=> compatible changes (additions) +# micro increment <=> no interface changes +LILV_VERSION = '0.24.5' +LILV_MAJOR_VERSION = '0' + +# Mandatory waf variables +APPNAME = 'lilv' # Package name for waf dist +VERSION = LILV_VERSION # Package version for waf dist +top = '.' # Source directory +out = 'build' # Build directory + +test_plugins = [ + 'bad_syntax', + 'failed_instantiation', + 'failed_lib_descriptor', + 'lib_descriptor', + 'missing_descriptor', + 'missing_name', + 'missing_plugin', + 'missing_port', + 'missing_port_name', + 'new_version', + 'old_version' +] + +def options(ctx): + ctx.load('compiler_c') + ctx.load('compiler_cxx') + ctx.load('python') + autowaf.set_options(ctx, test=True) + opt = ctx.get_option_group('Configuration options') + opt.add_option('--no-utils', action='store_true', dest='no_utils', + help='do not build command line utilities') + opt.add_option('--bindings', action='store_true', dest='bindings', + help='build python bindings') + opt.add_option('--dyn-manifest', action='store_true', dest='dyn_manifest', + help='build support for dynamic manifests') + opt.add_option('--no-bash-completion', action='store_true', + dest='no_bash_completion', + help='do not install bash completion script in CONFIGDIR') + opt.add_option('--static', action='store_true', dest='static', + help='build static library') + opt.add_option('--no-shared', action='store_true', dest='no_shared', + help='do not build shared library') + opt.add_option('--static-progs', action='store_true', dest='static_progs', + help='build programs as static binaries') + opt.add_option('--default-lv2-path', type='string', default='', + dest='default_lv2_path', + help='default LV2 path to use if LV2_PATH is unset') + +def configure(conf): + autowaf.display_header('Lilv Configuration') + conf.load('compiler_c', cache=True) + try: + conf.load('compiler_cxx', cache=True) + except: + pass + + if Options.options.bindings: + try: + conf.load('python', cache=True) + conf.check_python_headers() + autowaf.define(conf, 'LILV_PYTHON', 1); + except: + Logs.warn('Failed to configure Python (%s)\n' % sys.exc_info()[1]) + + conf.load('autowaf', cache=True) + autowaf.set_c_lang(conf, 'c99') + + conf.env.BASH_COMPLETION = not Options.options.no_bash_completion + conf.env.BUILD_UTILS = not Options.options.no_utils + conf.env.BUILD_SHARED = not Options.options.no_shared + conf.env.STATIC_PROGS = Options.options.static_progs + conf.env.BUILD_STATIC = (Options.options.static or + Options.options.static_progs) + + if not conf.env.BUILD_SHARED and not conf.env.BUILD_STATIC: + conf.fatal('Neither a shared nor a static build requested') + + autowaf.check_pkg(conf, 'lv2', uselib_store='LV2', + atleast_version='1.14.0', mandatory=True) + autowaf.check_pkg(conf, 'serd-0', uselib_store='SERD', + atleast_version='0.18.0', mandatory=True) + autowaf.check_pkg(conf, 'sord-0', uselib_store='SORD', + atleast_version='0.14.0', mandatory=True) + autowaf.check_pkg(conf, 'sratom-0', uselib_store='SRATOM', + atleast_version='0.4.0', mandatory=True) + autowaf.check_pkg(conf, 'sndfile', uselib_store='SNDFILE', + atleast_version='1.0.0', mandatory=False) + + defines = ['_POSIX_C_SOURCE=200809L', '_BSD_SOURCE', '_DEFAULT_SOURCE'] + if conf.env.DEST_OS == 'darwin': + defines += ['_DARWIN_C_SOURCE'] + + rt_lib = ['rt'] + if conf.env.DEST_OS == 'darwin' or conf.env.DEST_OS == 'win32': + rt_lib = [] + + autowaf.check_function(conf, 'c', 'lstat', + header_name = ['sys/stat.h'], + defines = defines, + define_name = 'HAVE_LSTAT', + mandatory = False) + + autowaf.check_function(conf, 'c', 'flock', + header_name = 'sys/file.h', + defines = defines, + define_name = 'HAVE_FLOCK', + mandatory = False) + + autowaf.check_function(conf, 'c', 'fileno', + header_name = 'stdio.h', + defines = defines, + define_name = 'HAVE_FILENO', + mandatory = False) + + autowaf.check_function(conf, 'c', 'clock_gettime', + header_name = ['sys/time.h','time.h'], + defines = ['_POSIX_C_SOURCE=200809L'], + define_name = 'HAVE_CLOCK_GETTIME', + uselib_store = 'CLOCK_GETTIME', + lib = rt_lib, + mandatory = False) + + conf.check_cc(define_name = 'HAVE_LIBDL', + lib = 'dl', + mandatory = False) + + autowaf.define(conf, 'LILV_VERSION', LILV_VERSION) + if Options.options.dyn_manifest: + autowaf.define(conf, 'LILV_DYN_MANIFEST', 1) + + lilv_path_sep = ':' + lilv_dir_sep = '/' + if conf.env.DEST_OS == 'win32': + lilv_path_sep = ';' + lilv_dir_sep = '\\\\' + + autowaf.define(conf, 'LILV_PATH_SEP', lilv_path_sep) + autowaf.define(conf, 'LILV_DIR_SEP', lilv_dir_sep) + + # Set default LV2 path + lv2_path = Options.options.default_lv2_path + if lv2_path == '': + if conf.env.DEST_OS == 'darwin': + lv2_path = lilv_path_sep.join(['~/Library/Audio/Plug-Ins/LV2', + '~/.lv2', + '/usr/local/lib/lv2', + '/usr/lib/lv2', + '/Library/Audio/Plug-Ins/LV2']) + elif conf.env.DEST_OS == 'haiku': + lv2_path = lilv_path_sep.join(['~/.lv2', + '/boot/common/add-ons/lv2']) + elif conf.env.DEST_OS == 'win32': + lv2_path = lilv_path_sep.join(['%APPDATA%\\\\LV2', + '%COMMONPROGRAMFILES%\\\\LV2']) + else: + libdirname = os.path.basename(conf.env.LIBDIR) + lv2_path = lilv_path_sep.join(['~/.lv2', + '/usr/%s/lv2' % libdirname, + '/usr/local/%s/lv2' % libdirname]) + autowaf.define(conf, 'LILV_DEFAULT_LV2_PATH', lv2_path) + + autowaf.set_lib_env(conf, 'lilv', LILV_VERSION) + conf.write_config_header('lilv_config.h', remove=False) + + autowaf.display_summary(conf) + autowaf.display_msg(conf, 'Default LV2_PATH', + conf.env.LILV_DEFAULT_LV2_PATH) + autowaf.display_msg(conf, 'Utilities', + bool(conf.env.BUILD_UTILS)) + autowaf.display_msg(conf, 'Unit tests', + bool(conf.env.BUILD_TESTS)) + autowaf.display_msg(conf, 'Dynamic manifest support', + bool(conf.env.LILV_DYN_MANIFEST)) + autowaf.display_msg(conf, 'Python bindings', + conf.is_defined('LILV_PYTHON')) + + conf.undefine('LILV_DEFAULT_LV2_PATH') # Cmd line errors with VC++ + print('') + +def build_util(bld, name, defines, libs=''): + obj = bld(features = 'c cprogram', + source = name + '.c', + includes = ['.', './src', './utils'], + use = 'liblilv', + target = name, + defines = defines, + install_path = '${BINDIR}') + autowaf.use_lib(bld, obj, 'SERD SORD SRATOM LV2 ' + libs) + if not bld.env.BUILD_SHARED or bld.env.STATIC_PROGS: + obj.use = 'liblilv_static' + if bld.env.STATIC_PROGS: + if not bld.env.MSVC_COMPILER: + obj.lib = ['m'] + obj.env.SHLIB_MARKER = obj.env.STLIB_MARKER + obj.linkflags = ['-static', '-Wl,--start-group'] + return obj + +def build(bld): + # C/C++ Headers + includedir = '${INCLUDEDIR}/lilv-%s/lilv' % LILV_MAJOR_VERSION + bld.install_files(includedir, bld.path.ant_glob('lilv/*.h')) + bld.install_files(includedir, bld.path.ant_glob('lilv/*.hpp')) + + lib_source = ''' + src/collections.c + src/instance.c + src/lib.c + src/node.c + src/plugin.c + src/pluginclass.c + src/port.c + src/query.c + src/scalepoint.c + src/state.c + src/ui.c + src/util.c + src/world.c + src/zix/tree.c + '''.split() + + lib = [] + libflags = ['-fvisibility=hidden'] + defines = [] + if bld.is_defined('HAVE_LIBDL'): + lib += ['dl'] + if bld.env.DEST_OS == 'win32': + lib = [] + if bld.env.MSVC_COMPILER: + libflags = [] + + # Pkgconfig file + autowaf.build_pc(bld, 'LILV', LILV_VERSION, LILV_MAJOR_VERSION, [], + {'LILV_MAJOR_VERSION' : LILV_MAJOR_VERSION, + 'LILV_PKG_DEPS' : 'lv2 serd-0 sord-0 sratom-0', + 'LILV_PKG_LIBS' : ' -l'.join([''] + lib)}) + + # Shared Library + if bld.env.BUILD_SHARED: + obj = bld(features = 'c cshlib', + export_includes = ['.'], + source = lib_source, + includes = ['.', './src'], + name = 'liblilv', + target = 'lilv-%s' % LILV_MAJOR_VERSION, + vnum = LILV_VERSION, + install_path = '${LIBDIR}', + defines = ['LILV_SHARED', 'LILV_INTERNAL'], + cflags = libflags, + lib = lib) + autowaf.use_lib(bld, obj, 'SERD SORD SRATOM LV2') + + # Static library + if bld.env.BUILD_STATIC: + obj = bld(features = 'c cstlib', + export_includes = ['.'], + source = lib_source, + includes = ['.', './src'], + name = 'liblilv_static', + target = 'lilv-%s' % LILV_MAJOR_VERSION, + vnum = LILV_VERSION, + install_path = '${LIBDIR}', + defines = defines + ['LILV_INTERNAL']) + autowaf.use_lib(bld, obj, 'SERD SORD SRATOM LV2') + + # Python bindings + if bld.is_defined('LILV_PYTHON'): + bld(features = 'subst', + is_copy = True, + source = 'bindings/python/lilv.py', + target = 'lilv.py', + install_path = '${PYTHONDIR}') + + if bld.env.BUILD_TESTS: + import re + + test_libs = lib + test_cflags = [''] + test_linkflags = [''] + if not bld.env.NO_COVERAGE: + test_cflags += ['--coverage'] + test_linkflags += ['--coverage'] + + # Make a pattern for shared objects without the 'lib' prefix + module_pattern = re.sub('^lib', '', bld.env.cshlib_PATTERN) + shlib_ext = module_pattern[module_pattern.rfind('.'):] + + for p in ['test'] + test_plugins: + obj = bld(features = 'c cshlib', + source = 'test/%s.lv2/%s.c' % (p, p), + name = p, + target = 'test/%s.lv2/%s' % (p, p), + install_path = None, + defines = defines, + cflags = test_cflags, + linkflags = test_linkflags, + lib = test_libs, + uselib = 'LV2') + obj.env.cshlib_PATTERN = module_pattern + + for p in test_plugins: + if not bld.path.find_node('test/%s.lv2/test_%s.c' % (p, p)): + continue + + obj = bld(features = 'c cprogram', + source = 'test/%s.lv2/test_%s.c' % (p, p), + target = 'test/test_%s' % p, + includes = ['.', './src'], + use = 'liblilv_profiled', + install_path = None, + defines = defines, + cflags = test_cflags, + linkflags = test_linkflags, + lib = test_libs, + uselib = 'LV2') + autowaf.use_lib(bld, obj, 'SERD SORD SRATOM LV2') + + # Test plugin data files + for p in ['test'] + test_plugins: + for i in [ 'manifest.ttl.in', p + '.ttl.in' ]: + bundle = 'test/%s.lv2/' % p + bld(features = 'subst', + source = bundle + i, + target = bundle + i.replace('.in', ''), + install_path = None, + SHLIB_EXT = shlib_ext) + + # Static profiled library (for unit test code coverage) + obj = bld(features = 'c cstlib', + source = lib_source, + includes = ['.', './src'], + name = 'liblilv_profiled', + target = 'lilv_profiled', + install_path = None, + defines = defines + ['LILV_INTERNAL'], + cflags = test_cflags, + linkflags = test_linkflags, + lib = test_libs) + autowaf.use_lib(bld, obj, 'SERD SORD SRATOM LV2') + + # Unit test program + testdir = os.path.abspath(autowaf.build_dir(APPNAME, 'test')) + bpath = os.path.join(testdir, 'test.lv2') + bpath = bpath.replace('\\', '/') + testdir = testdir.replace('\\', '/') + obj = bld(features = 'c cprogram', + source = 'test/lilv_test.c', + includes = ['.', './src'], + use = 'liblilv_profiled', + lib = test_libs, + target = 'test/lilv_test', + install_path = None, + defines = (defines + ['LILV_TEST_BUNDLE=\"%s/\"' % bpath] + + ['LILV_TEST_DIR=\"%s/\"' % testdir]), + cflags = test_cflags, + linkflags = test_linkflags) + autowaf.use_lib(bld, obj, 'SERD SORD SRATOM LV2') + + # C++ API test + if 'COMPILER_CXX' in bld.env: + obj = bld(features = 'cxx cxxprogram', + source = 'test/lilv_cxx_test.cpp', + includes = ['.', './src'], + use = 'liblilv_profiled', + lib = test_libs, + target = 'test/lilv_cxx_test', + install_path = None, + cxxflags = test_cflags, + linkflags = test_linkflags) + autowaf.use_lib(bld, obj, 'SERD SORD SRATOM LV2') + + if bld.is_defined('LILV_PYTHON'): + # Copy Python unittest files + for i in [ 'test_api.py' ]: + bld(features = 'subst', + is_copy = True, + source = 'bindings/test/python/' + i, + target = 'bindings/' + i, + install_path = None) + + # Build bindings test plugin + obj = bld(features = 'c cshlib', + source = 'bindings/test/bindings_test_plugin.c', + name = 'bindings_test_plugin', + target = 'bindings/bindings_test_plugin.lv2/bindings_test_plugin', + install_path = None, + defines = defines, + cflags = test_cflags, + linkflags = test_linkflags, + lib = test_libs, + uselib = 'LV2') + obj.env.cshlib_PATTERN = module_pattern + + # Bindings test plugin data files + for i in [ 'manifest.ttl.in', 'bindings_test_plugin.ttl.in' ]: + bld(features = 'subst', + source = 'bindings/test/' + i, + target = 'bindings/bindings_test_plugin.lv2/' + i.replace('.in', ''), + install_path = None, + SHLIB_EXT = shlib_ext) + + + # Utilities + if bld.env.BUILD_UTILS: + utils = ''' + utils/lilv-bench + utils/lv2info + utils/lv2ls + ''' + for i in utils.split(): + build_util(bld, i, defines) + + if bld.env.HAVE_SNDFILE: + obj = build_util(bld, 'utils/lv2apply', defines, 'SNDFILE') + + # lv2bench (less portable than other utilities) + if bld.is_defined('HAVE_CLOCK_GETTIME') and not bld.env.STATIC_PROGS: + obj = build_util(bld, 'utils/lv2bench', defines) + if not bld.env.MSVC_COMPILER and not bld.env.DEST_OS == 'darwin': + obj.lib = ['rt'] + + # Documentation + autowaf.build_dox(bld, 'LILV', LILV_VERSION, top, out) + + # Man pages + bld.install_files('${MANDIR}/man1', bld.path.ant_glob('doc/*.1')) + + # Bash completion + if bld.env.BASH_COMPLETION: + bld.install_as( + '${SYSCONFDIR}/bash_completion.d/lilv', 'utils/lilv.bash_completion') + + bld.add_post_fun(autowaf.run_ldconfig) + if bld.env.DOCS: + bld.add_post_fun(fix_docs) + +def fix_docs(ctx): + if ctx.cmd == 'build': + autowaf.make_simple_dox(APPNAME) + +def upload_docs(ctx): + import glob + os.system('rsync -ravz --delete -e ssh build/doc/html/ drobilla@drobilla.net:~/drobilla.net/docs/lilv/') + for page in glob.glob('doc/*.[1-8]'): + os.system('soelim %s | pre-grohtml troff -man -wall -Thtml | post-grohtml > build/%s.html' % (page, page)) + os.system('rsync -avz --delete -e ssh build/%s.html drobilla@drobilla.net:~/drobilla.net/man/' % page) + +def test(ctx): + assert ctx.env.BUILD_TESTS, "You have run waf configure without the --test flag. No tests were run." + autowaf.pre_test(ctx, APPNAME) + if ctx.is_defined('LILV_PYTHON'): + os.environ['LD_LIBRARY_PATH'] = os.getcwd() + autowaf.run_tests(ctx, APPNAME, ['python -m unittest discover bindings/']) + os.environ['PATH'] = 'test' + os.pathsep + os.getenv('PATH') + + Logs.pprint('GREEN', '') + autowaf.run_test(ctx, APPNAME, 'lilv_test', dirs=['./src','./test'], name='lilv_test') + + for p in test_plugins: + test_prog = 'test_' + p + ' ' + ('test/%s.lv2/' % p) + if os.path.exists('test/test_' + p): + autowaf.run_test(ctx, APPNAME, test_prog, 0, + dirs=['./src','./test','./test/%s.lv2' % p]) + + autowaf.post_test(ctx, APPNAME) + try: + shutil.rmtree('state') + except: + pass + +def lint(ctx): + "checks code for style issues" + import subprocess + cmd = ("clang-tidy -p=. -header-filter=.* -checks=\"*," + + "-clang-analyzer-alpha.*," + + "-google-readability-todo," + + "-llvm-header-guard," + + "-llvm-include-order," + + "-misc-unused-parameters," + + "-readability-else-after-return\" " + + "$(find .. -name '*.c')") + subprocess.call(cmd, cwd='build', shell=True) + +def posts(ctx): + path = str(ctx.path.abspath()) + autowaf.news_to_posts( + os.path.join(path, 'NEWS'), + {'title' : 'Lilv', + 'description' : autowaf.get_blurb(os.path.join(path, 'README')), + 'dist_pattern' : 'http://download.drobilla.net/lilv-%s.tar.bz2'}, + { 'Author' : 'drobilla', + 'Tags' : 'Hacking, LAD, LV2, Lilv' }, + os.path.join(out, 'posts')) |