diff options
-rw-r--r-- | AUTHORS | 4 | ||||
-rw-r--r-- | COPYING | 699 | ||||
-rw-r--r-- | INSTALL | 59 | ||||
-rw-r--r-- | README | 15 | ||||
-rw-r--r-- | THANKS | 3 | ||||
-rw-r--r-- | data/test.ttl | 32 | ||||
-rw-r--r-- | machina.desktop.in | 9 | ||||
-rw-r--r-- | src/client/ClientModel.cpp | 94 | ||||
-rw-r--r-- | src/client/ClientModel.hpp | 67 | ||||
-rw-r--r-- | src/client/ClientObject.cpp | 56 | ||||
-rw-r--r-- | src/client/ClientObject.hpp | 71 | ||||
-rw-r--r-- | src/client/wscript | 17 | ||||
-rw-r--r-- | src/engine/Action.hpp | 60 | ||||
-rw-r--r-- | src/engine/ActionFactory.cpp | 55 | ||||
-rw-r--r-- | src/engine/ActionFactory.hpp | 34 | ||||
-rw-r--r-- | src/engine/Controller.cpp | 228 | ||||
-rw-r--r-- | src/engine/Edge.cpp | 64 | ||||
-rw-r--r-- | src/engine/Edge.hpp | 60 | ||||
-rw-r--r-- | src/engine/Engine.cpp | 126 | ||||
-rw-r--r-- | src/engine/Evolver.cpp | 139 | ||||
-rw-r--r-- | src/engine/JackDriver.cpp | 479 | ||||
-rw-r--r-- | src/engine/JackDriver.hpp | 119 | ||||
-rw-r--r-- | src/engine/LearnRequest.cpp | 56 | ||||
-rw-r--r-- | src/engine/LearnRequest.hpp | 61 | ||||
-rw-r--r-- | src/engine/Loader.cpp | 193 | ||||
-rw-r--r-- | src/engine/MIDISink.hpp | 37 | ||||
-rw-r--r-- | src/engine/Machine.cpp | 389 | ||||
-rw-r--r-- | src/engine/MachineBuilder.cpp | 325 | ||||
-rw-r--r-- | src/engine/MachineBuilder.hpp | 87 | ||||
-rw-r--r-- | src/engine/MidiAction.cpp | 103 | ||||
-rw-r--r-- | src/engine/MidiAction.hpp | 57 | ||||
-rw-r--r-- | src/engine/Mutation.cpp | 190 | ||||
-rw-r--r-- | src/engine/Node.cpp | 271 | ||||
-rw-r--r-- | src/engine/Node.hpp | 116 | ||||
-rw-r--r-- | src/engine/Problem.cpp | 383 | ||||
-rw-r--r-- | src/engine/Problem.hpp | 135 | ||||
-rw-r--r-- | src/engine/Recorder.cpp | 69 | ||||
-rw-r--r-- | src/engine/Recorder.hpp | 68 | ||||
-rw-r--r-- | src/engine/SMFDriver.cpp | 162 | ||||
-rw-r--r-- | src/engine/SMFDriver.hpp | 67 | ||||
-rw-r--r-- | src/engine/SMFReader.cpp | 323 | ||||
-rw-r--r-- | src/engine/SMFReader.hpp | 87 | ||||
-rw-r--r-- | src/engine/SMFWriter.cpp | 241 | ||||
-rw-r--r-- | src/engine/SMFWriter.hpp | 73 | ||||
-rw-r--r-- | src/engine/Schrodinbit.hpp | 42 | ||||
-rw-r--r-- | src/engine/Slave.hpp | 73 | ||||
-rw-r--r-- | src/engine/Stateful.cpp | 39 | ||||
-rw-r--r-- | src/engine/Stateful.hpp | 70 | ||||
-rw-r--r-- | src/engine/URIs.cpp | 23 | ||||
-rw-r--r-- | src/engine/Updates.cpp | 66 | ||||
-rw-r--r-- | src/engine/machina/Atom.hpp | 217 | ||||
-rw-r--r-- | src/engine/machina/Context.hpp | 51 | ||||
-rw-r--r-- | src/engine/machina/Controller.hpp | 80 | ||||
-rw-r--r-- | src/engine/machina/Driver.hpp | 86 | ||||
-rw-r--r-- | src/engine/machina/Engine.hpp | 68 | ||||
-rw-r--r-- | src/engine/machina/Evolver.hpp | 72 | ||||
-rw-r--r-- | src/engine/machina/Loader.hpp | 49 | ||||
-rw-r--r-- | src/engine/machina/Machine.hpp | 132 | ||||
-rw-r--r-- | src/engine/machina/Model.hpp | 44 | ||||
-rw-r--r-- | src/engine/machina/Mutation.hpp | 60 | ||||
-rw-r--r-- | src/engine/machina/URIs.hpp | 93 | ||||
-rw-r--r-- | src/engine/machina/Updates.hpp | 46 | ||||
-rw-r--r-- | src/engine/machina/types.hpp | 69 | ||||
-rw-r--r-- | src/engine/quantize.hpp | 46 | ||||
-rw-r--r-- | src/engine/quantize_test.cpp | 41 | ||||
-rw-r--r-- | src/engine/smf_test.cpp | 84 | ||||
-rw-r--r-- | src/engine/wscript | 45 | ||||
-rw-r--r-- | src/gui/EdgeView.cpp | 142 | ||||
-rw-r--r-- | src/gui/EdgeView.hpp | 59 | ||||
-rw-r--r-- | src/gui/MachinaCanvas.cpp | 211 | ||||
-rw-r--r-- | src/gui/MachinaCanvas.hpp | 66 | ||||
-rw-r--r-- | src/gui/MachinaGUI.cpp | 750 | ||||
-rw-r--r-- | src/gui/MachinaGUI.hpp | 190 | ||||
-rw-r--r-- | src/gui/NodePropertiesWindow.cpp | 136 | ||||
-rw-r--r-- | src/gui/NodePropertiesWindow.hpp | 67 | ||||
-rw-r--r-- | src/gui/NodeView.cpp | 203 | ||||
-rw-r--r-- | src/gui/NodeView.hpp | 76 | ||||
-rw-r--r-- | src/gui/WidgetFactory.hpp | 65 | ||||
-rw-r--r-- | src/gui/machina.gladep | 9 | ||||
-rw-r--r-- | src/gui/machina.svg | 66 | ||||
-rw-r--r-- | src/gui/machina.ui | 1128 | ||||
-rw-r--r-- | src/gui/main.cpp | 100 | ||||
-rw-r--r-- | src/gui/wscript | 42 | ||||
-rw-r--r-- | src/machina.cpp | 86 | ||||
-rw-r--r-- | src/midi2machina.cpp | 81 | ||||
-rwxr-xr-x | util/machina2dot | 184 | ||||
-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 | 77 |
269 files changed, 11333 insertions, 25 deletions
@@ -0,0 +1,4 @@ +Author: + +David Robillard <d@drobilla.net> + @@ -1,25 +1,674 @@ -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. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. @@ -0,0 +1,59 @@ +Installation Instructions +========================= + +Basic Installation +------------------ + +Building this software requires only Python. To install with default options: + + ./waf configure + ./waf + ./waf install + +You may need to become root for the install stage, for example: + + 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 + +Installation Directories +------------------------ + +The --prefix option (or the PREFIX environment variable) can be used to change +the prefix which all files are installed under. There are also several options +allowing for more fine-tuned control, see the --help output for details. + +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 @@ -0,0 +1,15 @@ +Machina +======= + +Machina is a MIDI sequencer based on Finite State Automata. + +A machine can be constructed manually from the user interface, recorded from +MIDI input (free-form or step), or loaded from a MIDI file. The probability of +arcs can be manipulated to build a machine that produces structured but +constantly changing output. This way, Machina can be used as a generative +recording tool that plays back patterns similar to, but not identical to, the +original input. + +For more information, see <http://drobilla.net/software/machina>. + + -- David Robillard <d@drobilla.net> @@ -0,0 +1,3 @@ +Professor Albert Chan +Professor Frank Dehne +Professor Franz Oppacher diff --git a/data/test.ttl b/data/test.ttl new file mode 100644 index 0000000..35e30e6 --- /dev/null +++ b/data/test.ttl @@ -0,0 +1,32 @@ +@prefix : <http://drobilla.net/ns/machina#> . + +<> + a :Machine; + + :initialNode <#n1> ; + :node <#n2> ; + :node <#n3> ; + + :edge [ + :tail <#n1> ; + :head <#n2> ; + ] , [ + :tail <#n1> ; + :head <#n3> ; + ] . + + +<#n1> + a :Node ; + :midiNote 60 ; + :duration 20000 . + +<#n2> + a :Node ; + :midiNote 72 ; + :duration 10000 . + +<#n3> + a :Node ; + :midiNote 79 ; + :duration 10000 . diff --git a/machina.desktop.in b/machina.desktop.in new file mode 100644 index 0000000..b1598e7 --- /dev/null +++ b/machina.desktop.in @@ -0,0 +1,9 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Machina +Comment=MIDI sequencer based on probabilistic finite-state automata. +Exec=machina_gui +Terminal=false +Icon=machina.svg +Type=Application +Categories=Application;AudioVideo;Sound;Audio diff --git a/src/client/ClientModel.cpp b/src/client/ClientModel.cpp new file mode 100644 index 0000000..3a8770f --- /dev/null +++ b/src/client/ClientModel.cpp @@ -0,0 +1,94 @@ +/* + This file is part of Machina. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "ClientModel.hpp" + +namespace machina { +namespace client { + +SPtr<ClientObject> +ClientModel::find(uint64_t id) +{ + SPtr<ClientObject> key(new ClientObjectKey(id)); + Objects::iterator i = _objects.find(key); + if (i != _objects.end()) { + return *i; + } else { + return SPtr<ClientObject>(); + } +} + +SPtr<const ClientObject> +ClientModel::find(uint64_t id) const +{ + SPtr<ClientObject> key(new ClientObjectKey(id)); + Objects::const_iterator i = _objects.find(key); + if (i != _objects.end()) { + return *i; + } else { + return SPtr<ClientObject>(); + } +} + +void +ClientModel::new_object(uint64_t id, const Properties& properties) +{ + SPtr<ClientObject> key(new ClientObjectKey(id)); + Objects::iterator i = _objects.find(key); + if (i == _objects.end()) { + SPtr<ClientObject> object(new ClientObject(id, properties)); + _objects.insert(object); + signal_new_object.emit(object); + } else { + for (const auto& p : properties) { + (*i)->set(p.first, p.second); + } + } +} + +void +ClientModel::erase_object(uint64_t id) +{ + SPtr<ClientObject> key(new ClientObjectKey(id)); + Objects::iterator i = _objects.find(key); + if (i == _objects.end()) { + return; + } + + signal_erase_object.emit(*i); + (*i)->set_view(NULL); + _objects.erase(i); +} + +void +ClientModel::set(uint64_t id, URIInt key, const Atom& value) +{ + SPtr<ClientObject> object = find(id); + if (object) { + object->set(key, value); + } +} + +const Atom& +ClientModel::get(uint64_t id, URIInt key) const +{ + static const Atom null_atom; + SPtr<const ClientObject> object = find(id); + return object ? object->get(key) : null_atom; +} + +} +} diff --git a/src/client/ClientModel.hpp b/src/client/ClientModel.hpp new file mode 100644 index 0000000..e65fb93 --- /dev/null +++ b/src/client/ClientModel.hpp @@ -0,0 +1,67 @@ +/* + This file is part of Machina. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_CLIENTMODEL_HPP +#define MACHINA_CLIENTMODEL_HPP + +#include <set> + +#include <sigc++/sigc++.h> + +#include "machina/Model.hpp" + +#include "ClientObject.hpp" + +namespace Raul { +class Atom; +} + +namespace machina { +namespace client { + +class ClientModel : public Model +{ +public: + void new_object(uint64_t id, const Properties& properties); + + void erase_object(uint64_t id); + + SPtr<ClientObject> find(uint64_t id); + SPtr<const ClientObject> find(uint64_t id) const; + + void set(uint64_t id, URIInt key, const Atom& value); + const Atom& get(uint64_t id, URIInt key) const; + + sigc::signal< void, SPtr<ClientObject> > signal_new_object; + sigc::signal< void, SPtr<ClientObject> > signal_erase_object; + +private: + struct ClientObjectComparator { + inline bool operator()(SPtr<ClientObject> lhs, + SPtr<ClientObject> rhs) const { + return lhs->id() < rhs->id(); + } + + }; + + typedef std::set<SPtr<ClientObject>, ClientObjectComparator> Objects; + Objects _objects; +}; + +} +} + +#endif // MACHINA_CLIENTMODEL_HPP diff --git a/src/client/ClientObject.cpp b/src/client/ClientObject.cpp new file mode 100644 index 0000000..20b50a2 --- /dev/null +++ b/src/client/ClientObject.cpp @@ -0,0 +1,56 @@ +/* + This file is part of Machina. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <iostream> + +#include "ClientObject.hpp" + +namespace machina { +namespace client { + +ClientObject::ClientObject(uint64_t id, const Properties& properties) + : _id(id) + , _view(NULL) + , _properties(properties) +{} + +ClientObject::ClientObject(const ClientObject& copy, uint64_t id) + : _id(id) + , _view(NULL) + , _properties(copy._properties) +{} + +void +ClientObject::set(URIInt key, const Atom& value) +{ + _properties[key] = value; + signal_property.emit(key, value); +} + +const Atom& +ClientObject::get(URIInt key) const +{ + static const Atom null_atom; + Properties::const_iterator i = _properties.find(key); + if (i != _properties.end()) { + return i->second; + } else { + return null_atom; + } +} + +} +} diff --git a/src/client/ClientObject.hpp b/src/client/ClientObject.hpp new file mode 100644 index 0000000..6520b11 --- /dev/null +++ b/src/client/ClientObject.hpp @@ -0,0 +1,71 @@ +/* + This file is part of Machina. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_CLIENTOBJECT_HPP +#define MACHINA_CLIENTOBJECT_HPP + +#include <map> + +#include <sigc++/sigc++.h> + +#include "machina/Atom.hpp" +#include "machina/types.hpp" + +namespace machina { +namespace client { + +class ClientObject +{ +public: + explicit ClientObject(uint64_t id, const Properties& properties={}); + ClientObject(const ClientObject& copy, uint64_t id); + + inline uint64_t id() const { return _id; } + + void set(URIInt key, const Atom& value); + const Atom& get(URIInt key) const; + + sigc::signal<void, URIInt, Atom> signal_property; + + class View + { + public: + virtual ~View() {} + }; + + typedef std::map<URIInt, Atom> Properties; + const Properties& properties() { return _properties; } + + View* view() const { return _view; } + void set_view(View* view) { _view = view; } + +private: + uint64_t _id; + View* _view; + + Properties _properties; +}; + +/** Stub client object to use as a search key. */ +struct ClientObjectKey : public ClientObject +{ + explicit ClientObjectKey(uint64_t id) : ClientObject(id) {} +}; + +} +} + +#endif // MACHINA_CLIENTOBJECT_HPP diff --git a/src/client/wscript b/src/client/wscript new file mode 100644 index 0000000..8333b43 --- /dev/null +++ b/src/client/wscript @@ -0,0 +1,17 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf + +def build(bld): + obj = bld(features = 'cxx cxxshlib') + obj.source = ''' + ClientModel.cpp + ClientObject.cpp + ''' + obj.export_includes = ['.'] + obj.includes = ['.', '..', '../..'] + obj.name = 'libmachina_client' + obj.target = 'machina_client' + obj.use = 'libmachina_engine' + autowaf.use_lib(bld, obj, 'GLIBMM GTKMM RAUL LV2') + + bld.add_post_fun(autowaf.run_ldconfig) diff --git a/src/engine/Action.hpp b/src/engine/Action.hpp new file mode 100644 index 0000000..2916099 --- /dev/null +++ b/src/engine/Action.hpp @@ -0,0 +1,60 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_ACTION_HPP +#define MACHINA_ACTION_HPP + +#include <string> +#include <iostream> + +#include "raul/Maid.hpp" +#include "raul/TimeSlice.hpp" + +#include "machina/types.hpp" + +#include "MIDISink.hpp" +#include "Stateful.hpp" + +namespace machina { + +/** An Action, executed on entering or exiting of a state. + */ +struct Action + : public Raul::Maid::Disposable + , public Stateful +{ + bool operator==(const Action& rhs) const { return false; } + + virtual void execute(MIDISink* sink, Raul::TimeStamp time) = 0; + + virtual void write_state(Sord::Model& model) {} +}; + +class PrintAction : public Action +{ +public: + explicit PrintAction(const std::string& msg) : _msg(msg) {} + + void execute(MIDISink* sink, Raul::TimeStamp time) + { std::cout << "t=" << time << ": " << _msg << std::endl; } + +private: + std::string _msg; +}; + +} // namespace machina + +#endif // MACHINA_ACTION_HPP diff --git a/src/engine/ActionFactory.cpp b/src/engine/ActionFactory.cpp new file mode 100644 index 0000000..f031149 --- /dev/null +++ b/src/engine/ActionFactory.cpp @@ -0,0 +1,55 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "ActionFactory.hpp" +#include "MidiAction.hpp" + +namespace machina { + +SPtr<Action> +ActionFactory::copy(SPtr<Action> copy) +{ + SPtr<MidiAction> ma = dynamic_ptr_cast<MidiAction>(copy); + if (ma) { + return SPtr<Action>(new MidiAction(ma->event_size(), ma->event())); + } else { + return SPtr<Action>(); + } +} + +SPtr<Action> +ActionFactory::note_on(uint8_t note, uint8_t velocity) +{ + uint8_t buf[3]; + buf[0] = 0x90; + buf[1] = note; + buf[2] = velocity; + + return SPtr<Action>(new MidiAction(3, buf)); +} + +SPtr<Action> +ActionFactory::note_off(uint8_t note, uint8_t velocity) +{ + uint8_t buf[3]; + buf[0] = 0x80; + buf[1] = note; + buf[2] = velocity; + + return SPtr<Action>(new MidiAction(3, buf)); +} + +} // namespace Machine diff --git a/src/engine/ActionFactory.hpp b/src/engine/ActionFactory.hpp new file mode 100644 index 0000000..db7aa68 --- /dev/null +++ b/src/engine/ActionFactory.hpp @@ -0,0 +1,34 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_ACTIONFACTORY_HPP +#define MACHINA_ACTIONFACTORY_HPP + +#include "machina/types.hpp" + +namespace machina { + +struct Action; + +namespace ActionFactory { +SPtr<Action> copy(SPtr<Action> copy); +SPtr<Action> note_on(uint8_t note, uint8_t velocity=64); +SPtr<Action> note_off(uint8_t note, uint8_t velocity=64); +} + +} // namespace machina + +#endif // MACHINA_ACTIONFACTORY_HPP diff --git a/src/engine/Controller.cpp b/src/engine/Controller.cpp new file mode 100644 index 0000000..8f9e1a2 --- /dev/null +++ b/src/engine/Controller.cpp @@ -0,0 +1,228 @@ +/* + This file is part of Machina. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "machina/Controller.hpp" +#include "machina/Engine.hpp" +#include "machina/Machine.hpp" +#include "machina/Updates.hpp" + +#include "Edge.hpp" +#include "MidiAction.hpp" + +namespace machina { + +Controller::Controller(SPtr<Engine> engine, + Model& model) + : _engine(engine) + , _model(model) + , _updates(new Raul::RingBuffer(4096)) +{ + _engine->driver()->set_update_sink(_updates); +} + +uint64_t +Controller::create(const Properties& properties) +{ + std::map<URIInt, Atom>::const_iterator d = properties.find( + URIs::instance().machina_duration); + + double duration = 0.0; + if (d != properties.end()) { + duration = d->second.get<float>(); + } else { + std::cerr << "warning: new object has no duration" << std::endl; + } + + TimeDuration dur(_engine->machine()->time().unit(), duration); + SPtr<Node> node(new Node(dur)); + _objects.insert(node); + _model.new_object(node->id(), properties); + _engine->machine()->add_node(node); + return node->id(); +} + +void +Controller::announce(SPtr<Machine> machine) +{ + if (!machine) { + return; + } + + Forge& forge = _engine->forge(); + + // Announce nodes + for (const auto& n : machine->nodes()) { + Properties properties { + { URIs::instance().rdf_type, + forge.make_urid(URIs::instance().machina_Node) }, + { URIs::instance().machina_duration, + forge.make(float(n->duration().to_double())) } }; + if (n->is_initial()) { + properties.insert({ URIs::instance().machina_initial, + forge.make(true) }); + } + + SPtr<MidiAction> midi_action = dynamic_ptr_cast<MidiAction>( + n->enter_action()); + if (midi_action) { + Properties action_properties { + { URIs::instance().machina_note_number, + forge.make((int32_t)midi_action->event()[1]) } }; + + _model.new_object(midi_action->id(), action_properties); + properties.insert({ URIs::instance().machina_enter_action, + forge.make(int32_t(n->enter_action()->id())) }); + } + + _objects.insert(n); + _model.new_object(n->id(), properties); + } + + // Announce edges + for (const auto& n : machine->nodes()) { + for (const auto& e : n->edges()) { + const Properties properties { + { URIs::instance().rdf_type, + forge.make_urid(URIs::instance().machina_Edge) }, + { URIs::instance().machina_probability, + forge.make(e->probability()) }, + { URIs::instance().machina_tail_id, + forge.make((int32_t)n->id()) }, + { URIs::instance().machina_head_id, + forge.make((int32_t)e->head()->id()) } }; + + _objects.insert(e); + _model.new_object(e->id(), properties); + } + } +} + +SPtr<Stateful> +Controller::find(uint64_t id) +{ + SPtr<StatefulKey> key(new StatefulKey(id)); + Objects::iterator i = _objects.find(key); + if (i != _objects.end()) { + return *i; + } + return SPtr<Stateful>(); +} + +void +Controller::learn(SPtr<Raul::Maid> maid, uint64_t node_id) +{ + SPtr<Node> node = dynamic_ptr_cast<Node>(find(node_id)); + if (node) { + _engine->machine()->learn(maid, node); + } else { + std::cerr << "Failed to find node " << node_id << " for learn" + << std::endl; + } +} + +void +Controller::set_property(uint64_t object_id, + URIInt key, + const Atom& value) +{ + SPtr<Stateful> object = find(object_id); + if (object) { + object->set(key, value); + _model.set(object_id, key, value); + } +} + +uint64_t +Controller::connect(uint64_t tail_id, uint64_t head_id) +{ + SPtr<Node> tail = dynamic_ptr_cast<Node>(find(tail_id)); + SPtr<Node> head = dynamic_ptr_cast<Node>(find(head_id)); + + if (!tail) { + std::cerr << "error: tail node " << tail_id << " not found" << std::endl; + return 0; + } else if (!head) { + std::cerr << "error: head node " << head_id << " not found" << std::endl; + return 0; + } + + SPtr<Edge> edge(new Edge(tail, head)); + tail->add_edge(edge); + _objects.insert(edge); + + Forge& forge = _engine->forge(); + + const Properties properties = { + { URIs::instance().rdf_type, + forge.make_urid(URIs::instance().machina_Edge) }, + { URIs::instance().machina_probability, forge.make(1.0f) }, + { URIs::instance().machina_tail_id, + forge.make((int32_t)tail->id()) }, + { URIs::instance().machina_head_id, + forge.make((int32_t)head->id()) } }; + + _model.new_object(edge->id(), properties); + + return edge->id(); +} + +void +Controller::disconnect(uint64_t tail_id, uint64_t head_id) +{ + SPtr<Node> tail = dynamic_ptr_cast<Node>(find(tail_id)); + SPtr<Node> head = dynamic_ptr_cast<Node>(find(head_id)); + + SPtr<Edge> edge = tail->remove_edge_to(head); + if (edge) { + _model.erase_object(edge->id()); + } else { + std::cerr << "Edge not found" << std::endl; + } +} + +void +Controller::erase(uint64_t id) +{ + SPtr<StatefulKey> key(new StatefulKey(id)); + Objects::iterator i = _objects.find(key); + if (i == _objects.end()) { + return; + } + + SPtr<Node> node = dynamic_ptr_cast<Node>(*i); + if (node) { + _engine->machine()->remove_node(node); + } + + _model.erase_object((*i)->id()); + _objects.erase(i); +} + +void +Controller::process_updates() +{ + const uint32_t read_space = _updates->read_space(); + + uint64_t subject; + URIInt key; + Atom value; + for (uint32_t i = 0; i < read_space; ) { + i += read_set(_updates, &subject, &key, &value); + _model.set(subject, key, value); + } +} + +} diff --git a/src/engine/Edge.cpp b/src/engine/Edge.cpp new file mode 100644 index 0000000..7c206f9 --- /dev/null +++ b/src/engine/Edge.cpp @@ -0,0 +1,64 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "machina/Atom.hpp" +#include "machina/URIs.hpp" +#include "sord/sordmm.hpp" + +#include "Edge.hpp" +#include "Node.hpp" + +namespace machina { + +void +Edge::set(URIInt key, const Atom& value) +{ + if (key == URIs::instance().machina_probability) { + _probability = value.get<float>(); + } +} + +void +Edge::write_state(Sord::Model& model) +{ + using namespace Raul; + + const Sord::Node& rdf_id = this->rdf_id(model.world()); + + SPtr<Node> tail = _tail.lock(); + SPtr<Node> head = _head; + + if (!tail || !head) + return; + + assert(tail->rdf_id(model.world()).is_valid() + && head->rdf_id(model.world()).is_valid()); + + model.add_statement(rdf_id, + Sord::URI(model.world(), MACHINA_NS_tail), + tail->rdf_id(model.world())); + + model.add_statement(rdf_id, + Sord::URI(model.world(), MACHINA_NS_head), + head->rdf_id(model.world())); + + model.add_statement( + rdf_id, + Sord::URI(model.world(), MACHINA_NS_probability), + Sord::Literal::decimal(model.world(), _probability, 7)); +} + +} // namespace machina diff --git a/src/engine/Edge.hpp b/src/engine/Edge.hpp new file mode 100644 index 0000000..a04dd73 --- /dev/null +++ b/src/engine/Edge.hpp @@ -0,0 +1,60 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_EDGE_HPP +#define MACHINA_EDGE_HPP + +#include <list> + +#include "machina/types.hpp" + +#include "Action.hpp" +#include "Stateful.hpp" + +namespace machina { + +class Node; + +class Edge : public Stateful +{ +public: + Edge(WPtr<Node> tail, SPtr<Node> head, float probability=1.0f) + : _tail(tail) + , _head(head) + , _probability(probability) + {} + + void set(URIInt key, const Atom& value); + void write_state(Sord::Model& model); + + WPtr<Node> tail() { return _tail; } + SPtr<Node> head() { return _head; } + + void set_tail(WPtr<Node> tail) { _tail = tail; } + void set_head(SPtr<Node> head) { _head = head; } + + inline float probability() const { return _probability; } + inline void set_probability(float p) { _probability = p; } + +private: + WPtr<Node> _tail; + SPtr<Node> _head; + float _probability; +}; + +} // namespace machina + +#endif // MACHINA_EDGE_HPP diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp new file mode 100644 index 0000000..1aed7b6 --- /dev/null +++ b/src/engine/Engine.cpp @@ -0,0 +1,126 @@ +/* + This file is part of Machina. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "machina_config.h" +#include "machina/Engine.hpp" +#include "machina/Loader.hpp" +#include "machina/Machine.hpp" +#include "SMFDriver.hpp" +#ifdef HAVE_JACK +#include "JackDriver.hpp" +#endif + +namespace machina { + +Engine::Engine(Forge& forge, + SPtr<Driver> driver, + Sord::World& rdf_world) + : _driver(driver) + , _rdf_world(rdf_world) + , _loader(_forge, _rdf_world) + , _forge(forge) +{} + +SPtr<Driver> +Engine::new_driver(Forge& forge, + const std::string& name, + SPtr<Machine> machine) +{ + if (name == "jack") { +#ifdef HAVE_JACK + JackDriver* driver = new JackDriver(forge, machine); + driver->attach("machina"); + return SPtr<Driver>(driver); +#else + return SPtr<Driver>(); +#endif + } + if (name == "smf") { + return SPtr<Driver>(new SMFDriver(forge, machine->time().unit())); + } + + std::cerr << "Error: Unknown driver type `" << name << "'" << std::endl; + return SPtr<Driver>(); +} + +/** Load the machine at `uri`, and run it (replacing current machine). + * Safe to call while engine is processing. + */ +SPtr<Machine> +Engine::load_machine(const std::string& uri) +{ + SPtr<Machine> machine = _loader.load(uri); + SPtr<Machine> old_machine = _driver->machine(); // Keep reference + if (machine) { + _driver->set_machine(machine); // Switch driver to new machine and wait + } + + // Drop (possibly last) reference to old_machine in this thread + return machine; +} + +/** Build a machine from the MIDI at `uri`, and run it (replacing current machine). + * Safe to call while engine is processing. + */ +SPtr<Machine> +Engine::load_machine_midi(const std::string& uri, + double q, + Raul::TimeDuration dur) +{ + SPtr<Machine> machine = _loader.load_midi(uri, q, dur); + SPtr<Machine> old_machine = _driver->machine(); // Keep reference + if (machine) { + _driver->set_machine(machine); // Switch driver to new machine and wait + } + + // Drop (possibly last) reference to old_machine in this thread + return machine; +} + +void +Engine::export_midi(const std::string& filename, Raul::TimeDuration dur) +{ + SPtr<Machine> machine = _driver->machine(); + SPtr<machina::SMFDriver> file_driver( + new machina::SMFDriver(_forge, dur.unit())); + + const bool activated = _driver->is_activated(); + if (activated) { + _driver->deactivate(); // FIXME: disable instead + } + file_driver->writer()->start(filename, TimeStamp(dur.unit(), 0.0)); + file_driver->run(machine, dur); + machine->reset(NULL, machine->time()); + file_driver->writer()->finish(); + + if (activated) { + _driver->activate(); + } +} + +void +Engine::set_bpm(double bpm) +{ + _driver->set_bpm(bpm); +} + +void +Engine::set_quantization(double q) +{ + _driver->set_quantization(q); +} + +} // namespace machina diff --git a/src/engine/Evolver.cpp b/src/engine/Evolver.cpp new file mode 100644 index 0000000..973c9c8 --- /dev/null +++ b/src/engine/Evolver.cpp @@ -0,0 +1,139 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <iostream> + +#include "eugene/HybridMutation.hpp" +#include "eugene/Mutation.hpp" +#include "eugene/TournamentSelection.hpp" + +#include "machina/Evolver.hpp" +#include "machina/Mutation.hpp" + +#include "Problem.hpp" + +using namespace std; +using namespace eugene; + +namespace machina { + +Evolver::Evolver(TimeUnit unit, + const string& target_midi, + SPtr<Machine> seed) + : _rng(0) + , _problem(new Problem(unit, target_midi, seed)) + , _seed_fitness(-FLT_MAX) + , _exit_flag(false) +{ + SPtr<eugene::HybridMutation<Machine> > m(new HybridMutation<Machine>()); + + m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + new Mutation::Compress())); + m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + new Mutation::AddNode())); + //m->append_mutation(1/6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + // new Mutation::RemoveNode())); + //m->append_mutation(1/6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + // new Mutation::AdjustNode())); + m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + new Mutation::SwapNodes())); + m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + new Mutation::AddEdge())); + m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + new Mutation::RemoveEdge())); + m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + new Mutation::AdjustEdge())); + + std::shared_ptr< Selection<Machine> > s( + new TournamentSelection<Machine>(*_problem.get(), 3, 0.8)); + std::shared_ptr< Crossover<Machine> > crossover; + size_t gene_length = 20; // FIXME + Problem::Population pop; + _ga = SPtr<MachinaGA>( + new MachinaGA(_rng, + _problem, + s, + crossover, + m, + pop, + gene_length, + 20, + 2, + 1.0, + 0.0)); +} + +void +Evolver::start() +{ + if (!_thread) { + _thread = std::unique_ptr<std::thread>( + new std::thread(&Evolver::run, this)); + } +} + +void +Evolver::join() +{ + if (_thread && _thread->joinable()) { + _exit_flag = true; + _thread->join(); + } +} + +void +Evolver::seed(SPtr<Machine> parent) +{ + /*_best = SPtr<Machine>(new Machine(*parent.get())); + _best_fitness = _problem->fitness(*_best.get());*/ + _problem->seed(parent); + _seed_fitness = _problem->evaluate(*parent.get()); +} + +void +Evolver::run() +{ + float old_best = _ga->best_fitness(); + + //cout << "ORIGINAL BEST: " << _ga->best_fitness() << endl; + + _improvement = true; + + while (!_exit_flag) { + //cout << "{" << endl; + _problem->clear_fitness_cache(); + _ga->iteration(); + + float new_best = _ga->best_fitness(); + + /*cout << _problem->fitness_less(old_best, *_ga->best().get()) << endl; + cout << "best: " << _ga->best().get() << endl; + cout << "best fitness: " << _problem->fitness(*_ga->best().get()) << endl; + cout << "old best: " << old_best << endl; + cout << "new best: " << new_best << endl;*/ + cout << "generation best: " << new_best << endl; + + if (_problem->fitness_less_than(old_best, new_best)) { + _improvement = true; + old_best = new_best; + cout << "*** NEW BEST: " << new_best << endl; + } + + //cout << "}" << endl; + } +} + +} // namespace machina diff --git a/src/engine/JackDriver.cpp b/src/engine/JackDriver.cpp new file mode 100644 index 0000000..e7f2e97 --- /dev/null +++ b/src/engine/JackDriver.cpp @@ -0,0 +1,479 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <iostream> + +#include "machina/Context.hpp" +#include "machina/URIs.hpp" +#include "machina/Updates.hpp" +#include "machina_config.h" + +#include "Edge.hpp" +#include "JackDriver.hpp" +#include "LearnRequest.hpp" +#include "MachineBuilder.hpp" +#include "MidiAction.hpp" + +using namespace machina; +using namespace std; + +namespace machina { + +JackDriver::JackDriver(Forge& forge, SPtr<Machine> machine) + : Driver(forge, machine) + , _client(NULL) + , _machine_changed(0) + , _input_port(NULL) + , _output_port(NULL) + , _context(forge, 48000, MACHINA_PPQN, 120.0) + , _frames_unit(TimeUnit::FRAMES, 48000) + , _beats_unit(TimeUnit::BEATS, 19200) + , _stop(0) + , _stop_flag(false) + , _record_dur(_frames_unit) // = 0 + , _is_activated(false) +{ + _context.set_sink(this); +} + +JackDriver::~JackDriver() +{ + detach(); +} + +void +JackDriver::attach(const std::string& client_name) +{ + // Already connected + if (_client) { + return; + } + + jack_set_error_function(jack_error_cb); + + _client = jack_client_open(client_name.c_str(), JackNullOption, NULL, NULL); + + if (_client == NULL) { + _is_activated = false; + } else { + jack_set_error_function(jack_error_cb); + jack_on_shutdown(_client, jack_shutdown_cb, this); + jack_set_process_callback(_client, jack_process_cb, this); + } + + if (jack_client()) { + + _context.time().set_tick_rate(sample_rate()); + + _input_port = jack_port_register(jack_client(), + "in", + JACK_DEFAULT_MIDI_TYPE, + JackPortIsInput, + 0); + + if (!_input_port) { + std::cerr << "WARNING: Failed to create MIDI input port." + << std::endl; + } + + _output_port = jack_port_register(jack_client(), + "out", + JACK_DEFAULT_MIDI_TYPE, + JackPortIsOutput, + 0); + + if (!_output_port) { + std::cerr << "WARNING: Failed to create MIDI output port." + << std::endl; + } + + if (!_machine) { + _machine = SPtr<Machine>( + new Machine(TimeUnit::frames( + jack_get_sample_rate(jack_client())))); + } + } +} + +void +JackDriver::detach() +{ + if (_is_activated) { + _is_activated = false; + _stop.timed_wait(std::chrono::seconds(1)); + } + + if (_input_port) { + jack_port_unregister(jack_client(), _input_port); + _input_port = NULL; + } + + if (_output_port) { + jack_port_unregister(jack_client(), _output_port); + _output_port = NULL; + } + + if (_client) { + deactivate(); + jack_client_close(_client); + _client = NULL; + _is_activated = false; + } +} + +void +JackDriver::activate() +{ + if (!jack_activate(_client)) { + _is_activated = true; + } else { + _is_activated = false; + } +} + +void +JackDriver::deactivate() +{ + if (_client) { + jack_deactivate(_client); + } + + _is_activated = false; +} + +void +JackDriver::set_machine(SPtr<Machine> machine) +{ + if (machine == _machine) { + return; + } + + SPtr<Machine> last_machine = _last_machine; // Keep a reference + _machine_changed.reset(0); + assert(!last_machine.unique()); + _machine = machine; + if (is_activated()) { + _machine_changed.wait(); + } + assert(_machine == machine); + last_machine.reset(); +} + +void +JackDriver::read_input_recording(SPtr<Machine> machine, + const Raul::TimeSlice& time) +{ + const jack_nframes_t nframes = time.length_ticks().ticks(); + void* buf = jack_port_get_buffer(_input_port, nframes); + const jack_nframes_t n_events = jack_midi_get_event_count(buf); + + for (jack_nframes_t i = 0; i < n_events; ++i) { + jack_midi_event_t ev; + jack_midi_event_get(&ev, buf, i); + + const TimeStamp rel_time_frames = TimeStamp(_frames_unit, ev.time); + const TimeStamp time_frames = _record_dur + rel_time_frames; + _recorder->write(time.ticks_to_beats(time_frames), ev.size, ev.buffer); + } + + if (n_events > 0) { + _recorder->whip(); + } + + _record_dur += time.length_ticks(); +} + +void +JackDriver::read_input_playing(SPtr<Machine> machine, + const Raul::TimeSlice& time) +{ + const jack_nframes_t nframes = time.length_ticks().ticks(); + void* buf = jack_port_get_buffer(_input_port, nframes); + const jack_nframes_t n_events = jack_midi_get_event_count(buf); + + for (jack_nframes_t i = 0; i < n_events; ++i) { + jack_midi_event_t ev; + jack_midi_event_get(&ev, buf, i); + + if (ev.buffer[0] == 0x90) { + const SPtr<LearnRequest> learn = machine->pending_learn(); + if (learn) { + learn->enter_action()->set_event(ev.size, ev.buffer); + learn->start(_quantization, + TimeStamp(TimeUnit::frames(sample_rate()), + jack_last_frame_time(_client) + + ev.time, 0)); + } + + } else if (ev.buffer[0] == 0x80) { + const SPtr<LearnRequest> learn = machine->pending_learn(); + if (learn && learn->started()) { + learn->exit_action()->set_event(ev.size, ev.buffer); + learn->finish(TimeStamp(TimeUnit::frames(sample_rate()), + jack_last_frame_time(_client) + ev.time, + 0)); + + const uint64_t id = Stateful::next_id(); + write_set(_updates, id, + URIs::instance().rdf_type, + _forge.make_urid(URIs::instance().machina_MidiAction)); + write_set(_updates, learn->node()->id(), + URIs::instance().machina_enter_action, + _forge.make((int32_t)id)); + write_set(_updates, id, + URIs::instance().machina_note_number, + _forge.make((int32_t)ev.buffer[1])); + + machine->clear_pending_learn(); + } + } + } +} + +void +JackDriver::write_event(Raul::TimeStamp time, + size_t size, + const byte* event) +{ + if (!_output_port) { + return; + } + + const Raul::TimeSlice& slice = _context.time(); + + if (slice.beats_to_ticks(time) + slice.offset_ticks() < + slice.start_ticks()) { + std::cerr << "ERROR: Missed event by " + << (slice.start_ticks() + - slice.beats_to_ticks(time) + + slice.offset_ticks()) + << " ticks" + << "\n\tbpm: " << slice.bpm() + << "\n\tev time: " << slice.beats_to_ticks(time) + << "\n\tcycle_start: " << slice.start_ticks() + << "\n\tcycle_end: " << (slice.start_ticks() + + slice.length_ticks()) + << "\n\tcycle_length: " << slice.length_ticks() + << std::endl << std::endl; + return; + } + + const TimeDuration nframes = slice.length_ticks(); + const TimeStamp offset = slice.beats_to_ticks(time) + + slice.offset_ticks() - slice.start_ticks(); + + if (!(offset < slice.offset_ticks() + nframes)) { + std::cerr << "ERROR: Event offset " << offset << " outside cycle " + << "\n\tbpm: " << slice.bpm() + << "\n\tev time: " << slice.beats_to_ticks(time) + << "\n\tcycle_start: " << slice.start_ticks() + << "\n\tcycle_end: " << slice.start_ticks() + + slice.length_ticks() + << "\n\tcycle_length: " << slice.length_ticks() << std::endl; + } else { +#ifdef JACK_MIDI_NEEDS_NFRAMES + jack_midi_event_write( + jack_port_get_buffer(_output_port, nframes), offset, + event, size, nframes); +#else + jack_midi_event_write( + jack_port_get_buffer(_output_port, nframes.ticks()), offset.ticks(), + event, size); +#endif + } +} + +void +JackDriver::on_process(jack_nframes_t nframes) +{ + if (!_is_activated) { + _stop.post(); + return; + } + + _context.time().set_bpm(_bpm); + + assert(_output_port); + jack_midi_clear_buffer(jack_port_get_buffer(_output_port, nframes)); + + TimeStamp length_ticks(TimeStamp(_context.time().ticks_unit(), nframes)); + + _context.time().set_length(length_ticks); + _context.time().set_offset(TimeStamp(_context.time().ticks_unit(), 0, 0)); + + /* Take a reference to machine here and use only it during the process + * cycle so _machine can be switched with set_machine during a cycle. */ + SPtr<Machine> machine = _machine; + + // Machine was switched since last cycle, finalize old machine. + if (machine != _last_machine) { + if (_last_machine) { + assert(!_last_machine.unique()); // Realtime, can't delete + _last_machine->reset(_context.sink(), _last_machine->time()); // Exit all active states + _last_machine.reset(); // Cut our reference + } + _machine_changed.post(); // Signal we're done with it + } + + if (!machine) { + _last_machine = machine; + goto end; + } + + if (_stop_flag) { + machine->reset(_context.sink(), _context.time().start_beats()); + } + + switch (_play_state) { + case PlayState::STOPPED: + break; + case PlayState::PLAYING: + read_input_playing(machine, _context.time()); + break; + case PlayState::RECORDING: + case PlayState::STEP_RECORDING: + read_input_recording(machine, _context.time()); + break; + } + + if (machine->is_empty()) { + goto end; + } + + while (true) { + const uint32_t run_dur_frames = machine->run(_context, _updates); + + if (run_dur_frames == 0) { + // Machine didn't run at all (machine has no initial states) + machine->reset(_context.sink(), machine->time()); // Try again next cycle + _context.time().set_slice(TimeStamp(_frames_unit, 0, 0), + TimeStamp(_frames_unit, 0, 0)); + break; + + } else if (machine->is_finished()) { + // Machine ran for portion of cycle and is finished + machine->reset(_context.sink(), machine->time()); + + _context.time().set_slice(TimeStamp(_frames_unit, 0, 0), + TimeStamp(_frames_unit, nframes + - run_dur_frames, 0)); + _context.time().set_offset(TimeStamp(_frames_unit, run_dur_frames, + 0)); + + } else { + // Machine ran for entire cycle + _context.time().set_slice( + _context.time().start_ticks() + _context.time().length_ticks(), + TimeStamp(_frames_unit, 0, 0)); + break; + } + } + +end: + /* Remember the last machine run, in case a switch happens and + * we need to finalize it next cycle. */ + _last_machine = machine; + + if (_stop_flag) { + _context.time().set_slice(TimeStamp(_frames_unit, 0, 0), + TimeStamp(_frames_unit, 0, 0)); + _stop_flag = false; + _stop.post(); + } +} + +void +JackDriver::set_play_state(PlayState state) +{ + switch (state) { + case PlayState::STOPPED: + switch (_play_state) { + case PlayState::STOPPED: + break; + case PlayState::RECORDING: + case PlayState::STEP_RECORDING: + finish_record(); + // fallthru + case PlayState::PLAYING: + _stop_flag = true; + _stop.wait(); + } + break; + case PlayState::RECORDING: + start_record(false); + break; + case PlayState::STEP_RECORDING: + start_record(true); + break; + case PlayState::PLAYING: + if (_play_state == PlayState::RECORDING || + _play_state == PlayState::STEP_RECORDING) { + finish_record(); + } + } + Driver::set_play_state(state); +} + +void +JackDriver::start_record(bool step) +{ + const double q = (step || _quantize_record) ? _quantization : 0.0; + switch (_play_state) { + case PlayState::STOPPED: + case PlayState::PLAYING: + _recorder = SPtr<Recorder>( + new Recorder(_forge, 1024, _beats_unit, q, step)); + _record_dur = 0; + break; + case PlayState::RECORDING: + case PlayState::STEP_RECORDING: + _recorder->builder()->set_step(true); + break; + } + _play_state = step ? PlayState::STEP_RECORDING : PlayState::RECORDING; +} + +void +JackDriver::finish_record() +{ + _play_state = PlayState::PLAYING; + SPtr<Machine> machine = _recorder->finish(); + _recorder.reset(); + _machine->merge(*machine.get()); +} + +int +JackDriver::jack_process_cb(jack_nframes_t nframes, void* jack_driver) +{ + JackDriver* me = reinterpret_cast<JackDriver*>(jack_driver); + me->on_process(nframes); + return 0; +} + +void +JackDriver::jack_shutdown_cb(void* jack_driver) +{ + JackDriver* me = reinterpret_cast<JackDriver*>(jack_driver); + me->_client = NULL; +} + +void +JackDriver::jack_error_cb(const char* msg) +{ + cerr << "[JACK] Error: " << msg << endl; +} + +} // namespace machina diff --git a/src/engine/JackDriver.hpp b/src/engine/JackDriver.hpp new file mode 100644 index 0000000..4ebfaa1 --- /dev/null +++ b/src/engine/JackDriver.hpp @@ -0,0 +1,119 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_JACKDRIVER_HPP +#define MACHINA_JACKDRIVER_HPP + +#include <jack/jack.h> +#include <jack/midiport.h> + +#include "raul/Semaphore.hpp" + +#include "machina/Context.hpp" +#include "machina/Driver.hpp" +#include "machina/Machine.hpp" +#include "machina/types.hpp" + +#include "Recorder.hpp" + +namespace machina { + +class MidiAction; +class Node; + +/** Realtime JACK Driver. + * + * "Ticks" are individual frames when running under this driver, and all code + * in the processing context must be realtime safe (non-blocking). + */ +class JackDriver : public machina::Driver +{ +public: + JackDriver(Forge& forge, + SPtr<Machine> machine = SPtr<Machine>()); + ~JackDriver(); + + void attach(const std::string& client_name); + void detach(); + + void activate(); + void deactivate(); + + void set_machine(SPtr<Machine> machine); + + void write_event(Raul::TimeStamp time, + size_t size, + const unsigned char* event); + + void set_play_state(PlayState state); + + void start_transport() { jack_transport_start(_client); } + void stop_transport() { jack_transport_stop(_client); } + + void rewind_transport() { + jack_position_t zero; + zero.frame = 0; + zero.valid = (jack_position_bits_t)0; + jack_transport_reposition(_client, &zero); + } + + bool is_activated() const { return _is_activated; } + bool is_attached() const { return _client != NULL; } + bool is_realtime() const { return _client && jack_is_realtime(_client); } + + jack_nframes_t sample_rate() const { return jack_get_sample_rate(_client); } + jack_client_t* jack_client() const { return _client; } + +private: + void read_input_recording(SPtr<Machine> machine, + const Raul::TimeSlice& time); + + void read_input_playing(SPtr<Machine> machine, + const Raul::TimeSlice& time); + + static void jack_error_cb(const char* msg); + static int jack_process_cb(jack_nframes_t nframes, void* me); + static void jack_shutdown_cb(void* me); + + void start_record(bool step); + void finish_record(); + + void on_process(jack_nframes_t nframes); + + jack_client_t* _client; + + Raul::Semaphore _machine_changed; + SPtr<Machine> _last_machine; + + jack_port_t* _input_port; + jack_port_t* _output_port; + + Context _context; + + Raul::TimeUnit _frames_unit; + Raul::TimeUnit _beats_unit; + + Raul::Semaphore _stop; + bool _stop_flag; + + Raul::TimeDuration _record_dur; + SPtr<Recorder> _recorder; + bool _is_activated; +}; + +} // namespace machina + +#endif // MACHINA_JACKDRIVER_HPP diff --git a/src/engine/LearnRequest.cpp b/src/engine/LearnRequest.cpp new file mode 100644 index 0000000..5756dd1 --- /dev/null +++ b/src/engine/LearnRequest.cpp @@ -0,0 +1,56 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "LearnRequest.hpp" +#include "quantize.hpp" + +namespace machina { + +LearnRequest::LearnRequest(SPtr<Raul::Maid> maid, SPtr<Node> node) + : _started(false) + , _start_time(TimeUnit(TimeUnit::BEATS, 19200), 0, 0) // irrelevant + , _quantization(0) // irrelevant + , _node(node) + , _enter_action(new MidiAction(4, NULL)) + , _exit_action(new MidiAction(4, NULL)) +{ +} + +SPtr<LearnRequest> +LearnRequest::create(SPtr<Raul::Maid> maid, SPtr<Node> node) +{ + return SPtr<LearnRequest>(new LearnRequest(maid, node)); +} + +void +LearnRequest::start(double q, Raul::TimeStamp time) +{ + _started = true; + _start_time = time; + _quantization = q; +} + +/** Add the learned actions to the node */ +void +LearnRequest::finish(TimeStamp time) +{ + _node->set_enter_action(_enter_action); + _node->set_exit_action(_exit_action); + + _node->set_duration(time - _start_time); +} + +} diff --git a/src/engine/LearnRequest.hpp b/src/engine/LearnRequest.hpp new file mode 100644 index 0000000..9cdfc3c --- /dev/null +++ b/src/engine/LearnRequest.hpp @@ -0,0 +1,61 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_LEARNREQUEST_HPP +#define MACHINA_LEARNREQUEST_HPP + +#include "raul/Maid.hpp" + +#include "machina/types.hpp" + +#include "MidiAction.hpp" +#include "Node.hpp" + +namespace machina { + +class Node; +class MidiAction; + +/** A request to MIDI learn a certain node. + */ +class LearnRequest : public Raul::Maid::Disposable +{ +public: + static SPtr<LearnRequest> create(SPtr<Raul::Maid> maid, SPtr<Node> node); + + void start(double q, Raul::TimeStamp time); + void finish(TimeStamp time); + + inline bool started() const { return _started; } + + const SPtr<Node>& node() { return _node; } + const SPtr<MidiAction>& enter_action() { return _enter_action; } + const SPtr<MidiAction>& exit_action() { return _exit_action; } + +private: + LearnRequest(SPtr<Raul::Maid> maid, SPtr<Node> node); + + bool _started; + TimeStamp _start_time; + double _quantization; + SPtr<Node> _node; + SPtr<MidiAction> _enter_action; + SPtr<MidiAction> _exit_action; +}; + +} // namespace machina + +#endif // MACHINA_LEARNREQUEST_HPP diff --git a/src/engine/Loader.cpp b/src/engine/Loader.cpp new file mode 100644 index 0000000..1c662d2 --- /dev/null +++ b/src/engine/Loader.cpp @@ -0,0 +1,193 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <cmath> +#include <iostream> +#include <map> + +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" + +#include "machina/Loader.hpp" +#include "machina/URIs.hpp" +#include "machina/Machine.hpp" + +#include "Edge.hpp" +#include "MidiAction.hpp" +#include "Node.hpp" +#include "SMFDriver.hpp" +#include "machina_config.h" + +using namespace Raul; +using namespace std; + +namespace machina { + +Loader::Loader(Forge& forge, Sord::World& rdf_world) + : _forge(forge) + , _rdf_world(rdf_world) +{} + +static SPtr<Action> +load_action(Sord::Model& model, Sord::Node node) +{ + if (!node.is_valid()) { + return SPtr<Action>(); + } + + Sord::URI rdf_type(model.world(), MACHINA_URI_RDF "type"); + Sord::URI midi_NoteOn(model.world(), LV2_MIDI__NoteOn); + Sord::URI midi_NoteOff(model.world(), LV2_MIDI__NoteOff); + Sord::URI midi_noteNumber(model.world(), LV2_MIDI__noteNumber); + Sord::URI midi_velocity(model.world(), LV2_MIDI__velocity); + + Sord::Node type = model.get(node, rdf_type, Sord::Node()); + uint8_t status = 0; + if (type == midi_NoteOn) { + status = LV2_MIDI_MSG_NOTE_ON; + } else if (type == midi_NoteOff) { + status = LV2_MIDI_MSG_NOTE_OFF; + } else { + return SPtr<Action>(); + } + + Sord::Node num_node = model.get(node, midi_noteNumber, Sord::Node()); + Sord::Node vel_node = model.get(node, midi_velocity, Sord::Node()); + + const uint8_t num = num_node.is_int() ? num_node.to_int() : 64; + const uint8_t vel = vel_node.is_int() ? vel_node.to_int() : 64; + const uint8_t event[3] = { status, num, vel }; + + return SPtr<Action>(new MidiAction(sizeof(event), event)); +} + +/** Load (create) all objects from RDF into the engine. + * + * @param uri URI of machine (resolvable URI to an RDF document). + * @return Loaded Machine. + */ +SPtr<Machine> +Loader::load(const std::string& uri) +{ + std::string document_uri = uri; + + // If "URI" doesn't contain a colon, try to resolve as a filename + if (uri.find(":") == string::npos) { + document_uri = "file://" + document_uri; + } + + cout << "Loading " << document_uri << endl; + + TimeUnit beats(TimeUnit::BEATS, MACHINA_PPQN); + + SPtr<Machine> machine(new Machine(beats)); + + typedef std::map<Sord::Node, SPtr<Node> > Created; + Created created; + + Sord::URI base_uri(_rdf_world, document_uri); + Sord::Model model(_rdf_world, document_uri); + + SerdEnv* env = serd_env_new(base_uri.to_serd_node()); + model.load_file(env, SERD_TURTLE, document_uri); + serd_env_free(env); + + Sord::Node nil; + + Sord::URI machina_SelectorNode(_rdf_world, MACHINA_NS_SelectorNode); + Sord::URI machina_duration(_rdf_world, MACHINA_NS_duration); + Sord::URI machina_edge(_rdf_world, MACHINA_NS_arc); + Sord::URI machina_head(_rdf_world, MACHINA_NS_head); + Sord::URI machina_node(_rdf_world, MACHINA_NS_node); + Sord::URI machina_onEnter(_rdf_world, MACHINA_NS_onEnter); + Sord::URI machina_onExit(_rdf_world, MACHINA_NS_onExit); + Sord::URI machina_probability(_rdf_world, MACHINA_NS_probability); + Sord::URI machina_start(_rdf_world, MACHINA_NS_start); + Sord::URI machina_tail(_rdf_world, MACHINA_NS_tail); + Sord::URI rdf_type(_rdf_world, MACHINA_URI_RDF "type"); + + Sord::Node subject = base_uri; + + // Get start node ID (but re-use existing start node) + Sord::Iter i = model.find(subject, machina_start, nil); + if (i.end()) { + cerr << "error: Machine has no start node" << std::endl; + } + created[i.get_object()] = machine->initial_node(); + + // Get remaining nodes + for (Sord::Iter i = model.find(subject, machina_node, nil); !i.end(); ++i) { + const Sord::Node& id = i.get_object(); + if (created.find(id) != created.end()) { + cerr << "warning: Machine lists the same node twice" << std::endl; + continue; + } + + // Create node + Sord::Iter d = model.find(id, machina_duration, nil); + SPtr<Node> node(new Node(TimeStamp(beats, d.get_object().to_float()))); + machine->add_node(node); + created[id] = node; + + node->set_enter_action( + load_action(model, model.get(id, machina_onEnter, nil))); + node->set_exit_action( + load_action(model, model.get(id, machina_onExit, nil))); + } + + // Get arcs + for (Sord::Iter i = model.find(subject, machina_edge, nil); !i.end(); ++i) { + Sord::Node edge = i.get_object(); + Sord::Iter t = model.find(edge, machina_tail, nil); + Sord::Iter h = model.find(edge, machina_head, nil); + Sord::Iter p = model.find(edge, machina_probability, nil); + + Sord::Node tail = t.get_object(); + Sord::Node head = h.get_object(); + Sord::Node probability = p.get_object(); + + float prob = probability.to_float(); + + Created::iterator tail_i = created.find(tail); + Created::iterator head_i = created.find(head); + + if (tail_i != created.end() && head_i != created.end()) { + const SPtr<Node> tail = tail_i->second; + const SPtr<Node> head = head_i->second; + tail->add_edge(SPtr<Edge>(new Edge(tail, head, prob))); + } else { + cerr << "warning: Ignored edge between unknown nodes " + << tail << " -> " << head << endl; + } + } + + if (machine && !machine->nodes().empty()) { + machine->reset(NULL, machine->time()); + return machine; + } else { + return SPtr<Machine>(); + } +} + +SPtr<Machine> +Loader::load_midi(const std::string& uri, + double q, + Raul::TimeDuration dur) +{ + SPtr<SMFDriver> file_driver(new SMFDriver(_forge, dur.unit())); + return file_driver->learn(uri, q, dur); +} + +} // namespace machina diff --git a/src/engine/MIDISink.hpp b/src/engine/MIDISink.hpp new file mode 100644 index 0000000..983dbba --- /dev/null +++ b/src/engine/MIDISink.hpp @@ -0,0 +1,37 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_MIDI_SINK_HPP +#define MACHINA_MIDI_SINK_HPP + +#include "raul/Deletable.hpp" +#include "raul/TimeStamp.hpp" + +namespace machina { + +/** Pure virtual base for anything you can write MIDI to. */ +class MIDISink + : public Raul::Deletable +{ +public: + virtual void write_event(Raul::TimeStamp time, + size_t ev_size, + const uint8_t* ev) = 0; +}; + +} // namespace machina + +#endif // MACHINA_MIDI_SINK_HPP diff --git a/src/engine/Machine.cpp b/src/engine/Machine.cpp new file mode 100644 index 0000000..b144b6f --- /dev/null +++ b/src/engine/Machine.cpp @@ -0,0 +1,389 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <cstdlib> +#include <map> + +#include "sord/sordmm.hpp" + +#include "machina/Atom.hpp" +#include "machina/Context.hpp" +#include "machina/Machine.hpp" +#include "machina/URIs.hpp" +#include "machina/Updates.hpp" +#include "machina/types.hpp" + +#include "Edge.hpp" +#include "Node.hpp" +#include "LearnRequest.hpp" +#include "MidiAction.hpp" + +using namespace std; +using namespace Raul; + +namespace machina { + +Machine::Machine(TimeUnit unit) + : fitness(0.0) + , _initial_node(new Node(TimeStamp(unit, 0, 0), true)) + , _active_nodes(MAX_ACTIVE_NODES, SPtr<Node>()) + , _time(unit, 0, 0) + , _is_finished(false) +{ + _nodes.insert(_initial_node); +} + +void +Machine::assign(const Machine& copy) +{ + std::map< SPtr<Node>, SPtr<Node> > replacements; + + replacements[copy.initial_node()] = _initial_node; + + for (const auto& n : copy._nodes) { + if (!n->is_initial()) { + SPtr<machina::Node> node(new machina::Node(*n.get())); + _nodes.insert(node); + replacements[n] = node; + } + } + + for (const auto& n : _nodes) { + for (const auto& e : n->edges()) { + e->set_tail(n); + e->set_head(replacements[e->head()]); + } + } +} + +Machine::Machine(const Machine& copy) + : Stateful() // don't copy RDF ID + , fitness(0.0) + , _initial_node(new Node(TimeStamp(copy.time().unit(), 0, 0), true)) + , _active_nodes(MAX_ACTIVE_NODES, SPtr<Node>()) + , _time(copy.time()) + , _is_finished(false) +{ + _nodes.insert(_initial_node); + assign(copy); +} + +Machine& +Machine::operator=(const Machine& copy) +{ + if (© == this) { + return *this; + } + + fitness = copy.fitness; + + _active_nodes = std::vector< SPtr<Node> >(MAX_ACTIVE_NODES, SPtr<Node>()); + _is_finished = false; + _time = copy._time; + _pending_learn = SPtr<LearnRequest>(); + + _nodes.clear(); + _nodes.insert(_initial_node); + assign(copy); + + return *this; +} + +bool +Machine::operator==(const Machine& rhs) const +{ + return false; +} + +void +Machine::merge(const Machine& machine) +{ + for (const auto& m : machine.nodes()) { + if (m->is_initial()) { + for (const auto& e : m->edges()) { + e->set_tail(_initial_node); + _initial_node->edges().insert(e); + } + } else { + _nodes.insert(m); + } + } +} + +/** Always returns a node, unless there are none */ +SPtr<Node> +Machine::random_node() +{ + if (_nodes.empty()) { + return SPtr<Node>(); + } + + size_t i = rand() % _nodes.size(); + + // FIXME: O(n) worst case :( + for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n, + --i) { + if (i == 0) { + return *n; + } + } + + return SPtr<Node>(); +} + +/** May return NULL even if edges exist (with low probability) */ +SPtr<Edge> +Machine::random_edge() +{ + SPtr<Node> tail = random_node(); + + for (size_t i = 0; i < _nodes.size() && tail->edges().empty(); ++i) { + tail = random_node(); + } + + return tail ? tail->random_edge() : SPtr<Edge>(); +} + +void +Machine::add_node(SPtr<Node> node) +{ + _nodes.insert(node); +} + +void +Machine::remove_node(SPtr<Node> node) +{ + _nodes.erase(node); + + for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n) { + (*n)->remove_edge_to(node); + } +} + +void +Machine::reset(MIDISink* sink, Raul::TimeStamp time) +{ + if (!_is_finished) { + for (auto& n : _active_nodes) { + if (n) { + n->exit(sink, time); + n.reset(); + } + } + } + + _time = TimeStamp(_time.unit(), 0, 0); + _is_finished = false; +} + +SPtr<Node> +Machine::earliest_node() const +{ + SPtr<Node> earliest; + + for (const auto& n : _active_nodes) { + if (n && (!earliest || n->exit_time() < earliest->exit_time())) { + earliest = n; + } + } + + return earliest; +} + +bool +Machine::enter_node(Context& context, + SPtr<Node> node, + SPtr<Raul::RingBuffer> updates) +{ + assert(!node->is_active()); + assert(_active_nodes.size() == MAX_ACTIVE_NODES); + + /* FIXME: Would be best to use the MIDI note here as a hash key, at least + * while all actions are still MIDI notes... */ + size_t index = (rand() % MAX_ACTIVE_NODES); + for (size_t i = 0; i < MAX_ACTIVE_NODES; ++i) { + if (!_active_nodes[index]) { + node->enter(context.sink(), _time); + _active_nodes[index] = node; + + write_set(updates, + node->id(), + URIs::instance().machina_active, + context.forge().make(true)); + return true; + } + index = (index + 1) % MAX_ACTIVE_NODES; + } + + // If we get here, ran out of active node spots. Don't enter node + return false; +} + +void +Machine::exit_node(Context& context, + SPtr<Node> node, + SPtr<Raul::RingBuffer> updates) +{ + // Exit node + node->exit(context.sink(), _time); + + // Notify UI + write_set(updates, + node->id(), + URIs::instance().machina_active, + context.forge().make(false)); + + // Remove node from _active_nodes + for (auto& n : _active_nodes) { + if (n == node) { + n.reset(); + } + } + + // Activate successors + if (node->is_selector()) { + const double rand_normal = rand() / (double)RAND_MAX; // [0, 1] + double range_min = 0; + + for (const auto& e : node->edges()) { + if (!e->head()->is_active() + && rand_normal > range_min + && rand_normal < range_min + e->probability()) { + + enter_node(context, e->head(), updates); + break; + + } else { + range_min += e->probability(); + } + } + } else { + for (const auto& e : node->edges()) { + const double rand_normal = rand() / (double)RAND_MAX; // [0, 1] + if (rand_normal <= e->probability()) { + SPtr<Node> head = e->head(); + + if (!head->is_active()) { + enter_node(context, head, updates); + } + } + } + } +} + +uint32_t +Machine::run(Context& context, SPtr<Raul::RingBuffer> updates) +{ + if (_is_finished) { + return 0; + } + + const Raul::TimeSlice& time = context.time(); + + const TimeStamp end_frames = (time.start_ticks() + time.length_ticks()); + const TimeStamp end_beats = time.ticks_to_beats(end_frames); + + if (_time.is_zero()) { // Initial run + // Exit any active nodes + for (auto& n : _active_nodes) { + if (n && n->is_active()) { + n->exit(context.sink(), _time); + write_set(updates, + n->id(), + URIs::instance().machina_active, + context.forge().make(false)); + } + n.reset(); + } + + // Enter initial node + enter_node(context, _initial_node, updates); + + if (_initial_node->edges().empty()) { // Nowhere to go, exit + _is_finished = true; + return 0; + } + } + + while (true) { + SPtr<Node> earliest = earliest_node(); + if (!earliest) { + // No more active states, machine is finished + _is_finished = true; + break; + } + + const TimeStamp exit_time = earliest->exit_time(); + if (time.beats_to_ticks(exit_time) < end_frames) { + // Earliest active state ends this cycle, exit it + _time = earliest->exit_time(); + exit_node(context, earliest, updates); + + } else { + // Earliest active state ends in the future, done this cycle + _time = end_beats; + break; + } + + } + + return time.beats_to_ticks(_time).ticks() - time.start_ticks().ticks(); +} + +void +Machine::learn(SPtr<Raul::Maid> maid, SPtr<Node> node) +{ + _pending_learn = LearnRequest::create(maid, node); +} + +void +Machine::write_state(Sord::Model& model) +{ + using namespace Raul; + + model.add_statement(model.base_uri(), + Sord::URI(model.world(), MACHINA_URI_RDF "type"), + Sord::URI(model.world(), MACHINA_NS_Machine)); + + for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n) { + (*n)->write_state(model); + + if ((*n)->is_initial()) { + model.add_statement(model.base_uri(), + Sord::URI(model.world(), MACHINA_NS_start), + (*n)->rdf_id(model.world())); + } else { + model.add_statement(model.base_uri(), + Sord::URI(model.world(), MACHINA_NS_node), + (*n)->rdf_id(model.world())); + } + } + + for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n) { + for (Node::Edges::const_iterator e = (*n)->edges().begin(); + e != (*n)->edges().end(); ++e) { + + (*e)->write_state(model); + + model.add_statement(model.base_uri(), + Sord::URI(model.world(), MACHINA_NS_arc), + (*e)->rdf_id(model.world())); + } + + } +} + +} // namespace machina diff --git a/src/engine/MachineBuilder.cpp b/src/engine/MachineBuilder.cpp new file mode 100644 index 0000000..d812b06 --- /dev/null +++ b/src/engine/MachineBuilder.cpp @@ -0,0 +1,325 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" + +#include "machina/Machine.hpp" +#include "machina/types.hpp" + +#include "Edge.hpp" +#include "MachineBuilder.hpp" +#include "MidiAction.hpp" +#include "Node.hpp" +#include "quantize.hpp" + +using namespace std; +using namespace Raul; + +namespace machina { + +MachineBuilder::MachineBuilder(SPtr<Machine> machine, double q, bool step) + : _quantization(q) + , _time(machine->time().unit()) // = 0 + , _machine(machine) + , _initial_node(machine->initial_node()) // duration 0 + , _connect_node(_initial_node) + , _connect_node_end_time(_time) // = 0 + , _step_duration(_time.unit(), q) + , _step(step) +{} + +void +MachineBuilder::reset() +{ + _time = TimeStamp(_machine->time().unit()); // = 0 + _connect_node = _initial_node; + _connect_node_end_time = _time; // = 0 +} + +bool +MachineBuilder::is_delay_node(SPtr<Node> node) const +{ + return node != _initial_node && + !node->enter_action() && + !node->exit_action(); +} + +/** Set the duration of a node, with quantization. + */ +void +MachineBuilder::set_node_duration(SPtr<Node> node, + Raul::TimeDuration d) const +{ + if (_step) { + node->set_duration(_step_duration); + return; + } + + Raul::TimeStamp q_dur = quantize(TimeStamp(d.unit(), _quantization), d); + + // Never quantize a note to duration 0 + if (q_dur.is_zero() && (node->enter_action() || node->exit_action())) { + q_dur = _quantization; // Round up + } + node->set_duration(q_dur); +} + +/** Connect two nodes, inserting a delay node between them if necessary. + * + * If a delay node is added to the machine, it is returned. + */ +SPtr<Node> +MachineBuilder::connect_nodes(SPtr<Machine> m, + SPtr<Node> tail, + Raul::TimeStamp tail_end_time, + SPtr<Node> head, + Raul::TimeStamp head_start_time) +{ + SPtr<Node> delay_node; + if (tail == head) { + return delay_node; + } + + if (is_delay_node(tail) && tail->edges().empty()) { + // Tail is a delay node, just accumulate the time difference into it + set_node_duration(tail, + tail->duration() + head_start_time - tail_end_time); + tail->add_edge(SPtr<Edge>(new Edge(tail, head))); + } else if (_step || (head_start_time == tail_end_time)) { + // Connect directly + tail->add_edge(SPtr<Edge>(new Edge(tail, head))); + } else { + // Need to actually create a delay node + delay_node = SPtr<Node>(new Node(head_start_time - tail_end_time)); + tail->add_edge(SPtr<Edge>(new Edge(tail, delay_node))); + delay_node->add_edge(SPtr<Edge>(new Edge(delay_node, head))); + m->add_node(delay_node); + } + + return delay_node; +} + +void +MachineBuilder::note_on(Raul::TimeStamp t, size_t ev_size, uint8_t* buf) +{ + SPtr<Node> node; + if (_step && _poly_nodes.empty() && is_delay_node(_connect_node)) { + /* Stepping and the connect node is the merge node after a polyphonic + group. Re-use it to avoid creating delay nodes in step mode. */ + node = _connect_node; + node->set_duration(_step_duration); + } else { + node = SPtr<Node>(new Node(default_duration())); + } + + node->set_enter_action(SPtr<Action>(new MidiAction(ev_size, buf))); + + if (_step && _poly_nodes.empty()) { + t = _time = _time + _step_duration; // Advance time one step + } + + SPtr<Node> this_connect_node; + Raul::TimeStamp this_connect_node_end_time(t.unit()); + + /* If currently polyphonic, use a poly node with no successors as connect + node, for more sensible patterns like what a human would build. */ + if (!_poly_nodes.empty()) { + for (PolyList::iterator j = _poly_nodes.begin(); + j != _poly_nodes.end(); ++j) { + if (j->second->edges().empty()) { + this_connect_node = j->second; + this_connect_node_end_time = j->first + j->second->duration(); + break; + } + } + } + + /* Currently monophonic, or didn't find a poly node, so use _connect_node + which is maintained below on note off events. */ + if (!this_connect_node) { + this_connect_node = _connect_node; + this_connect_node_end_time = _connect_node_end_time; + } + + SPtr<Node> delay_node = connect_nodes( + _machine, this_connect_node, this_connect_node_end_time, node, t); + + if (delay_node) { + _connect_node = delay_node; + _connect_node_end_time = t; + } + + node->enter(NULL, t); + _active_nodes.push_back(node); +} + +void +MachineBuilder::resolve_note(Raul::TimeStamp time, + size_t ev_size, + uint8_t* buf, + SPtr<Node> resolved) +{ + resolved->set_exit_action(SPtr<Action>(new MidiAction(ev_size, buf))); + + if (_active_nodes.size() == 1) { + if (_step) { + time = _time = _time + _step_duration; + } + + // Last active note + _connect_node_end_time = time; + + if (!_poly_nodes.empty()) { + // Finish a polyphonic section + _connect_node = SPtr<Node>(new Node(TimeStamp(_time.unit(), 0, 0))); + _machine->add_node(_connect_node); + + connect_nodes(_machine, resolved, time, _connect_node, time); + + for (PolyList::iterator j = _poly_nodes.begin(); + j != _poly_nodes.end(); ++j) { + _machine->add_node(j->second); + if (j->second->edges().empty()) { + connect_nodes(_machine, j->second, + j->first + j->second->duration(), + _connect_node, time); + } + } + _poly_nodes.clear(); + + _machine->add_node(resolved); + + } else { + // Just monophonic + if (is_delay_node(_connect_node) + && _connect_node->duration().is_zero() + && (_connect_node->edges().size() == 1) + && ((*_connect_node->edges().begin())->head() == resolved)) { + // Trim useless delay node if possible (after poly sections) + + _connect_node->edges().clear(); + _connect_node->set_enter_action(resolved->enter_action()); + _connect_node->set_exit_action(resolved->exit_action()); + resolved->set_enter_action(SPtr<Action>()); + resolved->set_exit_action(SPtr<Action>()); + set_node_duration(_connect_node, resolved->duration()); + resolved = _connect_node; + _machine->add_node(_connect_node); + + } else { + _connect_node = resolved; + _machine->add_node(resolved); + } + } + + } else { + // Polyphonic, add this node to poly list + _poly_nodes.push_back(make_pair(resolved->enter_time(), resolved)); + _connect_node = resolved; + _connect_node_end_time = _time; + } + + if (resolved->is_active()) { + resolved->exit(NULL, _time); + } +} + +void +MachineBuilder::event(Raul::TimeStamp time, + size_t ev_size, + uint8_t* buf) +{ + if (ev_size == 0) { + return; + } + + if (!_step) { + _time = time; + } + + if ((buf[0] & 0xF0) == LV2_MIDI_MSG_NOTE_ON) { + note_on(time, ev_size, buf); + } else if ((buf[0] & 0xF0) == LV2_MIDI_MSG_NOTE_OFF) { + for (ActiveList::iterator i = _active_nodes.begin(); + i != _active_nodes.end(); ++i) { + SPtr<MidiAction> action = dynamic_ptr_cast<MidiAction>( + (*i)->enter_action()); + if (!action) { + continue; + } + + const size_t ev_size = action->event_size(); + const uint8_t* ev = action->event(); + + if ((ev[0] & 0xF0) == LV2_MIDI_MSG_NOTE_ON && + (ev[0] & 0x0F) == (buf[0] & 0x0F) && + ev[1] == buf[1]) { + // Same channel and note as on event + resolve_note(time, ev_size, buf, *i); + _active_nodes.erase(i); + break; + } + } + } +} + +/** Finish the constructed machine and prepare it for use. + * Resolve any stuck notes, quantize, etc. + */ +void +MachineBuilder::resolve() +{ + // Resolve stuck notes + if (!_active_nodes.empty()) { + for (list<SPtr<Node> >::iterator i = _active_nodes.begin(); + i != _active_nodes.end(); ++i) { + cerr << "WARNING: Resolving stuck note." << endl; + SPtr<MidiAction> action = dynamic_ptr_cast<MidiAction>( + (*i)->enter_action()); + if (!action) { + continue; + } + + const size_t ev_size = action->event_size(); + const uint8_t* ev = action->event(); + if (ev_size == 3 && (ev[0] & 0xF0) == LV2_MIDI_MSG_NOTE_ON) { + uint8_t st((LV2_MIDI_MSG_NOTE_OFF & 0xF0) | (ev[0] & 0x0F)); + const uint8_t note_off[3] = { st, ev[1], 0x40 }; + (*i)->set_exit_action( + SPtr<Action>(new MidiAction(3, note_off))); + set_node_duration((*i), _time - (*i)->enter_time()); + (*i)->exit(NULL, _time); + _machine->add_node((*i)); + } + } + _active_nodes.clear(); + } + + // Add initial node if necessary + if (!_machine->nodes().empty()) { + _machine->add_node(_initial_node); + } +} + +SPtr<Machine> +MachineBuilder::finish() +{ + resolve(); + + return _machine; +} + +} // namespace machina diff --git a/src/engine/MachineBuilder.hpp b/src/engine/MachineBuilder.hpp new file mode 100644 index 0000000..83baf78 --- /dev/null +++ b/src/engine/MachineBuilder.hpp @@ -0,0 +1,87 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_MACHINEBUILDER_HPP +#define MACHINA_MACHINEBUILDER_HPP + +#include <stdint.h> + +#include <list> + +#include "machina/types.hpp" +#include "raul/TimeStamp.hpp" + +namespace machina { + +class Machine; +class Node; + +class MachineBuilder +{ +public: + MachineBuilder(SPtr<Machine> machine, + double quantization, + bool step); + + void event(Raul::TimeStamp time, size_t size, unsigned char* buf); + + void set_step(bool step) { _step = step; } + + void reset(); + void resolve(); + + SPtr<Machine> finish(); + +private: + bool is_delay_node(SPtr<Node> node) const; + void set_node_duration(SPtr<Node> node, Raul::TimeDuration d) const; + + void note_on(Raul::TimeStamp t, size_t ev_size, uint8_t* buf); + + void resolve_note(Raul::TimeStamp t, + size_t ev_size, + uint8_t* buf, + SPtr<Node> resolved); + + SPtr<Node>connect_nodes(SPtr<Machine> m, + SPtr<Node> tail, + Raul::TimeStamp tail_end_time, + SPtr<Node> head, + Raul::TimeStamp head_start_time); + + Raul::TimeStamp default_duration() { + return _step ? _step_duration : Raul::TimeStamp(_time.unit(), 0, 0); + } + + typedef std::list<SPtr<Node> > ActiveList; + ActiveList _active_nodes; + + typedef std::list<std::pair<Raul::TimeStamp, SPtr<Node> > > PolyList; + PolyList _poly_nodes; + + double _quantization; + Raul::TimeStamp _time; + SPtr<Machine> _machine; + SPtr<Node> _initial_node; + SPtr<Node> _connect_node; + Raul::TimeStamp _connect_node_end_time; + Raul::TimeStamp _step_duration; + bool _step; +}; + +} // namespace machina + +#endif // MACHINA_MACHINEBUILDER_HPP diff --git a/src/engine/MidiAction.cpp b/src/engine/MidiAction.cpp new file mode 100644 index 0000000..b5df155 --- /dev/null +++ b/src/engine/MidiAction.cpp @@ -0,0 +1,103 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" +#include "machina/Atom.hpp" +#include "machina/URIs.hpp" +#include "machina/types.hpp" + +#include "MIDISink.hpp" +#include "MidiAction.hpp" + +namespace machina { + +/** Create a MIDI action. + * + * Creating a NULL MidiAction is okay, pass event=NULL and the action will + * simply do nothing until a set_event (for MIDI learning). + */ +MidiAction::MidiAction(size_t size, + const byte* event) + : _size(0) + , _max_size(size) +{ + _event = new byte[_max_size]; + set_event(size, event); +} + +MidiAction::~MidiAction() +{ + delete[] _event.load(); +} + +bool +MidiAction::set_event(size_t size, const byte* new_event) +{ + byte* const event = _event.load(); + if (size <= _max_size) { + _event = NULL; + if (size > 0 && new_event) { + memcpy(event, new_event, size); + } + _size = size; + _event = event; + return true; + } else { + return false; + } +} + +void +MidiAction::execute(MIDISink* sink, Raul::TimeStamp time) +{ + const byte* const ev = _event.load(); + if (ev && sink) { + sink->write_event(time, _size, ev); + } +} + +void +MidiAction::write_state(Sord::Model& model) +{ + const uint8_t* ev = event(); + const uint8_t type = (ev[0] & 0xF0); + if (type == LV2_MIDI_MSG_NOTE_ON) { + model.add_statement( + rdf_id(model.world()), + Sord::URI(model.world(), MACHINA_URI_RDF "type"), + Sord::URI(model.world(), LV2_MIDI__NoteOn)); + } else if (type == LV2_MIDI_MSG_NOTE_OFF) { + model.add_statement( + rdf_id(model.world()), + Sord::URI(model.world(), MACHINA_URI_RDF "type"), + Sord::URI(model.world(), LV2_MIDI__NoteOff)); + } else { + std::cerr << "warning: Unable to serialise MIDI event" << std::endl; + } + + model.add_statement( + rdf_id(model.world()), + Sord::URI(model.world(), LV2_MIDI__noteNumber), + Sord::Literal::integer(model.world(), (int)ev[1])); + if (ev[2] != 64) { + model.add_statement( + rdf_id(model.world()), + Sord::URI(model.world(), LV2_MIDI__velocity), + Sord::Literal::integer(model.world(), (int)ev[2])); + } +} + +} // namespace machina diff --git a/src/engine/MidiAction.hpp b/src/engine/MidiAction.hpp new file mode 100644 index 0000000..64a2c65 --- /dev/null +++ b/src/engine/MidiAction.hpp @@ -0,0 +1,57 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_MIDIACTION_HPP +#define MACHINA_MIDIACTION_HPP + +#include <atomic> + +#include "raul/TimeSlice.hpp" + +#include "machina/types.hpp" + +#include "Action.hpp" + +namespace machina { + +class MIDISink; + +class MidiAction : public Action +{ +public: + ~MidiAction(); + + MidiAction(size_t size, + const unsigned char* event); + + size_t event_size() { return _size; } + byte* event() { return _event.load(); } + + bool set_event(size_t size, const byte* event); + + void execute(MIDISink* sink, Raul::TimeStamp time); + + virtual void write_state(Sord::Model& model); + +private: + size_t _size; + const size_t _max_size; + std::atomic<byte*> _event; +}; + +} // namespace machina + +#endif // MACHINA_MIDIACTION_HPP diff --git a/src/engine/Mutation.cpp b/src/engine/Mutation.cpp new file mode 100644 index 0000000..776142d --- /dev/null +++ b/src/engine/Mutation.cpp @@ -0,0 +1,190 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <iostream> +#include <cstdlib> + +#include "machina/Machine.hpp" +#include "machina/Mutation.hpp" + +#include "ActionFactory.hpp" +#include "Edge.hpp" +#include "MidiAction.hpp" + +using namespace std; + +namespace machina { +namespace Mutation { + +void +Compress::mutate(Random& rng, Machine& machine) +{ + //cout << "COMPRESS" << endl; + + // Trim disconnected nodes + for (Machine::Nodes::iterator i = machine.nodes().begin(); + i != machine.nodes().end(); ) { + Machine::Nodes::iterator next = i; + ++next; + + if ((*i)->edges().empty()) { + machine.remove_node(*i); + } + + i = next; + } +} + +void +AddNode::mutate(Random& rng, Machine& machine) +{ + //cout << "ADD NODE" << endl; + + // Create random node + SPtr<Node> node(new Node(machine.time().unit())); + node->set_selector(true); + + SPtr<Node> note_node = machine.random_node(); + if (!note_node) { + return; + } + + uint8_t note = rand() % 128; + + SPtr<MidiAction> enter_action = dynamic_ptr_cast<MidiAction>( + note_node->enter_action()); + if (enter_action) { + note = enter_action->event()[1]; + } + + node->set_enter_action(ActionFactory::note_on(note)); + node->set_exit_action(ActionFactory::note_off(note)); + machine.add_node(node); + + // Insert after some node + SPtr<Node> tail = machine.random_node(); + if (tail && (tail != node) /* && !node->connected_to(tail)*/) { + tail->add_edge(SPtr<Edge>(new Edge(tail, node))); + } + + // Insert before some other node + SPtr<Node> head = machine.random_node(); + if (head && (head != node) /* && !head->connected_to(node)*/) { + node->add_edge(SPtr<Edge>(new Edge(node, head))); + } +} + +void +RemoveNode::mutate(Random& rng, Machine& machine) +{ + //cout << "REMOVE NODE" << endl; + + SPtr<Node> node = machine.random_node(); + if (node && !node->is_initial()) { + machine.remove_node(node); + } +} + +void +AdjustNode::mutate(Random& rng, Machine& machine) +{ + //cout << "ADJUST NODE" << endl; + + SPtr<Node> node = machine.random_node(); + if (node) { + SPtr<MidiAction> enter_action = dynamic_ptr_cast<MidiAction>( + node->enter_action()); + SPtr<MidiAction> exit_action = dynamic_ptr_cast<MidiAction>( + node->exit_action()); + if (enter_action && exit_action) { + const uint8_t note = rand() % 128; + enter_action->event()[1] = note; + exit_action->event()[1] = note; + } + node->set_changed(); + } +} + +void +SwapNodes::mutate(Random& rng, Machine& machine) +{ + //cout << "SWAP NODE" << endl; + + if (machine.nodes().size() <= 1) { + return; + } + + SPtr<Node> a = machine.random_node(); + SPtr<Node> b = machine.random_node(); + while (b == a) { + b = machine.random_node(); + } + + SPtr<MidiAction> a_enter = dynamic_ptr_cast<MidiAction>(a->enter_action()); + SPtr<MidiAction> a_exit = dynamic_ptr_cast<MidiAction>(a->exit_action()); + SPtr<MidiAction> b_enter = dynamic_ptr_cast<MidiAction>(b->enter_action()); + SPtr<MidiAction> b_exit = dynamic_ptr_cast<MidiAction>(b->exit_action()); + + uint8_t note_a = a_enter->event()[1]; + uint8_t note_b = b_enter->event()[1]; + + a_enter->event()[1] = note_b; + a_exit->event()[1] = note_b; + b_enter->event()[1] = note_a; + b_exit->event()[1] = note_a; +} + +void +AddEdge::mutate(Random& rng, Machine& machine) +{ + //cout << "ADJUST EDGE" << endl; + + SPtr<Node> tail = machine.random_node(); + SPtr<Node> head = machine.random_node(); + + if (tail && head && tail != head) { + // && !tail->connected_to(head) && !head->connected_to(tail) + SPtr<Edge> edge(new Edge(tail, head)); + edge->set_probability(rand() / (float)RAND_MAX); + tail->add_edge(SPtr<Edge>(new Edge(tail, head))); + } +} + +void +RemoveEdge::mutate(Random& rng, Machine& machine) +{ + //cout << "REMOVE EDGE" << endl; + + SPtr<Node> tail = machine.random_node(); + if (tail && !(tail->is_initial() && tail->edges().size() == 1)) { + tail->remove_edge(tail->random_edge()); + } +} + +void +AdjustEdge::mutate(Random& rng, Machine& machine) +{ + //cout << "ADJUST EDGE" << endl; + + SPtr<Edge> edge = machine.random_edge(); + if (edge) { + edge->set_probability(rand() / (float)RAND_MAX); + edge->tail().lock()->edges_changed(); + } +} + +} // namespace Mutation +} // namespace machina diff --git a/src/engine/Node.cpp b/src/engine/Node.cpp new file mode 100644 index 0000000..7d8a870 --- /dev/null +++ b/src/engine/Node.cpp @@ -0,0 +1,271 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <cassert> +#include <iostream> + +#include "sord/sordmm.hpp" + +#include "machina/Atom.hpp" +#include "machina/URIs.hpp" + +#include "ActionFactory.hpp" +#include "Edge.hpp" +#include "Node.hpp" + +using namespace Raul; +using namespace std; + +namespace machina { + +Node::Node(TimeDuration duration, bool initial) + : _enter_time(duration.unit()) + , _duration(duration) + , _is_initial(initial) + , _is_selector(false) + , _is_active(false) +{} + +Node::Node(const Node& copy) + : Stateful() // don't copy RDF ID + , _enter_time(copy._enter_time) + , _duration(copy._duration) + , _enter_action(ActionFactory::copy(copy._enter_action)) + , _exit_action(ActionFactory::copy(copy._exit_action)) + , _is_initial(copy._is_initial) + , _is_selector(copy._is_selector) + , _is_active(false) +{ + for (Edges::const_iterator i = copy._edges.begin(); i != copy._edges.end(); + ++i) { + SPtr<Edge> edge(new Edge(*i->get())); + _edges.insert(edge); + } +} + +static inline bool +action_equals(SPtr<const Action> a, SPtr<const Action> b) +{ + return (a == b) || (a && b && *a.get() == *b.get()); +} + +bool +Node::operator==(const Node& rhs) const +{ + return _duration == rhs._duration && + _is_initial == rhs._is_initial && + _is_selector == rhs._is_selector && + _is_active == rhs._is_active && + action_equals(_enter_action, rhs.enter_action()) && + action_equals(_exit_action, rhs.exit_action()); + // TODO: compare edges +} + +/** Always returns an edge, unless there are none */ +SPtr<Edge> +Node::random_edge() +{ + SPtr<Edge> ret; + if (_edges.empty()) { + return ret; + } + + size_t i = rand() % _edges.size(); + + // FIXME: O(n) worst case :( + for (Edges::const_iterator e = _edges.begin(); e != _edges.end(); ++e, + --i) { + if (i == 0) { + ret = *e; + break; + } + } + + return ret; +} + +void +Node::edges_changed() +{ + if (!_is_selector) { + return; + } + + // Normalize edge probabilities if we're a selector + double prob_sum = 0; + + for (Edges::iterator i = _edges.begin(); i != _edges.end(); ++i) { + prob_sum += (*i)->probability(); + } + + for (Edges::iterator i = _edges.begin(); i != _edges.end(); ++i) { + (*i)->set_probability((*i)->probability() / prob_sum); + } + + _changed = true; +} + +void +Node::set_selector(bool yn) +{ + _is_selector = yn; + + if (yn) { + edges_changed(); + } + + _changed = true; +} + +void +Node::set_enter_action(SPtr<Action> action) +{ + _enter_action = action; + _changed = true; +} + +void +Node::set_exit_action(SPtr<Action> action) +{ + _exit_action = action; + _changed = true; +} + +void +Node::enter(MIDISink* sink, TimeStamp time) +{ + if (!_is_active) { + _changed = true; + _is_active = true; + _enter_time = time; + + if (sink && _enter_action) { + _enter_action->execute(sink, time); + } + } +} + +void +Node::exit(MIDISink* sink, TimeStamp time) +{ + if (_is_active) { + if (sink && _exit_action) { + _exit_action->execute(sink, time); + } + + _changed = true; + _is_active = false; + _enter_time = 0; + } +} + +SPtr<Edge> +Node::edge_to(SPtr<Node> head) const +{ + // TODO: Make logarithmic + for (Edges::const_iterator i = _edges.begin(); i != _edges.end(); ++i) { + if ((*i)->head() == head) { + return *i; + } + } + return SPtr<Edge>(); +} + +void +Node::add_edge(SPtr<Edge> edge) +{ + assert(edge->tail().lock().get() == this); + if (edge_to(edge->head())) { + return; + } + + _edges.insert(edge); + edges_changed(); +} + +void +Node::remove_edge(SPtr<Edge> edge) +{ + _edges.erase(edge); + edges_changed(); +} + +bool +Node::connected_to(SPtr<Node> node) +{ + return bool(edge_to(node)); +} + +SPtr<Edge> +Node::remove_edge_to(SPtr<Node> node) +{ + SPtr<Edge> edge = edge_to(node); + if (edge) { + _edges.erase(edge); // TODO: avoid double search + edges_changed(); + } + return edge; +} + +void +Node::set(URIInt key, const Atom& value) +{ + if (key == URIs::instance().machina_initial) { + std::cerr << "error: Attempt to change node initial state" << std::endl; + } else if (key == URIs::instance().machina_selector) { + set_selector(value.get<int32_t>()); + } +} + +void +Node::write_state(Sord::Model& model) +{ + using namespace Raul; + + const Sord::Node& rdf_id = this->rdf_id(model.world()); + + if (_is_selector) + model.add_statement( + rdf_id, + Sord::URI(model.world(), MACHINA_URI_RDF "type"), + Sord::URI(model.world(), MACHINA_NS_SelectorNode)); + else + model.add_statement( + rdf_id, + Sord::URI(model.world(), MACHINA_URI_RDF "type"), + Sord::URI(model.world(), MACHINA_NS_Node)); + + model.add_statement( + rdf_id, + Sord::URI(model.world(), MACHINA_NS_duration), + Sord::Literal::decimal(model.world(), _duration.to_double(), 7)); + + if (_enter_action) { + _enter_action->write_state(model); + model.add_statement(rdf_id, + Sord::URI(model.world(), MACHINA_NS_onEnter), + _enter_action->rdf_id(model.world())); + } + + if (_exit_action) { + _exit_action->write_state(model); + model.add_statement(rdf_id, + Sord::URI(model.world(), MACHINA_NS_onExit), + _exit_action->rdf_id(model.world())); + } +} + +} // namespace machina diff --git a/src/engine/Node.hpp b/src/engine/Node.hpp new file mode 100644 index 0000000..cae75f5 --- /dev/null +++ b/src/engine/Node.hpp @@ -0,0 +1,116 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_NODE_HPP +#define MACHINA_NODE_HPP + +#include <set> + +#include "machina/types.hpp" + +#include "Action.hpp" +#include "MIDISink.hpp" +#include "Schrodinbit.hpp" +#include "Stateful.hpp" + +namespace machina { + +class Edge; +using Raul::TimeDuration; +using Raul::TimeStamp; +using Raul::TimeUnit; + +/** A node is a state (as in a FSM diagram), or "note". + * + * It contains a action, as well as a duration and pointers to its + * successors (states/nodes that (may) follow it). + * + * Initial nodes do not have enter actions (since they are entered at + * an undefined point in time <= 0). + */ +class Node : public Stateful +{ +public: + Node(TimeDuration duration, bool initial=false); + Node(const Node& copy); + + bool operator==(const Node& rhs) const; + + void set_enter_action(SPtr<Action> action); + void set_exit_action(SPtr<Action> action); + + SPtr<const Action> enter_action() const { return _enter_action; } + SPtr<Action> enter_action() { return _enter_action; } + SPtr<const Action> exit_action() const { return _exit_action; } + SPtr<Action> exit_action() { return _exit_action; } + + void enter(MIDISink* sink, TimeStamp time); + void exit(MIDISink* sink, TimeStamp time); + + void edges_changed(); + + void add_edge(SPtr<Edge> edge); + void remove_edge(SPtr<Edge> edge); + SPtr<Edge> remove_edge_to(SPtr<Node> node); + bool connected_to(SPtr<Node> node); + + void set(URIInt key, const Atom& value); + void write_state(Sord::Model& model); + + bool is_initial() const { return _is_initial; } + bool is_active() const { return _is_active; } + TimeStamp enter_time() const { return _enter_time; } + TimeStamp exit_time() const { return _enter_time + _duration; } + TimeDuration duration() const { return _duration; } + void set_duration(TimeDuration d) { _duration = d; } + bool is_selector() const { return _is_selector; } + void set_selector(bool i); + + inline bool changed() { return _changed; } + inline void set_changed() { _changed = true; } + + struct EdgeHeadOrder { + inline bool operator()(const SPtr<const Edge>& a, + const SPtr<const Edge>& b) { + return a.get() < b.get(); + } + }; + + SPtr<Edge> edge_to(SPtr<Node> head) const; + + typedef std::set<SPtr<Edge>, EdgeHeadOrder> Edges; + + Edges& edges() { return _edges; } + + SPtr<Edge> random_edge(); + +private: + Node& operator=(const Node& other); // undefined + + TimeStamp _enter_time; ///< valid iff _is_active + TimeDuration _duration; + SPtr<Action> _enter_action; + SPtr<Action> _exit_action; + Edges _edges; + Schrodinbit _changed; + bool _is_initial; + bool _is_selector; + bool _is_active; +}; + +} // namespace machina + +#endif // MACHINA_NODE_HPP diff --git a/src/engine/Problem.cpp b/src/engine/Problem.cpp new file mode 100644 index 0000000..3b05fed --- /dev/null +++ b/src/engine/Problem.cpp @@ -0,0 +1,383 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#define __STDC_LIMIT_MACROS 1 + +#include <stdint.h> + +#include <set> +#include <vector> +#include <iostream> + +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" + +#include "eugene/Problem.hpp" + +#include "machina/Machine.hpp" + +#include "ActionFactory.hpp" +#include "Edge.hpp" +#include "MidiAction.hpp" +#include "Problem.hpp" +#include "SMFReader.hpp" +#include "machina_config.h" + +using namespace std; + +namespace machina { + +Problem::Problem(TimeUnit unit, + const std::string& target_midi, + SPtr<Machine> seed) + : _unit(unit) + , _target(*this) + , _seed(new Machine(*seed.get())) +{ + SMFReader smf; + const bool opened = smf.open(target_midi); + assert(opened); + + smf.seek_to_track(2); // FIXME: ? + + uint8_t buf[4]; + uint32_t ev_size; + uint32_t delta_time; + while (smf.read_event(4, buf, &ev_size, &delta_time) >= 0) { + // time ignored + _target.write_event(TimeStamp(_unit, 0.0), ev_size, buf); +#if 0 + //_target._length += delta_time / (double)smf.ppqn(); + if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_ON) { + const uint8_t note = buf[1]; + /*++_target._note_frequency[note]; + ++_target.n_notes();*/ + ++_target._counts[note]; + _target._notes.push_back(note); + } +#endif + } + + cout << "Target notes: " << _target.n_notes() << endl; + + _target.compute(); +} + +float +Problem::evaluate(const Machine& const_machine) const +{ + #if 0 + //cout << "("; + + // kluuudge + Machine& machine = const_cast<Machine&>(const_machine); + + map<Machine*, float>::const_iterator cached = _fitness.find(&machine); + if (cached != _fitness.end()) { + return cached->second; + } + + SPtr<Evaluator> eval(new Evaluator(*this)); + + //machine.reset(); + machine.set_sink(eval); + + // FIXME: timing stuff here isn't right at all... + + static const unsigned ppqn = MACHINA_PPQN; + Raul::TimeSlice time(ppqn, ppqn, 120.0); + time.set_slice(TimeStamp(_unit, 0, 0), TimeDuration(_unit, 2 * ppqn)); + + machine.run(time); + if (eval->n_notes() == 0) { + return 0.0f; // bad dog + } + TimeStamp end(_unit, time.start_ticks().ticks() + 2 * ppqn); + time.set_slice(end, TimeStamp(_unit, 0, 0)); + + while (eval->n_notes() < _target.n_notes()) { + machine.run(time); + if (machine.is_finished()) { + machine.reset(time.start_ticks()); + } + time.set_slice(end, TimeStamp(end.unit(), 0, 0)); + } + + eval->compute(); + + // count +#if 0 + float f = 0; + + for (uint8_t i = 0; i < 128; ++i) { + /*if (eval->_note_frequency[i] <= _target._note_frequency[i]) + f += eval->_note_frequency[i]; + else + f -= _target._note_frequency[i] - eval->_note_frequency[i];*/ + //f -= fabs(eval->_note_frequency[i] - _target._note_frequency[i]); + } +#endif + //cout << ")"; + + // distance + //float f = distance(eval->_notes, _target._notes); + + float f = 0.0; + + for (Evaluator::Patterns::const_iterator i = eval->_patterns.begin(); + i != eval->_patterns.end(); ++i) { + // Reward for matching patterns + if (_target._patterns.find(i->first) != _target._patterns.end()) { + Evaluator::Patterns::const_iterator c = _target._patterns.find( + i->first); + const uint32_t cnt + = (c == _target._patterns.end()) ? 1 : c->second; + f += min(i->second, cnt) * (i->first.length()); + + } else { + // Punish for bad patterns + const uint32_t invlen = (eval->_order - i->first.length() + 1); + f -= (i->second / (float)eval->_patterns.size() + * (float)(invlen * invlen * invlen)) * 4; + } + } + + // Punish for missing patterns + for (Evaluator::Patterns::const_iterator i = _target._patterns.begin(); + i != _target._patterns.end(); ++i) { + if (eval->_patterns.find(i->first) == _target._patterns.end()) { + f -= i->second / (float)_target.n_notes() + * (float)(eval->_order - i->first.length() + 1); + } + } + + //cout << "f = " << f << endl; + + _fitness[&machine] = f; + + return f; + #endif + return 0.0f; +} + +void +Problem::Evaluator::write_event(Raul::TimeStamp time, + size_t ev_size, + const uint8_t* ev) throw (std::logic_error) +{ + if ((ev[0] & 0xF0) == LV2_MIDI_MSG_NOTE_ON) { + + const uint8_t note = ev[1]; + + if (_first_note == 0) { + _first_note = note; + } + + /*++_note_frequency[note]; + ++n_notes();*/ + //_notes.push_back(note); + if (_read.length() == 0) { + _read = note; + return; + } + if (_read.length() == _order) { + _read = _read.substr(1); + } + + _read = _read + (char)note; + + for (size_t i = 0; i < _read.length(); ++i) { + const string pattern = _read.substr(i); + Patterns::iterator p = _patterns.find(pattern); + if (p != _patterns.end()) { + ++(p->second); + } else { + _patterns[pattern] = 1; + } + } + + ++_counts[note]; + ++_n_notes; + + } +} + +void +Problem::Evaluator::compute() +{ + /* + for (uint8_t i=0; i < 128; ++i) { + if (_note_frequency[i] > 0) { + _note_frequency[i] /= (float)n_notes(); + //cout << (int)i << ":\t" << _note_frequency[i] << endl; + } + }*/ +} + +void +Problem::initial_population(eugene::Random& rng, + Population& pop, + size_t gene_size, + size_t pop_size) const +{ + // FIXME: ignores _seed and builds based on MIDI + // evolution of the visible machine would be nice.. + SPtr<Machine> base = SPtr<Machine>(new Machine(_unit)); + for (uint8_t i = 0; i < 128; ++i) { + if (_target._counts[i] > 0) { + //cout << "Initial note: " << (int)i << endl; + SPtr<Node> node(new Node(TimeDuration(_unit, 1 / 2.0))); + node->set_enter_action(ActionFactory::note_on(i)); + node->set_exit_action(ActionFactory::note_off(i)); + node->set_selector(true); + base->add_node(node); + } + } + + for (size_t i = 0; i < pop_size; ++i) { + // FIXME: double copy + Machine m(*base.get()); + + set< SPtr<Node> > unreachable; + + for (Machine::Nodes::iterator i = m.nodes().begin(); i != m.nodes().end(); + ++i) { + if (dynamic_ptr_cast<MidiAction>((*i)->enter_action())->event()[1] == + _target.first_note()) { + m.initial_node()->add_edge( + SPtr<Edge>(new Edge(m.initial_node(), *i))); + } else { + unreachable.insert(*i); + } + } + + SPtr<Node> cur = m.initial_node(); + unreachable.erase(cur); + SPtr<Node> head; + + while (!unreachable.empty()) { + if (rand() % 2) { + head = m.random_node(); + } else { + head = *unreachable.begin(); + } + + if (!head->connected_to(head) ) { + cur->add_edge(SPtr<Edge>(new Edge(cur, head))); + unreachable.erase(head); + cur = head; + } + } + + pop.push_back(m); + + /*cout << "initial # nodes: " << m.nodes().size(); + cout << "initial fitness: " << fitness(m) << endl;*/ + } +} + +/** levenshtein distance (edit distance) */ +size_t +Problem::distance(const std::vector<uint8_t>& source, + const std::vector<uint8_t>& target) const +{ + // Derived from http://www.merriampark.com/ldcpp.htm + + assert(source.size() < UINT16_MAX); + assert(target.size() < UINT16_MAX); + + // Step 1 + + const uint16_t n = source.size(); + const uint16_t m = target.size(); + if (n == 0) { + return m; + } + if (m == 0) { + return n; + } + + _matrix.resize(n + 1); + for (uint16_t i = 0; i <= n; i++) { + _matrix[i].resize(m + 1); + } + + // Step 2 + + for (uint16_t i = 0; i <= n; i++) { + _matrix[i][0] = i; + } + + for (uint16_t j = 0; j <= m; j++) { + _matrix[0][j] = j; + } + + // Step 3 + + for (uint16_t i = 1; i <= n; i++) { + + const uint8_t s_i = source[i - 1]; + + // Step 4 + + for (uint16_t j = 1; j <= m; j++) { + + const uint8_t t_j = target[j - 1]; + + // Step 5 + + uint16_t cost; + if (s_i == t_j) { + cost = 0; + } else { + cost = 1; + } + + // Step 6 + + const uint16_t above = _matrix[i - 1][j]; + const uint16_t left = _matrix[i][j - 1]; + const uint16_t diag = _matrix[i - 1][j - 1]; + uint16_t cell = min(above + 1, min(left + 1, diag + cost)); + + // Step 6A: Cover transposition, in addition to deletion, + // insertion and substitution. This step is taken from: + // Berghel, Hal ; Roach, David : "An Extension of Ukkonen's + // Enhanced Dynamic Programming ASM Algorithm" + // (http://www.acm.org/~hlb/publications/asm/asm.html) + + if (i > 2 && j > 2) { + uint16_t trans = _matrix[i - 2][j - 2] + 1; + if (source[i - 2] != t_j) { + trans++; + } + if (s_i != target[j - 2]) { + trans++; + } + if (cell > trans) { + cell = trans; + } + } + + _matrix[i][j] = cell; + } + } + + // Step 7 + + return _matrix[n][m]; +} + +} // namespace machina diff --git a/src/engine/Problem.hpp b/src/engine/Problem.hpp new file mode 100644 index 0000000..81e67d7 --- /dev/null +++ b/src/engine/Problem.hpp @@ -0,0 +1,135 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_PROBLEM_HPP +#define MACHINA_PROBLEM_HPP + +#include <stdint.h> + +#include <map> +#include <stdexcept> + +#include "machina/Machine.hpp" +#include "eugene/Problem.hpp" + +#include "MIDISink.hpp" + +namespace machina { + +class Problem : public eugene::Problem<Machine> { +public: + Problem(TimeUnit unit, + const std::string& target_midi, + SPtr<Machine> seed = SPtr<Machine>()); + virtual ~Problem() {} + + void seed(SPtr<Machine> parent) { _seed = parent; } + + float evaluate(const Machine& machine) const; + + bool fitness_less_than(float a, float b) const { return a < b; } + + void clear_fitness_cache() { _fitness.clear(); } + + void initial_population(eugene::Random& rng, + Population& pop, + size_t gene_size, + size_t pop_size) const; + +private: + size_t distance(const std::vector<uint8_t>& source, + const std::vector<uint8_t>& target) const; + + // count + /*struct FreqEvaluator : public Raul::MIDISink { + Evaluator(const Problem& problem) + : _problem(problem), _n_notes(0), _length(0) { + for (uint8_t i=0; i < 128; ++i) + _note_frequency[i] = 0; + } + void write_event(Raul::BeatTime time, + size_t ev_size, + const uint8_t* ev) throw (std::logic_error); + void compute(); + const Problem& _problem; + + size_t n_notes() const { return _n_notes; } + + float _note_frequency[128]; + size_t _n_notes; + double _length; + };*/ + + // distance + /*struct Evaluator : public Raul::MIDISink { + Evaluator(const Problem& problem) : _problem(problem) {} + void write_event(Raul::BeatTime time, + size_t ev_size, + const uint8_t* ev) throw (std::logic_error); + void compute(); + const Problem& _problem; + + size_t n_notes() const { return _notes.size(); } + + std::vector<uint8_t> _notes; + float _counts[128]; + };*/ + + struct Evaluator : public MIDISink { + explicit Evaluator(const Problem& problem) + : _problem(problem) + , _order(4) + , _n_notes(0) + , _first_note(0) + { + for (uint8_t i=0; i < 128; ++i) + _counts[i] = 0; + } + void write_event(TimeStamp time, + size_t ev_size, + const uint8_t* ev) throw (std::logic_error); + void compute(); + const Problem& _problem; + + size_t n_notes() const { return _n_notes; } + uint8_t first_note() const { return _first_note; } + + const uint32_t _order; + + std::string _read; + + typedef std::map<std::string, uint32_t> Patterns; + Patterns _patterns; + uint32_t _counts[128]; + size_t _n_notes; + uint8_t _first_note; + }; + + TimeUnit _unit; + + Evaluator _target; + SPtr<Machine> _seed; + + /// for levenshtein distance + mutable std::vector< std::vector<uint16_t> > _matrix; + + /// memoization + mutable std::map<Machine*, float> _fitness; +}; + +} // namespace machina + +#endif // MACHINA_PROBLEM_HPP diff --git a/src/engine/Recorder.cpp b/src/engine/Recorder.cpp new file mode 100644 index 0000000..21dfd4b --- /dev/null +++ b/src/engine/Recorder.cpp @@ -0,0 +1,69 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <ios> +#include <iostream> + +#include "MachineBuilder.hpp" +#include "Recorder.hpp" + +using namespace std; +using namespace Raul; + +namespace machina { + +Recorder::Recorder(Forge& forge, + size_t buffer_size, + TimeUnit unit, + double q, + bool step) + : _unit(unit) + , _record_buffer(buffer_size) + , _builder(new MachineBuilder(SPtr<Machine>(new Machine(unit)), q, step)) +{} + +void +Recorder::_whipped() +{ + TimeStamp t(_unit); + size_t size; + unsigned char buf[4]; + + while (true) { + bool success = _record_buffer.read(sizeof(TimeStamp), (uint8_t*)&t); + if (success) { + success = _record_buffer.read(sizeof(size_t), (uint8_t*)&size); + } + if (success) { + success = _record_buffer.read(size, buf); + } + if (success) { + _builder->event(t, size, buf); + } else { + break; + } + } +} + +SPtr<Machine> +Recorder::finish() +{ + SPtr<Machine> machine = _builder->finish(); + _builder.reset(); + return machine; +} + +} diff --git a/src/engine/Recorder.hpp b/src/engine/Recorder.hpp new file mode 100644 index 0000000..c98c42b --- /dev/null +++ b/src/engine/Recorder.hpp @@ -0,0 +1,68 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_RECORDER_HPP +#define MACHINA_RECORDER_HPP + +#include "raul/RingBuffer.hpp" + +#include "machina/Machine.hpp" +#include "machina/types.hpp" + +#include "Slave.hpp" + +namespace machina { + +class MachineBuilder; + +class Recorder + : public Slave +{ +public: + Recorder(Forge& forge, + size_t buffer_size, + TimeUnit unit, + double q, + bool step); + + inline void write(Raul::TimeStamp time, size_t size, + const unsigned char* buf) { + if (_record_buffer.write_space() < + (sizeof(TimeStamp) + sizeof(size_t) + size)) { + std::cerr << "Record buffer overflow" << std::endl; + return; + } else { + _record_buffer.write(sizeof(TimeStamp), (uint8_t*)&time); + _record_buffer.write(sizeof(size_t), (uint8_t*)&size); + _record_buffer.write(size, buf); + } + } + + SPtr<MachineBuilder> builder() { return _builder; } + + SPtr<Machine> finish(); + +private: + virtual void _whipped(); + + TimeUnit _unit; + Raul::RingBuffer _record_buffer; + SPtr<MachineBuilder> _builder; +}; + +} // namespace machina + +#endif // MACHINA_RECORDER_HPP diff --git a/src/engine/SMFDriver.cpp b/src/engine/SMFDriver.cpp new file mode 100644 index 0000000..3dc51e2 --- /dev/null +++ b/src/engine/SMFDriver.cpp @@ -0,0 +1,162 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <iostream> +#include <list> + +#include "machina/Context.hpp" +#include "machina/Machine.hpp" +#include "machina/types.hpp" + +#include "Edge.hpp" +#include "SMFDriver.hpp" +#include "SMFReader.hpp" +#include "SMFWriter.hpp" +#include "quantize.hpp" + +using namespace std; + +namespace machina { + +SMFDriver::SMFDriver(Forge& forge, Raul::TimeUnit unit) + : Driver(forge, SPtr<Machine>()) +{ + _writer = SPtr<SMFWriter>(new SMFWriter(unit)); +} + +/** Learn a single track from the MIDI file at `uri` + * + * @param track The track of the MIDI file to import, starting from 1. + * + * Currently only file:// URIs are supported. + * @return the resulting machine. + */ +SPtr<Machine> +SMFDriver::learn(const string& filename, + unsigned track, + double q, + Raul::TimeDuration max_duration) +{ + //assert(q.unit() == max_duration.unit()); + SPtr<Machine> m(new Machine(max_duration.unit())); + SPtr<MachineBuilder> builder = SPtr<MachineBuilder>( + new MachineBuilder(m, q, false)); + SMFReader reader; + + if (!reader.open(filename)) { + cerr << "Unable to open MIDI file " << filename << endl; + return SPtr<Machine>(); + } + + if (track > reader.num_tracks()) { + return SPtr<Machine>(); + } else { + learn_track(builder, reader, track, q, max_duration); + } + + m->reset(NULL, m->time()); + + if (m->nodes().size() > 1) { + return m; + } else { + return SPtr<Machine>(); + } +} + +/** Learn all tracks from a MIDI file into a single machine. + * + * This will result in one disjoint subgraph in the machine for each track. + */ +SPtr<Machine> +SMFDriver::learn(const string& filename, double q, Raul::TimeStamp max_duration) +{ + SPtr<Machine> m(new Machine(max_duration.unit())); + SPtr<MachineBuilder> builder = SPtr<MachineBuilder>( + new MachineBuilder(m, q, false)); + SMFReader reader; + + if (!reader.open(filename)) { + cerr << "Unable to open MIDI file " << filename << endl; + return SPtr<Machine>(); + } + + for (unsigned t = 1; t <= reader.num_tracks(); ++t) { + builder->reset(); + learn_track(builder, reader, t, q, max_duration); + } + + m->reset(NULL, m->time()); + + if (m->nodes().size() > 1) { + return m; + } else { + return SPtr<Machine>(); + } +} + +void +SMFDriver::learn_track(SPtr<MachineBuilder> builder, + SMFReader& reader, + unsigned track, + double q, + Raul::TimeDuration max_duration) +{ + const bool found_track = reader.seek_to_track(track); + if (!found_track) { + return; + } + + uint8_t buf[4]; + uint32_t ev_size; + uint32_t ev_delta_time; + + Raul::TimeUnit unit = Raul::TimeUnit(TimeUnit::BEATS, MACHINA_PPQN); + + uint64_t t = 0; + while (reader.read_event(4, buf, &ev_size, &ev_delta_time) >= 0) { + t += ev_delta_time; + + const uint32_t beats = t / (uint32_t)reader.ppqn(); + const uint32_t smf_ticks = t % (uint32_t)reader.ppqn(); + const double frac = smf_ticks / (double)reader.ppqn(); + const uint32_t ticks = frac * MACHINA_PPQN; + + if (!max_duration.is_zero() && t > max_duration.to_double()) { + break; + } + + if (ev_size > 0) { + // TODO: quantize + builder->event(TimeStamp(unit, beats, ticks), ev_size, buf); + } + } + + builder->resolve(); +} + +void +SMFDriver::run(SPtr<Machine> machine, Raul::TimeStamp max_time) +{ + // FIXME: unit kludge (tempo only) + Context context(_forge, machine->time().unit().ppt(), + _writer->unit().ppt(), 120.0); + context.set_sink(this); + context.time().set_slice(TimeStamp(max_time.unit(), 0, 0), + context.time().beats_to_ticks(max_time)); + machine->run(context, SPtr<Raul::RingBuffer>()); +} + +} // namespace machina diff --git a/src/engine/SMFDriver.hpp b/src/engine/SMFDriver.hpp new file mode 100644 index 0000000..e8b9a4b --- /dev/null +++ b/src/engine/SMFDriver.hpp @@ -0,0 +1,67 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_SMFDRIVER_HPP +#define MACHINA_SMFDRIVER_HPP + +#include "machina/Driver.hpp" +#include "machina/types.hpp" + +#include "MachineBuilder.hpp" +#include "SMFReader.hpp" +#include "SMFWriter.hpp" + +namespace machina { + +class Node; +class Machine; + +class SMFDriver : public Driver +{ +public: + SMFDriver(Forge& forge, Raul::TimeUnit unit); + + SPtr<Machine> learn(const std::string& filename, + double q, + Raul::TimeDuration max_duration); + + SPtr<Machine> learn(const std::string& filename, + unsigned track, + double q, + Raul::TimeDuration max_duration); + + void run(SPtr<Machine> machine, Raul::TimeStamp max_time); + + void write_event(Raul::TimeStamp time, + size_t ev_size, + const unsigned char* ev) + { _writer->write_event(time, ev_size, ev); } + + SPtr<SMFWriter> writer() { return _writer; } + +private: + SPtr<SMFWriter> _writer; + + void learn_track(SPtr<MachineBuilder> builder, + SMFReader& reader, + unsigned track, + double q, + Raul::TimeDuration max_duration); +}; + +} // namespace machina + +#endif // MACHINA_SMFDRIVER_HPP diff --git a/src/engine/SMFReader.cpp b/src/engine/SMFReader.cpp new file mode 100644 index 0000000..5976f75 --- /dev/null +++ b/src/engine/SMFReader.cpp @@ -0,0 +1,323 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <arpa/inet.h> +#include <cassert> +#include <cstdio> +#include <cstring> +#include <string> + +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" + +#include "SMFReader.hpp" + +using std::endl; + +namespace machina { + +/** Return the size of the given event NOT including the status byte, + * or -1 if unknown (eg sysex) + */ +static int +midi_event_size(unsigned char status) +{ + if (status >= 0x80 && status <= 0xE0) { + status &= 0xF0; // mask off the channel + } + + switch (status) { + case LV2_MIDI_MSG_NOTE_OFF: + case LV2_MIDI_MSG_NOTE_ON: + case LV2_MIDI_MSG_NOTE_PRESSURE: + case LV2_MIDI_MSG_CONTROLLER: + case LV2_MIDI_MSG_BENDER: + case LV2_MIDI_MSG_SONG_POS: + return 2; + + case LV2_MIDI_MSG_PGM_CHANGE: + case LV2_MIDI_MSG_CHANNEL_PRESSURE: + case LV2_MIDI_MSG_MTC_QUARTER: + case LV2_MIDI_MSG_SONG_SELECT: + return 1; + + case LV2_MIDI_MSG_TUNE_REQUEST: + case LV2_MIDI_MSG_CLOCK: + case LV2_MIDI_MSG_START: + case LV2_MIDI_MSG_CONTINUE: + case LV2_MIDI_MSG_STOP: + case LV2_MIDI_MSG_ACTIVE_SENSE: + case LV2_MIDI_MSG_RESET: + return 0; + + case LV2_MIDI_MSG_SYSTEM_EXCLUSIVE: + return -1; + } + + return -1; +} + +SMFReader::SMFReader(const std::string filename) + : _fd(NULL) + , _ppqn(0) + , _track(0) + , _track_size(0) +{ + if (filename.length() > 0) { + open(filename); + } +} + +SMFReader::~SMFReader() +{ + if (_fd) { + close(); + } +} + +bool +SMFReader::open(const std::string& filename) +{ + if (_fd) { + throw std::logic_error( + "Attempt to start new read while write in progress."); + } + + std::cout << "Opening SMF file " << filename << " for reading." << endl; + + _fd = fopen(filename.c_str(), "r+"); + + if (_fd) { + // Read type (bytes 8..9) + fseek(_fd, 0, SEEK_SET); + char mthd[5]; + mthd[4] = '\0'; + fread(mthd, 1, 4, _fd); + if (strcmp(mthd, "MThd")) { + std::cerr << filename << " is not an SMF file, aborting." << endl; + fclose(_fd); + _fd = NULL; + return false; + } + + // Read type (bytes 8..9) + fseek(_fd, 8, SEEK_SET); + uint16_t type_be = 0; + fread(&type_be, 2, 1, _fd); + _type = ntohs(type_be); + + // Read number of tracks (bytes 10..11) + uint16_t num_tracks_be = 0; + fread(&num_tracks_be, 2, 1, _fd); + _num_tracks = ntohs(num_tracks_be); + + // Read PPQN (bytes 12..13) + uint16_t ppqn_be = 0; + fread(&ppqn_be, 2, 1, _fd); + _ppqn = ntohs(ppqn_be); + + // TODO: Absolute (SMPTE seconds) time support + if ((_ppqn & 0x8000) != 0) { + throw UnsupportedTime(); + } + + seek_to_track(1); + + return true; + } else { + return false; + } +} + +/** Seek to the start of a given track, starting from 1. + * Returns true if specified track was found. + */ +bool +SMFReader::seek_to_track(unsigned track) +{ + if (track == 0) { + throw std::logic_error("Seek to track 0 out of range (must be >= 1)"); + } + + if (!_fd) { + throw std::logic_error("Attempt to seek to track on unopened SMF file."); + } + + unsigned track_pos = 0; + + fseek(_fd, 14, SEEK_SET); + char id[5]; + id[4] = '\0'; + uint32_t chunk_size = 0; + + while (!feof(_fd)) { + fread(id, 1, 4, _fd); + + if (!strcmp(id, "MTrk")) { + ++track_pos; + } else { + std::cerr << "Unknown chunk ID " << id << endl; + } + + uint32_t chunk_size_be; + fread(&chunk_size_be, 4, 1, _fd); + chunk_size = ntohl(chunk_size_be); + + if (track_pos == track) { + break; + } + + fseek(_fd, chunk_size, SEEK_CUR); + } + + if (!feof(_fd) && track_pos == track) { + _track = track; + _track_size = chunk_size; + return true; + } else { + return false; + } +} + +/** Read an event from the current position in file. + * + * File position MUST be at the beginning of a delta time, or this will die very messily. + * ev.buffer must be of size ev.size, and large enough for the event. The returned event + * will have it's time field set to it's delta time (so it's the caller's responsibility + * to keep track of delta time, even for ignored events). + * + * Returns event length (including status byte) on success, 0 if event was + * skipped (eg a meta event), or -1 on EOF (or end of track). + * + * If `buf` is not large enough to hold the event, 0 will be returned, but ev_size + * set to the actual size of the event. + */ +int +SMFReader::read_event(size_t buf_len, + uint8_t* buf, + uint32_t* ev_size, + uint32_t* delta_time) +{ + if (_track == 0) { + throw std::logic_error("Attempt to read from unopened SMF file"); + } + + if (!_fd || feof(_fd)) { + return -1; + } + + assert(buf_len > 0); + assert(buf); + assert(ev_size); + assert(delta_time); + + // Running status state + static uint8_t last_status = 0; + static uint32_t last_size = 0; + + *delta_time = read_var_len(_fd); + int status = fgetc(_fd); + if (status == EOF) { + throw PrematureEOF(); + } else if (status > 0xFF) { + throw CorruptFile(); + } + + if (status < 0x80) { + if (last_status == 0) { + throw CorruptFile(); + } + status = last_status; + *ev_size = last_size; + fseek(_fd, -1, SEEK_CUR); + } else { + last_status = status; + *ev_size = midi_event_size(status) + 1; + last_size = *ev_size; + } + + buf[0] = static_cast<uint8_t>(status); + + if (status == 0xFF) { + *ev_size = 0; + if (feof(_fd)) { + throw PrematureEOF(); + } + uint8_t type = fgetc(_fd); + const uint32_t size = read_var_len(_fd); + + if (type == 0x2F) { + return -1; // we hit the logical EOF anyway... + } else { + fseek(_fd, size, SEEK_CUR); + return 0; + } + } + + if ((*ev_size > buf_len) || (*ev_size == 0) || feof(_fd)) { + // Skip event, return 0 + fseek(_fd, *ev_size - 1, SEEK_CUR); + return 0; + } else { + // Read event, return size + if (ferror(_fd)) { + throw CorruptFile(); + } + + fread(buf + 1, 1, *ev_size - 1, _fd); + + if (((buf[0] & 0xF0) == 0x90) && (buf[2] == 0) ) { + buf[0] = (0x80 | (buf[0] & 0x0F)); + buf[2] = 0x40; + } + + return *ev_size; + } +} + +void +SMFReader::close() +{ + if (_fd) { + fclose(_fd); + } + + _fd = NULL; +} + +uint32_t +SMFReader::read_var_len(FILE* fd) +{ + if (feof(fd)) { + throw PrematureEOF(); + } + + uint32_t value; + uint8_t c; + + if ((value = getc(fd)) & 0x80) { + value &= 0x7F; + do { + if (feof(fd)) { + throw PrematureEOF(); + } + value = (value << 7) + ((c = getc(fd)) & 0x7F); + } while (c & 0x80); + } + + return value; +} + +} // namespace machina diff --git a/src/engine/SMFReader.hpp b/src/engine/SMFReader.hpp new file mode 100644 index 0000000..7147f38 --- /dev/null +++ b/src/engine/SMFReader.hpp @@ -0,0 +1,87 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_SMF_READER_HPP +#define MACHINA_SMF_READER_HPP + +#include <stdexcept> +#include <string> +#include <inttypes.h> +#include "raul/TimeStamp.hpp" + +namespace machina { + +/** Standard Midi File (Type 0) Reader + * + * Currently this only reads SMF files with tempo-based timing. + * \ingroup raul + */ +class SMFReader +{ +public: + class PrematureEOF + : public std::exception + { + const char* what() const throw() { return "Unexpected end of file"; } + }; + class CorruptFile + : public std::exception + { + const char* what() const throw() { return "Corrupted file"; } + }; + class UnsupportedTime + : public std::exception + { + const char* what() const throw() { return + "Unsupported time stamp type (SMPTE)"; } + }; + + explicit SMFReader(const std::string filename = ""); + ~SMFReader(); + + bool open(const std::string& filename); + + bool seek_to_track(unsigned track); + + uint16_t type() const { return _type; } + uint16_t ppqn() const { return _ppqn; } + size_t num_tracks() { return _num_tracks; } + + int read_event(size_t buf_len, + uint8_t* buf, + uint32_t* ev_size, + uint32_t* ev_delta_time); + + void close(); + + static uint32_t read_var_len(FILE* fd); + +protected: + /** size of SMF header, including MTrk chunk header */ + static const uint32_t HEADER_SIZE = 22; + + std::string _filename; + FILE* _fd; + uint16_t _type; + uint16_t _ppqn; + uint16_t _num_tracks; + uint32_t _track; + uint32_t _track_size; +}; + +} // namespace machina + +#endif // MACHINA_SMF_READER_HPP diff --git a/src/engine/SMFWriter.cpp b/src/engine/SMFWriter.cpp new file mode 100644 index 0000000..25d25c9 --- /dev/null +++ b/src/engine/SMFWriter.cpp @@ -0,0 +1,241 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <arpa/inet.h> +#include <stdint.h> +#include <stdio.h> + +#include <cassert> +#include <cstdio> +#include <cstring> +#include <limits> +#include <string> + +#include "SMFWriter.hpp" + +using std::endl; + +namespace machina { + +/** Create a new SMF writer. + * + * @param unit Must match the time stamp of ALL events passed to write, or + * terrible things will happen. + * + * *** NOTE: Only beat time is implemented currently. + */ +SMFWriter::SMFWriter(Raul::TimeUnit unit) + : _fd(NULL) + , _unit(unit) + , _start_time(unit, 0, 0) + , _last_ev_time(unit, 0, 0) + , _track_size(0) + , _header_size(0) +{ + if (unit.type() == Raul::TimeUnit::BEATS) { + assert(unit.ppt() < std::numeric_limits<uint16_t>::max()); + } +} + +SMFWriter::~SMFWriter() +{ + if (_fd) { + finish(); + } +} + +/** Start a write to an SMF file. + * + * @param filename Filename to write to. + * @param start_time Beat time corresponding to t=0 in the file (timestamps + * passed to write_event will have this value subtracted before writing). + */ +bool +SMFWriter::start(const std::string& filename, + Raul::TimeStamp start_time) +{ + if (_fd) { + throw std::logic_error( + "Attempt to start new write while write in progress."); + } + + std::cout << "Opening SMF file " << filename << " for writing." << endl; + + _fd = fopen(filename.c_str(), "w+"); + + if (_fd) { + _track_size = 0; + _filename = filename; + _start_time = start_time; + _last_ev_time = 0; + // write a tentative header to pad file out so writing starts at the right offset + write_header(); + } + + return (_fd == 0) ? false : true; +} + +/** Write an event at the end of the file. + * + * @param time The absolute time of the event, relative to the start of the + * file (the start_time parameter to start). Must be monotonically increasing + * on successive calls to this method. + */ +void +SMFWriter::write_event(Raul::TimeStamp time, + size_t ev_size, + const unsigned char* ev) +{ + if (time < _start_time) { + throw std::logic_error("Event time is before file start time"); + } else if (time < _last_ev_time) { + throw std::logic_error("Event time not monotonically increasing"); + } else if (time.unit() != _unit) { + throw std::logic_error("Event has unexpected time unit"); + } + + Raul::TimeStamp delta_time = time; + delta_time -= _start_time; + + fseek(_fd, 0, SEEK_END); + + uint64_t delta_ticks = delta_time.ticks() * _unit.ppt() + + delta_time.subticks(); + size_t stamp_size = 0; + + /* If delta time is too long (i.e. overflows), write out empty + * "proprietary" events to reach the desired time. + * Any SMF reading application should interpret this correctly + * (by accumulating the delta time and ignoring the event) */ + while (delta_ticks > VAR_LEN_MAX) { + static unsigned char null_event[] = { 0xFF, 0x7F, 0x0 }; + stamp_size = write_var_len(VAR_LEN_MAX); + fwrite(null_event, 1, 3, _fd); + _track_size += stamp_size + 3; + delta_ticks -= VAR_LEN_MAX; + } + + assert(delta_ticks <= VAR_LEN_MAX); + stamp_size = write_var_len(static_cast<uint32_t>(delta_ticks)); + fwrite(ev, 1, ev_size, _fd); + + _last_ev_time = time; + _track_size += stamp_size + ev_size; +} + +void +SMFWriter::flush() +{ + if (_fd) { + fflush(_fd); + } +} + +void +SMFWriter::finish() +{ + if (!_fd) { + throw std::logic_error( + "Attempt to finish write with no write in progress."); + } + + write_footer(); + fclose(_fd); + _fd = NULL; +} + +void +SMFWriter::write_header() +{ + std::cout << "SMF Flushing header\n"; + + const uint16_t type = htons(0); // SMF Type 0 (single track) + const uint16_t ntracks = htons(1); // Number of tracks (always 1 for Type 0) + const uint16_t division = htons(_unit.ppt()); // PPQN + + char data[6]; + memcpy(data, &type, 2); + memcpy(data + 2, &ntracks, 2); + memcpy(data + 4, &division, 2); + //data[4] = _ppqn & 0xF0; + //data[5] = _ppqn & 0x0F; + + _fd = freopen(_filename.c_str(), "r+", _fd); + assert(_fd); + fseek(_fd, 0, 0); + write_chunk("MThd", 6, data); + write_chunk_header("MTrk", _track_size); +} + +void +SMFWriter::write_footer() +{ + std::cout << "Writing EOT\n"; + + fseek(_fd, 0, SEEK_END); + write_var_len(1); // whatever... + static const unsigned char eot[4] = { 0xFF, 0x2F, 0x00 }; // end-of-track meta-event + fwrite(eot, 1, 4, _fd); +} + +void +SMFWriter::write_chunk_header(const char id[4], uint32_t length) +{ + const uint32_t length_be = htonl(length); + + fwrite(id, 1, 4, _fd); + fwrite(&length_be, 4, 1, _fd); +} + +void +SMFWriter::write_chunk(const char id[4], uint32_t length, void* data) +{ + write_chunk_header(id, length); + + fwrite(data, 1, length, _fd); +} + +/** Write an SMF variable length value. + * + * @return size (in bytes) of the value written. + */ +size_t +SMFWriter::write_var_len(uint32_t value) +{ + size_t ret = 0; + + uint32_t buffer = value & 0x7F; + + while ((value >>= 7)) { + buffer <<= 8; + buffer |= ((value & 0x7F) | 0x80); + } + + while (true) { + //printf("Writing var len byte %X\n", (unsigned char)buffer); + ++ret; + fputc(buffer, _fd); + if (buffer & 0x80) { + buffer >>= 8; + } else { + break; + } + } + + return ret; +} + +} // namespace machina diff --git a/src/engine/SMFWriter.hpp b/src/engine/SMFWriter.hpp new file mode 100644 index 0000000..18bf3ec --- /dev/null +++ b/src/engine/SMFWriter.hpp @@ -0,0 +1,73 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_SMF_WRITER_HPP +#define MACHINA_SMF_WRITER_HPP + +#include <stdexcept> +#include <string> + +#include "raul/TimeStamp.hpp" + +#include "MIDISink.hpp" + +namespace machina { + +/** Standard Midi File (Type 0) Writer + * \ingroup raul + */ +class SMFWriter + : public MIDISink +{ +public: + explicit SMFWriter(Raul::TimeUnit unit); + ~SMFWriter(); + + bool start(const std::string& filename, + Raul::TimeStamp start_time); + + Raul::TimeUnit unit() const { return _unit; } + + void write_event(Raul::TimeStamp time, + size_t ev_size, + const unsigned char* ev); + + void flush(); + + void finish(); + +protected: + static const uint32_t VAR_LEN_MAX = 0x0FFFFFFF; + + void write_header(); + void write_footer(); + + void write_chunk_header(const char id[4], uint32_t length); + void write_chunk(const char id[4], uint32_t length, void* data); + size_t write_var_len(uint32_t val); + + std::string _filename; + FILE* _fd; + Raul::TimeUnit _unit; + Raul::TimeStamp _start_time; + Raul::TimeStamp _last_ev_time; ///< Time last event was written relative to _start_time + uint32_t _track_size; + uint32_t _header_size; ///< size of SMF header, including MTrk chunk header +}; + +} // namespace machina + +#endif // MACHINA_SMF_WRITER_HPP diff --git a/src/engine/Schrodinbit.hpp b/src/engine/Schrodinbit.hpp new file mode 100644 index 0000000..0721eb9 --- /dev/null +++ b/src/engine/Schrodinbit.hpp @@ -0,0 +1,42 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef SCHRODINBIT_HPP +#define SCHRODINBIT_HPP + +/** A flag which becomes false when its value is observed + */ +class Schrodinbit +{ +public: + Schrodinbit() : _flag(false) {} + + inline operator bool() { + const bool ret = _flag; + _flag = false; + return ret; + } + + inline bool operator=(bool flag) { + _flag = flag; + return flag; + } + +private: + bool _flag; +}; + +#endif // SCHRODINBIT_HPP diff --git a/src/engine/Slave.hpp b/src/engine/Slave.hpp new file mode 100644 index 0000000..74ec06b --- /dev/null +++ b/src/engine/Slave.hpp @@ -0,0 +1,73 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_SLAVE_HPP +#define MACHINA_SLAVE_HPP + +#include <thread> + +#include "raul/Semaphore.hpp" + +namespace machina { + +/** Thread driven by (realtime safe) signals. + * + * Use this to perform some task in a separate thread you want to 'drive' + * from a realtime (or otherwise) thread. + */ +class Slave +{ +public: + Slave() + : _whip(0) + , _exit_flag(false) + , _thread(&Slave::_run, this) + {} + + virtual ~Slave() { + _exit_flag = true; + _whip.post(); + _thread.join(); + } + + /** Tell the slave to do whatever work it does. Realtime safe. */ + inline void whip() { _whip.post(); } + +protected: + /** Worker method. + * + * This is called once from this thread every time whip() is called. + * Implementations likely want to put a single (non loop) chunk of code + * here, e.g. to process an event. + */ + virtual void _whipped() = 0; + + Raul::Semaphore _whip; + +private: + inline void _run() { + while (_whip.wait() && !_exit_flag) { + _whipped(); + } + } + + bool _exit_flag; + std::thread _thread; +}; + +} // namespace machina + +#endif // MACHINA_SLAVE_HPP diff --git a/src/engine/Stateful.cpp b/src/engine/Stateful.cpp new file mode 100644 index 0000000..7608a06 --- /dev/null +++ b/src/engine/Stateful.cpp @@ -0,0 +1,39 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "Stateful.hpp" + +namespace machina { + +uint64_t Stateful::_next_id = 1; + +Stateful::Stateful() + : _id(next_id()) +{} + +const Sord::Node& +Stateful::rdf_id(Sord::World& world) const +{ + if (!_rdf_id.is_valid()) { + std::ostringstream ss; + ss << "b" << _id; + _rdf_id = Sord::Node(world, Sord::Node::BLANK, ss.str()); + } + + return _rdf_id; +} + +} // namespace machina diff --git a/src/engine/Stateful.hpp b/src/engine/Stateful.hpp new file mode 100644 index 0000000..b15d863 --- /dev/null +++ b/src/engine/Stateful.hpp @@ -0,0 +1,70 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_STATEFUL_HPP +#define MACHINA_STATEFUL_HPP + +#include <stdint.h> + +#include "sord/sordmm.hpp" + +#include "machina/Atom.hpp" +#include "machina/types.hpp" + +namespace Raul { +class Atom; +} + +namespace machina { + +class URIs; + +class Stateful +{ +public: + Stateful(); + + virtual ~Stateful() {} + + virtual void set(URIInt key, const Atom& value) {} + virtual void write_state(Sord::Model& model) = 0; + + uint64_t id() const { return _id; } + const Sord::Node& rdf_id(Sord::World& world) const; + + static uint64_t next_id() { return _next_id++; } + +protected: + explicit Stateful(uint64_t id) : _id(id) {} + +private: + static uint64_t _next_id; + + uint64_t _id; + mutable Sord::Node _rdf_id; +}; + +class StatefulKey : public Stateful +{ +public: + explicit StatefulKey(uint64_t id) : Stateful(id) {} + + void write_state(Sord::Model& model) {} +}; + +} // namespace machina + +#endif // MACHINA_STATEFUL_HPP diff --git a/src/engine/URIs.cpp b/src/engine/URIs.cpp new file mode 100644 index 0000000..2b43b81 --- /dev/null +++ b/src/engine/URIs.cpp @@ -0,0 +1,23 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "machina/URIs.hpp" + +namespace machina { + +URIs* URIs::_instance = NULL; + +} // namespace machina diff --git a/src/engine/Updates.cpp b/src/engine/Updates.cpp new file mode 100644 index 0000000..ce655e4 --- /dev/null +++ b/src/engine/Updates.cpp @@ -0,0 +1,66 @@ +/* + This file is part of Machina. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "lv2/lv2plug.in/ns/ext/atom/atom.h" + +#include "machina/Atom.hpp" +#include "machina/Updates.hpp" +#include "machina/types.hpp" +#include "raul/RingBuffer.hpp" + +namespace machina { + +void +write_set(SPtr<Raul::RingBuffer> buf, + uint64_t subject, + URIInt key, + const Atom& value) +{ + const uint32_t update_type = UPDATE_SET; + buf->write(sizeof(update_type), &update_type); + buf->write(sizeof(subject), &subject); + buf->write(sizeof(key), &key); + + const LV2_Atom atom = { value.size(), value.type() }; + buf->write(sizeof(LV2_Atom), &atom); + buf->write(value.size(), value.get_body()); +} + +uint32_t +read_set(SPtr<Raul::RingBuffer> buf, + uint64_t* subject, + URIInt* key, + Atom* value) +{ + uint32_t update_type = 0; + buf->read(sizeof(update_type), &update_type); + if (update_type != UPDATE_SET) { + return 0; + } + + buf->read(sizeof(*subject), subject); + buf->read(sizeof(*key), key); + + LV2_Atom atom = { 0, 0 }; + buf->read(sizeof(LV2_Atom), &atom); + *value = Atom(atom.size, atom.type, NULL); + buf->read(atom.size, value->get_body()); + + return sizeof(update_type) + sizeof(*subject) + sizeof(*key) + + sizeof(LV2_Atom) + atom.size; +} + +} diff --git a/src/engine/machina/Atom.hpp b/src/engine/machina/Atom.hpp new file mode 100644 index 0000000..710cc26 --- /dev/null +++ b/src/engine/machina/Atom.hpp @@ -0,0 +1,217 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_ATOM_HPP +#define MACHINA_ATOM_HPP + +#include <stdint.h> + +#include <cassert> +#include <cstdlib> +#include <cstring> +#include <string> + +namespace machina { + +class Forge; + +/** + A generic typed data container. + + An Atom holds a value with some type and size, both specified by a uint32_t. + Values with size less than sizeof(void*) are stored inline: no dynamic + allocation occurs so Atoms may be created in hard real-time threads. + Otherwise, if the size is larger than sizeof(void*), the value will be + dynamically allocated in a separate chunk of memory. +*/ +class Atom { +public: + Atom() : _size(0), _type(0) { _val._blob = NULL; } + ~Atom() { dealloc(); } + + typedef uint32_t TypeID; + + /** Contruct a raw atom. + * + * Typically this is not used directly, use Forge methods to make atoms. + */ + Atom(uint32_t size, TypeID type, const void* body) + : _size(size) + , _type(type) + { + if (is_reference()) { + _val._blob = malloc(size); + } + if (body) { + memcpy(get_body(), body, size); + } + } + + Atom(const Atom& copy) + : _size(copy._size) + , _type(copy._type) + { + if (is_reference()) { + _val._blob = malloc(_size); + memcpy(_val._blob, copy._val._blob, _size); + } else { + memcpy(&_val, ©._val, _size); + } + } + + Atom& operator=(const Atom& other) { + if (&other == this) { + return *this; + } + dealloc(); + _size = other._size; + _type = other._type; + if (is_reference()) { + _val._blob = malloc(_size); + memcpy(_val._blob, other._val._blob, _size); + } else { + memcpy(&_val, &other._val, _size); + } + return *this; + } + + inline bool operator==(const Atom& other) const { + if (_type == other.type() && _size == other.size()) { + if (is_reference()) { + return !memcmp(_val._blob, other._val._blob, _size); + } else { + return !memcmp(&_val, &other._val, _size); + } + } + return false; + } + + inline bool operator!=(const Atom& other) const { + return !operator==(other); + } + + inline bool operator<(const Atom& other) const { + if (_type == other.type()) { + if (is_reference()) { + return memcmp(_val._blob, other._val._blob, _size) < 0; + } else { + return memcmp(&_val, &other._val, _size) < 0; + } + } + return _type < other.type(); + } + + inline uint32_t size() const { return _size; } + inline bool is_valid() const { return _type; } + inline TypeID type() const { return _type; } + + inline const void* get_body() const { + return is_reference() ? _val._blob : &_val; + } + + inline void* get_body() { + return is_reference() ? _val._blob : &_val; + } + + template <typename T> const T& get() const { + assert(size() == sizeof(T)); + return *static_cast<const T*>(get_body()); + } + + template <typename T> const T* ptr() const { + return static_cast<const T*>(get_body()); + } + +private: + friend class Forge; + + /** Free dynamically allocated value, if applicable. */ + inline void dealloc() { + if (is_reference()) { + free(_val._blob); + } + } + + /** Return true iff this value is dynamically allocated. */ + inline bool is_reference() const { + return _size > sizeof(_val); + } + + uint32_t _size; + TypeID _type; + + union { + intptr_t _val; + void* _blob; + } _val; +}; + +class Forge { +public: + Forge() + : Int(1) + , Float(2) + , Bool(3) + , URI(4) + , URID(5) + , String(6) + {} + + virtual ~Forge() {} + + Atom make() { return Atom(); } + Atom make(int32_t v) { return Atom(sizeof(v), Int, &v); } + Atom make(float v) { return Atom(sizeof(v), Float, &v); } + Atom make(bool v) { + const int32_t iv = v ? 1 : 0; + return Atom(sizeof(int32_t), Bool, &iv); + } + + Atom make_urid(int32_t v) { return Atom(sizeof(int32_t), URID, &v); } + + Atom alloc(uint32_t size, uint32_t type, const void* val) { + return Atom(size, type, val); + } + + Atom alloc(const char* v) { + const size_t len = strlen(v); + return Atom(len + 1, String, v); + } + + Atom alloc(const std::string& v) { + return Atom(v.length() + 1, String, v.c_str()); + } + + Atom alloc_uri(const char* v) { + const size_t len = strlen(v); + return Atom(len + 1, URI, v); + } + + Atom alloc_uri(const std::string& v) { + return Atom(v.length() + 1, URI, v.c_str()); + } + + Atom::TypeID Int; + Atom::TypeID Float; + Atom::TypeID Bool; + Atom::TypeID URI; + Atom::TypeID URID; + Atom::TypeID String; +}; + +} // namespace machina + +#endif // MACHINA_ATOM_HPP diff --git a/src/engine/machina/Context.hpp b/src/engine/machina/Context.hpp new file mode 100644 index 0000000..766975b --- /dev/null +++ b/src/engine/machina/Context.hpp @@ -0,0 +1,51 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_CONTEXT_HPP +#define MACHINA_CONTEXT_HPP + +#include "machina/Atom.hpp" +#include "machina/types.hpp" +#include "raul/TimeSlice.hpp" + +namespace machina { + +class MIDISink; + +class Context +{ +public: + Context(Forge& forge, uint32_t rate, uint32_t ppqn, double bpm) + : _forge(forge) + , _time(rate, ppqn, bpm) + {} + + void set_sink(MIDISink* sink) { _sink = sink; } + + Forge& forge() { return _forge; } + const Raul::TimeSlice& time() const { return _time; } + Raul::TimeSlice& time() { return _time; } + MIDISink* sink() { return _sink; } + +private: + Forge& _forge; + Raul::TimeSlice _time; + MIDISink* _sink; +}; + +} // namespace machina + +#endif // MACHINA_CONTEXT_HPP diff --git a/src/engine/machina/Controller.hpp b/src/engine/machina/Controller.hpp new file mode 100644 index 0000000..833b5b2 --- /dev/null +++ b/src/engine/machina/Controller.hpp @@ -0,0 +1,80 @@ +/* + This file is part of Machina. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_CONTROLLER_HPP +#define MACHINA_CONTROLLER_HPP + +#include <stdint.h> + +#include <set> + +#include "raul/RingBuffer.hpp" +#include "raul/Maid.hpp" + +#include "machina/Model.hpp" +#include "machina/URIs.hpp" +#include "machina/types.hpp" + +#include "Stateful.hpp" + +namespace Raul { +class Atom; +} + +namespace machina { + +class Engine; +class Machine; + +class Controller +{ +public: + Controller(SPtr<Engine> engine, Model& model); + + uint64_t create(const Properties& properties); + uint64_t connect(uint64_t tail_id, uint64_t head_id); + + void set_property(uint64_t object_id, URIInt key, const Atom& value); + + void learn(SPtr<Raul::Maid> maid, uint64_t node_id); + void disconnect(uint64_t tail_id, uint64_t head_id); + void erase(uint64_t id); + + void announce(SPtr<Machine> machine); + + void process_updates(); + +private: + SPtr<Stateful> find(uint64_t id); + + struct StatefulComparator { + inline bool operator()(SPtr<Stateful> a, SPtr<Stateful> b) const { + return a->id() < b->id(); + } + }; + + typedef std::set<SPtr<Stateful>, StatefulComparator> Objects; + Objects _objects; + + SPtr<Engine> _engine; + Model& _model; + + SPtr<Raul::RingBuffer> _updates; +}; + +} + +#endif // MACHINA_CONTROLLER_HPP diff --git a/src/engine/machina/Driver.hpp b/src/engine/machina/Driver.hpp new file mode 100644 index 0000000..546c4d6 --- /dev/null +++ b/src/engine/machina/Driver.hpp @@ -0,0 +1,86 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_DRIVER_HPP +#define MACHINA_DRIVER_HPP + +#include "raul/RingBuffer.hpp" + +#include "machina/types.hpp" + +#include "MIDISink.hpp" + +namespace machina { + +class Machine; + +class Driver : public MIDISink +{ +public: + Driver(Forge& forge, SPtr<Machine> machine) + : _forge(forge) + , _machine(machine) + , _play_state(PlayState::STOPPED) + , _bpm(120.0) + , _quantization(0.125) + , _quantize_record(0) + {} + + enum class PlayState { + STOPPED, + PLAYING, + RECORDING, + STEP_RECORDING + }; + + virtual ~Driver() {} + + SPtr<Machine> machine() { return _machine; } + + virtual void set_machine(SPtr<Machine> machine) { + _machine = machine; + } + + SPtr<Raul::RingBuffer> update_sink() { return _updates; } + + void set_update_sink(SPtr<Raul::RingBuffer> b) { + _updates = b; + } + + virtual void set_bpm(double bpm) { _bpm = bpm; } + virtual void set_quantization(double q) { _quantization = q; } + virtual void set_quantize_record(bool q) { _quantize_record = q; } + virtual void set_play_state(PlayState state) { _play_state = state; } + + virtual bool is_activated() const { return false; } + virtual void activate() {} + virtual void deactivate() {} + + PlayState play_state() const { return _play_state; } + +protected: + Forge& _forge; + SPtr<Machine> _machine; + SPtr<Raul::RingBuffer> _updates; + PlayState _play_state; + double _bpm; + double _quantization; + bool _quantize_record; +}; + +} // namespace machina + +#endif // MACHINA_JACKDRIVER_HPP diff --git a/src/engine/machina/Engine.hpp b/src/engine/machina/Engine.hpp new file mode 100644 index 0000000..dd40f42 --- /dev/null +++ b/src/engine/machina/Engine.hpp @@ -0,0 +1,68 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_ENGINE_HPP +#define MACHINA_ENGINE_HPP + +#include <string> + +#include "machina/Atom.hpp" +#include "machina/Driver.hpp" +#include "machina/Loader.hpp" +#include "machina/types.hpp" + +namespace machina { + +class Machine; + +class Engine +{ +public: + Engine(Forge& forge, + SPtr<Driver> driver, + Sord::World& rdf_world); + + Sord::World& rdf_world() { return _rdf_world; } + + static SPtr<Driver> new_driver(Forge& forge, + const std::string& name, + SPtr<Machine> machine); + + SPtr<Driver> driver() { return _driver; } + SPtr<Machine> machine() { return _driver->machine(); } + Forge& forge() { return _forge; } + + SPtr<Machine> load_machine(const std::string& uri); + SPtr<Machine> load_machine_midi(const std::string& uri, + double q, + Raul::TimeDuration dur); + + void export_midi(const std::string& filename, + Raul::TimeDuration dur); + + void set_bpm(double bpm); + void set_quantization(double beat_fraction); + +private: + SPtr<Driver> _driver; + Sord::World& _rdf_world; + Loader _loader; + Forge _forge; +}; + +} // namespace machina + +#endif // MACHINA_ENGINE_HPP diff --git a/src/engine/machina/Evolver.hpp b/src/engine/machina/Evolver.hpp new file mode 100644 index 0000000..400c177 --- /dev/null +++ b/src/engine/machina/Evolver.hpp @@ -0,0 +1,72 @@ +/* + This file is part of Machina. + Copyright 2007-2017 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_EVOLVER_HPP +#define MACHINA_EVOLVER_HPP + +#include <atomic> +#include <memory> +#include <thread> + +#include "eugene/GA.hpp" +#include "eugene/Random.hpp" +#include "machina/types.hpp" +#include "raul/TimeStamp.hpp" + +#include "Machine.hpp" +#include "Schrodinbit.hpp" + +namespace eugene { +template<typename G> class HybridMutation; +} + +namespace machina { + +class Problem; + +class Evolver +{ +public: + Evolver(Raul::TimeUnit unit, + const std::string& target_midi, + SPtr<Machine> seed); + + void seed(SPtr<Machine> parent); + bool improvement() { return _improvement; } + + void start(); + void join(); + + const Machine& best() { return _ga->best(); } + + typedef eugene::GA<Machine> MachinaGA; + +private: + void run(); + + eugene::Random _rng; + SPtr<MachinaGA> _ga; + SPtr<Problem> _problem; + float _seed_fitness; + Schrodinbit _improvement; + std::atomic<bool> _exit_flag; + + std::unique_ptr<std::thread> _thread; +}; + +} // namespace machina + +#endif // MACHINA_EVOLVER_HPP diff --git a/src/engine/machina/Loader.hpp b/src/engine/machina/Loader.hpp new file mode 100644 index 0000000..3fa66ff --- /dev/null +++ b/src/engine/machina/Loader.hpp @@ -0,0 +1,49 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_LOADER_HPP +#define MACHINA_LOADER_HPP + +#include "machina/Atom.hpp" +#include "machina/types.hpp" +#include "raul/TimeStamp.hpp" +#include "sord/sordmm.hpp" + +using Sord::Namespaces; + +namespace machina { + +class Machine; + +class Loader +{ +public: + Loader(Forge& forge, Sord::World& rdf_world); + + SPtr<Machine> load(const std::string& filename); + + SPtr<Machine> load_midi(const std::string& filename, + double q, + Raul::TimeDuration dur); + +private: + Forge& _forge; + Sord::World& _rdf_world; +}; + +} // namespace machina + +#endif // MACHINA_LOADER_HPP diff --git a/src/engine/machina/Machine.hpp b/src/engine/machina/Machine.hpp new file mode 100644 index 0000000..7c9855d --- /dev/null +++ b/src/engine/machina/Machine.hpp @@ -0,0 +1,132 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_MACHINE_HPP +#define MACHINA_MACHINE_HPP + +#include <vector> +#include <set> + +#include "machina/types.hpp" +#include "machina/Atom.hpp" +#include "raul/RingBuffer.hpp" +#include "raul/TimeSlice.hpp" +#include "sord/sordmm.hpp" + +#include "types.hpp" +#include "Node.hpp" + +namespace machina { + +class Context; +class LearnRequest; + +/** A (Finite State) Machine. + */ +class Machine : public Stateful +{ +public: + explicit Machine(TimeUnit unit); + + /** Copy a Machine. + * + * Creates a deep copy which is the 'same' machine, but with fresh state, + * i.e. all nodes are inactive and time is at zero. + */ + Machine(const Machine& copy); + + /** Completely replace this machine's contents with a deep copy. */ + Machine& operator=(const Machine& copy); + + bool operator==(const Machine& rhs) const; + + /** Merge another machine into this machine. */ + void merge(const Machine& machine); + + bool is_empty() { return _nodes.empty(); } + bool is_finished() { return _is_finished; } + + void add_node(SPtr<Node> node); + void remove_node(SPtr<Node> node); + void learn(SPtr<Raul::Maid> maid, SPtr<Node> node); + + void write_state(Sord::Model& model); + + /** Exit all active nodes and reset time to 0. */ + void reset(MIDISink* sink, Raul::TimeStamp time); + + /** Run the machine for a (real) time slice. + * + * Returns the duration of time the machine actually ran in frames. + * + * Caller can check is_finished() to determine if the machine still has any + * active nodes. If not, time() will return the exact time stamp the + * machine actually finished on (so it can be restarted immediately + * with sample accuracy if necessary). + */ + uint32_t run(Context& context, SPtr<Raul::RingBuffer> updates); + + // Any context + inline Raul::TimeStamp time() const { return _time; } + + SPtr<LearnRequest> pending_learn() { return _pending_learn; } + void clear_pending_learn() { _pending_learn.reset(); } + + typedef std::set< SPtr<Node> > Nodes; + Nodes& nodes() { return _nodes; } + const Nodes& nodes() const { return _nodes; } + + SPtr<Node> initial_node() const { return _initial_node; } + + SPtr<Node> random_node(); + SPtr<Edge> random_edge(); + + float fitness; // For GA + +private: + /** Return the active Node with the earliest exit time. */ + SPtr<Node> earliest_node() const; + + void assign(const Machine& other); + + /** Enter a node at the current time (called by run()). + * + * @return true if node was entered, otherwise voics are exhausted. + */ + bool enter_node(Context& context, + SPtr<Node> node, + SPtr<Raul::RingBuffer> updates); + + /** Exit a node at the current time (called by run()). */ + void exit_node(Context& context, + SPtr<Node> node, + SPtr<Raul::RingBuffer> updates); + + static const size_t MAX_ACTIVE_NODES = 128; + + SPtr<Node> _initial_node; + std::vector< SPtr<Node> > _active_nodes; + + SPtr<LearnRequest> _pending_learn; + Nodes _nodes; + Raul::TimeStamp _time; + + bool _is_finished; +}; + +} // namespace machina + +#endif // MACHINA_MACHINE_HPP diff --git a/src/engine/machina/Model.hpp b/src/engine/machina/Model.hpp new file mode 100644 index 0000000..32a332e --- /dev/null +++ b/src/engine/machina/Model.hpp @@ -0,0 +1,44 @@ +/* + This file is part of Machina. + Copyright 2014 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_MODEL_HPP +#define MACHINA_MODEL_HPP + +#include <stdint.h> + +#include <map> + +#include "machina/Atom.hpp" +#include "machina/types.hpp" + +namespace machina { + +class Model +{ +public: + virtual ~Model() {} + + virtual void new_object(uint64_t id, const Properties& properties) = 0; + + virtual void erase_object(uint64_t id) = 0; + + virtual void set(uint64_t id, URIInt key, const Atom& value) = 0; + virtual const Atom& get(uint64_t id, URIInt key) const = 0; +}; + +} // namespace machina + +#endif // MACHINA_MODEL_HPP diff --git a/src/engine/machina/Mutation.hpp b/src/engine/machina/Mutation.hpp new file mode 100644 index 0000000..69f5ee4 --- /dev/null +++ b/src/engine/machina/Mutation.hpp @@ -0,0 +1,60 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_MACHINE_MUTATION_HPP +#define MACHINA_MACHINE_MUTATION_HPP + +#include "machina_config.h" + +#ifdef HAVE_EUGENE +# include "eugene/Mutation.hpp" +# define SUPER : public eugene::Mutation<Machine> +#else +# define SUPER : public Mutation +#endif + +namespace machina { + +#ifdef HAVE_EUGENE +typedef eugene::Random Random; +#else +struct Random {}; +#endif + +class Machine; + +namespace Mutation { + +struct Mutation { + virtual ~Mutation() {} + + virtual void mutate(Random& rng, Machine& machine) = 0; +}; + +struct Compress SUPER { void mutate(Random& rng, Machine& machine); }; +struct AddNode SUPER { void mutate(Random& rng, Machine& machine); }; +struct RemoveNode SUPER { void mutate(Random& rng, Machine& machine); }; +struct AdjustNode SUPER { void mutate(Random& rng, Machine& machine); }; +struct SwapNodes SUPER { void mutate(Random& rng, Machine& machine); }; +struct AddEdge SUPER { void mutate(Random& rng, Machine& machine); }; +struct RemoveEdge SUPER { void mutate(Random& rng, Machine& machine); }; +struct AdjustEdge SUPER { void mutate(Random& rng, Machine& machine); }; + +} // namespace Mutation + +} // namespace machina + +#endif // MACHINA_MACHINE_MUTATION_HPP diff --git a/src/engine/machina/URIs.hpp b/src/engine/machina/URIs.hpp new file mode 100644 index 0000000..f770723 --- /dev/null +++ b/src/engine/machina/URIs.hpp @@ -0,0 +1,93 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_URIS_HPP +#define MACHINA_URIS_HPP + +#include <stdint.h> + +#include "machina/Atom.hpp" +#include "machina/types.hpp" + +#define MACHINA_URI_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#" + +#define MACHINA_NS "http://drobilla.net/ns/machina#" + +#define MACHINA_NS_Machine MACHINA_NS "Machine" +#define MACHINA_NS_Node MACHINA_NS "Node" +#define MACHINA_NS_SelectorNode MACHINA_NS "SelectorNode" +#define MACHINA_NS_arc MACHINA_NS "arc" +#define MACHINA_NS_duration MACHINA_NS "duration" +#define MACHINA_NS_head MACHINA_NS "head" +#define MACHINA_NS_node MACHINA_NS "node" +#define MACHINA_NS_onEnter MACHINA_NS "onEnter" +#define MACHINA_NS_onExit MACHINA_NS "onExit" +#define MACHINA_NS_probability MACHINA_NS "probability" +#define MACHINA_NS_start MACHINA_NS "start" +#define MACHINA_NS_tail MACHINA_NS "tail" + +namespace machina { + +class URIs +{ +public: + static void init() { _instance = new URIs(); } + + static inline const URIs& instance() { assert(_instance); return *_instance; } + + URIInt machina_Edge; + URIInt machina_MidiAction; + URIInt machina_Node; + URIInt machina_active; + URIInt machina_canvas_x; + URIInt machina_canvas_y; + URIInt machina_duration; + URIInt machina_enter_action; + URIInt machina_exit_action; + URIInt machina_head_id; + URIInt machina_initial; + URIInt machina_note_number; + URIInt machina_probability; + URIInt machina_selector; + URIInt machina_tail_id; + URIInt rdf_type; + +private: + URIs() + : machina_Edge(100) + , machina_MidiAction(101) + , machina_Node(102) + , machina_active(1) + , machina_canvas_x(2) + , machina_canvas_y(3) + , machina_duration(4) + , machina_enter_action(11) + , machina_exit_action(12) + , machina_head_id(5) + , machina_initial(6) + , machina_note_number(13) + , machina_probability(7) + , machina_selector(8) + , machina_tail_id(9) + , rdf_type(10) + {} + + static URIs* _instance; +}; + +} // namespace machina + +#endif // MACHINA_URIS_HPP diff --git a/src/engine/machina/Updates.hpp b/src/engine/machina/Updates.hpp new file mode 100644 index 0000000..ff09af9 --- /dev/null +++ b/src/engine/machina/Updates.hpp @@ -0,0 +1,46 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_UPDATES_HPP +#define MACHINA_UPDATES_HPP + +#include <stdint.h> + +#include "machina/Atom.hpp" +#include "machina/types.hpp" +#include "raul/RingBuffer.hpp" + +namespace machina { + +enum UpdateType { + UPDATE_SET = 1 +}; + +void +write_set(SPtr<Raul::RingBuffer> buf, + uint64_t subject, + URIInt key, + const Atom& value); + +uint32_t +read_set(SPtr<Raul::RingBuffer> buf, + uint64_t* subject, + URIInt* key, + Atom* value); + +} // namespace machina + +#endif // MACHINA_UPDATES_HPP diff --git a/src/engine/machina/types.hpp b/src/engine/machina/types.hpp new file mode 100644 index 0000000..61137b7 --- /dev/null +++ b/src/engine/machina/types.hpp @@ -0,0 +1,69 @@ +/* + This file is part of Machina. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_TYPES_HPP +#define MACHINA_TYPES_HPP + +#include <map> +#include <memory> + +#include "raul/Maid.hpp" + +namespace machina { + +typedef unsigned char byte; + +typedef uint32_t URIInt; + +class Atom; +typedef std::map<URIInt, Atom> Properties; + +#if __cplusplus >= 201103L +template <class T> +using SPtr = std::shared_ptr<T>; + +template <class T> +using WPtr = std::weak_ptr<T>; + +template <class T> +using MPtr = Raul::managed_ptr<T>; +#else +#define SPtr std::shared_ptr +#define WPtr std::weak_ptr +#define MPtr Raul::managed_ptr +#endif + +template <class T> +void NullDeleter(T* ptr) {} + +template<class T, class U> +SPtr<T> static_ptr_cast(const SPtr<U>& r) { + return std::static_pointer_cast<T>(r); +} + +template<class T, class U> +SPtr<T> dynamic_ptr_cast(const SPtr<U>& r) { + return std::dynamic_pointer_cast<T>(r); +} + +template<class T, class U> +SPtr<T> const_ptr_cast(const SPtr<U>& r) { + return std::const_pointer_cast<T>(r); +} + +} // namespace machina + +#endif // MACHINA_TYPES_HPP diff --git a/src/engine/quantize.hpp b/src/engine/quantize.hpp new file mode 100644 index 0000000..9cf939a --- /dev/null +++ b/src/engine/quantize.hpp @@ -0,0 +1,46 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_QUANTIZE_HPP +#define MACHINA_QUANTIZE_HPP + +#include <cmath> + +#include "raul/TimeStamp.hpp" + +namespace machina { + +inline TimeStamp +quantize(TimeStamp q, TimeStamp t) +{ + assert(q.unit() == t.unit()); + // FIXME: Precision problem? Should probably stay in discrete domain + const double qd = q.to_double(); + const double td = t.to_double(); + return TimeStamp(t.unit(), (qd > 0) ? lrint(td / qd) * qd : td); +} + +inline double +quantize(double q, double t) +{ + return (q > 0) + ? lrint(t / q) * q + : t; +} + +} // namespace machina + +#endif // MACHINA_QUANTIZE_HPP diff --git a/src/engine/quantize_test.cpp b/src/engine/quantize_test.cpp new file mode 100644 index 0000000..5410489 --- /dev/null +++ b/src/engine/quantize_test.cpp @@ -0,0 +1,41 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <iostream> + +#include "quantize.hpp" + +using namespace std; +using namespace machina; + +int +main() +{ + TimeStamp q(TimeUnit(TimeUnit::BEATS, 19200), 0.25); + + for (double in = 0.0; in < 32; in += 0.23) { + TimeStamp beats(TimeUnit(TimeUnit::BEATS, 19200), in); + + /*cout << "Q(" << in << ", 1/4) = " + << quantize(q, beats) << endl;*/ + + if (quantize(q, beats).subticks() % (19200 / 4) != 0) { + return 1; + } + } + + return 0; +} diff --git a/src/engine/smf_test.cpp b/src/engine/smf_test.cpp new file mode 100644 index 0000000..d98a696 --- /dev/null +++ b/src/engine/smf_test.cpp @@ -0,0 +1,84 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <iostream> +#include <string> +#include "raul/log.hpp" +#include "raul/SMFReader.hpp" +#include "raul/SMFWriter.hpp" + +using namespace std; +using namespace machina; + +int +main(int argc, char** argv) +{ +#define CHECK(cond) \ + do { if (!(cond)) { \ + error << "Test at " << __FILE__ << ":" << __LINE__ \ + << " failed: " << __STRING(cond) << endl; \ + return 1; \ + } \ + } \ + while (0) + + static const uint16_t ppqn = 19200; + + const char* filename = NULL; + + if (argc < 2) { + filename = "./test.mid"; + SMFWriter writer(TimeUnit(TimeUnit::BEATS, ppqn)); + writer.start(string(filename), TimeStamp(writer.unit(), 0, 0)); + writer.finish(); + } else { + filename = argv[1]; + } + + SMFReader reader; + bool opened = reader.open(filename); + + if (!opened) { + cerr << "Unable to open SMF file " << filename << endl; + return -1; + } + + CHECK(reader.type() == 0); + CHECK(reader.num_tracks() == 1); + CHECK(reader.ppqn() == ppqn); + + for (unsigned t = 1; t <= reader.num_tracks(); ++t) { + reader.seek_to_track(t); + + unsigned char buf[4]; + uint32_t ev_size; + uint32_t ev_delta_time; + while (reader.read_event(4, buf, &ev_size, &ev_delta_time) >= 0) { + + cout << t << ": Event, size = " << ev_size << ", time = " + << ev_delta_time; + cout << ":\t"; + cout.flags(ios::hex); + for (uint32_t i = 0; i < ev_size; ++i) { + cout << "0x" << static_cast<int>(buf[i]) << " "; + } + cout.flags(ios::dec); + cout << endl; + } + } + + return 0; +} diff --git a/src/engine/wscript b/src/engine/wscript new file mode 100644 index 0000000..1ec63a2 --- /dev/null +++ b/src/engine/wscript @@ -0,0 +1,45 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf + +def build(bld): + core_source = ''' + ActionFactory.cpp + Controller.cpp + Edge.cpp + Engine.cpp + JackDriver.cpp + LearnRequest.cpp + Loader.cpp + Machine.cpp + MachineBuilder.cpp + MidiAction.cpp + Mutation.cpp + Node.cpp + Recorder.cpp + SMFDriver.cpp + SMFReader.cpp + SMFWriter.cpp + Stateful.cpp + Updates.cpp + URIs.cpp + ''' + if bld.env.HAVE_EUGENE: + core_source += ''' + Evolver.cpp + Problem.cpp + ''' + obj = bld(features = 'cxx cxxshlib') + obj.source = core_source + obj.export_includes = ['.'] + obj.includes = ['.', '..', '../..'] + obj.name = 'libmachina_engine' + obj.target = 'machina_engine' + obj.cxxflags = '-pthread' + if bld.env.CXX_NAME != 'clang': + obj.linkflags = '-pthread' + core_libs = 'RAUL SERD SORD JACK LV2' + if bld.env.HAVE_EUGENE: + core_libs += ' EUGENE ' + autowaf.use_lib(bld, obj, core_libs) + + bld.add_post_fun(autowaf.run_ldconfig) diff --git a/src/gui/EdgeView.cpp b/src/gui/EdgeView.cpp new file mode 100644 index 0000000..b4c6099 --- /dev/null +++ b/src/gui/EdgeView.cpp @@ -0,0 +1,142 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "ganv/Canvas.hpp" + +#include "machina/Controller.hpp" +#include "machina/types.hpp" + +#include "EdgeView.hpp" +#include "MachinaCanvas.hpp" +#include "MachinaGUI.hpp" +#include "NodeView.hpp" + +namespace machina { +namespace gui { + +/* probability colour stuff */ + +#define RGB_TO_UINT(r, g, b) ((((guint)(r)) << 16) | (((guint)(g)) << 8) | ((guint)(b))) +#define RGB_TO_RGBA(x, a) (((x) << 8) | ((((guint)a) & 0xff))) +#define RGBA_TO_UINT(r, g, b, a) RGB_TO_RGBA(RGB_TO_UINT(r, g, b), a) + +#define UINT_RGBA_R(x) (((uint32_t)(x)) >> 24) +#define UINT_RGBA_G(x) ((((uint32_t)(x)) >> 16) & 0xff) +#define UINT_RGBA_B(x) ((((uint32_t)(x)) >> 8) & 0xff) +#define UINT_RGBA_A(x) (((uint32_t)(x)) & 0xff) + +#define MONO_INTERPOLATE(v1, v2, t) ((int)rint((v2) * (t) + (v1) * (1 - (t)))) + +#define UINT_INTERPOLATE(c1, c2, t) \ + RGBA_TO_UINT(MONO_INTERPOLATE(UINT_RGBA_R(c1), UINT_RGBA_R(c2), t), \ + MONO_INTERPOLATE(UINT_RGBA_G(c1), UINT_RGBA_G(c2), t), \ + MONO_INTERPOLATE(UINT_RGBA_B(c1), UINT_RGBA_B(c2), t), \ + MONO_INTERPOLATE(UINT_RGBA_A(c1), UINT_RGBA_A(c2), t) ) + +inline static uint32_t edge_color(float prob) +{ + static const uint32_t min = 0xFF4444FF; + static const uint32_t mid = 0xFFFF44FF; + static const uint32_t max = 0x44FF44FF; + + if (prob <= 0.5) { + return UINT_INTERPOLATE(min, mid, prob * 2.0); + } else { + return UINT_INTERPOLATE(mid, max, (prob - 0.5) * 2.0); + } +} + +/* end probability colour stuff */ + +using namespace Ganv; + +EdgeView::EdgeView(Canvas& canvas, + NodeView* src, + NodeView* dst, + SPtr<machina::client::ClientObject> edge) + : Ganv::Edge(canvas, src, dst, 0x9FA0A0FF, true, false) + , _edge(edge) +{ + set_color(edge_color(probability())); + + edge->signal_property.connect( + sigc::mem_fun(this, &EdgeView::on_property)); + + signal_event().connect( + sigc::mem_fun(this, &EdgeView::on_event)); +} + +EdgeView::~EdgeView() +{ + _edge->set_view(NULL); +} + +float +EdgeView::probability() const +{ + return _edge->get(URIs::instance().machina_probability).get<float>(); +} + +double +EdgeView::length_hint() const +{ + NodeView* tail = dynamic_cast<NodeView*>(get_tail()); + return tail->node()->get(URIs::instance().machina_duration).get<float>() + * 10.0; +} + +void +EdgeView::show_label(bool show) +{ + set_color(edge_color(probability())); +} + +bool +EdgeView::on_event(GdkEvent* ev) +{ + MachinaCanvas* canvas = dynamic_cast<MachinaCanvas*>(this->canvas()); + Forge& forge = canvas->app()->forge(); + + if (ev->type == GDK_BUTTON_PRESS) { + if (ev->button.state & GDK_CONTROL_MASK) { + if (ev->button.button == 1) { + canvas->app()->controller()->set_property( + _edge->id(), + URIs::instance().machina_probability, + forge.make(float(probability() - 0.1f))); + return true; + } else if (ev->button.button == 3) { + canvas->app()->controller()->set_property( + _edge->id(), + URIs::instance().machina_probability, + forge.make(float(probability() + 0.1f))); + return true; + } + } + } + return false; +} + +void +EdgeView::on_property(machina::URIInt key, const Atom& value) +{ + if (key == URIs::instance().machina_probability) { + set_color(edge_color(value.get<float>())); + } +} + +} // namespace machina +} // namespace gui diff --git a/src/gui/EdgeView.hpp b/src/gui/EdgeView.hpp new file mode 100644 index 0000000..1ad2dd0 --- /dev/null +++ b/src/gui/EdgeView.hpp @@ -0,0 +1,59 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_EDGEVIEW_HPP +#define MACHINA_EDGEVIEW_HPP + +#include "ganv/Edge.hpp" + +#include "client/ClientObject.hpp" + +#include "machina/types.hpp" + +namespace machina { +namespace gui { + +class NodeView; + +class EdgeView + : public Ganv::Edge + , public machina::client::ClientObject::View +{ +public: + EdgeView(Ganv::Canvas& canvas, + NodeView* src, + NodeView* dst, + SPtr<machina::client::ClientObject> edge); + + ~EdgeView(); + + void show_label(bool show); + + virtual double length_hint() const; + +private: + bool on_event(GdkEvent* ev); + void on_property(machina::URIInt key, const Atom& value); + + float probability() const; + + SPtr<machina::client::ClientObject> _edge; +}; + +} // namespace machina +} // namespace gui + +#endif // MACHINA_EDGEVIEW_HPP diff --git a/src/gui/MachinaCanvas.cpp b/src/gui/MachinaCanvas.cpp new file mode 100644 index 0000000..a9e51c4 --- /dev/null +++ b/src/gui/MachinaCanvas.cpp @@ -0,0 +1,211 @@ +/* + This file is part of Machina. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <map> + +#include "raul/TimeStamp.hpp" + +#include "client/ClientModel.hpp" +#include "client/ClientObject.hpp" +#include "machina/Controller.hpp" +#include "machina/Engine.hpp" +#include "machina/types.hpp" + +#include "EdgeView.hpp" +#include "MachinaCanvas.hpp" +#include "MachinaGUI.hpp" +#include "NodeView.hpp" + +using namespace Raul; +using namespace Ganv; + +namespace machina { +namespace gui { + +MachinaCanvas::MachinaCanvas(MachinaGUI* app, int width, int height) + : Canvas(width, height) + , _app(app) + , _connect_node(NULL) + , _did_connect(false) +{ + widget().grab_focus(); + + signal_event.connect(sigc::mem_fun(this, &MachinaCanvas::on_event)); +} + +void +MachinaCanvas::connect_nodes(GanvNode* node, void* data) +{ + MachinaCanvas* canvas = (MachinaCanvas*)data; + NodeView* view = dynamic_cast<NodeView*>(Glib::wrap(node)); + if (!view || view == canvas->_connect_node) { + return; + } + if (canvas->get_edge(view, canvas->_connect_node)) { + canvas->action_disconnect(view, canvas->_connect_node); + canvas->_did_connect = true; + } else if (!canvas->get_edge(canvas->_connect_node, view)) { + canvas->action_connect(view, canvas->_connect_node); + canvas->_did_connect = true; + } +} + +bool +MachinaCanvas::node_clicked(NodeView* node, GdkEventButton* event) +{ + if (event->state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK)) { + return false; + + } else if (event->button == 2) { + // Middle click: learn + _app->controller()->learn(_app->maid(), node->node()->id()); + return false; + + } else if (event->button == 1) { + // Left click: connect/disconnect + _connect_node = node; + for_each_selected_node(connect_nodes, this); + const bool handled = _did_connect; + _connect_node = NULL; + _did_connect = false; + if (_app->chain_mode()) { + return false; // Cause Ganv to select as usual + } else { + return handled; // If we did something, stop event here + } + } + + return false; +} + +bool +MachinaCanvas::on_event(GdkEvent* event) +{ + if (event->type == GDK_BUTTON_RELEASE + && event->button.button == 3 + && !(event->button.state & (GDK_CONTROL_MASK))) { + + action_create_node(event->button.x, event->button.y); + return true; + } + return false; +} + +void +MachinaCanvas::on_new_object(SPtr<client::ClientObject> object) +{ + const machina::URIs& uris = URIs::instance(); + const Atom& type = object->get(uris.rdf_type); + if (!type.is_valid()) { + return; + } + + if (type.get<URIInt>() == uris.machina_Node) { + const Atom& node_x = object->get(uris.machina_canvas_x); + const Atom& node_y = object->get(uris.machina_canvas_y); + float x, y; + if (node_x.type() == _app->forge().Float && + node_y.type() == _app->forge().Float) { + x = node_x.get<float>(); + y = node_y.get<float>(); + } else { + int scroll_x, scroll_y; + get_scroll_offsets(scroll_x, scroll_y); + x = scroll_x + 128.0; + y = scroll_y + 128.0; + } + + NodeView* view = new NodeView(_app->window(), *this, object, x, y); + + //if ( ! node->enter_action() && ! node->exit_action() ) + // view->set_base_color(0x101010FF); + + view->signal_clicked().connect( + sigc::bind<0>(sigc::mem_fun(this, &MachinaCanvas::node_clicked), + view)); + + object->set_view(view); + + } else if (type.get<URIInt>() == uris.machina_Edge) { + SPtr<machina::client::ClientObject> tail = _app->client_model()->find( + object->get(uris.machina_tail_id).get<int32_t>()); + SPtr<machina::client::ClientObject> head = _app->client_model()->find( + object->get(uris.machina_head_id).get<int32_t>()); + + if (!tail || !head) { + std::cerr << "Invalid arc " + << object->get(uris.machina_tail_id).get<int32_t>() + << " => " + << object->get(uris.machina_head_id).get<int32_t>() + << std::endl; + return; + } + + NodeView* tail_view = dynamic_cast<NodeView*>(tail->view()); + NodeView* head_view = dynamic_cast<NodeView*>(head->view()); + + object->set_view(new EdgeView(*this, tail_view, head_view, object)); + + } else { + std::cerr << "Unknown object type " << type.get<URIInt>() << std::endl; + } +} + +void +MachinaCanvas::on_erase_object(SPtr<client::ClientObject> object) +{ + const Atom& type = object->get(URIs::instance().rdf_type); + if (type.get<URIInt>() == URIs::instance().machina_Node) { + delete object->view(); + object->set_view(NULL); + } else if (type.get<URIInt>() == URIs::instance().machina_Edge) { + remove_edge(dynamic_cast<Ganv::Edge*>(object->view())); + object->set_view(NULL); + } else { + std::cerr << "Unknown object type" << std::endl; + } +} + +void +MachinaCanvas::action_create_node(double x, double y) +{ + const Properties props = { + { URIs::instance().rdf_type, + _app->forge().make_urid(URIs::instance().machina_Node) }, + { URIs::instance().machina_canvas_x, + _app->forge().make((float)x) }, + { URIs::instance().machina_canvas_y, + _app->forge().make((float)y) }, + { URIs::instance().machina_duration, + _app->forge().make((float)_app->default_length()) } }; + + _app->controller()->create(props); +} + +void +MachinaCanvas::action_connect(NodeView* tail, NodeView* head) +{ + _app->controller()->connect(tail->node()->id(), head->node()->id()); +} + +void +MachinaCanvas::action_disconnect(NodeView* tail, NodeView* head) +{ + _app->controller()->disconnect(tail->node()->id(), head->node()->id()); +} + +} // namespace machina +} // namespace gui diff --git a/src/gui/MachinaCanvas.hpp b/src/gui/MachinaCanvas.hpp new file mode 100644 index 0000000..f32e6cb --- /dev/null +++ b/src/gui/MachinaCanvas.hpp @@ -0,0 +1,66 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_CANVAS_HPP_HPP +#define MACHINA_CANVAS_HPP_HPP + +#include <string> + +#include "ganv/Canvas.hpp" +#include "machina/types.hpp" + +using namespace Ganv; + +namespace machina { + +namespace client { class ClientObject; } + +namespace gui { + +class MachinaGUI; +class NodeView; + +class MachinaCanvas : public Canvas +{ +public: + MachinaCanvas(MachinaGUI* app, int width, int height); + + void on_new_object(SPtr<machina::client::ClientObject> object); + void on_erase_object(SPtr<machina::client::ClientObject> object); + + MachinaGUI* app() { return _app; } + +protected: + bool on_event(GdkEvent* event); + + bool node_clicked(NodeView* node, GdkEventButton* ev); + +private: + void action_create_node(double x, double y); + void action_connect(NodeView* tail, NodeView* head); + void action_disconnect(NodeView* tail, NodeView* head); + + static void connect_nodes(GanvNode* node, void* data); + + MachinaGUI* _app; + NodeView* _connect_node; + bool _did_connect; +}; + +} // namespace machina +} // namespace gui + +#endif // MACHINA_CANVAS_HPP_HPP diff --git a/src/gui/MachinaGUI.cpp b/src/gui/MachinaGUI.cpp new file mode 100644 index 0000000..ff71ca7 --- /dev/null +++ b/src/gui/MachinaGUI.cpp @@ -0,0 +1,750 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "machina_config.h" + +#include <cmath> +#include <fstream> +#include <limits.h> +#include <pthread.h> +#include "sord/sordmm.hpp" +#include "machina/Controller.hpp" +#include "machina/Engine.hpp" +#include "machina/Machine.hpp" +#include "machina/Mutation.hpp" +#include "machina/Updates.hpp" +#include "client/ClientModel.hpp" +#include "WidgetFactory.hpp" +#include "MachinaGUI.hpp" +#include "MachinaCanvas.hpp" +#include "NodeView.hpp" +#include "EdgeView.hpp" + +#ifdef HAVE_EUGENE +#include "machina/Evolver.hpp" +#endif + +namespace machina { +namespace gui { + +MachinaGUI::MachinaGUI(SPtr<machina::Engine> engine) + : _unit(TimeUnit::BEATS, 19200) + , _engine(engine) + , _client_model(new machina::client::ClientModel()) + , _controller(new machina::Controller(_engine, *_client_model.get())) + , _maid(new Raul::Maid()) + , _refresh(false) + , _evolve(false) + , _chain_mode(true) +{ + _canvas = SPtr<MachinaCanvas>(new MachinaCanvas(this, 1600*2, 1200*2)); + + Glib::RefPtr<Gtk::Builder> xml = WidgetFactory::create(); + + xml->get_widget("machina_win", _main_window); + xml->get_widget("about_win", _about_window); + xml->get_widget("help_dialog", _help_dialog); + xml->get_widget("toolbar", _toolbar); + xml->get_widget("open_menuitem", _menu_file_open); + xml->get_widget("save_menuitem", _menu_file_save); + xml->get_widget("save_as_menuitem", _menu_file_save_as); + xml->get_widget("quit_menuitem", _menu_file_quit); + xml->get_widget("zoom_in_menuitem", _menu_zoom_in); + xml->get_widget("zoom_out_menuitem", _menu_zoom_out); + xml->get_widget("zoom_normal_menuitem", _menu_zoom_normal); + xml->get_widget("arrange_menuitem", _menu_view_arrange); + xml->get_widget("import_midi_menuitem", _menu_import_midi); + xml->get_widget("export_midi_menuitem", _menu_export_midi); + xml->get_widget("export_graphviz_menuitem", _menu_export_graphviz); + xml->get_widget("view_toolbar_menuitem", _menu_view_toolbar); + xml->get_widget("view_labels_menuitem", _menu_view_labels); + xml->get_widget("help_about_menuitem", _menu_help_about); + xml->get_widget("help_help_menuitem", _menu_help_help); + xml->get_widget("canvas_scrolledwindow", _canvas_scrolledwindow); + xml->get_widget("bpm_spinbutton", _bpm_spinbutton); + xml->get_widget("quantize_checkbutton", _quantize_checkbutton); + xml->get_widget("quantize_spinbutton", _quantize_spinbutton); + xml->get_widget("stop_but", _stop_button); + xml->get_widget("play_but", _play_button); + xml->get_widget("record_but", _record_button); + xml->get_widget("step_record_but", _step_record_button); + xml->get_widget("chain_but", _chain_button); + xml->get_widget("fan_but", _fan_button); + xml->get_widget("load_target_but", _load_target_button); + xml->get_widget("evolve_toolbar", _evolve_toolbar); + xml->get_widget("evolve_but", _evolve_button); + xml->get_widget("mutate_but", _mutate_button); + xml->get_widget("compress_but", _compress_button); + xml->get_widget("add_node_but", _add_node_button); + xml->get_widget("remove_node_but", _remove_node_button); + xml->get_widget("adjust_node_but", _adjust_node_button); + xml->get_widget("add_edge_but", _add_edge_button); + xml->get_widget("remove_edge_but", _remove_edge_button); + xml->get_widget("adjust_edge_but", _adjust_edge_button); + + _canvas_scrolledwindow->add(_canvas->widget()); + _canvas_scrolledwindow->signal_event().connect(sigc::mem_fun(this, + &MachinaGUI::scrolled_window_event)); + + _canvas_scrolledwindow->property_hadjustment().get_value()->set_step_increment(10); + _canvas_scrolledwindow->property_vadjustment().get_value()->set_step_increment(10); + + _stop_button->signal_toggled().connect( + sigc::mem_fun(this, &MachinaGUI::stop_toggled)); + _play_button->signal_toggled().connect( + sigc::mem_fun(this, &MachinaGUI::play_toggled)); + _record_button->signal_toggled().connect( + sigc::mem_fun(this, &MachinaGUI::record_toggled)); + _step_record_button->signal_toggled().connect( + sigc::mem_fun(this, &MachinaGUI::step_record_toggled)); + + _chain_button->signal_toggled().connect( + sigc::mem_fun(this, &MachinaGUI::chain_toggled)); + _fan_button->signal_toggled().connect( + sigc::mem_fun(this, &MachinaGUI::fan_toggled)); + + _menu_file_open->signal_activate().connect( + sigc::mem_fun(this, &MachinaGUI::menu_file_open)); + _menu_file_save->signal_activate().connect( + sigc::mem_fun(this, &MachinaGUI::menu_file_save)); + _menu_file_save_as->signal_activate().connect( + sigc::mem_fun(this, &MachinaGUI::menu_file_save_as)); + _menu_file_quit->signal_activate().connect( + sigc::mem_fun(this, &MachinaGUI::menu_file_quit)); + _menu_zoom_in->signal_activate().connect( + sigc::mem_fun(this, &MachinaGUI::on_zoom_in)); + _menu_zoom_out->signal_activate().connect( + sigc::mem_fun(this, &MachinaGUI::on_zoom_out)); + _menu_zoom_normal->signal_activate().connect( + sigc::mem_fun(this, &MachinaGUI::on_zoom_normal)); + _menu_view_arrange->signal_activate().connect( + sigc::mem_fun(this, &MachinaGUI::arrange)); + _menu_import_midi->signal_activate().connect( + sigc::mem_fun(this, &MachinaGUI::menu_import_midi)); + _menu_export_midi->signal_activate().connect( + sigc::mem_fun(this, &MachinaGUI::menu_export_midi)); + _menu_export_graphviz->signal_activate().connect( + sigc::mem_fun(this, &MachinaGUI::menu_export_graphviz)); + _menu_view_toolbar->signal_toggled().connect( + sigc::mem_fun(this, &MachinaGUI::show_toolbar_toggled)); + _menu_view_labels->signal_toggled().connect( + sigc::mem_fun(this, &MachinaGUI::show_labels_toggled)); + _menu_help_about->signal_activate().connect( + sigc::mem_fun(this, &MachinaGUI::menu_help_about)); + _menu_help_help->signal_activate().connect( + sigc::mem_fun(this, &MachinaGUI::menu_help_help)); + _bpm_spinbutton->signal_changed().connect( + sigc::mem_fun(this, &MachinaGUI::tempo_changed)); + _quantize_checkbutton->signal_toggled().connect( + sigc::mem_fun(this, &MachinaGUI::quantize_record_changed)); + _quantize_spinbutton->signal_changed().connect( + sigc::mem_fun(this, &MachinaGUI::quantize_changed)); + + _mutate_button->signal_clicked().connect( + sigc::bind(sigc::mem_fun(this, &MachinaGUI::random_mutation), + SPtr<Machine>())); + _compress_button->signal_clicked().connect( + sigc::hide_return(sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate), + SPtr<Machine>(), 0))); + _add_node_button->signal_clicked().connect( + sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate), + SPtr<Machine>(), 1)); + _remove_node_button->signal_clicked().connect( + sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate), + SPtr<Machine>(), 2)); + _adjust_node_button->signal_clicked().connect( + sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate), + SPtr<Machine>(), 3)); + _add_edge_button->signal_clicked().connect( + sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate), + SPtr<Machine>(), 4)); + _remove_edge_button->signal_clicked().connect( + sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate), + SPtr<Machine>(), 5)); + _adjust_edge_button->signal_clicked().connect( + sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate), + SPtr<Machine>(), 6)); + + _canvas->widget().show(); + + _main_window->present(); + + _quantize_checkbutton->set_active(false); + update_toolbar(); + + // Idle callback to drive the maid (collect garbage) + Glib::signal_timeout().connect( + sigc::bind_return(sigc::mem_fun(_maid.get(), &Raul::Maid::cleanup), + true), + 1000); + + // Idle callback to update node states + Glib::signal_timeout().connect( + sigc::mem_fun(this, &MachinaGUI::idle_callback), 100); + +#ifdef HAVE_EUGENE + _load_target_button->signal_clicked().connect( + sigc::mem_fun(this, &MachinaGUI::load_target_clicked)); + _evolve_button->signal_clicked().connect( + sigc::mem_fun(this, &MachinaGUI::evolve_toggled)); + Glib::signal_timeout().connect( + sigc::mem_fun(this, &MachinaGUI::evolve_callback), 1000); +#else + _evolve_toolbar->hide(); +#endif + + _client_model->signal_new_object.connect( + sigc::mem_fun(this, &MachinaGUI::on_new_object)); + _client_model->signal_erase_object.connect( + sigc::mem_fun(this, &MachinaGUI::on_erase_object)); + + rebuild_canvas(); +} + +MachinaGUI::~MachinaGUI() +{ +} + +#ifdef HAVE_EUGENE +bool +MachinaGUI::evolve_callback() +{ + if (_evolve && _evolver->improvement()) { + _engine->driver()->set_machine( + SPtr<Machine>(new Machine(_evolver->best()))); + _controller->announce(_engine->machine()); + } + + return true; +} +#endif + +bool +MachinaGUI::idle_callback() +{ + _controller->process_updates(); + return true; +} + +static void +destroy_edge(GanvEdge* edge, void* data) +{ + MachinaGUI* gui = (MachinaGUI*)data; + EdgeView* view = dynamic_cast<EdgeView*>(Glib::wrap(edge)); + if (view) { + NodeView* tail = dynamic_cast<NodeView*>(view->get_tail()); + NodeView* head = dynamic_cast<NodeView*>(view->get_head()); + gui->controller()->disconnect(tail->node()->id(), head->node()->id()); + } +} + +static void +destroy_node(GanvNode* node, void* data) +{ + MachinaGUI* gui = (MachinaGUI*)data; + NodeView* view = dynamic_cast<NodeView*>(Glib::wrap(GANV_NODE(node))); + if (view) { + const SPtr<client::ClientObject> node = view->node(); + gui->canvas()->for_each_edge_on( + GANV_NODE(view->gobj()), destroy_edge, gui); + gui->controller()->erase(node->id()); + } +} + +bool +MachinaGUI::scrolled_window_event(GdkEvent* event) +{ + if (event->type == GDK_KEY_PRESS) { + if (event->key.keyval == GDK_Delete) { + _canvas->for_each_selected_node(destroy_node, this); + return true; + } + } + + return false; +} + +void +MachinaGUI::arrange() +{ + _canvas->arrange(); +} + +void +MachinaGUI::load_target_clicked() +{ + Gtk::FileChooserDialog dialog(*_main_window, + "Load MIDI file for evolution", Gtk::FILE_CHOOSER_ACTION_OPEN); + dialog.set_local_only(false); + dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK); + + Gtk::FileFilter filt; + filt.add_pattern("*.mid"); + filt.set_name("MIDI Files"); + dialog.set_filter(filt); + + const int result = dialog.run(); + + if (result == Gtk::RESPONSE_OK) + _target_filename = dialog.get_filename(); +} + +#ifdef HAVE_EUGENE +void +MachinaGUI::evolve_toggled() +{ + if (_evolve_button->get_active()) { + _evolver = SPtr<Evolver>( + new Evolver(_unit, _target_filename, _engine->machine())); + _evolve = true; + stop_toggled(); + _engine->driver()->set_machine(SPtr<Machine>()); + _evolver->start(); + } else { + _evolver->join(); + _evolve = false; + SPtr<Machine> new_machine = SPtr<Machine>( + new Machine(_evolver->best())); + _engine->driver()->set_machine(new_machine); + _controller->announce(_engine->machine()); + _engine->driver()->activate(); + } +} +#endif + +void +MachinaGUI::random_mutation(SPtr<Machine> machine) +{ + if (!machine) + machine = _engine->machine(); + + mutate(machine, machine->nodes().size() < 2 ? 1 : rand() % 7); +} + +void +MachinaGUI::mutate(SPtr<Machine> machine, unsigned mutation) +{ + #if 0 + if (!machine) + machine = _engine->machine(); + + using namespace Mutation; + + switch (mutation) { + case 0: + Compress().mutate(*machine.get()); + _canvas->build(machine, _menu_view_labels->get_active()); + break; + case 1: + AddNode().mutate(*machine.get()); + _canvas->build(machine, _menu_view_labels->get_active()); + break; + case 2: + RemoveNode().mutate(*machine.get()); + _canvas->build(machine, _menu_view_labels->get_active()); + break; + case 3: + AdjustNode().mutate(*machine.get()); + idle_callback(); // update nodes + break; + case 4: + AddEdge().mutate(*machine.get()); + _canvas->build(machine, _menu_view_labels->get_active()); + break; + case 5: + RemoveEdge().mutate(*machine.get()); + _canvas->build(machine, _menu_view_labels->get_active()); + break; + case 6: + AdjustEdge().mutate(*machine.get()); + _canvas->update_edges(); + break; + default: throw; + } + #endif +} + +void +MachinaGUI::update_toolbar() +{ + const Driver::PlayState state = _engine->driver()->play_state(); + _record_button->set_active(state == Driver::PlayState::RECORDING); + _step_record_button->set_active(state == Driver::PlayState::STEP_RECORDING); + _play_button->set_active(state == Driver::PlayState::PLAYING); +} + +void +MachinaGUI::rebuild_canvas() +{ + _controller->announce(_engine->machine()); + _canvas->arrange(); +} + +void +MachinaGUI::quantize_record_changed() +{ + _engine->driver()->set_quantize_record(_quantize_checkbutton->get_active()); +} + +void +MachinaGUI::quantize_changed() +{ + _engine->set_quantization(1.0 / _quantize_spinbutton->get_value()); +} + +void +MachinaGUI::tempo_changed() +{ + _engine->set_bpm(_bpm_spinbutton->get_value_as_int()); +} + +void +MachinaGUI::menu_file_quit() +{ + _main_window->hide(); +} + +void +MachinaGUI::menu_file_open() +{ + Gtk::FileChooserDialog dialog(*_main_window, "Open Machine", Gtk::FILE_CHOOSER_ACTION_OPEN); + dialog.set_local_only(false); + dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK); + + Gtk::FileFilter filt; + filt.add_pattern("*.machina.ttl"); + filt.set_name("Machina Machines (Turtle/RDF)"); + dialog.set_filter(filt); + + const int result = dialog.run(); + + if (result == Gtk::RESPONSE_OK) { + SPtr<machina::Machine> new_machine = _engine->load_machine(dialog.get_uri()); + if (new_machine) { + rebuild_canvas(); + _save_uri = dialog.get_uri(); + } + } +} + +void +MachinaGUI::menu_file_save() +{ + if (_save_uri == "" || _save_uri.substr(0, 5) != "file:") { + menu_file_save_as(); + } else { + if (_save_uri.substr(0, 5) != "file:") + menu_file_save_as(); + + Sord::Model model(_engine->rdf_world(), _save_uri); + _engine->machine()->write_state(model); + model.write_to_file(_save_uri, SERD_TURTLE); + } +} + +void +MachinaGUI::menu_file_save_as() +{ + Gtk::FileChooserDialog dialog(*_main_window, "Save Machine", + Gtk::FILE_CHOOSER_ACTION_SAVE); + + dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK); + + if (_save_uri.length() > 0) + dialog.set_uri(_save_uri); + + const int result = dialog.run(); + + assert(result == Gtk::RESPONSE_OK + || result == Gtk::RESPONSE_CANCEL + || result == Gtk::RESPONSE_NONE); + + if (result == Gtk::RESPONSE_OK) { + string filename = dialog.get_filename(); + + if (filename.length() < 13 || filename.substr(filename.length()-12) != ".machina.ttl") + filename += ".machina.ttl"; + + const std::string uri = Glib::filename_to_uri(filename); + + bool confirm = false; + std::fstream fin; + fin.open(filename.c_str(), std::ios::in); + if (fin.is_open()) { // File exists + string msg = "A file named \""; + msg += filename + "\" already exists.\n\nDo you want to replace it?"; + Gtk::MessageDialog confirm_dialog(dialog, + msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true); + if (confirm_dialog.run() == Gtk::RESPONSE_YES) + confirm = true; + else + confirm = false; + } else { // File doesn't exist + confirm = true; + } + fin.close(); + + if (confirm) { + _save_uri = uri; + Sord::Model model(_engine->rdf_world(), _save_uri); + _engine->machine()->write_state(model); + model.write_to_file(_save_uri, SERD_TURTLE); + } + } +} + +void +MachinaGUI::menu_import_midi() +{ + Gtk::FileChooserDialog dialog(*_main_window, "Learn from MIDI file", + Gtk::FILE_CHOOSER_ACTION_OPEN); + dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK); + + Gtk::FileFilter filt; + filt.add_pattern("*.mid"); + filt.set_name("MIDI Files"); + dialog.set_filter(filt); + + Gtk::HBox* extra_widget = Gtk::manage(new Gtk::HBox()); + Gtk::SpinButton* length_sb = Gtk::manage(new Gtk::SpinButton()); + length_sb->set_increments(1, 10); + length_sb->set_range(0, INT_MAX); + length_sb->set_value(0); + extra_widget->pack_start(*Gtk::manage(new Gtk::Label("")), true, true); + extra_widget->pack_start(*Gtk::manage(new Gtk::Label("Maximum Length (0 = unlimited): ")), false, false); + extra_widget->pack_start(*length_sb, false, false); + dialog.set_extra_widget(*extra_widget); + extra_widget->show_all(); + + const int result = dialog.run(); + + if (result == Gtk::RESPONSE_OK) { + const double length_dbl = length_sb->get_value_as_int(); + const Raul::TimeStamp length(_unit, length_dbl); + + SPtr<machina::Machine> machine = _engine->load_machine_midi( + dialog.get_filename(), 0.0, length); + + if (machine) { + dialog.hide(); + machine->reset(NULL, machine->time()); + _engine->driver()->set_machine(machine); + _canvas->clear(); + rebuild_canvas(); + } else { + Gtk::MessageDialog msg_dialog(dialog, "Error loading MIDI file", + false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); + msg_dialog.run(); + } + } +} + +void +MachinaGUI::menu_export_midi() +{ + Gtk::FileChooserDialog dialog(*_main_window, "Export to a MIDI file", + Gtk::FILE_CHOOSER_ACTION_SAVE); + dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK); + + Gtk::FileFilter filt; + filt.add_pattern("*.mid"); + filt.set_name("MIDI Files"); + dialog.set_filter(filt); + + Gtk::HBox* extra_widget = Gtk::manage(new Gtk::HBox()); + Gtk::SpinButton* dur_sb = Gtk::manage(new Gtk::SpinButton()); + dur_sb->set_increments(1, 10); + dur_sb->set_range(0, INT_MAX); + dur_sb->set_value(0); + extra_widget->pack_start(*Gtk::manage(new Gtk::Label("")), true, true); + extra_widget->pack_start(*Gtk::manage(new Gtk::Label("Duration (beats): ")), false, false); + extra_widget->pack_start(*dur_sb, false, false); + dialog.set_extra_widget(*extra_widget); + extra_widget->show_all(); + + const int result = dialog.run(); + + if (result == Gtk::RESPONSE_OK) { + const double dur_dbl = dur_sb->get_value_as_int(); + const Raul::TimeStamp dur(_unit, dur_dbl); + _engine->export_midi(dialog.get_filename(), dur); + } +} + +void +MachinaGUI::menu_export_graphviz() +{ + Gtk::FileChooserDialog dialog(*_main_window, "Export to a GraphViz DOT file", + Gtk::FILE_CHOOSER_ACTION_SAVE); + dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK); + + const int result = dialog.run(); + + if (result == Gtk::RESPONSE_OK) + _canvas->export_dot(dialog.get_filename().c_str()); +} + +void +MachinaGUI::show_toolbar_toggled() +{ + if (_menu_view_toolbar->get_active()) + _toolbar->show(); + else + _toolbar->hide(); +} + +void +MachinaGUI::on_zoom_in() +{ + _canvas->set_font_size(_canvas->get_font_size() + 1.0); +} + +void +MachinaGUI::on_zoom_out() +{ + _canvas->set_font_size(_canvas->get_font_size() - 1.0); +} + +void +MachinaGUI::on_zoom_normal() +{ + _canvas->set_zoom(1.0); +} + +static void +show_node_label(GanvNode* node, void* data) +{ + bool show = *(bool*)data; + Ganv::Node* nodemm = Glib::wrap(node); + NodeView* const nv = dynamic_cast<NodeView*>(nodemm); + if (nv) { + nv->show_label(show); + } +} + +static void +show_edge_label(GanvEdge* edge, void* data) +{ + bool show = *(bool*)data; + Ganv::Edge* edgemm = Glib::wrap(edge); + EdgeView* const ev = dynamic_cast<EdgeView*>(edgemm); + if (ev) { + ev->show_label(show); + } +} + +void +MachinaGUI::show_labels_toggled() +{ + bool show = _menu_view_labels->get_active(); + + _canvas->for_each_node(show_node_label, &show); + _canvas->for_each_edge(show_edge_label, &show); +} + +void +MachinaGUI::menu_help_about() +{ + _about_window->set_transient_for(*_main_window); + _about_window->show(); +} + +void +MachinaGUI::menu_help_help() +{ + _help_dialog->set_transient_for(*_main_window); + _help_dialog->run(); + _help_dialog->hide(); +} + +void +MachinaGUI::stop_toggled() +{ + if (_stop_button->get_active()) { + const Driver::PlayState old_state = _engine->driver()->play_state(); + _engine->driver()->set_play_state(Driver::PlayState::STOPPED); + if (old_state == Driver::PlayState::RECORDING || + old_state == Driver::PlayState::STEP_RECORDING) { + rebuild_canvas(); + } + } +} + +void +MachinaGUI::play_toggled() +{ + if (_play_button->get_active()) { + const Driver::PlayState old_state = _engine->driver()->play_state(); + _engine->driver()->set_play_state(Driver::PlayState::PLAYING); + if (old_state == Driver::PlayState::RECORDING || + old_state == Driver::PlayState::STEP_RECORDING) { + rebuild_canvas(); + } + } +} + +void +MachinaGUI::record_toggled() +{ + if (_record_button->get_active()) { + _engine->driver()->set_play_state(Driver::PlayState::RECORDING); + } +} + +void +MachinaGUI::step_record_toggled() +{ + if (_step_record_button->get_active()) { + _engine->driver()->set_play_state(Driver::PlayState::STEP_RECORDING); + } +} + +void +MachinaGUI::chain_toggled() +{ + if (_chain_button->get_active()) { + _chain_mode = true; + } +} + +void +MachinaGUI::fan_toggled() +{ + if (_fan_button->get_active()) { + _chain_mode = false; + } +} + +void +MachinaGUI::on_new_object(SPtr<client::ClientObject> object) +{ + _canvas->on_new_object(object); +} + +void +MachinaGUI::on_erase_object(SPtr<client::ClientObject> object) +{ + _canvas->on_erase_object(object); +} + +} // namespace machina +} // namespace gui diff --git a/src/gui/MachinaGUI.hpp b/src/gui/MachinaGUI.hpp new file mode 100644 index 0000000..bb111a2 --- /dev/null +++ b/src/gui/MachinaGUI.hpp @@ -0,0 +1,190 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_GUI_HPP +#define MACHINA_GUI_HPP + +#include <string> + +#include <gtkmm.h> + +#include "raul/Maid.hpp" +#include "raul/TimeStamp.hpp" + +#include "machina/types.hpp" +#include "machina_config.h" + +using namespace std; + +namespace machina { + +class Machine; +class Engine; +class Evolver; +class Controller; + +namespace client { +class ClientModel; +class ClientObject; +} + +namespace gui { + +class MachinaCanvas; + +class MachinaGUI +{ +public: + explicit MachinaGUI(SPtr<machina::Engine> engine); + ~MachinaGUI(); + + SPtr<MachinaCanvas> canvas() { return _canvas; } + SPtr<machina::Engine> engine() { return _engine; } + SPtr<machina::Controller> controller() { return _controller; } + Forge& forge() { return _forge; } + SPtr<Raul::Maid> maid() { return _maid; } + Gtk::Window* window() { return _main_window; } + + void attach(); + void quit() { _main_window->hide(); } + + bool chain_mode() const { return _chain_mode; } + + double default_length() const { + return 1 / (double)_quantize_spinbutton->get_value(); + } + + inline void queue_refresh() { _refresh = true; } + + void on_new_object(SPtr<machina::client::ClientObject> object); + void on_erase_object(SPtr<machina::client::ClientObject> object); + + SPtr<machina::client::ClientModel> client_model() { + return _client_model; + } + +protected: + void menu_file_quit(); + void menu_file_open(); + void menu_file_save(); + void menu_file_save_as(); + void menu_import_midi(); + void menu_export_midi(); + void menu_export_graphviz(); + void on_zoom_in(); + void on_zoom_out(); + void on_zoom_normal(); + void show_toolbar_toggled(); + void show_labels_toggled(); + void menu_help_about(); + void menu_help_help(); + void arrange(); + void load_target_clicked(); + + void random_mutation(SPtr<machina::Machine> machine); + void mutate(SPtr<machina::Machine> machine, unsigned mutation); + void update_toolbar(); + void rebuild_canvas(); + + bool scrolled_window_event(GdkEvent* ev); + bool idle_callback(); + +#ifdef HAVE_EUGENE + void evolve_toggled(); + bool evolve_callback(); +#endif + + void stop_toggled(); + void play_toggled(); + void record_toggled(); + void step_record_toggled(); + + void chain_toggled(); + void fan_toggled(); + + void quantize_record_changed(); + void quantize_changed(); + void tempo_changed(); + + string _save_uri; + string _target_filename; + + Raul::TimeUnit _unit; + + SPtr<MachinaCanvas> _canvas; + SPtr<machina::Engine> _engine; + SPtr<machina::client::ClientModel> _client_model; + SPtr<machina::Controller> _controller; + + SPtr<Raul::Maid> _maid; + SPtr<machina::Evolver> _evolver; + + Forge _forge; + + Gtk::Main* _gtk_main; + + Gtk::Window* _main_window; + Gtk::Dialog* _help_dialog; + Gtk::AboutDialog* _about_window; + Gtk::Toolbar* _toolbar; + Gtk::MenuItem* _menu_file_open; + Gtk::MenuItem* _menu_file_save; + Gtk::MenuItem* _menu_file_save_as; + Gtk::MenuItem* _menu_file_quit; + Gtk::MenuItem* _menu_zoom_in; + Gtk::MenuItem* _menu_zoom_out; + Gtk::MenuItem* _menu_zoom_normal; + Gtk::MenuItem* _menu_view_arrange; + Gtk::MenuItem* _menu_import_midi; + Gtk::MenuItem* _menu_export_midi; + Gtk::MenuItem* _menu_export_graphviz; + Gtk::MenuItem* _menu_help_about; + Gtk::CheckMenuItem* _menu_view_labels; + Gtk::CheckMenuItem* _menu_view_toolbar; + Gtk::MenuItem* _menu_help_help; + Gtk::ScrolledWindow* _canvas_scrolledwindow; + Gtk::TextView* _status_text; + Gtk::Expander* _messages_expander; + Gtk::SpinButton* _bpm_spinbutton; + Gtk::CheckButton* _quantize_checkbutton; + Gtk::SpinButton* _quantize_spinbutton; + Gtk::ToggleToolButton* _stop_button; + Gtk::ToggleToolButton* _play_button; + Gtk::ToggleToolButton* _record_button; + Gtk::ToggleToolButton* _step_record_button; + Gtk::RadioButton* _chain_button; + Gtk::RadioButton* _fan_button; + Gtk::ToolButton* _load_target_button; + Gtk::Toolbar* _evolve_toolbar; + Gtk::ToggleToolButton* _evolve_button; + Gtk::ToolButton* _mutate_button; + Gtk::ToolButton* _compress_button; + Gtk::ToolButton* _add_node_button; + Gtk::ToolButton* _remove_node_button; + Gtk::ToolButton* _adjust_node_button; + Gtk::ToolButton* _add_edge_button; + Gtk::ToolButton* _remove_edge_button; + Gtk::ToolButton* _adjust_edge_button; + + bool _refresh; + bool _evolve; + bool _chain_mode; +}; + +} // namespace machina +} // namespace gui + +#endif // MACHINA_GUI_HPP diff --git a/src/gui/NodePropertiesWindow.cpp b/src/gui/NodePropertiesWindow.cpp new file mode 100644 index 0000000..f22eb57 --- /dev/null +++ b/src/gui/NodePropertiesWindow.cpp @@ -0,0 +1,136 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <string> + +#include "client/ClientObject.hpp" +#include "machina/URIs.hpp" +#include "raul/TimeStamp.hpp" + +#include "MachinaGUI.hpp" +#include "NodePropertiesWindow.hpp" +#include "WidgetFactory.hpp" + +using namespace std; + +namespace machina { +namespace gui { + +NodePropertiesWindow* NodePropertiesWindow::_instance = NULL; + +NodePropertiesWindow::NodePropertiesWindow( + BaseObjectType* cobject, + const Glib::RefPtr<Gtk::Builder>& xml) + : Gtk::Dialog(cobject) + , _gui(NULL) +{ + property_visible() = false; + + xml->get_widget("node_properties_note_spinbutton", _note_spinbutton); + xml->get_widget("node_properties_duration_spinbutton", _duration_spinbutton); + xml->get_widget("node_properties_apply_button", _apply_button); + xml->get_widget("node_properties_cancel_button", _cancel_button); + xml->get_widget("node_properties_ok_button", _ok_button); + + _apply_button->signal_clicked().connect( + sigc::mem_fun(this, &NodePropertiesWindow::apply_clicked)); + _cancel_button->signal_clicked().connect( + sigc::mem_fun(this, &NodePropertiesWindow::cancel_clicked)); + _ok_button->signal_clicked().connect( + sigc::mem_fun(this, &NodePropertiesWindow::ok_clicked)); +} + +NodePropertiesWindow::~NodePropertiesWindow() +{} + +void +NodePropertiesWindow::apply_clicked() +{ +#if 0 + const uint8_t note = _note_spinbutton->get_value(); + if (!_node->enter_action()) { + _node->set_enter_action(ActionFactory::note_on(note)); + _node->set_exit_action(ActionFactory::note_off(note)); + } else { + SPtr<MidiAction> action = dynamic_ptr_cast<MidiAction>(_node->enter_action()); + action->event()[1] = note; + action = dynamic_ptr_cast<MidiAction>(_node->exit_action()); + action->event()[1] = note; + } +#endif + _node->set(URIs::instance().machina_duration, + _gui->forge().make(float(_duration_spinbutton->get_value()))); +} + +void +NodePropertiesWindow::cancel_clicked() +{ + assert(this == _instance); + delete _instance; + _instance = NULL; +} + +void +NodePropertiesWindow::ok_clicked() +{ + apply_clicked(); + cancel_clicked(); +} + +void +NodePropertiesWindow::set_node(MachinaGUI* gui, + SPtr<machina::client::ClientObject> node) +{ + _gui = gui; + _node = node; + #if 0 + SPtr<MidiAction> enter_action = dynamic_ptr_cast<MidiAction>(node->enter_action()); + if (enter_action && ( enter_action->event_size() > 1) + && ( (enter_action->event()[0] & 0xF0) == 0x90) ) { + _note_spinbutton->set_value(enter_action->event()[1]); + _note_spinbutton->show(); + } else if (!enter_action) { + _note_spinbutton->set_value(60); + _note_spinbutton->show(); + } else { + _note_spinbutton->hide(); + } + #endif + _duration_spinbutton->set_value( + node->get(URIs::instance().machina_duration).get<float>()); +} + +void +NodePropertiesWindow::present(MachinaGUI* gui, + Gtk::Window* parent, + SPtr<machina::client::ClientObject> node) +{ + if (!_instance) { + Glib::RefPtr<Gtk::Builder> xml = WidgetFactory::create(); + + xml->get_widget_derived("node_properties_dialog", _instance); + + if (parent) { + _instance->set_transient_for(*parent); + } + } + + _instance->set_node(gui, node); + _instance->show(); +} + +} // namespace machina +} // namespace gui diff --git a/src/gui/NodePropertiesWindow.hpp b/src/gui/NodePropertiesWindow.hpp new file mode 100644 index 0000000..a999004 --- /dev/null +++ b/src/gui/NodePropertiesWindow.hpp @@ -0,0 +1,67 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef NODEPROPERTIESWINDOW_HPP +#define NODEPROPERTIESWINDOW_HPP + +#include <gtkmm.h> + +#include "machina/types.hpp" + +namespace machina { + +namespace client { class ClientObject; } + +namespace gui { + +class MachinaGUI; + +class NodePropertiesWindow : public Gtk::Dialog +{ +public: + NodePropertiesWindow(BaseObjectType* cobject, + const Glib::RefPtr<Gtk::Builder>& xml); + + ~NodePropertiesWindow(); + + static void present(MachinaGUI* gui, + Gtk::Window* parent, + SPtr<machina::client::ClientObject> node); + +private: + void set_node(MachinaGUI* gui, + SPtr<machina::client::ClientObject> node); + + void apply_clicked(); + void cancel_clicked(); + void ok_clicked(); + + static NodePropertiesWindow* _instance; + + MachinaGUI* _gui; + SPtr<machina::client::ClientObject> _node; + + Gtk::SpinButton* _note_spinbutton; + Gtk::SpinButton* _duration_spinbutton; + Gtk::Button* _apply_button; + Gtk::Button* _cancel_button; + Gtk::Button* _ok_button; +}; + +} // namespace machina +} // namespace gui + +#endif // NODEPROPERTIESWINDOW_HPP diff --git a/src/gui/NodeView.cpp b/src/gui/NodeView.cpp new file mode 100644 index 0000000..a1b96e4 --- /dev/null +++ b/src/gui/NodeView.cpp @@ -0,0 +1,203 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "machina/Controller.hpp" +#include "machina/URIs.hpp" +#include "machina/types.hpp" + +#include "client/ClientModel.hpp" + +#include "MachinaCanvas.hpp" +#include "MachinaGUI.hpp" +#include "NodePropertiesWindow.hpp" +#include "NodeView.hpp" + +using namespace std; + +namespace machina { +namespace gui { + +NodeView::NodeView(Gtk::Window* window, + Ganv::Canvas& canvas, + SPtr<machina::client::ClientObject> node, + double x, + double y) + : Ganv::Circle(canvas, "", x, y) + , _window(window) + , _node(node) + , _default_border_color(get_border_color()) + , _default_fill_color(get_fill_color()) +{ + set_fit_label(false); + set_radius_ems(1.25); + + signal_event().connect(sigc::mem_fun(this, &NodeView::on_event)); + + MachinaCanvas* mcanvas = dynamic_cast<MachinaCanvas*>(&canvas); + if (is(mcanvas->app()->forge(), URIs::instance().machina_initial)) { + set_border_width(4.0); + set_is_source(true); + const uint8_t alpha[] = { 0xCE, 0xB1, 0 }; + set_label((const char*)alpha); + } + + node->signal_property.connect(sigc::mem_fun(this, &NodeView::on_property)); + + for (const auto& p : node->properties()) { + on_property(p.first, p.second); + } +} + +NodeView::~NodeView() +{ + _node->set_view(NULL); +} + +bool +NodeView::on_double_click(GdkEventButton*) +{ + MachinaCanvas* canvas = dynamic_cast<MachinaCanvas*>(this->canvas()); + NodePropertiesWindow::present(canvas->app(), _window, _node); + return true; +} + +bool +NodeView::is(Forge& forge, machina::URIInt key) +{ + const Atom& value = _node->get(key); + return value.type() == forge.Bool && value.get<int32_t>(); +} + +bool +NodeView::on_event(GdkEvent* event) +{ + MachinaCanvas* canvas = dynamic_cast<MachinaCanvas*>(this->canvas()); + Forge& forge = canvas->app()->forge(); + if (event->type == GDK_BUTTON_PRESS) { + if (event->button.state & GDK_CONTROL_MASK) { + if (event->button.button == 1) { + canvas->app()->controller()->set_property( + _node->id(), + URIs::instance().machina_selector, + forge.make(!is(forge, URIs::instance().machina_selector))); + return true; + } + } else { + return _signal_clicked.emit(&event->button); + } + } else if (event->type == GDK_2BUTTON_PRESS) { + return on_double_click(&event->button); + } + return false; +} + +static void +midi_note_name(uint8_t num, uint8_t buf[8]) +{ + static const char* notes = "CCDDEFFGGAAB"; + static const bool is_sharp[] = { 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0 }; + + const uint8_t octave = num / 12; + const uint8_t id = num - octave * 12; + const uint8_t sub[] = { 0xE2, 0x82, uint8_t(0x80 + octave) }; + const uint8_t sharp[] = { 0xE2, 0x99, 0xAF }; + + int b = 0; + buf[b++] = notes[id]; + if (is_sharp[id]) { + for (unsigned s = 0; s < sizeof(sharp); ++s) { + buf[b++] = sharp[s]; + } + } + for (unsigned s = 0; s < sizeof(sub); ++s) { + buf[b++] = sub[s]; + } + buf[b++] = 0; +} + +void +NodeView::show_label(bool show) +{ + if (show && _enter_action) { + Atom note_number = _enter_action->get( + URIs::instance().machina_note_number); + if (note_number.is_valid()) { + uint8_t buf[8]; + midi_note_name(note_number.get<int32_t>(), buf); + set_label((const char*)buf); + return; + } + } + + set_label(""); +} + +void +NodeView::on_property(machina::URIInt key, const Atom& value) +{ + static const uint32_t active_color = 0x408040FF; + static const uint32_t active_border_color = 0x00FF00FF; + + if (key == URIs::instance().machina_selector) { + if (value.get<int32_t>()) { + set_dash_length(4.0); + } else { + set_dash_length(0.0); + } + } else if (key == URIs::instance().machina_initial) { + set_border_width(value.get<int32_t>() ? 4.0 : 1.0); + set_is_source(value.get<int32_t>()); + } else if (key == URIs::instance().machina_active) { + if (value.get<int32_t>()) { + if (get_fill_color() != active_color) { + set_fill_color(active_color); + set_border_color(active_border_color); + } + } else if (get_fill_color() == active_color) { + set_default_colors(); + } + } else if (key == URIs::instance().machina_enter_action) { + const uint64_t action_id = value.get<int32_t>(); + MachinaCanvas* canvas = dynamic_cast<MachinaCanvas*>(this->canvas()); + _enter_action_connection.disconnect(); + _enter_action = canvas->app()->client_model()->find(action_id); + if (_enter_action) { + _enter_action_connection = _enter_action->signal_property.connect( + sigc::mem_fun(this, &NodeView::on_action_property)); + for (auto i : _enter_action->properties()) { + on_action_property(i.first, i.second); + } + } + } +} + +void +NodeView::on_action_property(machina::URIInt key, const Atom& value) +{ + if (key == URIs::instance().machina_note_number) { + show_label(true); + } +} + +void +NodeView::set_default_colors() +{ + set_fill_color(_default_fill_color); + set_border_color(_default_border_color); +} + +} // namespace machina +} // namespace gui diff --git a/src/gui/NodeView.hpp b/src/gui/NodeView.hpp new file mode 100644 index 0000000..6e0681c --- /dev/null +++ b/src/gui/NodeView.hpp @@ -0,0 +1,76 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_NODEVIEW_HPP +#define MACHINA_NODEVIEW_HPP + +#include "ganv/Circle.hpp" + +#include "client/ClientObject.hpp" + +#include "machina/types.hpp" + +namespace machina { +namespace gui { + +class NodeView + : public Ganv::Circle + , public machina::client::ClientObject::View +{ +public: + NodeView(Gtk::Window* window, + Canvas& canvas, + SPtr<machina::client::ClientObject> node, + double x, + double y); + + ~NodeView(); + + SPtr<machina::client::ClientObject> node() { return _node; } + + void show_label(bool show); + + void update_state(bool show_labels); + + void set_default_colors(); + + sigc::signal<bool, GdkEventButton*>& signal_clicked() { + return _signal_clicked; + } + +private: + bool on_event(GdkEvent* ev); + bool on_double_click(GdkEventButton* ev); + void on_property(machina::URIInt key, const Atom& value); + void on_action_property(machina::URIInt key, const Atom& value); + + bool is(Forge& forge, machina::URIInt key); + + Gtk::Window* _window; + SPtr<machina::client::ClientObject> _node; + uint32_t _default_border_color; + uint32_t _default_fill_color; + + SPtr<machina::client::ClientObject> _enter_action; + sigc::connection _enter_action_connection; + + sigc::signal<bool, GdkEventButton*> _signal_clicked; +}; + +} // namespace machina +} // namespace gui + +#endif // MACHINA_NODEVIEW_HPP diff --git a/src/gui/WidgetFactory.hpp b/src/gui/WidgetFactory.hpp new file mode 100644 index 0000000..08118dc --- /dev/null +++ b/src/gui/WidgetFactory.hpp @@ -0,0 +1,65 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <fstream> +#include <iostream> +#include <string> + +#include <gtkmm.h> + +#include "machina_config.h" + +namespace machina { +namespace gui { + +class WidgetFactory +{ +public: + static Glib::RefPtr<Gtk::Builder> create() { + Glib::RefPtr<Gtk::Builder> xml; + + // Check for the .ui file in current directory + std::string ui_filename = "./machina.ui"; + std::ifstream fs(ui_filename.c_str()); + if (fs.fail()) { + // didn't find it, check MACHINA_DATA_DIR + fs.clear(); + ui_filename = MACHINA_DATA_DIR; + ui_filename += "/machina.ui"; + + fs.open(ui_filename.c_str()); + if (fs.fail()) { + std::cerr << "No machina.ui in current directory or " + << MACHINA_DATA_DIR << "." << std::endl; + exit(EXIT_FAILURE); + } + fs.close(); + } + + try { + xml = Gtk::Builder::create_from_file(ui_filename); + } catch (const Gtk::BuilderError& ex) { + std::cerr << ex.what() << std::endl; + throw ex; + } + + return xml; + } + +}; + +} // namespace machina +} // namespace gui diff --git a/src/gui/machina.gladep b/src/gui/machina.gladep new file mode 100644 index 0000000..c27832f --- /dev/null +++ b/src/gui/machina.gladep @@ -0,0 +1,9 @@ +<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*--> +<!DOCTYPE glade-project SYSTEM "http://glade.gnome.org/glade-project-2.0.dtd"> + +<glade-project> + <name>Machina</name> + <program_name>machina</program_name> + <language>C++</language> + <gnome_support>FALSE</gnome_support> +</glade-project> diff --git a/src/gui/machina.svg b/src/gui/machina.svg new file mode 100644 index 0000000..39f7be0 --- /dev/null +++ b/src/gui/machina.svg @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.45pre1" + width="256" + height="256" + version="1.0" + sodipodi:docbase="/home/dave/code/lad/machina/src/gui" + sodipodi:docname="machina-icon.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + sodipodi:modified="true"> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs5" /> + <sodipodi:namedview + inkscape:window-height="771" + inkscape:window-width="1183" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + inkscape:zoom="1.4142136" + inkscape:cx="310.87907" + inkscape:cy="40.263308" + inkscape:window-x="293" + inkscape:window-y="52" + inkscape:current-layer="svg2" + width="256px" + height="256px" /> + <path + style="fill:#000000" + id="path2189" + d="M 9.0278551,221.40628 C 22.572047,206.65805 31.269455,188.04472 39.259152,169.8919 C 51.644822,140.58208 56.041288,109.13092 58.849953,77.700978 C 59.962909,56.867589 61.443535,35.864275 59.495794,15.031194 C 63.699947,13.959444 67.938586,10.617016 72.108252,11.815945 C 77.429885,13.346107 71.833885,22.901101 72.179608,28.42755 C 73.082013,42.852664 75.819667,56.648771 78.944682,70.727057 C 85.857753,99.135712 98.546656,125.08352 122.5541,142.3916 C 149.02904,155.16169 159.76491,132.36389 173.41824,112.97967 C 189.89026,87.286307 201.67487,59.107084 212.79124,30.80732 C 238.81023,-0.42720544 213.47911,64.070958 211.76716,70.33904 C 202.71922,108.04829 200.75405,146.89462 201.60034,185.52025 C 202.59785,202.30805 203.03716,219.51338 208.40658,235.60545 C 209.82998,239.98783 211.97855,244.0493 214.19967,248.06447 L 201.80123,255.17231 C 199.63995,251.03059 197.23968,246.9206 196.00991,242.38503 C 191.48082,225.58243 192.01187,207.89788 191.55783,190.64995 C 191.61755,152.05096 193.33386,113.23415 201.28376,75.349455 C 205.86472,56.717664 207.3011,28.642568 227.34088,20.082503 C 229.12544,19.320222 225.62336,23.562881 224.76461,25.303071 C 211.8352,53.436232 199.91298,82.147117 183.57547,108.55509 C 167.9424,131.6754 143.08989,165.0319 111.95557,149.10157 C 88.367319,131.04024 76.047527,104.6256 68.72284,76.231983 C 66.537387,67.180737 52.831088,26.99333 59.36089,18.303914 C 62.551525,14.058035 67.78841,11.838043 72.002169,8.6051067 C 71.542368,29.947073 70.679719,51.279977 69.135855,72.573834 C 66.386515,103.98497 62.160375,135.44301 50.178851,164.87632 C 42.654028,182.74324 34.458191,200.83743 22.235303,216.04826 L 9.0278551,221.40628 z " /> + <path + style="fill:#000000" + id="path2191" + d="M 177.4044,227.49356 C 186.73664,226.83386 194.85497,230.46984 202.63542,235.17331 C 208.34182,239.12994 205.55279,237.07748 211.00405,241.32859 L 199.24569,248.73751 C 193.86468,244.57093 196.63478,246.5644 190.93236,242.7613 C 183.22589,238.37114 175.24676,235.06127 166.16497,235.78874 L 177.4044,227.49356 z " /> + <path + style="fill:#000000" + id="path2193" + d="M 202.904,253.28805 C 206.25526,244.218 211.55226,236.16787 216.96397,228.23361 C 217.73013,227.17184 218.49629,226.11007 219.26244,225.0483 L 232.24972,219.44712 C 231.4278,220.51566 230.60588,221.58419 229.78396,222.65273 C 224.30649,230.41756 219.07262,238.3307 215.72758,247.29568 L 202.904,253.28805 z " /> +</svg> diff --git a/src/gui/machina.ui b/src/gui/machina.ui new file mode 100644 index 0000000..89404fc --- /dev/null +++ b/src/gui/machina.ui @@ -0,0 +1,1128 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="2.16"/> + <!-- interface-naming-policy toplevel-contextual --> + <object class="GtkRadioAction" id="play_action"> + <property name="stock_id">gtk-media-play</property> + <property name="draw_as_radio">True</property> + <property name="group">record_action</property> + </object> + <object class="GtkRadioAction" id="step_record_action"> + <property name="label" translatable="yes">Step record</property> + <property name="stock_id">gtk-add</property> + <property name="draw_as_radio">True</property> + <property name="group">record_action</property> + </object> + <object class="GtkRadioAction" id="stop_action"> + <property name="stock_id">gtk-media-stop</property> + <property name="draw_as_radio">True</property> + <property name="group">record_action</property> + </object> + <object class="GtkAboutDialog" id="about_win"> + <property name="can_focus">False</property> + <property name="destroy_with_parent">True</property> + <property name="type_hint">normal</property> + <property name="program_name">Machina</property> + <property name="copyright" translatable="yes">© 2013 David Robillard <http://drobilla.net></property> + <property name="comments" translatable="yes">A MIDI sequencer based on + probabilistic finite-state automata</property> + <property name="website">http://drobilla.net/software/machina</property> + <property name="license" translatable="yes">Machina is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +Machina is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Machina; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +</property> + <property name="authors">David Robillard <d@drobilla.net></property> + <property name="translator_credits" translatable="yes" comments="TRANSLATORS: Replace this string with your names, one name per line.">translator-credits</property> + <property name="logo">machina.svg</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area1"> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + </object> + <object class="GtkAdjustment" id="bpm_adjustment"> + <property name="lower">1</property> + <property name="upper">480</property> + <property name="value">120</property> + <property name="step_increment">1</property> + <property name="page_increment">10</property> + </object> + <object class="GtkAdjustment" id="duration_adjustment"> + <property name="upper">64</property> + <property name="value">0.25</property> + <property name="step_increment">1</property> + <property name="page_increment">4</property> + </object> + <object class="GtkDialog" id="help_dialog"> + <property name="can_focus">False</property> + <property name="border_width">8</property> + <property name="title" translatable="yes">Machina Help</property> + <property name="resizable">False</property> + <property name="window_position">center-on-parent</property> + <property name="icon_name">gtk-help</property> + <property name="type_hint">dialog</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="closebutton1"> + <property name="label">gtk-close</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">False</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label5"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="label" translatable="yes"><big><b>Nodes</b></big> +Nodes represent notes, which have a pitch and duration. +When a node is highlighted green, it is playing. + +Play begins at the initial node α. Whenever all nodes become +inactive or stop is pressed, play returns to the initial node. + +Nodes with dashed borders are selectors. Only one successor is +played after a selector, i.e. only one outgoing arc is traversed. + + • Right click the canvas to create a new node + • Middle click a node to learn a MIDI note + • Double click a node to edit its properties + • Ctrl+Left click a node to make it a selector + +<big><b>Arcs</b></big> +When a node is finished playing, play travels along outgoing +arcs, depending on their probability. The colour of an arc +indicates its probability, where green is high and red is low. + + • Ctrl+Left click an arc to decrease its probability + • Ctrl+Right click an arc to increase its probability + +<big><b>Recording</b></big> +A machine can be built by recording MIDI input. To record, press +the record button and play some MIDI notes. To finish recording, +press stop or play and the new nodes will be added to the machine. + +Normal recording inserts delay nodes to reproduce the timing of +the input. To avoid this, use step recording which directly connects +nodes to their successors with no delays in-between. + +<big><b>Connecting</b></big> +Connecting nodes is based on the selection. If there is a selection, +clicking a node will connect the selection to that node. + +There are two modes: chain and fan. In chain mode, the selection is +moved to the clicked node for quickly connecting long chains of nodes. +In fan mode, the selection is unchanged for quickly connecting the +selection to many nodes.</property> + <property name="use_markup">True</property> + <property name="justify">fill</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="padding">8</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-7">closebutton1</action-widget> + </action-widgets> + </object> + <object class="GtkAdjustment" id="length_adjustment"> + <property name="lower">1</property> + <property name="upper">256</property> + <property name="value">8</property> + <property name="step_increment">1</property> + <property name="page_increment">8</property> + </object> + <object class="GtkWindow" id="machina_win"> + <property name="can_focus">False</property> + <property name="border_width">1</property> + <property name="title" translatable="yes">Machina</property> + <property name="default_width">640</property> + <property name="default_height">480</property> + <property name="icon">machina.svg</property> + <child> + <object class="GtkVBox" id="main_vbox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkMenuBar" id="menubar"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkMenuItem" id="file_menu"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">_File</property> + <property name="use_underline">True</property> + <child type="submenu"> + <object class="GtkMenu" id="file_menu_menu"> + <property name="can_focus">False</property> + <child> + <object class="GtkImageMenuItem" id="open_menuitem"> + <property name="label">gtk-open</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <signal name="activate" handler="on_open_session_menuitem_activate" swapped="no"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem" id="save_menuitem"> + <property name="label">gtk-save</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <signal name="activate" handler="on_save_session_menuitem_activate" swapped="no"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem" id="save_as_menuitem"> + <property name="label">gtk-save-as</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <signal name="activate" handler="on_save_session_as_menuitem_activate" swapped="no"/> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem" id="separator5"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + </child> + <child> + <object class="GtkImageMenuItem" id="import_midi_menuitem"> + <property name="label">_Import MIDI...</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <accelerator key="I" signal="activate" modifiers="GDK_CONTROL_MASK"/> + <signal name="activate" handler="on_learn_midi_menuitem_activate" swapped="no"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem" id="export_midi_menuitem"> + <property name="label">_Export MIDI...</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <accelerator key="E" signal="activate" modifiers="GDK_CONTROL_MASK"/> + <signal name="activate" handler="on_export_midi_menuitem_activate" swapped="no"/> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem" id="separator7"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + </child> + <child> + <object class="GtkImageMenuItem" id="export_graphviz_menuitem"> + <property name="label">Export _GraphViz...</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <accelerator key="d" signal="activate" modifiers="GDK_CONTROL_MASK"/> + <signal name="activate" handler="on_export_graphviz_menuitem_activate" swapped="no"/> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem" id="separator6"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + </child> + <child> + <object class="GtkImageMenuItem" id="quit_menuitem"> + <property name="label">gtk-quit</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <signal name="activate" handler="on_quit1_activate" swapped="no"/> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkMenuItem" id="view_menu"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">_View</property> + <property name="use_underline">True</property> + <child type="submenu"> + <object class="GtkMenu" id="view_menu_menu"> + <property name="can_focus">False</property> + <child> + <object class="GtkCheckMenuItem" id="view_labels_menuitem"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">_Labels</property> + <property name="use_underline">True</property> + <accelerator key="L" signal="activate" modifiers="GDK_CONTROL_MASK"/> + <signal name="activate" handler="on_view_edge_labels_menuitem_activate" swapped="no"/> + </object> + </child> + <child> + <object class="GtkCheckMenuItem" id="view_toolbar_menuitem"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">_Toolbar</property> + <property name="use_underline">True</property> + <property name="active">True</property> + <accelerator key="T" signal="activate" modifiers="GDK_CONTROL_MASK"/> + <signal name="activate" handler="on_toolbar2_activate" swapped="no"/> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem" id="separatormenuitem3"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + </child> + <child> + <object class="GtkImageMenuItem" id="zoom_in_menuitem"> + <property name="label">gtk-zoom-in</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <accelerator key="plus" signal="activate" modifiers="GDK_CONTROL_MASK"/> + <accelerator key="equal" signal="activate" modifiers="GDK_CONTROL_MASK"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem" id="zoom_out_menuitem"> + <property name="label">gtk-zoom-out</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <accelerator key="minus" signal="activate" modifiers="GDK_CONTROL_MASK"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem" id="zoom_normal_menuitem"> + <property name="label">gtk-zoom-100</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <accelerator key="0" signal="activate" modifiers="GDK_CONTROL_MASK"/> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem" id="separatormenuitem1"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + </child> + <child> + <object class="GtkMenuItem" id="arrange_menuitem"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Arrange</property> + <property name="use_underline">True</property> + <accelerator key="g" signal="activate" modifiers="GDK_CONTROL_MASK"/> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkMenuItem" id="help_menu"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">_Help</property> + <property name="use_underline">True</property> + <child type="submenu"> + <object class="GtkMenu" id="help_menu_menu"> + <property name="can_focus">False</property> + <child> + <object class="GtkImageMenuItem" id="help_help_menuitem"> + <property name="label">gtk-help</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <signal name="activate" handler="on_help_about_menuitem_activate" swapped="no"/> + </object> + </child> + <child> + <object class="GtkImageMenuItem" id="help_about_menuitem"> + <property name="label">gtk-about</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <signal name="activate" handler="on_about1_activate" swapped="no"/> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <child> + <object class="GtkToolbar" id="toolbar"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="toolbar_style">icons</property> + <property name="show_arrow">False</property> + <property name="icon_size">2</property> + <property name="icon_size_set">True</property> + <child> + <object class="GtkToggleToolButton" id="stop_but"> + <property name="related_action">stop_action</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="tooltip_text" translatable="yes">Stop</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="play_but"> + <property name="related_action">play_action</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="tooltip_text" translatable="yes">Play</property> + <property name="active">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="record_but"> + <property name="related_action">record_action</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="tooltip_text" translatable="yes">Record</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="step_record_but"> + <property name="related_action">step_record_action</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="tooltip_text" translatable="yes">Step record</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="separatortoolitem1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="toolitem1"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkHBox" id="hbox3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkSpinButton" id="bpm_spinbutton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="tooltip_text" translatable="yes">Playback tempo</property> + <property name="max_length">3</property> + <property name="invisible_char">•</property> + <property name="width_chars">3</property> + <property name="primary_icon_activatable">False</property> + <property name="secondary_icon_activatable">False</property> + <property name="primary_icon_sensitive">True</property> + <property name="secondary_icon_sensitive">True</property> + <property name="adjustment">bpm_adjustment</property> + <property name="climb_rate">1</property> + <property name="numeric">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label8"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes"> BPM</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="separatortoolitem2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="toolitem3"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkHBox" id="hbox4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">4</property> + <child> + <object class="GtkCheckButton" id="quantize_checkbutton"> + <property name="label" translatable="yes">Quantize 1/</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="tooltip_text" translatable="yes">Quantize recorded notes</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="quantize_spinbutton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="tooltip_text" translatable="yes">Note type for quantization</property> + <property name="primary_icon_activatable">False</property> + <property name="secondary_icon_activatable">False</property> + <property name="primary_icon_sensitive">True</property> + <property name="secondary_icon_sensitive">True</property> + <property name="adjustment">quantize_adjustment</property> + <property name="climb_rate">1</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="separatortoolitem3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <object class="GtkToolItem" id="toolitem2"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">4</property> + <child> + <object class="GtkRadioButton" id="chain_but"> + <property name="label" translatable="yes">Chain</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="tooltip_text" translatable="yes">Move selection to head node after connection</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="fan_but"> + <property name="label" translatable="yes">Fan</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="tooltip_text" translatable="yes">Keep selection on tail node after connection</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + <property name="group">chain_but</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToolbar" id="evolve_toolbar"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="toolbar_style">icons</property> + <property name="show_arrow">False</property> + <property name="icon_size">2</property> + <property name="icon_size_set">True</property> + <child> + <object class="GtkSeparatorToolItem" id="toolbutton7"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + </object> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="load_target_but"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="stock_id">gtk-open</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="toolbutton5"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + </object> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <object class="GtkToggleToolButton" id="evolve_but"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Evolve machine (towards target MIDI)</property> + <property name="stock_id">gtk-execute</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="toolbutton4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + </object> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="mutate_but"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="stock_id">gtk-dialog-warning</property> + <accelerator key="m" signal="clicked" modifiers="GDK_CONTROL_MASK"/> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="toolbutton3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + </object> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="compress_but"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="stock_id">gtk-convert</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="toolbutton2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + </object> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="add_node_but"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Add Node</property> + <property name="stock_id">gtk-new</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="remove_node_but"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Delete Node</property> + <property name="stock_id">gtk-delete</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="adjust_node_but"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Adjust Node</property> + <property name="stock_id">gtk-edit</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkSeparatorToolItem" id="toolbutton1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + </object> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="add_edge_but"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Add Edge</property> + <property name="stock_id">gtk-connect</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="remove_edge_but"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Remove Edge</property> + <property name="stock_id">gtk-disconnect</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> + <object class="GtkToolButton" id="adjust_edge_but"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Adjust Edge</property> + <property name="stock_id">gtk-select-color</property> + </object> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="canvas_scrolledwindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="shadow_type">in</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="padding">2</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + </object> + <object class="GtkAdjustment" id="note_num_adjustment"> + <property name="upper">127</property> + <property name="value">64</property> + <property name="step_increment">1</property> + <property name="page_increment">10</property> + </object> + <object class="GtkRadioAction" id="record_action"> + <property name="stock_id">gtk-media-record</property> + <property name="draw_as_radio">True</property> + </object> + <object class="GtkDialog" id="node_properties_dialog"> + <property name="can_focus">False</property> + <property name="border_width">8</property> + <property name="title" translatable="yes">dialog1</property> + <property name="resizable">False</property> + <property name="icon">machina.svg</property> + <property name="type_hint">dialog</property> + <property name="skip_taskbar_hint">True</property> + <property name="skip_pager_hint">True</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">8</property> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="node_properties_apply_button"> + <property name="label">gtk-apply</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">False</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="node_properties_cancel_button"> + <property name="label">gtk-cancel</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">False</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="node_properties_ok_button"> + <property name="label">gtk-ok</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">False</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="n_rows">2</property> + <property name="n_columns">2</property> + <property name="column_spacing">4</property> + <property name="row_spacing">8</property> + <child> + <object class="GtkSpinButton" id="node_properties_duration_spinbutton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + <property name="primary_icon_activatable">False</property> + <property name="secondary_icon_activatable">False</property> + <property name="primary_icon_sensitive">True</property> + <property name="secondary_icon_sensitive">True</property> + <property name="adjustment">duration_adjustment</property> + <property name="digits">3</property> + <property name="numeric">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkLabel" id="label7"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Duration: </property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkLabel" id="label6"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Note: </property> + </object> + <packing> + <property name="x_options">GTK_FILL</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="node_properties_note_spinbutton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="primary_icon_activatable">False</property> + <property name="secondary_icon_activatable">False</property> + <property name="primary_icon_sensitive">True</property> + <property name="secondary_icon_sensitive">True</property> + <property name="adjustment">note_num_adjustment</property> + <property name="climb_rate">1</property> + <property name="numeric">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="y_options"/> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-10">node_properties_apply_button</action-widget> + <action-widget response="-6">node_properties_cancel_button</action-widget> + <action-widget response="-5">node_properties_ok_button</action-widget> + </action-widgets> + </object> + <object class="GtkAdjustment" id="quantize_adjustment"> + <property name="lower">1</property> + <property name="upper">256</property> + <property name="value">1</property> + <property name="step_increment">1</property> + <property name="page_increment">8</property> + </object> +</interface> diff --git a/src/gui/main.cpp b/src/gui/main.cpp new file mode 100644 index 0000000..547966f --- /dev/null +++ b/src/gui/main.cpp @@ -0,0 +1,100 @@ +/* + This file is part of Machina. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <signal.h> + +#include <iostream> +#include <string> + +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" +#include "machina/Engine.hpp" +#include "machina/Loader.hpp" +#include "machina/Machine.hpp" +#include "machina/URIs.hpp" +#include "machina_config.h" +#include "sord/sordmm.hpp" + +#include "MachinaGUI.hpp" + +using namespace std; +using namespace machina; + +int +main(int argc, char** argv) +{ + Glib::thread_init(); + machina::URIs::init(); + + Sord::World rdf_world; + rdf_world.add_prefix("", MACHINA_NS); + rdf_world.add_prefix("midi", LV2_MIDI_PREFIX); + + Forge forge; + SPtr<machina::Machine> machine; + + Raul::TimeUnit beats(TimeUnit::BEATS, MACHINA_PPQN); + + // Load machine, if given + if (argc >= 2) { + const string filename = argv[1]; + const string ext = filename.substr(filename.length() - 4); + + if (ext == ".ttl") { + cout << "Loading machine from " << filename << endl; + machine = Loader(forge, rdf_world).load(filename); + + } else if (ext == ".mid") { + cout << "Building machine from MIDI file " << filename << endl; + + double q = 0.0; + if (argc >= 3) { + q = strtod(argv[2], NULL); + cout << "Quantization: " << q << endl; + } + + machine = Loader(forge, rdf_world).load_midi( + filename, q, Raul::TimeDuration(beats, 0, 0)); + } + + if (!machine) { + cerr << "Failed to load machine, exiting" << std::endl; + return 1; + } + } + + + if (!machine) { + machine = SPtr<Machine>(new Machine(beats)); + } + + // Create driver + SPtr<Driver> driver(Engine::new_driver(forge, "jack", machine)); + if (!driver) { + cerr << "warning: Failed to create Jack driver, using SMF" << endl; + driver = SPtr<Driver>(Engine::new_driver(forge, "smf", machine)); + } + + SPtr<Engine> engine(new Engine(forge, driver, rdf_world)); + + Gtk::Main app(argc, argv); + + driver->activate(); + gui::MachinaGUI gui(engine); + + app.run(*gui.window()); + + return 0; +} diff --git a/src/gui/wscript b/src/gui/wscript new file mode 100644 index 0000000..0eef312 --- /dev/null +++ b/src/gui/wscript @@ -0,0 +1,42 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf + +def build(bld): + obj = bld(features = 'cxx cxxshlib') + obj.source = ''' + EdgeView.cpp + MachinaCanvas.cpp + MachinaGUI.cpp + NodePropertiesWindow.cpp + NodeView.cpp + ''' + + obj.includes = ['.', '..', '../..', '../engine'] + obj.export_includes = ['.'] + obj.name = 'libmachina_gui' + obj.target = 'machina_gui' + obj.use = 'libmachina_engine libmachina_client' + autowaf.use_lib(bld, obj, ''' + GANV + GLADEMM + GLIBMM + GNOMECANVAS + GTKMM + RAUL + SORD + SIGCPP + EUGENE + LV2 + ''') + + # GUI runtime files + bld.install_files('${DATADIR}/machina', 'machina.ui') + bld.install_files('${DATADIR}/machina', 'machina.svg') + + # Executable + obj = bld(features = 'cxx cxxprogram') + obj.target = 'machina_gui' + obj.source = 'main.cpp' + obj.includes = ['.', '../..', '../engine'] + obj.use = 'libmachina_engine libmachina_gui' + autowaf.use_lib(bld, obj, 'GTHREAD GLIBMM GTKMM SORD RAUL MACHINA EUGENE GANV LV2') diff --git a/src/machina.cpp b/src/machina.cpp new file mode 100644 index 0000000..32eab05 --- /dev/null +++ b/src/machina.cpp @@ -0,0 +1,86 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "machina_config.h" + +#include <iostream> +#include <signal.h> +#include "machina/Action.hpp" +#include "machina/Edge.hpp" +#include "machina/Engine.hpp" +#include "machina/Machine.hpp" +#include "machina/MidiAction.hpp" +#ifdef HAVE_EUGENE +# include "machina/Problem.hpp" +#endif + +#include "JackDriver.hpp" + +using namespace std; +using namespace machina; + +bool quit = false; + +void +catch_int(int) +{ + signal(SIGINT, catch_int); + signal(SIGTERM, catch_int); + + std::cout << "Interrupted" << std::endl; + + quit = true; +} + +int +main(int argc, char** argv) +{ + if (argc != 2) { + cout << "Usage: " << argv[0] << " FILE" << endl; + return -1; + } + + if (!Glib::thread_supported()) { + Glib::thread_init(); + } + + SPtr<JackDriver> driver(new JackDriver()); + + Sord::World rdf_world; + Engine engine(driver, rdf_world); + + /* FIXME: Would be nice if this could take URIs on the cmd line + char* uri = (char*)calloc(6 + strlen(argv[1]), sizeof(char)); + strcpy(uri, "file:"); + strcat(uri, argv[1]); + engine.load_machine(uri); + free(uri); + */ + engine.load_machine(argv[1]); + + driver->attach("machina"); + + signal(SIGINT, catch_int); + signal(SIGTERM, catch_int); + + while (!quit) { + sleep(1); + } + + driver->detach(); + + return 0; +} diff --git a/src/midi2machina.cpp b/src/midi2machina.cpp new file mode 100644 index 0000000..fdff2f7 --- /dev/null +++ b/src/midi2machina.cpp @@ -0,0 +1,81 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Machina is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <iostream> +#include <signal.h> +#include "sord/sordmm.hpp" +#include "machina/Engine.hpp" +#include "machina/Machine.hpp" +#include "machina/Action.hpp" +#include "machina/Edge.hpp" +#include "machina/SMFDriver.hpp" +#include "machina/MidiAction.hpp" + +using namespace std; +using namespace machina; + +bool quit = false; + +void +catch_int(int) +{ + signal(SIGINT, catch_int); + signal(SIGTERM, catch_int); + + std::cout << "Interrupted" << std::endl; + + quit = true; +} + +int +main(int argc, char** argv) +{ + if (argc != 3) { + cout << "Usage: midi2machina QUANTIZATION FILE" << endl; + cout << "Specify quantization in beats, e.g. 1.0, or 0 for none" + << endl; + return -1; + } + + SPtr<SMFDriver> driver(new SMFDriver()); + + SPtr<Machine> machine = driver->learn(argv[2], strtof(argv[1], NULL)); + + if (!machine) { + cout << "Failed to load MIDI file." << endl; + return -1; + } + + /* + string out_filename = argv[1]; + if (out_filename.find_last_of("/") != string::npos) + out_filename = out_filename.substr(out_filename.find_last_of("/")+1); + + const string::size_type last_dot = out_filename.find_last_of("."); + if (last_dot != string::npos && last_dot != 0) + out_filename = out_filename.substr(0, last_dot); + out_filename += ".machina"; + + cout << "Writing output to " << out_filename << endl; + */ + + Sord::World world; + Sord::Model model(world); + machine->write_state(model); + model.serialise_to_file_handle(stdout); + + return 0; +} diff --git a/util/machina2dot b/util/machina2dot new file mode 100755 index 0000000..22cfbf0 --- /dev/null +++ b/util/machina2dot @@ -0,0 +1,184 @@ +#!/usr/bin/python + +import sys +import RDF + +model = RDF.Model() +parser = RDF.Parser(name="guess") + +if len(sys.argv) != 2: + print "Usage: machina2dot FILE" + sys.exit(-1) + +parser.parse_into_model(model, "file:" + sys.argv[1]) + +print """ +digraph finite_state_machine { + rankdir=LR; + { + node [ shape = doublecircle ]; +""", + +written_nodes = {} +node_durations = {} +invis_id = 0; + + +# Initial selectors + +initial_selectors_query = RDF.SPARQLQuery(""" +PREFIX machina: <http://drobilla.net/ns/machina#> +SELECT DISTINCT ?n ?dur ?note WHERE { + ?m machina:initialNode ?n . + ?n a machina:SelectorNode ; + machina:duration ?dur . + OPTIONAL { ?n machina:enterAction ?a . + ?a machina:midiNote ?note } +} +""") + +for result in initial_selectors_query.execute(model): + node_id = result['n'].blank_identifier + duration = float(result['dur'].literal_value['string']) + written_nodes[node_id] = True; + node_durations[node_id] = duration + print "\t{ node [ style = invis ] ", + print "invis%d" % invis_id, " }" + + label = "d=%.1f" % duration + if result['note']: + label += "\\nn=%s" % result['note'].literal_value['string'] + + print '\t', node_id, "[ label=\"%s\" ]" % label + print '\t', "invis%d" % invis_id, " -> ", node_id + invis_id += 1 + +print """ } { + node [ shape = circle ]; +""" + + +# Initial non-selectors + +initial_nodes_query = RDF.SPARQLQuery(""" +PREFIX machina: <http://drobilla.net/ns/machina#> +SELECT DISTINCT ?n ?dur ?note WHERE { + ?m machina:initialNode ?n . + ?n a machina:Node ; + machina:duration ?dur . + OPTIONAL { ?n machina:enterAction ?a . + ?a machina:midiNote ?note } +} +""") + +for result in initial_nodes_query.execute(model): + node_id = result['n'].blank_identifier + duration = float(result['dur'].literal_value['string']) + + if written_nodes.has_key(node_id): + continue + + written_nodes[node_id] = True; + node_durations[node_id] = duration + print "\t{ node [ style = invis ] ", + print "invis%d" % invis_id, " }" + + label = "d=%.1f" % duration + if result['note']: + label += "\\nn=%s" % result['note'].literal_value['string'] + + print '\t', node_id, "[ label=\"%s\" ]" % label + print '\t', "invis%d" % invis_id, " -> ", node_id + invis_id += 1 + + +# Non-initial selectors + +print """ } { + node [ shape = doublecircle ]; +""" + +selectors_query = RDF.SPARQLQuery(""" +PREFIX machina: <http://drobilla.net/ns/machina#> +SELECT DISTINCT ?n ?dur ?note WHERE { + ?m machina:node ?n . + ?n a machina:SelectorNode ; + machina:duration ?dur . + OPTIONAL { ?n machina:enterAction ?a . + ?a machina:midiNote ?note } +} +""") + + +for result in selectors_query.execute(model): + node_id = result['n'].blank_identifier + duration = float(result['dur'].literal_value['string']) + + if written_nodes.has_key(node_id): + continue + + node_durations[node_id] = duration + + label = "d=%.1f" % duration + if result['note']: + label += "\\nn=%s" % result['note'].literal_value['string'] + + print '\t', node_id, "[ label=\"%s\" ]" % label + + + +# Non-initial non-selectors + +print """ } { + node [ shape = circle ]; +""" + +nodes_query = RDF.SPARQLQuery(""" +PREFIX machina: <http://drobilla.net/ns/machina#> +SELECT DISTINCT ?n ?dur ?note WHERE { + ?m machina:node ?n . + ?n a machina:Node ; + machina:duration ?dur . + OPTIONAL { ?n machina:enterAction ?a . + ?a machina:midiNote ?note } +} +""") + + +for result in nodes_query.execute(model): + node_id = result['n'].blank_identifier + duration = float(result['dur'].literal_value['string']) + + if written_nodes.has_key(node_id): + continue + + node_durations[node_id] = duration + + label = "d=%.1f" % duration + if result['note']: + label += "\\nn=%s" % result['note'].literal_value['string'] + + print '\t', node_id, "[ label=\"%s\" ]" % label + + +# Edges + +edge_query = RDF.SPARQLQuery(""" +PREFIX machina: <http://drobilla.net/ns/machina#> +SELECT DISTINCT ?tail ?head ?prob WHERE { + ?e a machina:Edge ; + machina:tail ?tail ; + machina:head ?head ; + machina:probability ?prob . +} +""") + +for edge in edge_query.execute(model): + print '\t', edge['tail'].blank_identifier, ' -> ', + print edge['head'].blank_identifier, ' ', + print "[ label = \"%1.2f\"" % float(edge['prob'].literal_value['string']), + print "minlen = ", node_durations[edge['tail'].blank_identifier]+0.1, " ];" + +print """ + } +}""" 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 be3bfe2..be3bfe2 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,77 @@ +#!/usr/bin/env python + +import os +import subprocess + +from waflib.extras import autowaf + +# Version of this package (even if built as a child) +MACHINA_VERSION = '0.5.0' + +# Variables for 'waf dist' +APPNAME = 'machina' +VERSION = MACHINA_VERSION + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + opt.load('compiler_cxx') + autowaf.set_options(opt) + +def configure(conf): + autowaf.display_header('Machina Configuration') + conf.load('compiler_cxx', cache=True) + conf.load('autowaf', cache=True) + autowaf.set_cxx_lang(conf, 'c++11') + + autowaf.check_pkg(conf, 'lv2', uselib_store='LV2', + atleast_version='1.2.0', mandatory=True) + autowaf.check_pkg(conf, 'glibmm-2.4', uselib_store='GLIBMM', + atleast_version='2.14.0', mandatory=True) + autowaf.check_pkg(conf, 'gthread-2.0', uselib_store='GTHREAD', + atleast_version='2.14.0', mandatory=True) + autowaf.check_pkg(conf, 'gtkmm-2.4', uselib_store='GTKMM', + atleast_version='2.12.0', mandatory=False) + autowaf.check_pkg(conf, 'jack', uselib_store='JACK', + atleast_version='0.120.0', mandatory=True) + autowaf.check_pkg(conf, 'raul', uselib_store='RAUL', + atleast_version='0.8.9', mandatory=True) + autowaf.check_pkg(conf, 'ganv-1', uselib_store='GANV', + atleast_version='1.2.1', mandatory=False) + autowaf.check_pkg(conf, 'serd-0', uselib_store='SERD', + atleast_version='0.4.0', mandatory=False) + autowaf.check_pkg(conf, 'sord-0', uselib_store='SORD', + atleast_version='0.4.0', mandatory=False) + autowaf.check_pkg(conf, 'eugene', uselib_store='EUGENE', + atleast_version='0.0.0', mandatory=False) + + # Check for posix_memalign (OSX, amazingly, doesn't have it) + autowaf.check_function(conf, 'cxx', 'posix_memalign', + header_name = 'stdlib.h', + define_name = 'HAVE_POSIX_MEMALIGN', + mandatory = False) + + if conf.env.HAVE_GTKMM and conf.env.HAVE_GANV: + autowaf.define(conf, 'MACHINA_BUILD_GUI', 1) + + autowaf.define(conf, 'MACHINA_PPQN', 19200) + autowaf.define(conf, 'MACHINA_DATA_DIR', + os.path.join(conf.env.DATADIR, 'machina')) + + conf.write_config_header('machina_config.h', remove=False) + + autowaf.display_summary(conf, + {'Jack': bool(conf.env.HAVE_JACK), + 'GUI': bool(conf.env.MACHINA_BUILD_GUI)}) + +def build(bld): + bld.recurse('src/engine') + bld.recurse('src/client') + + if bld.env.MACHINA_BUILD_GUI: + bld.recurse('src/gui') + +def lint(ctx): + subprocess.call('cpplint.py --filter=-whitespace/comments,-whitespace/tab,-whitespace/braces,-whitespace/labels,-build/header_guard,-readability/casting,-readability/todo,-build/namespaces,-whitespace/line_length,-runtime/rtti,-runtime/references,-whitespace/blank_line,-runtime/sizeof,-readability/streams,-whitespace/operators,-whitespace/parens,-build/include,-build/storage_class `find -name *.cpp -or -name *.hpp`', shell=True) |