pax_global_header00006660000000000000000000000064130764373630014526gustar00rootroot0000000000000052 comment=ff39afb9d5857675c683e75e0910538b8db265bf python-rtmidi-1.1.0/000077500000000000000000000000001307643736300143345ustar00rootroot00000000000000python-rtmidi-1.1.0/.gitignore000066400000000000000000000010011307643736300163140ustar00rootroot00000000000000# Python bytecode *.py[cod] __pycache__ # Cython output src/_rtmidi.cpp # C extensions *.so # Generated documentation #INSTALL.rst # Packages *.egg *.egg-info dist/ build/ *.eggs/ eggs/ parts/ .installed.cfg setuptools-*.tar.gz distribute-*.tar.gz MANIFEST # Installer logs pip-log.txt # Unit test / coverage reports .cache/ .coverage .tox nosetests.xml htmlcov # Translations *.mo # Misc IDEs .mr.developer.cfg .project .pydevproject # Complexity output/*.html output/*/index.html # Sphinx docs/_build python-rtmidi-1.1.0/AUTHORS.rst000066400000000000000000000003611307643736300162130ustar00rootroot00000000000000======= Credits ======= Development Lead ---------------- * Christopher Arndt Contributors ------------ * Michiel Overtoom * Orhan Kavrakoğlu * tekHedd Testing ------- * Martin Tarenskeen * Pierre Castellotti python-rtmidi-1.1.0/CHANGELOG.rst000066400000000000000000000306161307643736300163630ustar00rootroot00000000000000Changelog ========= For details and minor changes, please see the `version control log messages `_. 2017-04-21 version 1.1.0 ------------------------ Project infrastructure: * Updated project homepage URL; copyright year and link to docs in readme. Building: * Added script to automate updating github pages docs. Enhancements / Changes: * Synced with upstream RtMidi_ (2.1.1-907a94c). * Applied patch from https://github.com/thestk/rtmidi/pull/89. This means that when using the ALSA API port names are reported in the form ``: `` (this change was actually already in version 1.0.0). * Added new ``MidiIn`` / ``MidiOut`` method ``is_port_open``. * ``MidiIn`` / ``MidiOut`` constructors and ``open_port`` / ``open_virtual_port`` methods now raise ``TypeError`` when an and invalid type is passed as the client resp. port name. Documentation: * Various small documentation improvements. Examples: * Basic examples: some clean-up, more comments, updated API usage. * Added new advanced example script ``midiwrapper.py``. * Added new advanced example script ``recvrpn.py``. * ``wavetablemodstep.py``: added command line param to set controller number. * ``midi2command``: Fixed wrong mock lru_cache substitution for Python < 3.2. 2016-11-07 version 1.0.0 ------------------------ Project infrastructure: * Added automatic documentation publishing on readthedocs.org. Documentation: * Added auto docs for MidiIn/MidiOut classes to sphinx docs. * Removed pre-release related information from installation docs. Building: * Added generated INSTALL.rst to repo to make ReadTheDocs integration work. Examples: * Added new example script ``panic.py``. 2016-10-09 version 1.0.0rc1 --------------------------- Project infrastructure: * Moved repository to Github. Fixes: * ``midiutil.open_midiport``: * Correctly report and log I/O direction and instance type. * Fix naming of virtual port. Enhancements / Changes: * Synced with upstream RtMidi_ (2.1.1-399a8ee). * ``midiutil``: * The function ``midiutil.open_port`` has been renamed to ``open_midiport``. * Added convenience functions ``open_midiinput`` and ``open_midioutput``, which wrap ``open_midiport``. * RtMidi API to use can be specified via the ``RTMIDI_API`` environment variable. Only used when ``API_UNSPECIFIED`` is passed for the ``api`` argument. Value should be one of the ``API_*`` constant names with out the ``API_`` prefix, e.g. ``UNIX_JACK`` for the Jack API. * Cython wrapper class hierarchy restructured to better match the underlying C++ classes and remove code duplication. * Some source code re-ordering was done. Documentation: * Added basic structure and initial content of Sphinx documentation. * Documented exceptions raised by ``MidiIn/Out.open_[virtual_]port()``. * Some docstring corrections and formatting fixes. Building: * Simplified ``setup.py`` by throwing out old compatibility stuff. * Explicitly call ``PyEval_InitThreads`` from Cython code instead of using undocumented compiler macro. Examples: * Moved `osc2midi` example into its own repository at https://github.com/SpotlightKid/osc2rtmidi.git * Add new ``sequencer`` example. * Add new ``noteon2osc`` example. * ``midifilter``: * Moved ``main.py`` to ``__main__.py``, removed old code and fixed command line args access. * Streamlined event matching. * Added ``CCToBankChange`` filter. * ``Queue`` module renamed to ``queue`` in Python 3. * Fixed opening of output port erroneously used ``"input"``. * Fixed positional command line args handling. * Set command name for argparse. * ``midi2command``: * Added README. * Added command line option to select backend API. * Catch errors when opening port. * Set client and port name. * Cache command lookup (Python 3.2+ only). * ``sysexsaver``: * Moved ``main.py`` to ``__main__.py``, some refactoring. * ``models.py``: Fixed wrong entry for manufacturer ``(0, 32, 81)``. * Moved module level code into ``main`` function. * Include model name in output file, if possible. * ``drumseq``: * Fixed global access in ``Sequencer`` class. * Use ``args.FileType`` for pattern command line args. 2014-06-11 version 0.5b1 ------------------------ Fixes: * Synced RtMidi_ code with git repo @ 2c7a6664d6, which fixed several issues (see https://github.com/thestk/rtmidi/issues?state=closed). * ``MidiIn/Out.open_virtual_port`` returns ``self`` for context manager support, consistent with ``MidiIn/Out.open_port``. * Fix Python <= 2.6 incompatible encode method call (python-rtmidi officially only supports Python >= 2.7). Thanks to Michiel Overtoom for reporting this. * Respect passed MIDI api when requesting MidiOut instance from ``midiutil.open_midiport``. .. _rtmidi: https://github.com/thestk/rtmidi Enhancements / Changes: * Support for Windows Kernel Streaming API was removed in RtMidi (it was broken anyway) and consequently in ``python-rtmidi`` as well. * Raise ``RtMidiError`` exception when trying to open a (virtual) port on a ``MidiIn/Out`` instance that already has an open (virtual) port. * Add some common synonyms for MIDI events and controllers and some source comments about controller usage to ``midiconstants`` module. Documentation: * Fix and clarify ``queue_size_limit`` default value in docstrings * Various docstring consistency improvements and minor fixes. Examples: * New example script ``midi2command.py``, which executes external commands on reception of configurable MIDI events, with example configuration. * New example directory ``drumseq`` with a simple drum pattern sequencer and example drum patterns. Thanks to Michiel Overtoom for the original script! 2013-11-10 version 0.4.3b1 -------------------------- Building: * Add numeric suffix to version number to comply with PEP 440. * Add missing ``fill_template.py`` to source distribution. * Set default setuptools version in ``ez_setup.py`` to 1.3.2, which contains fix for bug #99 mentioned below. Documentation: * Add note to installation guide about required ``--pre`` option with pip. 2013-11-07 version 0.4.2b ------------------------- Fixes: * Add missing ``API_*`` constant to list of exported names of ``_rtmidi`` module. Enhancements / Changes: * Change default value of ``encoding`` argument of ``get_ports`` and ``get_port_name`` methods to `"auto"`, which selects appropriate encoding based on system and backend API used. * Add ``api`` parameter to ``midiutil.open_midiport`` function to select backend API. * Make client name for ``MidiOut`` and `` MidiIn`` different again, because some backend APIs might require unique client names. Building: * Include workaround for setuptools bug (see bitbucket issue #99) in setup file. * Add custom distutils command to fill placeholders in ``INSTALL.rst.in`` template with release meta data. * Setuptools is now required, pure distutils won't work anymore, so removing the fallback import of ``setup`` from distutils. 2013-11-05 version 0.4.1b ------------------------- Building: * Include missing ``_rtmidi.cpp`` file in source distribution. Documentation: * Fill in release data placeholders in ``INSTALL.rst``. 2013-11-05 version 0.4b ----------------------- Fixes: * Fix string conversion in constructors and ``open_*`` methods. * Change default value ``queue_size_limit`` argument to ``MidiIn`` constructor to 1024. * Update version number in ``RtMidi.cpp/h`` to reflect actual code state. Enhancements / Changes: * Elevated development status to beta. * Allow ``MidiIn/Out.open_port`` methods to be used with the ``with`` statement and the port will be closed at the end of the block. * ``MidiIn``/``MidiOut`` and ``open*()`` methods: allow to specify ``None`` as client or port name to get the default names. * Move ``midiconstants`` module from examples into ``rtmidi`` package and added ``midiutil`` module. * ``midiutils.open_midiport``: * Allow to pass (substring of) port name as alternative to port number. * Re-raise ``EOFError`` and ``KeyboardInterrupt`` instead of using ``sys.exit()``. * Add ``client_name`` and ``port_name`` arguments. * Add ``use_virtual`` argument (default ``False``) to request opening of a virtual MIDI port. * Add ``interactive`` keyword argument (default ``True``) to disable interactive prompt for port. * Raise ``NotImplemented`` error when trying to open a virtual port with Windows MultiMedia API. * Change default name of virtual ports. Documentation: * Re-organize package description and installation instructions into several files and add separate text files with changelog and license information. * Add detailed instructions for compiling from source on Windows * Add docstrings to all methods and functions in ``_rtmidi`` module. * Add docstring for ``midiutils.open_midiport`` function. Examples: * Add new example package ``osc2midi``, a simple, uni-directional OSC to MIDI mapper. * New example script ``sendsysex.py`` to demonstrate sending of MIDI system exclusive messages. * New example script ``wavetablemodstep.py`` to demonstrate sending of MIDI control change messages. * New ``sysexsaver`` example. * Convert ``midifilter`` example script into a package. * Upgrade from ``optparse`` to ``argparse`` in example scripts. * Enable logging in test scripts. Building: * Switch from ``distribute`` back to ``setuptools``. * Include ``ez_setup.py`` in source distribution. * Include examples in source distribution. * Install ``osc2midi`` example as package and command line script. * Enable C++ exceptions on Windows build. 2013-01-23 version 0.3.1a ------------------------- Enhancements: * Increase sysex input buffer size for WinMM API again to 8192 (8k) bytes. Requested by Martin Tarenskeen. 2013-01-14 version 0.3a ----------------------- Bug fixes: * Add ``encoding`` parameter to ``get_port_name`` methods of ``MidiIn`` and ``MidiOut`` to be able to handle non-UTF-8 port names, e.g. on Windows (reported by Pierre Castellotti). * Add ``encoding`` parameter to ``get_ports`` method as well and pass it through to ``get_port_name``. Use it in the test scripts. Enhancements: * Increase sysex input buffer size for WinMM API to 4096 bytes. Examples: * Add new ``midifilter.py`` example script. Building: * Add ``setuptools``/``distribute`` support. 2012-07-22 version 0.2a ----------------------- Bug fixes: * Fix uninitialized pointer bug in ``RtMidi.cpp`` in 'MidiOutJack' class, which caused a warning in the jack process callback when creating a ``MidiOut`` instance with the JACK API. * ``testmidiin_*.py``: fix superfluous decoding of port name (caused error with Python 3). Enhancements: * Simplify some code, some things gleaned from rtmidi_python. * Documentation typo fixes and more information on Windows compilation. * Enhancements in test scripts: * ``test_probe_ports.py``: Catch exceptions when creating port. * ``test_midiin_*.py``: * Better error message for missing/invalid port number. * Show how to convert event delta time into absolute time when receiving input. Building: * Building on OS X 10.6.9 with CoreMIDI and JACK for OS X successfully tested and test run without errors. * WinMM support now compiles with Visual Studio 2008 Express and tests work under Windows XP SP3 32-bit. * Add command line option to exclude WinMM or WinKS API from compilation. * Add missing ``extra_compile_args`` to Extension kwargs in setup file. * Add ``library_dirs`` to Extension kwargs in setup file. * Use ``-frtti`` compiler option on OS X (neccessary on 10.7?). * Fix file name conflict on case-insensitive file systems by prefixing ``rtmidi.{pyx,cpp}`` with an underscore * Provide correct compiler flags for compiling with Windows MultiMedia API. * Adapt windows library and include path for Visual Studio 2008 Express. * add support for compiling with Windows Kernel Streaming API (does not not compile due to syntax errors in RtMidi.cpp yet). 2012-07-13 version 0.1a ----------------------- First public release. python-rtmidi-1.1.0/INSTALL-windows.rst000066400000000000000000000100241307643736300176610ustar00rootroot00000000000000How to install python-rtmidi from source on Windows =================================================== These instruction should work for installing ``python-rtmidi`` from source using Python 2.7 or Python 3.5 in the 32-bit (you can run these on Windows 64-bit versions with no problems) or 64-bit versions. Please follow all the steps below in the exact order. Installing required software ---------------------------- You probably need administrator rights for some or all of the following steps. #. Install the latest release of Python 2.7 and/or Python 3.5 from https://www.python.org/downloads/windows/ to the default location (i.e. ``C:\Python27`` resp. ``C:\Python35``). You can install either or both the 32-bit and the 64-bit version. In the installer, enable the option to install pip_. Optionally, for only one of the chosen Python versions, enable the options to add the installation directory to your ``PATH`` and set it as the system's default version. Also enable the option to install the ``py`` help script (only available with some Python versions). #. Install virtualenv_ from a command prompt:: > python -m pip install -U virtualenv Repeat this for all Python versions you have installed (run ``py --help`` to get help on how to run different python version from the command line). #. Go to https://wiki.python.org/moin/WindowsCompilers and follow the instructions there to select and install the correct version(s) of the Visual C++ compiler for the version(s) of Python you installed. You can install several versions of Visual C++ at the same time. After installation, use Windows Update to get any pending security updates and fixes. Setting up a virtual environment -------------------------------- #. Open a command line and run:: > python -m virtualenv rtmidi > rtmidi\Scripts\activate #. Update pip and setuptools_ within your virtual environment to the latest versions with:: (rtmidi)> pip install -U pip setuptools #. Install Cython (still in the same command line window):: (rtmidi)> pip install Cython Download & unpack python-rtmidi source -------------------------------------- Get the latest python-rtmidi distribution as a Zip archive from https://pypi.python.org/pypi/python-rtmidi and unpack it somewhere. You can do the downloading and unpacking in one step using pip:: > pip install --no-install -d . "python-rtmidi" Alternatively, clone the python-rtmidi git repository:: > git clone https://github.com/SpotlightKid/python-rtmidi.git In the command line window you opened above, change into the ``python-rtmidi`` directory, which you created by unpacking the source or cloning the repository:: (rtmidi)> cd python-rtmidi Build & install python-rtmidi ----------------------------- Just run the usual setup command from within the source directory with the active virtual environment, i.e. from still the same command line window:: (rtmidi)> python setup.py install Verify your installation ------------------------ Change out of the ``python-rtmidi`` source directory (important!) and run:: (rtmidi)> cd .. (rtmidi)> python >>> import rtmidi >>> rtmidi.API_WINDOWS_MM in rtmidi.get_compiled_api() True >>> midiout = rtmidi.MidiOut() >>> midiout.get_ports() [u'Microsoft GS Wavetable Synth'] If you have any other MIDI outputs (hardware MIDI interfaces, MIDI Yoke etc.) active, they should be listed by ``get_ports()`` as well. *That's it, congratulations!* Notes ----- Windows Kernel Streaming support in RtMidi has been removed (it was broken anyway) and consequently in ``python-rtmidi`` as well. Compiling with MinGW also does not work out-of-the-box yet. If you have any useful hints, please let the author know. .. _pip: https://pypi.python.org/pypi/pip .. _setuptools: https://pypi.python.org/pypi/setuptools .. _virtualenv: https://pypi.python.org/pypi/virtualenv python-rtmidi-1.1.0/INSTALL.rst000066400000000000000000000131211307643736300161720ustar00rootroot00000000000000============ Installation ============ **python-rtmidi** uses the de-facto standard Python distutils and setuptools_ based packaging system and can be installed from the Python Package Index via pip_. Since it is a Python C(++)-extension, a C++ compiler and build environment as well as some system-dependent libraries are needed to install, unless wheel packages with precompiled binaries are available for your system. See the Requirements_ section below for details. From PyPI --------- If you have all the requirements_, you should be able to install the package with pip_:: $ pip install python-rtmidi This will download the source distribution from python-rtmidi's `PyPI page`_, compile the extension and install it in your active Python installation. Unless you want to change the Cython_ source file ``_rtmidi.pyx``, there is no need to have Cython installed. python-rtmidi also works well with virtualenv_ and virtualenvwrapper_. If you have both installed, creating an isolated environment for testing and/or using python-rtmidi is as easy as:: $ mkvirtualenv rtmidi (rtmidi)$ pip install python-rtmidi Pre-compiled Binaries --------------------- Binary wheels with a pre-compiled version for Windows with Windows MultiMedia API support are available through PyPI for several Python versions. If you install python-rtmidi via pip (see above), these wheels should be picked up by it automatically, if you have compatible Python and Windows versions. From the Source Distribution ---------------------------- Of course, you can also download the source distribution package as a Zip archive or tarball, extract it and install using the common ``distutils`` commands, e.g.:: $ wget https://pypi.python.org/pypi/python-rtmidipython-rtmidi-1.1.0.tar.gz $ tar -xzf python-rtmidi-1.1.0.tar.gz $ cd python-rtmidi-1.1.0 $ python setup.py install From the Source Code Repository ------------------------------- Lastly, you can check out the python-rtmidi source code from the Git repository and then install it from your working copy. Since the repository does not include the C++ module source code pre-compiled from the Cython source, you'll also need to install Cython >= 0.17, either via pip or from its Git repository. Using virtualenv/virtualenvwrapper is strongly recommended in this scenario: Make a virtual environment:: $ mkvirtualenv rtmidi (rtmidi)$ cdvirtualenv Install ``Cython`` from PyPI:: (rtmidi)$ pip install Cython or the Git repository:: (rtmidi)$ git clone https://github.com/cython/cython.git (rtmidi)$ cd cython (rtmidi)$ python setup.py install (rtmidi)$ cd .. Install **python-rtmidi**:: (rtmidi)$ git clone https://github.com/SpotlightKid/python-rtmidi.git (rtmidi)$ cd python-rtmidi (rtmidi)$ python setup.py install .. _requirements: Requirements ============ Naturally, you'll need a C++ compiler and a build environment. See the platform-specific hints below. If you want to change the Cython source file ``_rtmidi.pyx`` or want to recompile ``_rtmidi.cpp`` with a newer Cython version, you'll need to install Cython >= 0.17. The ``_rtmidi.cpp`` file in the current source distribution (version 1.1.0) is tagged with:: /* Generated by Cython 0.25.2 */ RtMidi (and therefore python-rtmidi) supports several low-level MIDI frameworks on different operating systems. Only one of the available options needs to be present on the target system, but support for more than one can be compiled in. The setup script will try to detect available libraries and should use the appropriate compilations flags automatically. * Linux: ALSA, JACK * OS X: CoreMIDI, JACK * Windows: MultiMedia (MM) Linux ----- First you need a C++ compiler and the pthread library. Install the ``build-essential`` package on debian-based systems to get these. Then you'll need Python development headers and libraries. On debian-based systems, install the ``python-dev`` package. If you use the official installers from python.org you should already have these. To get ALSA support, you must install development files for the ``libasound`` library (debian package: ``libasound-dev``). For JACK support, install the ``libjack`` development files (``libjack-dev`` or ``libjack-jackd2-dev``). OS X ---- Install the latest Xcode version or ``g++`` from MacPorts or homebrew (untested). CoreMIDI support comes with installing Xcode. For JACK support, install `JACK for OS X`_ with the full installer. .. note:: If you have a version of OS X and Xcode which still supports building binaries for PPC, you'll have to tell distribute to build the package only for i386 and x86_64 architectures:: env ARCHFLAGS="-arch i386 -arch x86_64" python setup.py install Windows ------- Please see the detailed instructions for Windows in :doc:`install-windows`. User Contributed Documentation ------------------------------ The python-rtmidi wiki on GitHub contains some `user contributed documentation`_ for additional installation scenarios. Please check these, if you have trouble installing python-rtmidi in an uncommon or not-yet-covered environment. .. _pypi page: http://python.org/pypi/python-rtmidi#downloads .. _cython: http://cython.org/ .. _pip: http://python.org/pypi/pip .. _setuptools: http://python.org/pypi/setuptools .. _virtualenv: http://pypi.python.org/pypi/virtualenv .. _virtualenvwrapper: http://www.doughellmann.com/projects/virtualenvwrapper/ .. _jack for os x: http://www.jackosx.com/ .. _pyliblo: http://das.nasophon.de/pyliblo/ .. _user contributed documentation: https://github.com/SpotlightKid/python-rtmidi/wiki/User-contributed-documentation python-rtmidi-1.1.0/INSTALL.rst.in000066400000000000000000000130211307643736300165760ustar00rootroot00000000000000============ Installation ============ **python-rtmidi** uses the de-facto standard Python distutils and setuptools_ based packaging system and can be installed from the Python Package Index via pip_. Since it is a Python C(++)-extension, a C++ compiler and build environment as well as some system-dependent libraries are needed to install, unless wheel packages with precompiled binaries are available for your system. See the Requirements_ section below for details. From PyPI --------- If you have all the requirements_, you should be able to install the package with pip_:: $ pip install python-rtmidi This will download the source distribution from python-rtmidi's `PyPI page`_, compile the extension and install it in your active Python installation. Unless you want to change the Cython_ source file ``_rtmidi.pyx``, there is no need to have Cython installed. python-rtmidi also works well with virtualenv_ and virtualenvwrapper_. If you have both installed, creating an isolated environment for testing and/or using python-rtmidi is as easy as:: $ mkvirtualenv rtmidi (rtmidi)$ pip install python-rtmidi Pre-compiled Binaries --------------------- Binary wheels with a pre-compiled version for Windows with Windows MultiMedia API support are available through PyPI for several Python versions. If you install python-rtmidi via pip (see above), these wheels should be picked up by it automatically, if you have compatible Python and Windows versions. From the Source Distribution ---------------------------- Of course, you can also download the source distribution package as a Zip archive or tarball, extract it and install using the common ``distutils`` commands, e.g.:: $ wget ${download_url}python-rtmidi-${version}.tar.gz $ tar -xzf python-rtmidi-${version}.tar.gz $ cd python-rtmidi-${version} $ python setup.py install From the Source Code Repository ------------------------------- Lastly, you can check out the python-rtmidi source code from the Git repository and then install it from your working copy. Since the repository does not include the C++ module source code pre-compiled from the Cython source, you'll also need to install Cython >= 0.17, either via pip or from its Git repository. Using virtualenv/virtualenvwrapper is strongly recommended in this scenario: Make a virtual environment:: $ mkvirtualenv rtmidi (rtmidi)$ cdvirtualenv Install ``Cython`` from PyPI:: (rtmidi)$ pip install Cython or the Git repository:: (rtmidi)$ git clone https://github.com/cython/cython.git (rtmidi)$ cd cython (rtmidi)$ python setup.py install (rtmidi)$ cd .. Install **python-rtmidi**:: (rtmidi)$ git clone ${repository} (rtmidi)$ cd python-rtmidi (rtmidi)$ python setup.py install .. _requirements: Requirements ============ Naturally, you'll need a C++ compiler and a build environment. See the platform-specific hints below. If you want to change the Cython source file ``_rtmidi.pyx`` or want to recompile ``_rtmidi.cpp`` with a newer Cython version, you'll need to install Cython >= 0.17. The ``_rtmidi.cpp`` file in the current source distribution (version ${version}) is tagged with:: ${cpp_info} RtMidi (and therefore python-rtmidi) supports several low-level MIDI frameworks on different operating systems. Only one of the available options needs to be present on the target system, but support for more than one can be compiled in. The setup script will try to detect available libraries and should use the appropriate compilations flags automatically. * Linux: ALSA, JACK * OS X: CoreMIDI, JACK * Windows: MultiMedia (MM) Linux ----- First you need a C++ compiler and the pthread library. Install the ``build-essential`` package on debian-based systems to get these. Then you'll need Python development headers and libraries. On debian-based systems, install the ``python-dev`` package. If you use the official installers from python.org you should already have these. To get ALSA support, you must install development files for the ``libasound`` library (debian package: ``libasound-dev``). For JACK support, install the ``libjack`` development files (``libjack-dev`` or ``libjack-jackd2-dev``). OS X ---- Install the latest Xcode version or ``g++`` from MacPorts or homebrew (untested). CoreMIDI support comes with installing Xcode. For JACK support, install `JACK for OS X`_ with the full installer. .. note:: If you have a version of OS X and Xcode which still supports building binaries for PPC, you'll have to tell distribute to build the package only for i386 and x86_64 architectures:: env ARCHFLAGS="-arch i386 -arch x86_64" python setup.py install Windows ------- Please see the detailed instructions for Windows in :doc:`install-windows`. User Contributed Documentation ------------------------------ The python-rtmidi wiki on GitHub contains some `user contributed documentation`_ for additional installation scenarios. Please check these, if you have trouble installing python-rtmidi in an uncommon or not-yet-covered environment. .. _pypi page: http://python.org/pypi/python-rtmidi#downloads .. _cython: http://cython.org/ .. _pip: http://python.org/pypi/pip .. _setuptools: http://python.org/pypi/setuptools .. _virtualenv: http://pypi.python.org/pypi/virtualenv .. _virtualenvwrapper: http://www.doughellmann.com/projects/virtualenvwrapper/ .. _jack for os x: http://www.jackosx.com/ .. _pyliblo: http://das.nasophon.de/pyliblo/ .. _user contributed documentation: https://github.com/SpotlightKid/python-rtmidi/wiki/User-contributed-documentation python-rtmidi-1.1.0/LICENSE.txt000066400000000000000000000053251307643736300161640ustar00rootroot00000000000000Copyright & License =================== python-rtmidi was written by Christopher Arndt, 2012 - 2017. The software is released unter the MIT License: Copyright (c) 2012 - 2017 Christopher Arndt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. RtMidi is distributed under a modified MIT License: RtMidi: realtime MIDI i/o C++ classes Copyright (c) 2003 - 2016 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is asked to send the modifications to the original developer so that they can be incorporated into the canonical version. This is, however, not a binding provision of this license. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-rtmidi-1.1.0/MANIFEST.in000066400000000000000000000005401307643736300160710ustar00rootroot00000000000000include fill_template.py include LICENSE.txt include Makefile include requirements-dev.txt include tox.ini include *.rst include src/*.cpp src/*.h src/*.pyx exclude *.rst.in exclude deploy.sh exclude *.sh graft examples graft tests recursive-exclude * __pycache__ recursive-exclude * *.py[co] recursive-include docs *.rst conf.py Makefile make.bat python-rtmidi-1.1.0/Makefile000066400000000000000000000035171307643736300160020ustar00rootroot00000000000000.PHONY: clean-pyc clean-build docs clean PYTHON ?= python SOURCES = src/_rtmidi.cpp src/RtMidi.cpp help: @echo "build - build extension module (and place it in the rtmidi package)" @echo "clean-build - remove build artifacts" @echo "clean-docs - remove docs output" @echo "clean-pyc - remove Python file artifacts" @echo "lint - check style with flake8" @echo "check-docs - check docstrings with pycodestyle" @echo "test - run tests with the default Python against working dir" @echo "test-all - run tests on every Python version with tox" @echo "coverage - check code coverage quickly with the default Python" @echo "docs - generate Sphinx HTML documentation, including API docs" @echo "release - package and upload a release" @echo "dist - build distribution packages" build: $(SOURCES) $(PYTHON) setup.py build_ext --inplace clean: clean-build clean-docs clean-pyc rm -fr htmlcov/ clean-build: rm -fr build/ rm -fr dist/ rm -fr *.egg-info rm -fr rtmidi/*.so rm -fr src/_rtmidi.cpp clean-docs: rm -fr docs/_build clean-pyc: find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + find . -name __pycache__ -type d -exec rm -rf {} + lint: flake8 rtmidi tests examples check-docs: pydocstyle rtmidi src test: PYTHONPATH=examples $(PYTHON) setup.py test test-all: tox coverage: coverage run --source rtmidi setup.py test coverage report -m coverage html xdg-open htmlcov/index.html docs: release rm -f docs/rtmidi.rst rm -f docs/modules.rst $(PYTHON) setup.py build_ext --inplace sphinx-apidoc -o docs/ rtmidi cat docs/classes.rst >> docs/rtmidi.rst $(MAKE) -C docs clean $(MAKE) -C docs html xdg-open docs/_build/html/index.html release: clean $(PYTHON) setup.py release release_upload: clean $(PYTHON) setup.py release_upload dist: clean release ls -l dist python-rtmidi-1.1.0/README.rst000066400000000000000000000021571307643736300160300ustar00rootroot00000000000000Welcome to python-rtmidi! ========================= RtMidi_ is a set of C++ classes which provides a concise and simple, cross-platform API (Application Programming Interface) for realtime MIDI input/output across Linux (ALSA & JACK), Macintosh OS X (CoreMidi & JACK), and Windows (Multimedia Library) operating systems. **python-rtmidi** is a Python binding for RtMidi implemented with Cython_ and provides a thin wrapper around the RtMidi C++ interface. The API is basically the same as the C++ one but with the naming scheme of classes, methods and parameters adapted to the Python PEP-8 conventions and requirements of the Python package naming structure. python-rtmidi supports Python 2 (tested with Python 2.7) and Python 3 (3.3, 3.4, 3.5). For more information, visit python-rtmidi's web page: https://chrisarndt.de/projects/python-rtmidi See the file ``INSTALL.rst`` for installation instructions, ``CHANGELOG.rst`` for a history of changes per release and ``LICENSE.txt`` for information about copyright and usage terms. .. _rtmidi: http://www.music.mcgill.ca/~gary/rtmidi/index.html .. _cython: http://cython.org/ python-rtmidi-1.1.0/deploy.sh000077500000000000000000000004431307643736300161700ustar00rootroot00000000000000#!/bin/bash SRCDIR="$HOME/work/python-rtmidi/" DSTDIR="work/rtmidi/src/python-rtmidi" DSTHOST="192.168.100.1" rsync -av --update --checksum \ --exclude .svn \ --exclude sysex \ --exclude dist \ --exclude build \ --exclude __pycache__ \ "$SRCDIR" "$DSTHOST:$DSTDIR" python-rtmidi-1.1.0/docs/000077500000000000000000000000001307643736300152645ustar00rootroot00000000000000python-rtmidi-1.1.0/docs/Makefile000066400000000000000000000151721307643736300167320ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/complexity.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/complexity.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/complexity" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/complexity" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." python-rtmidi-1.1.0/docs/authors.rst000066400000000000000000000000341307643736300175000ustar00rootroot00000000000000.. include:: ../AUTHORS.rst python-rtmidi-1.1.0/docs/classes.rst000066400000000000000000000002261307643736300174530ustar00rootroot00000000000000 Classes ~~~~~~~ .. autoclass:: rtmidi.MidiIn :members: :inherited-members: .. autoclass:: rtmidi.MidiOut :members: :inherited-members: python-rtmidi-1.1.0/docs/conf.py000077500000000000000000000205421307643736300165710ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # complexity documentation build configuration file, created by # sphinx-quickstart on Tue Jul 9 22:26:36 2013. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os # If extensions (or modules to document with autodoc) are in another # directory, add these directories to sys.path here. If the directory is # relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # Get the project root dir, which is the parent dir of this cwd = os.getcwd() project_root = os.path.dirname(cwd) # Insert the project root dir as the first element in the PYTHONPATH. # This lets us ensure that the source package is imported, and that its # version is used. sys.path.insert(0, project_root) meta = {} release_info = os.path.join(project_root, 'rtmidi', 'release.py') exec(compile(open(release_info).read(), release_info, 'exec'), {}, meta) # -- General configuration --------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = meta['name'] copyright = u'2012 - 2016, %s' % meta['author'] # The version info for the project you're documenting, acts as replacement # for |version| and |release|, also used in various other places throughout # the built documents. # # The short X.Y version. version = meta['version'] # The full version, including alpha/beta/rc tags. release = meta['version'] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to # some non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built # documents. #keep_warnings = False # -- Options for HTML output ------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'classic' # Theme options are theme-specific and customize the look and feel of a # theme further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as # html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the # top of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon # of the docs. This file should be a Windows icon file (.ico) being # 16x16 or 32x32 pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) # here, relative to this directory. They are copied after the builtin # static files, so a file named "default.css" will overwrite the builtin # "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names # to template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. # Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. # Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages # will contain a tag referring to it. The value of this option # must be the base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'rtmididoc' # -- Options for LaTeX output ------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', 'rtmidi.tex', u'python-rtmidi Documentation', meta['author'], 'manual'), ] # The name of an image file (relative to this directory) to place at # the top of the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings # are parts, not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output ------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'rtmidi', u'python-rtmidi Documentation', [meta['author']], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ---------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'rtmidi', u'python-rtmidi Documentation', meta['author'], meta['name'], 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False python-rtmidi-1.1.0/docs/contributing.rst000066400000000000000000000062151307643736300205310ustar00rootroot00000000000000============ Contributing ============ Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. You can contribute in many ways: Types of Contributions ---------------------- Report Bugs ~~~~~~~~~~~ Report bugs at https://github.com/SpotlightKid/python-rtmidi/issues. If you are reporting a bug, please include: * Your operating system name and version. * Any details about your local setup that might be helpful in troubleshooting. * Detailed steps to reproduce the bug. Fix Bugs ~~~~~~~~ Look through the GitHub issues for bugs. Anything tagged with "bug" is open to whoever wants to implement it. Implement Features ~~~~~~~~~~~~~~~~~~ Look through the GitHub issues for features. Anything tagged with "feature" is open to whoever wants to implement it. Write Documentation ~~~~~~~~~~~~~~~~~~~ python-rtmidi could always use more documentation, whether as part of the official python-rtmidi docs, in docstrings, or even on the web in blog posts, articles, and such. Submit Feedback ~~~~~~~~~~~~~~~ The best way to send feedback is to file an issue at https://github.com/SpotlightKid/python-rtmidi/issues. If you are proposing a feature: * Explain in detail how it would work. * Keep the scope as narrow as possible, to make it easier to implement. * Remember that this is a volunteer-driven project, and that contributions are welcome :) Get Started! ------------ Ready to contribute? Here's how to set up `python-rtmidi` for local development. 1. Fork the `python-rtmidi` repo on GitHub. 2. Clone your fork locally:: $ git clone git@github.com:your_name_here/python-rtmidi.git 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: $ mkvirtualenv python-rtmidi $ cd python-rtmidi/ $ pip install -r requirements-dev.txt $ python setup.py develop 4. Create a branch for local development:: $ git checkout -b name-of-your-bugfix-or-feature Now you can make your changes locally. 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: $ make lint $ make test $ make test-all To get flake8 and tox, just ``pip install`` them into your virtualenv. 6. Commit your changes and push your branch to GitHub:: $ git add . $ git commit -m "Your detailed description of your changes." $ git push -u origin name-of-your-bugfix-or-feature 7. Submit a pull request through the GitHub website. Pull Request Guidelines ----------------------- Before you submit a pull request, check that it meets these guidelines: 1. The pull request should include tests. 2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the release notes in ``CHANGELOG.rst``. 3. The pull request should work for Python 2.7, and 3.3, 3.4, 3.5 and 3.6. Run tox to make sure that the tests pass for all supported Python versions. Tips ---- To run a subset of tests:: $ py.test tests.test_rtmidi python-rtmidi-1.1.0/docs/history.rst000066400000000000000000000000361307643736300175160ustar00rootroot00000000000000.. include:: ../CHANGELOG.rst python-rtmidi-1.1.0/docs/index.rst000066400000000000000000000010541307643736300171250ustar00rootroot00000000000000.. complexity documentation master file, created by sphinx-quickstart on Tue Jul 9 22:26:36 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to python-rtmidi's documentation! ========================================= Contents: .. toctree:: :maxdepth: 1 readme installation install-windows usage modules contributing authors history license Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-rtmidi-1.1.0/docs/install-windows.rst000066400000000000000000000000441307643736300211520ustar00rootroot00000000000000.. include:: ../INSTALL-windows.rst python-rtmidi-1.1.0/docs/installation.rst000066400000000000000000000000341307643736300205140ustar00rootroot00000000000000.. include:: ../INSTALL.rst python-rtmidi-1.1.0/docs/license.rst000066400000000000000000000000341307643736300174350ustar00rootroot00000000000000.. include:: ../LICENSE.txt python-rtmidi-1.1.0/docs/make.bat000066400000000000000000000145031307643736300166740ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\complexity.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\complexity.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end python-rtmidi-1.1.0/docs/modules.rst000066400000000000000000000000671307643736300174710ustar00rootroot00000000000000rtmidi ====== .. toctree:: :maxdepth: 4 rtmidi python-rtmidi-1.1.0/docs/readme.rst000066400000000000000000000000331307643736300172470ustar00rootroot00000000000000.. include:: ../README.rst python-rtmidi-1.1.0/docs/rtmidi.rst000066400000000000000000000013531307643736300173100ustar00rootroot00000000000000rtmidi package ============== Submodules ---------- rtmidi\.midiconstants module ---------------------------- .. automodule:: rtmidi.midiconstants :members: :undoc-members: :show-inheritance: rtmidi\.midiutil module ----------------------- .. automodule:: rtmidi.midiutil :members: :undoc-members: :show-inheritance: rtmidi\.release module ---------------------- .. automodule:: rtmidi.release :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: rtmidi :members: :undoc-members: :show-inheritance: Classes ~~~~~~~ .. autoclass:: rtmidi.MidiIn :members: :inherited-members: .. autoclass:: rtmidi.MidiOut :members: :inherited-members: python-rtmidi-1.1.0/docs/usage.rst000066400000000000000000000013021307643736300171160ustar00rootroot00000000000000======== Usage ======== Here's a quick example of how to use **python-rtmidi** to open the first available MIDI output port and send a middle C note on MIDI channel 1:: import time import rtmidi midiout = rtmidi.MidiOut() available_ports = midiout.get_ports() if available_ports: midiout.open_port(0) else: midiout.open_virtual_port("My virtual output") note_on = [0x90, 60, 112] # channel 1, middle C, velocity 112 note_off = [0x80, 60, 0] midiout.send_message(note_on) time.sleep(0.5) midiout.send_message(note_off) del midiout More usage examples can be found in the ``tests`` and ``examples`` directory of the source distribution. python-rtmidi-1.1.0/examples/000077500000000000000000000000001307643736300161525ustar00rootroot00000000000000python-rtmidi-1.1.0/examples/advanced/000077500000000000000000000000001307643736300177175ustar00rootroot00000000000000python-rtmidi-1.1.0/examples/advanced/midiwrapper.py000066400000000000000000000026061307643736300226200ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """Wrap MidiOut to add convenience methods for sending common MIDI events.""" import time import rtmidi from rtmidi.midiconstants import NOTE_OFF, NOTE_ON, PROGRAM_CHANGE class MidiOutWrapper: def __init__(self, midi, ch=1): self.channel = ch self._midi = midi def channel_message(self, command, *data, ch=None): """Send a MIDI channel mode message.""" command = (command & 0xf0) | ((ch if ch else self.channel) - 1 & 0xf) msg = [command] + [value & 0x7f for value in data] self._midi.send_message(msg) def note_off(self, note, velocity=0, ch=None): """Send a 'Note Off' message.""" self.channel_message(NOTE_OFF, note, velocity, ch=ch) def note_on(self, note, velocity=127, ch=None): """Send a 'Note On' message.""" self.channel_message(NOTE_ON, note, velocity, ch=ch) def program_change(self, program, ch=None): """Send a 'Program Change' message.""" self.channel_message(PROGRAM_CHANGE, program, ch=ch) # add more convenience methods for other common MIDI events here... if __name__ == '__main__': mout = rtmidi.MidiOut() mout.open_virtual_port() input('Connect to new MIDI port and then press key...') mw = MidiOutWrapper(mout, ch=3) mw.program_change(40) mw.note_on(60) time.sleep(1) mw.note_off(60) python-rtmidi-1.1.0/examples/advanced/recvrpn.py000066400000000000000000000047211307643736300217540ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """Receive and decode RPN messages. RPN (registered parameter number) messages are just regular Control Change messages with a special semantic. To change an RPN value, the sender first sends the parameter number with CC #100 (LSB) and #101 (MSB) and then the parameter value with CC #38 (LSB) and #6 (MSB). Both the parameter number and value are 14-bit values (MSB * 128 + LSB). See also: http://www.somascape.org/midi/tech/spec.html#rpns """ import time from collections import defaultdict from rtmidi.midiconstants import (CONTROL_CHANGE, DATA_DECREMENT, DATA_ENTRY_LSB, DATA_ENTRY_MSB, DATA_INCREMENT, RPN_LSB, RPN_MSB) from rtmidi.midiutil import open_midiinput class RPNDecoder: def __init__(self, channel=1): self.channel = (channel - 1) & 0xF self.rpn = 0 self.values = defaultdict(int) self.last_changed = None def __call__(self, event, data=None): msg, deltatime = event # event type = upper four bits of first byte if msg[0] == (CONTROL_CHANGE | self.channel): cc, value = msg[1], msg[2] if cc == RPN_LSB: self.rpn = (self.rpn >> 7) * 128 + value elif cc == RPN_MSB: self.rpn = value * 128 + (self.rpn & 0x7F) elif cc == DATA_INCREMENT: self.set_rpn(self.rpn, min(2 ** 14, self.values[self.rpn] + 1)) elif cc == DATA_DECREMENT: self.set_rpn(self.rpn, max(0, self.values[self.rpn] - 1)) elif cc == DATA_ENTRY_LSB: self.set_rpn(self.rpn, (self.values[self.rpn] >> 7) * 128 + value) elif cc == DATA_ENTRY_MSB: self.set_rpn(self.rpn, value * 128 + (self.values[self.rpn] & 0x7F)) def set_rpn(self, rpn, value): self.values[rpn] = value self.last_changed = rpn def main(args=None): decoder = RPNDecoder() m_in, port_name = open_midiinput(args[0] if args else None) m_in.set_callback(decoder) try: while True: rpn = decoder.last_changed if rpn: print("RPN %i: %i" % (rpn, decoder.values[rpn])) decoder.last_changed = None time.sleep(0.1) except KeyboardInterrupt: pass if __name__ == '__main__': import sys sys.exit(main(sys.argv[1:]) or 0) python-rtmidi-1.1.0/examples/basic/000077500000000000000000000000001307643736300172335ustar00rootroot00000000000000python-rtmidi-1.1.0/examples/basic/contextmanager.py000066400000000000000000000010231307643736300226200ustar00rootroot00000000000000#!/usr/bin/env python # # test_midiin_callback.py # """Shows how to use a MidiOut instance as a context manager.""" import time import rtmidi from rtmidi.midiconstants import NOTE_OFF, NOTE_ON NOTE = 60 # middle C midiout = rtmidi.MidiOut() with (midiout.open_port(0) if midiout.get_ports() else midiout.open_virtual_port("My virtual output")): note_on = [NOTE_ON, NOTE, 112] note_off = [NOTE_OFF, NOTE, 0] midiout.send_message(note_on) time.sleep(0.5) midiout.send_message(note_off) del midiout python-rtmidi-1.1.0/examples/basic/midiin_callback.py000077500000000000000000000025241307643736300227000ustar00rootroot00000000000000#!/usr/bin/env python # # test_midiin_callback.py # """Show how to receive MIDI input by setting a callback function.""" from __future__ import print_function import logging import sys import time from rtmidi.midiutil import open_midiinput log = logging.getLogger('midiin_callback') logging.basicConfig(level=logging.DEBUG) class MidiInputHandler(object): def __init__(self, port): self.port = port self._wallclock = time.time() def __call__(self, event, data=None): message, deltatime = event self._wallclock += deltatime print("[%s] @%0.6f %r" % (self.port, self._wallclock, message)) # Prompts user for MIDI input port, unless a valid port number or name # is given as the first argument on the command line. # API backend defaults to ALSA on Linux. port = sys.argv[1] if len(sys.argv) > 1 else None try: midiin, port_name = open_midiinput(port) except (EOFError, KeyboardInterrupt): sys.exit() print("Attaching MIDI input callback handler.") midiin.set_callback(MidiInputHandler(port_name)) print("Entering main loop. Press Control-C to exit.") try: # Just wait for keyboard interrupt, # everything else is handled via the input callback. while True: time.sleep(1) except KeyboardInterrupt: print('') finally: print("Exit.") midiin.close_port() del midiin python-rtmidi-1.1.0/examples/basic/midiin_poll.py000077500000000000000000000020371307643736300221110ustar00rootroot00000000000000#!/usr/bin/env python # # test_midiin_poll.py # """Show how to receive MIDI input by polling an input port.""" from __future__ import print_function import logging import sys import time from rtmidi.midiutil import open_midiinput log = logging.getLogger('midiin_poll') logging.basicConfig(level=logging.DEBUG) # Prompts user for MIDI input port, unless a valid port number or name # is given as the first argument on the command line. # API backend defaults to ALSA on Linux. port = sys.argv[1] if len(sys.argv) > 1 else None try: midiin, port_name = open_midiinput(port) except (EOFError, KeyboardInterrupt): sys.exit() print("Entering main loop. Press Control-C to exit.") try: timer = time.time() while True: msg = midiin.get_message() if msg: message, deltatime = msg timer += deltatime print("[%s] @%0.6f %r" % (port_name, timer, message)) time.sleep(0.01) except KeyboardInterrupt: print('') finally: print("Exit.") midiin.close_port() del midiin python-rtmidi-1.1.0/examples/basic/midiout.py000077500000000000000000000016701307643736300212660ustar00rootroot00000000000000#!/usr/bin/env python # # test_midiout.py # """Show how to open an output port and send MIDI events.""" from __future__ import print_function import logging import sys import time from rtmidi.midiutil import open_midioutput from rtmidi.midiconstants import NOTE_OFF, NOTE_ON log = logging.getLogger('midiout') logging.basicConfig(level=logging.DEBUG) # Prompts user for MIDI input port, unless a valid port number or name # is given as the first argument on the command line. # API backend defaults to ALSA on Linux. port = sys.argv[1] if len(sys.argv) > 1 else None try: midiout, port_name = open_midioutput(port, "output") except (EOFError, KeyboardInterrupt): sys.exit() note_on = [NOTE_ON, 60, 112] # channel 1, middle C, velocity 112 note_off = [NOTE_OFF, 60, 0] print("Sending NoteOn event.") midiout.send_message(note_on) time.sleep(1) print("Sending NoteOff event.") midiout.send_message(note_off) del midiout print("Exit.") python-rtmidi-1.1.0/examples/basic/noteon2osc.py000066400000000000000000000017641307643736300217060ustar00rootroot00000000000000#!/usr/bin/env python # # noteon2osc.py # """Send an OSC message when a MIDI Note On message is received.""" import sys import time import liblo from rtmidi.midiconstants import NOTE_ON from rtmidi.midiutil import open_midiinput def midiin_callback(event, data=None): message, deltatime = event if message[0] & 0xF0 == NOTE_ON: status, note, velocity = message channel = (status & 0xF) + 1 liblo.send( ('localhost', 9001), '/midi/%i/noteon' % channel, note, velocity) try: # Prompts user for MIDI input port, unless a valid port number or name # is given as the first argument on the command line. # API backend defaults to ALSA on Linux. port = sys.argv[1] if len(sys.argv) > 1 else None with open_midiinput(port, client_name='noteon2osc')[0] as midiin: midiin.set_callback(midiin_callback) while True: time.sleep(1) except (EOFError, KeyboardInterrupt): print("Bye.") python-rtmidi-1.1.0/examples/basic/panic.py000066400000000000000000000012551307643736300207020ustar00rootroot00000000000000#!/usr/bin/env python # # panic.py # """Send AllSoundOff and ResetAllControllers on all JACK MIDI outputs and all channels.""" from __future__ import print_function import rtmidi from rtmidi.midiconstants import (ALL_SOUND_OFF, CONTROL_CHANGE, RESET_ALL_CONTROLLERS) midiout = rtmidi.MidiOut(rtapi=rtmidi.API_UNIX_JACK) print(__doc__) for portnum, portname in enumerate(midiout.get_ports()): print("Port:", portname) midiout.open_port(portnum) for channel in range(16): midiout.send_message([CONTROL_CHANGE, ALL_SOUND_OFF, 0]) midiout.send_message([CONTROL_CHANGE, RESET_ALL_CONTROLLERS, 0]) midiout.close_port() python-rtmidi-1.1.0/examples/basic/probe_ports.py000077500000000000000000000030401307643736300221430ustar00rootroot00000000000000#!/usr/bin/env python # # test_probe_ports.py # """Shows how to probe for available MIDI input and output ports.""" from rtmidi import (API_LINUX_ALSA, API_MACOSX_CORE, API_RTMIDI_DUMMY, API_UNIX_JACK, API_WINDOWS_MM, MidiIn, MidiOut, get_compiled_api) try: input = raw_input except NameError: # Python 3 StandardError = Exception apis = { API_MACOSX_CORE: "OS X CoreMIDI", API_LINUX_ALSA: "Linux ALSA", API_UNIX_JACK: "Jack Client", API_WINDOWS_MM: "Windows MultiMedia", API_RTMIDI_DUMMY: "RtMidi Dummy" } available_apis = get_compiled_api() for api, api_name in sorted(apis.items()): if api in available_apis: try: reply = input("Probe ports using the %s API? (Y/n) " % api_name) if reply.strip().lower() not in ['', 'y', 'yes']: continue except (KeyboardInterrupt, EOFError): print('') break for name, class_ in (("input", MidiIn), ("output", MidiOut)): try: midi = class_(api) ports = midi.get_ports() except StandardError as exc: print("Could not probe MIDI %s ports: %s" % (name, exc)) continue if not ports: print("No MIDI %s ports found." % name) else: print("Available MIDI %s ports:\n" % name) for port, name in enumerate(ports): print("[%i] %s" % (port, name)) print('') del midi python-rtmidi-1.1.0/examples/drumseq/000077500000000000000000000000001307643736300176325ustar00rootroot00000000000000python-rtmidi-1.1.0/examples/drumseq/README.rst000066400000000000000000000042131307643736300213210ustar00rootroot00000000000000Drum Pattern Sequencer ====================== This example was contributed by Michiel Overtoom [1]_. I just embellished it a bit and added command line option handling. You can see Michiel's original script in the Subversion history. The script ``drumseq.py`` implements a simple drum pattern sequencer, which reads patterns from text files in a very simple and easy to edit format, and plays them back as a MIDI note sequence to a given MIDI output. Each line starts with the MIDI note number of a drum sound and is followed a sequence of characters indicating at which step this drum should be triggered. Different characters map to different velocities and a ``.`` signifies velocity zero, i.e. the drum note will not be triggered at this step. A dash ``-`` is a tie, neither a note off nor a new note is sent at this step. Each line must have the same number of steps. One step is nominally a 1/16 note, but you are free to define a pattern with twelve steps and increase the BPM by a factor of 4/3 to get a triplet-feel. Lines starting with a hash (``#``) are ignored and can be used for comments or temporarily muting a drum sound. The third field of each line, after the pattern sequence, should name or describe the drum sound to use, but you are free to put there whatever you want, the field is not used by the sequenzer. You can change the MIDI port, channel, bank and program via command line options and also specify the BPM at which the pattern is played back. For a mapping of midi notes to General MIDI drum sounds, see http://en.wikipedia.org/wiki/General_MIDI#Percussion For a General MIDI compatible software synthesizer, see: OS X SimpleSynth_ Linux Qsynth_ (GUI) or fluidsynth_ (command line) Windows Builtin The patterns whose filenames start with ``example_`` are taken from the article *The Rhythm Method. Effective Drum Programming* published by the Sound on Sound magazine [2]_. .. [1] http://www.michielovertoom.com/ .. [2] http://www.soundonsound.com/sos/feb98/articles/rythm.html .. _simplesynth: http://notahat.com/simplesynth/ .. _qsynth: http://qsynth.sourceforge.net/ .. _fluidsynth: http://sourceforge.net/apps/trac/fluidsynth/ python-rtmidi-1.1.0/examples/drumseq/break-on-through.txt000066400000000000000000000004231307643736300235460ustar00rootroot00000000000000# Break On Through # 188 bpm # 1...|...|...|...2...|...|...|... 36 x.......x.......x.......x.....s. Bassdrum 37 m.....m.....m.......m.....m..... Rimshot 44 ....m.......m.......m.......m... Pedal Hi-hat 59 s-s+s-s-s-s-s-s-s-s+s-s-s-s-s-s- Ride 2 (try "Ride 1" (51) as well!) python-rtmidi-1.1.0/examples/drumseq/drumseq.py000077500000000000000000000145521307643736300216760ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # drumseq.py # # MIDI Drum sequencer prototype, by Michiel Overtoom, motoom@xs4all.nl # """Play drum pattern from file to MIDI out.""" from __future__ import print_function import argparse import sys import threading from random import gauss from time import sleep, time as timenow import rtmidi from rtmidi.midiutil import open_midioutput from rtmidi.midiconstants import (ALL_SOUND_OFF, BANK_SELECT_LSB, BANK_SELECT_MSB, CHANNEL_VOLUME, CONTROL_CHANGE, NOTE_ON, PROGRAM_CHANGE) FUNKYDRUMMER = """ # 1...|...|...|... 36 x.x.......x..x.. Bassdrum 38 ....x..m.m.mx..m Snare 42 xxxxx.x.xxxxx.xx Closed Hi-hat 46 .....x.x.....x.. Open Hi-hat """ class Sequencer(threading.Thread): """MIDI output and scheduling thread.""" def __init__(self, midiout, pattern, bpm, channel=9, volume=127): super(Sequencer, self).__init__() self.midiout = midiout self.bpm = max(20, min(bpm, 400)) self.interval = 15. / self.bpm self.pattern = pattern self.channel = channel self.volume = volume self.start() def run(self): self.done = False self.callcount = 0 self.activate_drumkit(self.pattern.kit) cc = CONTROL_CHANGE | self.channel self.midiout.send_message([cc, CHANNEL_VOLUME, self.volume & 0x7F]) # give MIDI instrument some time to activate drumkit sleep(0.3) self.started = timenow() while not self.done: self.worker() self.callcount += 1 # Compensate for drift: # calculate the time when the worker should be called again. nexttime = self.started + self.callcount * self.interval timetowait = max(0, nexttime - timenow()) if timetowait: sleep(timetowait) else: print("Oops!") self.midiout.send_message([cc, ALL_SOUND_OFF, 0]) def worker(self): """Variable time worker function. i.e., output notes, emtpy queues, etc. """ self.pattern.playstep(self.midiout, self.channel) def activate_drumkit(self, kit): if isinstance(kit, (list, tuple)): msb, lsb, pc = kit elif kit is not None: msb = lsb = None pc = kit cc = CONTROL_CHANGE | self.channel if msb is not None: self.midiout.send_message([cc, BANK_SELECT_MSB, msb & 0x7F]) if lsb is not None: self.midiout.send_message([cc, BANK_SELECT_LSB, lsb & 0x7F]) if kit is not None and pc is not None: self.midiout.send_message([PROGRAM_CHANGE | self.channel, pc & 0x7F]) class Drumpattern(object): """Container and iterator for a multi-track step sequence.""" velocities = { "-": None, # continue note ".": 0, # off "+": 10, # ghost "s": 60, # soft "m": 100, # medium "x": 120, # hard } def __init__(self, pattern, kit=0, humanize=0): self.instruments = [] self.kit = kit self.humanize = humanize pattern = (line.strip() for line in pattern.splitlines()) pattern = (line for line in pattern if line and line[0] != '#') for line in pattern: parts = line.split(" ", 2) if len(parts) == 3: patch, strokes, description = parts patch = int(patch) self.instruments.append((patch, strokes)) self.steps = len(strokes) self.step = 0 self._notes = {} def reset(self): self.step = 0 def playstep(self, midiout, channel=9): for note, strokes in self.instruments: char = strokes[self.step] velocity = self.velocities.get(char) if velocity is not None: if self._notes.get(note): midiout.send_message([NOTE_ON | channel, note, 0]) self._notes[note] = 0 if velocity > 0: if self.humanize: velocity += int(round(gauss(0, velocity * self.humanize))) midiout.send_message([NOTE_ON | channel, note, max(1, velocity)]) self._notes[note] = velocity self.step += 1 if self.step >= self.steps: self.step = 0 def main(args=None): ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) aadd = ap.add_argument aadd('-b', '--bpm', type=float, default=100, help="Beats per minute (BPM) (default: %(default)s)") aadd('-c', '--channel', type=int, default=10, metavar='CH', help="MIDI channel (default: %(default)s)") aadd('-p', '--port', help="MIDI output port number (default: ask)") aadd('-k', '--kit', type=int, metavar='KIT', help="Drum kit MIDI program number (default: none)") aadd('--bank-msb', type=int, metavar='MSB', help="MIDI bank select MSB (CC#00) number (default: none)") aadd('--bank-lsb', type=int, metavar='MSB', help="MIDI bank select LSB (CC#32) number (default: none)") aadd('-H', '--humanize', type=float, default=0.0, metavar='VAL', help="Random velocity variation (float, default: 0, try ~0.03)") aadd('pattern', nargs='?', type=argparse.FileType(), help="Drum pattern file (default: use built-in pattern)") args = ap.parse_args(args if args is not None else sys.argv[1:]) if args.pattern: pattern = args.pattern.read() else: pattern = FUNKYDRUMMER kit = (args.bank_msb, args.bank_lsb, args.kit) pattern = Drumpattern(pattern, kit=kit, humanize=args.humanize) try: midiout, port_name = open_midioutput( args.port, api=rtmidi.API_UNIX_JACK, client_name="drumseq", port_name="MIDI Out") except (EOFError, KeyboardInterrupt): return seq = Sequencer(midiout, pattern, args.bpm, args.channel - 1) print("Playing drum loop at %.1f BPM, press Control-C to quit." % seq.bpm) try: while True: sleep(1) except KeyboardInterrupt: print('') finally: seq.done = True # And kill it. seq.join() del midiout print("Done") if __name__ == "__main__": sys.exit(main(sys.argv[1:]) or 0) python-rtmidi-1.1.0/examples/drumseq/example_01.txt000066400000000000000000000004571307643736300223340ustar00rootroot00000000000000# Example Groove 1 # 100-120 bpm # |...|...|...|...|...|...|...|... 46 ..............................x- Open Hi-hat 44 ....x.......x.......x.......x... Pedal Hi-hat 42 x.s.x.s.x.s.x.s.x.s.x.s.x.s.x... Closed Hi-hat 38 ....x.......x.......x.......x... Snare 36 x.......x.....s.x.......x....... Bassdrum python-rtmidi-1.1.0/examples/drumseq/example_02.txt000066400000000000000000000004571307643736300223350ustar00rootroot00000000000000# Example Groove 2 # 100-120 bpm # 1...|...|...|...2...|...|...|... 36 x.....s.x.......x.....m.x....... Bassdrum 38 ....x.......x.......x.......x... Snare 42 x.s.x.s.x.s.x.s.x.s.x...x.s.x.s. Closed Hi-hat 44 ....x.......x.......x.......x... Pedal Hi-hat 46 ......................x-........ Open Hi-hat python-rtmidi-1.1.0/examples/drumseq/example_03.txt000066400000000000000000000003771307643736300223370ustar00rootroot00000000000000# Example Groove 3 # 100-120 bpm # 1...|...|...|...2...|...|...|... 36 x.......x.....m.x.......x.....s. Bassdrum 38 ....x.......x.......x.......x... Snare 42 x...s...x...x...x...s...x...s... Closed Hi-hat 44 ..m...s...m...ss..m...s...m...s. Pedal Hi-hat python-rtmidi-1.1.0/examples/drumseq/example_04.txt000066400000000000000000000003161307643736300223310ustar00rootroot00000000000000# Example Groove 4 # 100-120 bpm # 1...|...|...|...2...|...|...|... 36 x.......x.....s.x.......x.....m. Bassdrum 38 ....x.......x.......x.......x... Snare 42 xssx.xssssxs.xssxssx.xssssxs.xss Closed Hi-hat python-rtmidi-1.1.0/examples/drumseq/example_05.txt000066400000000000000000000004461307643736300223360ustar00rootroot00000000000000# Example Groove 5 # 100-120 bpm # 1...|...|...|...2...|...|...|... 36 x.......x.s...s.x.......x.....m. Bassdrum 38 ....x.......x.......x.......x... Snare 42 x.xm.xm.xmxmxmxxxm.xm.xm.xx.xmxm Closed Hi-hat 51 x...x..s.x..x...x...x...m.sm.x.. Ride 54 ..m...s...m...s...m...s...m...s. Tambourine python-rtmidi-1.1.0/examples/drumseq/example_06.txt000066400000000000000000000004571307643736300223410ustar00rootroot00000000000000# Example Groove 6 # 100-120 bpm # 1...|...|...|...2...|...|...|... 36 x.s.....x.m.....x.........x..... Bassdrum 38 ....x.......x.......x.......x... Snare 42 x...m..xm...x...x...m...x...s... Closed Hi-hat 44 .....s...s...m.s.....s...s...... Pedal Hi-hat 46 ..x-..m...x-..m...x-..m-..x-..m- Open Hi-hat python-rtmidi-1.1.0/examples/drumseq/example_07.txt000066400000000000000000000004561307643736300223410ustar00rootroot00000000000000# Example Groove 7 # 100-120 bpm # 1...|...|...|...2...|...|...|... 36 xs.m....x.x....m..x...x..m.s.... Bassdrum 38 ....x.......x.......x.......x.mm Snare 42 xx..x.s.x.sx..s.x.s.x.s.x.s.x.s. Closed Hi-hat 44 x.x.xx..x...x..x..x.x..x..x.x.x. Pedal Hi-hat 54 msxsmsxsmsxsmsxsmsxsmsxsxsmsxsms Tambourine python-rtmidi-1.1.0/examples/drumseq/example_08.txt000066400000000000000000000004561307643736300223420ustar00rootroot00000000000000# Example Groove 8 # 100-120 bpm # 1...|...|...|...2...|...|...|... 36 xs....m...xm...mx.x...x..m.s.... Bassdrum 38 ....x....x..x.s.....x.......xmmm Snare 42 xx..x.s.x.sx..s.x.s.x.s.x.s.x.s. Closed Hi-hat 44 x.x.xx..x...x.x.....x..x..x.x.x. Pedal Hi-hat 54 msxsmsxsmsxsmsxsmsxsmsxsxsmsxsms Tambourine python-rtmidi-1.1.0/examples/drumseq/example_09.txt000066400000000000000000000006621307643736300223420ustar00rootroot00000000000000# Example Groove 9 # 110-130 bpm (for a real tempo of 82.5-97.5 bpm) # 1.....|.....|.....|.....2.....|.....|.....|..... 36 x.s..m......x..x.......m...x.....x....m..s...... Bassdrum 38 ......x-..........x-..........x-..........x-.m-m Snare 42 x.x...x..s..x..s.x...s..x..s..x..s..x..s..x..s.. Closed Hi-hat 44 x..x..x.x...x.....x....x...x..x....x...x..x..x.. Pedal Hi-hat 54 m-sx-sm-sx-sm-sx-sm-sx-sm-sx-sm-sx-sx-sm-sx-sm-s Tambourine python-rtmidi-1.1.0/examples/drumseq/example_10.txt000066400000000000000000000006631307643736300223330ustar00rootroot00000000000000# Example Groove 10 # 110-130 bpm (for a real tempo of 82.5-97.5 bpm) # 1.....|.....|.....|.....2.....|.....|.....|..... 36 x.s......m.....x.m.....mx..x.....x....m..s...... Bassdrum 38 ......x--.....x-..x--s-.......x--.........x-mm-m Snare 42 x.x...x..s..x..s.x...s..x..s..x..s..x..s..x..s.. Closed Hi-hat 44 x..x..x.x...x.....x....x......x....x...x..x..x.. Pedal Hi-hat 54 m-sx-sm-sx-sm-sx-sm-sx-sm-sx-sm-sx-sx-sm-sx-sm-s Tambourine python-rtmidi-1.1.0/examples/drumseq/example_11.txt000066400000000000000000000004531307643736300223310ustar00rootroot00000000000000# Example Groove 11 # 90-110 bpm # 1...|...|...|...2...|...|...|... 36 x..m....x..x..smx.m....sx.x..... Bassdrum 38 .............m........s......m.. Snare 1 40 ....m....m.sx.......m....s..m... Snare 2 46 ..m-............m-.............. Open Hi-hat 44 mx..m.m.m.s.sms...msm.m.s.mss.sm Pedal Hi-hat python-rtmidi-1.1.0/examples/drumseq/example_12.txt000066400000000000000000000005331307643736300223310ustar00rootroot00000000000000# Example Groove 12 # 90-110 bpm # 1...|...|...|...2...|...|...|... 36 x...............x.........x..... Bassdrum 37 x.....m.m...m.......m..x....x... Rimshot 38 ........x.....x.........x....... Snare 42 x.m.x.m...m.x.m.x.m.x.m...m.x.m. Closed Hi-hat 44 x...x.......x.......x........... Pedal Hi-hat 46 ........x-..............x-...... Open Hi-hat python-rtmidi-1.1.0/examples/drumseq/funkydrummer.txt000066400000000000000000000002351307643736300231230ustar00rootroot00000000000000# Funky Drummer # 1...|...|...|... 36 x.x.......x..x.. Bassdrum 38 ....x..m.m.mx..m Snare 42 xxxxx.x.xxxxx.xx Closed Hi-hat 46 .....x.x.....x.. Open Hi-hat python-rtmidi-1.1.0/examples/drumseq/rosanna-shuffle.txt000066400000000000000000000003151307643736300234650ustar00rootroot00000000000000# Rosanna Shuffle # about 124 bpm (for a real tempo of 93 bpm) # 1..|..|..|..2..|..|..|.. 36 x....m...x.....m..s..... Bassdrum 40 .+-.+-m+-.+-.+-.+-m+-.++ Snare 2 42 x-sx-sx-sx-sx-sx-sx-sx-s Closed Hi-hatpython-rtmidi-1.1.0/examples/drumseq/template.txt000066400000000000000000000007351307643736300222130ustar00rootroot00000000000000# Pattern Template # 100-120 bpm # 1...|...|...|...2...|...|...|... 36 ................................ Bassdrum 37 ................................ Rimshot 38 ................................ Snare 42 ................................ Closed Hi-hat 44 ................................ Pedal Hi-hat 46 ................................ Open Hi-hat 49 ................................ Crash 51 ................................ Ride 54 ................................ Tambourine python-rtmidi-1.1.0/examples/midi2command/000077500000000000000000000000001307643736300205155ustar00rootroot00000000000000python-rtmidi-1.1.0/examples/midi2command/000-playback.mp3000066400000000000000000001316261307643736300232320ustar00rootroot00000000000000Infom  #%'*,.1368=?ADFHKMORTY[]`bdgilnpuwz|~9LAME3.99r$B Cq^K;1M4hъ** ʈ2x2+XP$('"V!X`qU;jJޫh:I淚R4Xf\LեP#"E!l[$֫J&33rW~^EkgMM1~ysw9"*wsrQkb-w^c1.޳stSa[uʤ@"I&UT-7c0VNeLH Mga݌j>V 8BVKMsje*uܶ|v[X{վJaʶ)*hTҁyBPe/#QqI) V,2QWwTʛZ߬;^3=6dYJW2"Fcg;m{2G\X53wwYewW/T@#[U*U'!\b/~(-JFsIB0k(((6R#3-P)Y qm `1-Q`k}f`"T@eȵu|H ]bUvyUet3if_B&>U1nA*US'h]ƞZ6.;kzѯ+ٖ DPA!.0L(Ag( ,8m/K(a/ e nCAN@IJ%m0yYxIL ao! 6ShLA,"fSoE "RtAf1; ,SM.{S?J"z< t[LnYZ󮢢-T9չR=g"q91<-^8Dp!r3ƓG}+_Zac/XQvТq? c JѠ3TA2ҽ,hg"=d8P g,'H;0A,;f99A"5*]Z H8, T;%d2}Eu7ڏ`=:JAwWպL 4.I@*Ih"89 4(bؽ @oK/'nU4uO#K 81c4.=<f> ӟ@V3*R_SXYENzMBM>"֙f!*%NYt t:RQi ?QԢ DJL. &pP$9wĂf6Qa Z?h4@%C #P`N0P0t B3:cT8jʂRe19P a )aܓ1680Ds6 ȭlo=&܊o]V KC @I 25ݷP"VSBr1DÍ&`pP"H&#ITL0 )"L& 2clM\2dd n x|itš8d4h|kʒr$ ȉB.""TXЖ(Dҡxm՞;dXsçpP.ϡ@h7u0!0P5%4& @nLAS2Ctw0pgBb4`3toÞa00`X,hd$ ( h! ))ˡ9Q"ЍI@\)@A444G@ty{Z!nl1L`.Q}kÑa@j[D2hT^,6)AFx $O4 ˥TmUD`Y6b6 QRM0.%I|&@\d sVw$ wNy@CX #CDŽ % C. ڂ$$p0s4`Gb@= uT$ FւKf:DhoQ`q;(P/!`PHb`* \͛_ IASTd0U }A!P2G T c^DE!c!`9jRˈdSv! QG;,(8#eJT1Ve# 1L,7Y *}"ҼA|' ?B4P(@_ "BE-d}*yHbd0p)x.rej ٕИ<Jqg78BS5@\pʉɂHP5 g\PgضWNvmeb]P~oj*&v )dyYp4m>`PP$z(%q,a : P8, 3م)۠ىY&Yq ZƑ "2i8aģ _2F  wcIءRrEP"V0bx`>4>@PK`axLcixINc84``~br`HJH:6);\@0Xрƌë]L !&T^v Eb ]6 }v ՚э4\qgrURlhGӘ,rn\W{'8-)"Qm9@0 11w2<2 N'  V Pad㔁APq' J &?hr1qr8ueMYWe0xI4+(22 x ;Y% [V+u?u_.# P9$X`3" b* TQC%@`"q(l`PZ,8c@`0d0{rCCiEDz'}>ƐF CQ Y EPYUFk fTxc}ց\'WgiT17aA `,r4`Lfid*wk ͩ DAnBc띘#"``8"cjr~aPXd)Boc`pA4 P 8졌KX \`FOdr@60K|0@4e ʁ ^48*1eoFv`Xqh@L`abzaLeG#'\ЂV ^ے#c BSacL x "8! `MT2HirC0cq32GD7IC clMWm;  26ЀQ@boDlPa8CN2".B @`q*pa Fgel}A, c uqK#0Qsm*SPHG@h4a`/# y%2؆  6, SdNnHwrE%vZ]vWd&U\-LkvCj q%x2aa$X!0_4ق8=)2'`owk9>=a BM¬@2d*L_WTV1l{#&^X |P2X@[XU2Ѐ;SÞWl)OڔGyRW&{#!pye3w b4"08cU0t` n[9A 21qqڂ1bPabo̡|~<4(X.“A~RU0DJ5#ä!09/%ꨁ%F9ѳ7Vt7-o[N{~_~9uWS1 i6au-L04*@H^a9hLP-?AH0ԋ*]&jG37`Ti@ T}5샙!OtM c.,]֢ }.|D&kAy-jF9hZ[Q1ߖњz\, LA]OP@ 3B\:\|H ղT$8$ }@à N@ԭ0`j&&R jf,bHf\JH0W L0REP4D1 =qD!CLJ5(dAUP4@9v;PQu)9A!  i`61$T rX LP FSɑ+q¡L>rB$%g042K=uK@ 402T3o0 (01Dd81lsҀ.BJ4 nb KuaXŜ}?;n_sӘ룞cU#cN,\ƹX{ReSf+s 8XS @0st03x17P130oTs }#PK1@D1(р탙6 OuK?My2,8CͅxxI8)AP.ՎtLke">IV2a*{CSCѴvc" P}@yLseC \s(h!,0p6,s p&0s0@G0@:icΒ1Q )@Q"& ,bcӶ` [Z0(zU]:&)nUUa,,62}oo]Z׶L7՛^o55.eTN"Vcr3A*P;u0#;6.ac @I`R2i`Fda` yBpʪ&Rއ&E' OhT>My׫F.#aK(DxTq-3g@UGK \Tv&Sw߿͙39Vfcyƚ6<x#<)DŽ"p,3" .0E0N1142MR^|gdlS HAHh2bL!AJ!@造e` o LsqG3Պ[o} 콩U!lSiV*C@`>F`fcnI,e1&0w3+0 S( ھ mh y 7ܮ˿PkA6o[,*Lǃ mZEƵ$Hp7^IIlpj`€=Fӄ LaC P`: !!Pq"cpqhhb(rb5saXWI@( c탘~ ohT."qqh6B a<0`w @@Ñ#e?d@FI~ O쌽_}ףDm=9XfSu@1D ;P ` 7F`0倖0lPc01v }1IXASiI@0EC f[ dX 6z$.= PI5 p%  i'j_*hoZ"Jg#Z)QT R:0X . q!cãDFP:((t0h! s< >Ҙ( \9O0`̿ @LW)pܮPBσ: otKA9EfUP߼U 7qУ )~>ߦO7٧͠({0bǸarqDQpQ0"0&qb`$ aoҡ`fL%d0+`ޓ*`:pe>'@YH43SA[q PU BçԀ@<t8aw@0Q Č˥P@ `p3+ȔO.ld'+.֣ 1,P|$b{AF SaTp0 |&[0cB1:CY0[@e0} #>Pb0v0 0C`:0> S1?r t Abb\ q3h  T @2 jm`p*\Mϕ5_ڳuڍM3]\9cm/RFћ2KהI1F.L@'T!L|P (LʗI Qq )3yy щ@1I1ɆtÈC sC5D& J@!>Džȑ =4nou~=[ޟW2&qR!1\C̒NX!PT-LX0 ƁH@( n< L402Dap° `2 ޶v  ;AxA8 Ռ $!DmC2yP)}a`]'[̭߯ blSFÄXM PXij xPMЊ(6* 0)U@HS312=}3Z0c $2~0P+0- c@1ZS  i@A2Bj>taa+ q܄q@X$0Icehd*J@5>.n'v6oMsT,|Z)ޖFo*"4Ū$K9G 8< ;a⢡B.m1P '"<;JTjE&h 5-aޞP'T95LYdimC\"M  I:v# `a&`H D4`QT P#Mۧݿ˫bw3.s8.e!E"$a 8!4,<œ>N`m%Ʌ0IO@LDBXAh۟t .3V.@blpF`bTn4`28 rÄ| i|5M,0h~M?ڛ2eTr*&;8DI0XDiH &"!&$"1xSP8 0610 3=1$1&00J@] c`4Gf2t byspC2@@(:F$X9PP^lݨ Gv_M??џv?uo{OD1x'^XgvV!dEBv~,Byōv$Ca3]#JzD2h8A88k̤!#!aaLbJBbD>!04; P0 020320))4p0* Re tԆ 9>_@u;\_^?|$&"J9L4ʸ_I޴]&H[5F (Ӫ$1 h&!Du@}' (Odt0L vL Lk"@I\ fJ  ;.A pFG H15 0C8 P !`Իm5^yVrєs̮J+Y\ΥGƣQiOv4hAI1d00QnH.yQS* XqRA`F m:ZCe13Z0sB c"[,FLAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU1(PB0t'80r O9;A81 #5PC0r3@t<?0= &1`:0!8s"0!!01$`<njid`LQ @$,hNH pYH<T]Mߒ1AVO_mnȆ%Tv9c͗ O|[/*]Y۫-v?rt>ŀH( @I@X<4L (L(ȹW1 S0@dYģ $\LT@ QĈ`.#p SЕ@>w5uaO'}Z6r6bЪ0ux$s>Ͱwa*a& ,,#F ׺a0(M&Q`=d ]L) 鋚aXш 4ޖ K 8 9\1IUE`뿦޺*}2229^#8(@em&DHHIͮBQ6uVafI̜xԁkIh K)H)$9XK}8O )0|aPC,J0TD_fv_CP1+sX@ND3LXwS K_6-{zӵWD e*PsHeۃML'9cmGIQD$A[rEB1Q+ @0`90azv0P ̲x QŒ4 X@# Ҁ _D9A8A`4Tk#§1z`h Ӱ8lz4ѻh'o|їN9OBdmM(P8p<"vvUIb#$4ʓ2e LA0PhL! a,F(b&Ie$F .s001CIBC@ 8rs|88 J4`$LQTKZ./B/^_տ֞6kQ*3qbʂVI&Z E`H4´u͉b]1% Ѩ4a:(lqP'ًPiG P[PV8L4L ]!y =j  9A8% y8w@PA:h }dk^_|Ϯ~ko7(CQ ISt9V.BohI0O4$C$@WH8wLD W t8 ƒ4Ŕ,8mpzp`` φ`,& `L2 nj`TF!ȐDd!}G ƽO7?uZu|?Tslwz3B =[6emO IvUM!*gZU` > )p1\Q-x/ 񌸍XQyH05Yz|@8 s@X 9':`pd, $*%(cz(o };{Z9]k-ͣ_^WN|+BVMl4*"902DN} r'UՁbA dXi@ 7 L!BL=L"LLQL)Lx  x0H 8& P `@ b `*H"6/UFF'R+с0hW32#gpNJh) R020brF01 1\-p=08::s01>0 0+Є0?"  9 '840f!=2 @L%uσ/ٯRڳh?Y=On*:E f3in @`B A@Fv ^Hf B.$aIX1 L9!(HbqzBb)P?р@X `Mˬ,cI`*24| F4oZ UXDK۳;tK*)$ED`،QK9"5`DjYTְpaC6 M1J-1 0%C{ ڽk>dյ:WS,]{_Vt 1B%.gk+|2.! `<036.YIPI)` I[H8P̈L8dLǮ؊ 9a1 10q09" ҷ9qߠC~k2zJ|Fve1iA:*-'6A,FIz(6fJ#&<L @h¨0NݎPL|YGLœ$10TULBLLL 4 4laNI($axl* %2 g E"  z/ʺ/{(Cjv{g?3Y=Q]TdGoZCk^Uz69+dee됕G-շu0`50azG0ƎbfS<` F$"Nt)'`F5ha ^`(XA`BlZ 9CAA0d\-TWڣg7?)S;3O4YYk_u?B5U_T 0)x$$S.([0Pg%̟e ,mY1mz1s0H0320<pr0A1: s a3E#@gLI !@@hLGd\bT #8F9׉o.5-tVinȪj9΍eg+(D:R }T͞{ӜX^[Cj4UY4\l0 џØ, M`! i lC LF˼ L4 )b 98fa FR@6]U__751н6hDEieF(QAh4XRLnm[2BeTAb!Wi^(i2dCy!(:`,~qF``F-!e /a)Za*@cV@2&g`@+~* [/g.2Lfc_Am{Y2BpZufw'72eEjjwI#X` tXh<,)Z]<\18m7USU@ fu\&#8`*>dHR19S1Qc1Fa0!}3sY0&C 2c0  9#A1,c @p$Vg= Қ^ۖU[N[FLTYU@CwPNh|v\aS52n1.\w%ث$&b#o5L h\L<LdœLKL(8$@H.Ŋ, 6c @6DÀ@k )@Yh(F+yo??͖\.Mi_A~jweVQ@ gH.d&BE-(-N)gY=de1S080tsf00ey(' W1JF8J $&cڝ%Pj / Aa1 IYs p(ّa9@Y@I1gjȈY& 9!t@~b @Dp`UBbJ5.۽6lf!%4.>~53SP'ޢV W&x3+Q6/B ɡrby.\ _ d 78 JbFOf0dvyb`g^b DCä@iyDf1jw?v_EEꫳo_&yYaNYƘ#`ӐT9!]]R9^3mpڥ:[a,s? Qu 08&p% σELaܘ1Hwad xZZIFabhE !VH@mO}WFʴgw2ڏ7[rwQRشWΓEHSH\HcMRBW!&@D 8.[B戈Y 88X ޝ}-!LL aNcƐl xl`f`F*Aat`e@'.0;,X[ݾ]67-0eiS}rE[-ٯ9}-\e}_Fx~͜GM.]c̮i}IwH$BS0C3nw`y0 c #1C# 0?DC0xP՜  Ly,D` 0Is]0 "A+Sg? GKr2gRQgwMZqlHbDir"pIBj#i1 `W#HF(r5]]]!-0P;0#8ga0L ?@!$釠 =ٓH8 D  F".sX /A1z*|$+^od^-iz%c*y_OӣPv: kxj7.FOw}\F e%~ur"B`hpu:oMIy\Wt PٌA(ä .hФ$itP@8jLqT ¸M`$ L_7o" ?>fdf2a*q5_O[)F&nOj^.R]XDӖ.xZ"UchԬXJ@bvbkF0@:01v0O~0;@Q8'I@zH|ɠ PL,ĕX /$AaA1d! I!4\ȿU5$az}Z׷3,[(enq s*D B 4Pe(n:2URB |4J!yr4(` a((BNhLID(ʠ 4° XL¤ PB&d&lAl46 @YCc__e*g}:ο{ufg{v1W]e}ml{r1INe](h#Rq=J}wIJY5LψEʌ(aJ!T0\"%c@g0A3`0y:8#`V0}0P% AP, @5rlx(V 9 AA2 ='G~PCK, ̼TgiKOѝ,rssv4\.EJeCbr.oyexBhlY U!q{oP (( Nj L$LfA& .@D!, Eb``-q+@x#3+iHyV "ЎԝͲTVt,DC$1]Lߥ}4IkkK+&Y`/T-D0 9WD[nab^U05aX R9rO( &0c%0Sk0{ pcF\¬Ԍ ʃtaR ap Z  +2 pH~h,pa&z~jvyblw{*v%n-̲lLj:8\nsb'(hMʈ&\lhmqP+XP@hQLPLA0ˆ LAHàL) и f.dad0Cp'88+)P; 0 Zdmhdi?b:fPGR3]֨Dvo5ˆzSyg^p^#H'd&#Q$I!I &dL$@7090190Ba 0х .I*ه5Y 0ag8/>d:  7 AA&fA `B _=+EgO6n)sHf܇o}[6i=urݻ-Yu8LۨEԅSˡDtȨqD9e+i0GHӱ>ID@2NpxxQ w'WsADP+)9 ˆ7&GPՉifUfiIɵ!]K$; =_~g> ٜ]tV#%>'k]z E28^<`&J_(-H%a, Yz"p2?`Xٍɛd.D ĴL l0 )Cfْ AA~xR"lCߢH㹐]a@6(?q<:gfz-3=kZ\]0(FGܔ*Z~tVŃq}*׎JO3x܊:@LD=l M& 0ˆÂ8eL=@$ cnaf @4 iFX gPF}g8,&Y/2ޟ);e3]LsduUF2w>^㔾yIT9QeҖmzjV$jj hP #L}`AAf4+!K@)s3G_j0s00RSc~001#p;( 6ax0!0T `2*0.Q̄AJ $&("Ƶ*"g~_#I?V34kdkMSI D(I ʒ% XP6|E4O@РtI#DD0  [PA8jѐ#gQI5 #h  L,j&a",2+a̐9 DMH~miWIVWϙ~aѝ!2u☱o>$:#2OR` xihboDզ؆B`GTUL LG`L1GDL3d8a0R<0 PYk Bׂ 7#Aa*IC܇+ޞsܞd>>Tro{~Lm3ԭmWch jPz%rÅJZ j ÊԸ>>IRv!8, AUAoLOE` 6$0Mˆ 8T ` ( LȴfA$*@T[vm]MݕTUͥt;MȭyW|}݉JYr-K2|ZVK ʆ5bI]! Fe8R#8\ @,0qSҶ3?0-m 3 7 @ \ p!T4TYo 72DŽHs!AOF2l"S3/ꦝjdřu[kzXvp݈IJnhxI P[+OZ+zS@x8 B$a0# _8Jaa0?1)3 z00H0}#C1  x0lP fh& /:rg?ByX%*ov*Ьwffڋf{-ݸi^DyӼ6ڌ8vޮ)K&tX$GIeFS$S""i xku0c#0S#j0`s 9qܛfQ&4"a  a_n".2,h`aņ_IWNW[>O^۵ܞ컵_ݵn,6#P!SA9_܅q4냉3,vJN JBL;*,A Az*&9+Gɟa15yvNFHQ6 W4@aLaO@*( -9/U{- FB>1::Jt$kA @+JXqi3q r xH@Zd` A@ :5wޞj?{c)`sJuRwOvroL)z|bԻ#_G,z%E7V L?=ZB,׏0ؒpjLS' &PP0!O\,\m͐Ldżn,T4:bF C I;D0B DA2yD+/+kĩ*q fld!~OTZ[Rbknf،yw7 p䂡RL:uՏR.)TFcj4aX0spV7 00 K0 G0)D0`k1z  K0#E,S $PY#Ϣ #dwo32 j#3,no.vi;9jZ&Lv dᄆ6}*ILLCԔ#6;V-9TVcNjdGB0S00P cD@0S0!;S @.VP(NaD $| T0z aIk}N:jyjD%#CF :{2y~gs72J̤PLn{*Pc[T:+ KZX˫9CFk-?,qKe~8D?jh1 fgn̦ ` Edav]%aB Fc&7B1)0HM"Qjsk/?OYTv)MG,mQd2TZն/umm`rؚ"Zcod:P9-)G$3'⧋)LpǐImBq9'EaU!GAy}080$07 r0e8 0D q6! P ܘVw L @ L& z ]Β_E +B 2;*3*?d(uyՔA*fE5UhRk=~_iCzȣisJ9& -i#e}CH$*VOX&pK\C?u Q|( ᪒V;oQ Gل9%0 h@f@bf $^n ,t0Hvj ?_ș,qIFa+l‡:?/)2k5kˍȵ.{˅D+,9D hhKSҕ̎Vlan CJ lv%"0[orz s+0$3Q c5~69ذKX $ \  AKO#nCaڦTdUgfwuS=fvfo9jkP}><}_:DњRYG)[+mo46mi!ьCsE%e띝;ࡥp' PԘ1ǘwH8RQ)SB@! )ȡ( r|( Ԏ9s#>m#HL8fsD0 dtdH n.ۿ2V]Fx&;i[SBφi͞* B Y˅A)E b00E C\0\ X@eRƘ$L(d X2!P0Ҩ1.DD $ 2> *z1̌a%19/?x %xqq&`nF=Cdm(θ[^{ua#F%O+Vei yB'Wa`1AW1*) faTb!` c @bP} ⷃ6c_JHR*NCB :"Y3k'L[Y٫q3 ~ѕRBgMkЯZR"̼S|04P|K fp>O} (!Ҧ  8 N`2Cfa `RbT-& &``31Y F R>0 b@7t8 f*2efssT:T1`ɮ$3k\՛?tk[K/;?ḳf DcəBEȎT0eӡo2M5tqq005OC0, L1 p>0&#HC0 HU ŀ0 *D€,r4 VdkW4c*GfeE!Lc@,KQ𮈴ZPQ' "$˪(0 .a]X4-IC sbH\dQ),D 4`OD7$n +B 2\w"$z= OC2RB+%J_~w-o N"ڵ4puI] t飢ᒄEAy\$pSsԀ}'hhg`]H\_ LTJL)@P` dL#@L Vzs"EN"$9ECxxh!i+I L0(DqɅ3ET~`˜N`2"lj`vP 0#C00 >4c$s<020`0Œ >%x(jK:fG+5ȎCUbŚTVv"UM6^3gYyNs:$%jYB~6y`pUՓ2~9T,Nx6t/BP B4E20rٍjـ@. `:#(4HdL-@ P Unf {3JM?b?ؚ32TSb$) l!9byˀͣl_5}1bqWe7YyW&IڱZXQ^5<~W`'.Ywx.dz lDLuLt!C,S:"p +J\($Xk4 @tĤ="xa꘠ @\* ud(uFD"UwC-̙PbQ5N{5~/-0ԾUJgf L`ؾ@ Q-1>R? Oh˩Ĥx3fx XAD`B(P, V``f` dh@```F`.ɀ $(X0Pk bH.HVB5~Ui;~i;^{m"j,٢4>1FKʋI!fZx-8~rk}-@$O|X Т~0C0Q ciT+0 c (ÄyMSGL8Й @asr"!ilA-w A2׿#9HA Aq5"L3M%W%r|TךUwN1/;Cާr k$It/a;V/ԙEt|P+ӊ3 TXG !}' !A#FYp8p~s0V$1PJ,54֮MBc)2;2*,cc8l j̶=?cnq۫uŌlzWoVc ӶNS | =Dž:jO咿9FT,*Cجp<!`bpT#0`0D>!0^C!0!X{P_CpBHn_ڲ  B 7{_vES0$Vum[n3!4RN.g49- 垆A+ JBFD@!8>CYP ij+8ɁYLap S  1@7zH(h's5OwUg #3wZ9b8Cȟz֎?󓶷C:|nϚ}}Cp4P/q.Z24 #֍6ďX+Eb;%&LĒ4 actPZO -V(P$a;HL&ǤGLg(Ł4\/ā`4 ~YlD@8 772}kseb7u] Le#*|䩳sԝe^Ю~Ϝj-=ag\arYeb \!B;bi 'e %aTrÞ#`0Qf09i@%>i`  È[HC80hLjP VlvfG5md9N +=6Կzfk{f:{Nq`ھW(WW-n[#9 0NCvSL2Tr Hb{Qe] X!)D` (Q0тP 3 `A 0]Ya@"+P A>^3-.j&F>9Z2\̏e)_yO3U]n&ul2Ƞ:9FKWQFp46! s[{f+N^ej*ږ~ 9w!v*aeS5GOZtu%bJgtx5aQ K"Y p| `(ݷj(@09OR; ` 4CWHb`0A\` iya 23(C;"9.u#l4`?pO,o󓳷3KzVW9eʕ/6 4'|..!B!?D#u#Ci\8=#8)0\_C2F NmDB`J`& a8Ɖz`fa/f *!x`d*.(bgro۴b323"HBgƣw$#"2jg=мctիXqx M ߯CpYhĞz+ҩ#b)vG7e!|l-W6w: \j**p>  90L!< Fez`f.pb``.0BCXh;Ѩ$uTdIPYWKB >oEs̖uu\VKfܵ Eѿ}}/M:ik4~Ͷ %f}: Fi"T 2mV^fVF"GJ$J.uP!Ln0a P0 .d.0<300n3Aq3c--q9@ +^ej :D9G^ZyaٕPffHَ?&ֶ4֚urx)stиל=Y|0 ^DS <@5H\zd}ڀU$ {0b(ɥL('P``EƠ`~b #c2y=#n[pЬ667"{l=f8*鄝k{;EJĢ\P ݞHkP.u3(+&jJN 9%~r ңΥ 4` @_g8pb8 [zfίEuKPcDFr1lGMGF~߽{m̹|huCǧ )WMCv5KFo+Q1x;n9BJYҕrYQHI@.&\`D0&@T0Fs3C#S AY09W !'=*#ڞ o >ndNʒ-JS^gcZtEBFYϩIz֔w,Ԥxke;48:W<5gƓpR²I EZ?%h|ĊQ:C|90Ǒ܌"pL"/2]n5 1 91g?Aj10l*1r0"Z$ذBPqgYF9|jRDWcKD cYÝR:]ʽt!OopR%I`=d^<#گzϠ+G5 *Sls1=%[^Qc8F2q>g(.*+ p 髙2^n)D"B[!]LK9Ulu1C gl^)=j ":ym浧єvS̴[DbTu3tRb!R\M35!&z~Өj5s۪ @™L!`+"pM q˜!h@ p`-03jTr[2 .>onϼAՙTGB2r9j(S2dUMgy1I ^ .tG]7.89fFDc^B].Kzx4 !KqȖFSNު~o[2 |6c5<0(BW(   bR]e@N&kT+J΁,GFPlH&;%8wusLS?}\~fy[6iOcHQdOqbAYa$v6^vaItV% :Њ+@`6&ј#8`AH@`B-&U`ra8aXB(1an[2 A2WҎfu; ܃#;I;t9m,˓}V~˚j`mS[rxO T E1$Jlj2Ј,gw` 0CT,\  XLϨN\\"l԰ 6hq:*;_}6ΫdGM0`DSskD'_S}p=-t,XF{$HOC?`kgV-K&"2|t,,tdڈIsJ1|"Nho* Kˠ^1WK .K,a|3b f;L BHL"b 񨲝Qg=Z  Y A>3:f:"!њ+ 0y-ԮrSvu|!9E81(qJ0mX'96VbaHTlxċ2@m%( fGjcDix aHc$z*b Jq$]״IoK#v:QDc dSfVE)_jWJofΫM5>/j]5BJ1cTaQ\z6.b,B ]VnP7qxE.GJR~BcW}*0P$`d @2`R1fW#0`v`)f $Á{=bHkJ[G*e;T 7! >hB%`fTQe0&bdVR{om<}_ekx56X)cB,rTH6iƟʞUhQClo(Ψ(D!5PӜec,:N-yPB0O]x?:F&. + 1!H1xJHIފUO iwJz+u %єrb#KR"/2z_1/hQH ]h_=ZWjKgSJU id-iڻ)P_ove+Vّ փ|PMiiVؾH: l` 09*o`h)FH$  S4MAf CjA $$tq B >ۙMcj(KVKYT.jj#w\vغ>PWoq癈eSSU'%7Plh~xO+? 52, AH~>pYG@`B ,* dhPp 6`V ae` `f`r@ 6귃׼YJ%_F1{dF9*$o5 ɵ[WlP&h*P3 aj7DiS,v*ۚ OU+*X1>Ukj$ttdṰoj00&'@D& Ʊ !u ?ÛK_cv/;-  A>tPv+&ykԙd쿢+5g:aڽ)keSbF{T.etiG[ӍGz-BЇ9/\)DjDC $RH0`<31+0D2 04T1H#1>@1hFVă_ "IZNۯu}K#JCJ9C Ff8H*b*ýiofFc2^#oJ-egz%2Fķ . i GE}]! nP1TG3HA_ͤ9Fým QBp`0%}0(#P0Z3 Ch#a[F@ n_٪Wn6AA>5uRU)dPٌU`NʮrJ4ʕ* K浇Y˽2t[ۙ(=ے *rԫ}fUB H>ctxkdAe^Jj % F=|)6.Ť@#HGF08=ʀ=.UKJFCqB$!UaJQ%:0qM`Q*E{RKUgֵMf@&fxFrsqT=ٚ^S߯y(:vd[ǭD&,| ]4ÞPP`:mjD`[aᑡ 'a|__  %e1 d} $z(9K[<SرOظd~%V>BQ8tJZ?*V:Y^)VO: 3cAc#01,4 , %< [wZ#P:bTDv#PuoR3,vؗTF;ʾjdx_S-tI}bSa@EEUI,?>Mز4eB ‖P/2,O4 zaO! EL<`0U?L>C@ܰP<){5ӷwt3  >AԬtBB5є4[:A Ur)}j/kq=g mC͙yPfqP^XBU,hJΔRBMuDX D)#M˹ܲppF(Np >4~f+) 3JJ*yoE_tTtd=be:D0$)kb!4[lObof|2F%~[aEyc2DtZ|2i:C2% pcRlkI#\Iж)\hsiCbha3Sj6Z Pl/ q 8P1@ˆ|uȁ`$8L!Yز*AA>{3ڽxE`ְR:6-؞ ut}мG{u{Zxq,X^qHI+"} FOQ%)Z+9+ &5R|GS't&"Ƒ\,vX A)d(\:L@`p`*Ch`YPk.`BFPerf`A  {qhA;03\ȿWS"*;51YI81Y]U׻~\]ұm<]>{VzG|F2Y+zng) sM D5z܇9- jdSZڋ У>ھ8tb :T]BPHVI(9QJ*#usUz2Hۚzg_hLF(7]ez)nU%|G*nU!-Ywi.apJETZz*,NMi Q#.ACsƻ#s@ @tTb :c{Xb.`dl<;DHX5i:=Rẇ:UBG*VQ-GrʇMY3Ei퇺- X/s20@e{u#!*|Q7"vF4KvG/j7'2o3C[@`t,h NJ2BAɊ0)1t@e ` \tr !klm{:r#:2>ewct2D;-+5du9RX2ѱV+ySǙYYu jT)*,8WJRp-̾_v!h 0L:,0%0(1&3 01T;1,CC ꈔET8i!wj۩I3XXE3! (} pG\39t|;EhwxkSe?Kv^"#Jt莾zҭ<\31I+Q q8 r;#o*]" A*B48L  %f1"`&0! dj~%< 6.AA>v>W"hʪe2 A) ġ\^Y5k+i\"xSާo|ᗌzN^jo{28ʇ'YȲl-*Q >:@i9@ɂ@ é`@]rÄ@ۤɽn2NknV1ɫ2^Vӽ溯{Q)Pj q.`p[wJxJ櫝lFP9q292!a k4x_LbjDP+s;A CZs61P(.Wd c S?> A>eZèATs=G<>j+|g&ff,Tz>\w. ?T+Q&% mJ+%SHN+No˱Z r`$0°Q130D1;#g"Ÿ_G~ُ[n"Z3vPa)qpwQvA(CwEd=9$:;3,Q*xP]QY\SI.7mycԔ#&l^Vu[r b4Dad.Ha̕=&X %E .6V'&NsZZE`QsEa@) G`F@#pHCUNR/}I)zȨ$I+WtusIV"3%s,'-o%X 1*Tb[dZ|; bEjvN FYNg3\ІŁF*D)VxsHg&&+Pa}b$ R|a($@Y  yT2քk?خA>V S)tfتj7T͢:d*wC'9ϑsB:P/Vt41NF#$r'J&Lgŀky#-X+T04Pc,HQ,&p@52(mؖ- Aa5쌨0ʀY1DCn6ۯOff OZh梞g#qqoH^/:\%@u\CE #21(ØBSY^OcBs2d 4VQ"lD.) X0`A"?Ϊֳ* )ՐE̢Et3DDp#+> &$0 c)Hֿ3bS;u_3јrhnQtDsjmQ܁I,E ?#M6buVΛLwcB9PG`%C\j!|peL+S!;OvfŽ{GZpBLW5%)fT>:E^` #X 7 SH4aQ'X#JI}t 2K4a0-LL\KL%IIClb,A"eU- kb9]cUWnB A>$A)| UBHTF-6bFCY%YKud%yw9>˜hcʄ73!V*Ɍ")cTz z)M%WK "tBJb%*>Ǡ6+H* Y02Tbpc 5MF qx_CKy5,ϗz̲!nEI洊V饙paeJ1bQ#)HTm&HiCG  (+X,+e`,(@+0X_RPBd,"|J X&&'oZf;UF4fUp6,8 @b|@(ő@txH٨跠IV$>G .vg"0ED/ "Հ.4UfF[@WKE3u[nK8MPqz5Hgn=N.YNOd,-ǪCJ3:\sq|¸[CZT*f7%ҹigpV/ZYSJVfn,MqX(Z{#{fe+ťkS#NJX3KGLJ 7kBYܽ.LJ#a''a__pLIM(U%vtu;nر??:BO6LAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU2 :+[CpgJڑqCd),u⑹@|ϊ%RI4,T> endobj % 'F1': class PDFType1Font 2 0 obj % Font Helvetica << /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >> endobj % 'F2': class PDFType1Font 3 0 obj % Font Helvetica-Bold << /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font >> endobj % 'Page1': class PDFPage 4 0 obj % Page dictionary << /Contents 8 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 7 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >> /Type /Page >> endobj % 'R5': class PDFCatalog 5 0 obj % Document Root << /Outlines 9 0 R /PageLabels 10 0 R /PageMode /UseNone /Pages 7 0 R /Type /Catalog >> endobj % 'R6': class PDFInfo 6 0 obj << /Author () /CreationDate (D:20140316094828-01'00') /Creator (\(unspecified\)) /Keywords () /Producer (ReportLab PDF Library - www.reportlab.com) /Subject (\(unspecified\)) /Title (This is just a test) >> endobj % 'R7': class PDFPages 7 0 obj % page tree << /Count 1 /Kids [ 4 0 R ] /Type /Pages >> endobj % 'R8': class PDFStream 8 0 obj % page stream << /Length 318 >> stream 1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET q 1 0 0 1 62.69291 741.0236 cm q 0 0 0 rg BT 1 0 0 1 0 4 Tm /F2 20 Tf 24 TL 153.8049 0 Td (This is just a test) Tj T* -153.8049 0 Td ET Q Q q 1 0 0 1 62.69291 719.0236 cm q BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (Nothing to see here) Tj (\205) Tj T* ET Q Q endstream endobj % 'R9': class PDFOutlines 9 0 obj << /Count 0 /Type /Outlines >> endobj % 'R10': class PDFPageLabels 10 0 obj % Document Root << /Nums [ 0 11 0 R ] >> endobj % 'R11': class PDFPageLabel 11 0 obj % None << /S /D /St 1 >> endobj xref 0 12 0000000000 65535 f 0000000113 00000 n 0000000221 00000 n 0000000386 00000 n 0000000559 00000 n 0000000836 00000 n 0000000991 00000 n 0000001253 00000 n 0000001358 00000 n 0000001774 00000 n 0000001856 00000 n 0000001949 00000 n trailer << /ID % ReportLab generated PDF document -- digest (http://www.reportlab.com) [(\344u\243;K\323\216{\012\250C\350\327\255Xt) (\344u\243;K\323\216{\012\250C\350\327\255Xt)] /Info 6 0 R /Root 5 0 R /Size 12 >> startxref 1996 %%EOF python-rtmidi-1.1.0/examples/midi2command/README.rst000066400000000000000000000127461307643736300222160ustar00rootroot00000000000000midi2command ============ The ``midi2command`` example script shows how you can use ``python-rtmidi`` to receive MIDI messages and execute different external programs and scripts when a MIDI message is received. A configuration file defines which program is called depending on the type and data of the received message. Usage ----- Simply call the ``midi2command.py`` script with the name of a configuration file as the first and sole positional argument, i.e. :: python midi2command.py example.cfg You can optionally specify the MIDI input port on which the script should listen for incoming messages with the ``-p`` option (or the ``--port`` long form). The port may be specified as an integer or a (case-sensitive) substring of the port name. In the latter case either the first matching port is used, or, if no matching port is found, a list of available input ports is printed and the user is prompted to select one. If no port is specified, ``midi2command`` opens a virtual MIDI input port. For systems where several MIDI backend API are available (i.e. ALSA and JACK on Linux or CoreMIDI and JACK on OS X), you can select the backend to use with the ``-b`` (or ``--backend``) option. The available backends are: * alsa (Linux) * coremidi (OS X) * jack (Linux, OS X) * windowsmm (Windows) If you do not specify a backend or one which is not available on the system, the first backend which has any input ports available will be used. Here's the full synopsis of the command:: usage: midi2command.py [-h] [-b {alsa,coremidi,jack,windowsmm}] [-p PORT] [-v] CONFIG Execute external commands when specific MIDI messages are received. positional arguments: CONFIG Configuration file in YAML syntax. optional arguments: -h, --help show this help message and exit -b {alsa,coremidi,jack,windowsmm}, --backend {alsa,coremidi,jack,windowsmm} MIDI backend API (default: OS dependant) -p PORT, --port PORT MIDI input port name or number (default: open virtual input) -v, --verbose verbose output Configuration ------------- The script takes the name of a configuration file in YAML_ syntax as the first and only positional argument. The configuration consist of a list of mappings, where each mapping defines one command. Here is a simple example configuration defining two commands. The description of each command explains what it does:: - name: My Backingtracks description: Play audio file (from current directory) with filename matching -playback.mp3 when program change on channel 16 is received status: programchange channel: 16 command: plaympeg %(data1)03i-playback.mp3 - name: My Lead Sheets description: Open PDF file (from current directory) with filename matching -sheet.pdf when control change 14 on channel 16 is received status: controllerchange channel: 16 data: 14 command: evince %(data2)03i-sheet.pdf The value of the ``status`` key is matched against the status byte of the incoming MIDI message. The ``status`` key is required and may have one of the following values: * noteon * noteoff * programchange * controllerchange * pitchbend * polypressure * channelpressure The value of ``data`` key is matched against the data bytes of the incoming MIDI message. It may be a single integer or a list or tuple with one or two integer items. If ``data`` is not present or empty, the configuration matches against any incoming MIDI message with a matching status byte. If ``data`` is a single integer or a list or tuple with only one item, only the first data byte of the MIDI message must match. The command to execute when a matching MIDI message is received is specified with the value of the ``command`` key. This should be the full command line with all needed options and arguments to the external program. The command line is parsed into tokens by `shlex.split()`_ and then passed to `subprocess.Popen()`_. The command line is not passed to a shell, so it is not supported to prepend environment variable assignments or use any shell variable substitutions or similar. If you need this functionality, just use a wrapper shell or batch script for your external program. The program must be found on your ``PATH`` or you need to specify the absolute or relative path to the executable. The command will be executed in the current working directory, i.e. the one you started ``midi2command`` in. You can put some placeholders into the command string, which will be replaced with values from the MIDI message which triggered the command. The placeholders must adhere to the `Python string formatting`_ syntax and use the dictionary style, e.g. ``%(status)02X`` would be replaced with the value of the MIDI status byte in hexadecimal notation (with upper case letter ciphers) and zero padded to two ciphers. These are the valid placeholder keys, all corresponding values being integers or ``None`` if not present: * status * channel (1-based) * data1 (``None`` if MIDI message has only one byte) * data2 (``None`` if MIDI message has only one or two bytes) .. _yaml: http://www.yaml.org/spec/1.2/spec.html .. _shlex.split(): https://docs.python.org/2/library/shlex.html#shlex.split .. _subprocess.popen(): https://docs.python.org/2/library/subprocess.html#subprocess.Popen .. _python string formatting: https://docs.python.org/2/library/stdtypes.html#string-formatting-operations python-rtmidi-1.1.0/examples/midi2command/example.cfg000066400000000000000000000010121307643736300226230ustar00rootroot00000000000000# example configuration for the midi2command.py script - name: My Backingtracks description: Play audio file with filename matching -playback.mp3 when program change on channel 16 is received status: programchange channel: 16 command: plaympeg %(data1)03i-playback.mp3 - name: My Lead Sheets description: Open PDF with filename matching -sheet.pdf when control change 14 on channel 16 is received status: controllerchange channel: 16 data: 14 command: evince %(data2)03i-sheet.pdf python-rtmidi-1.1.0/examples/midi2command/midi2command.py000066400000000000000000000157621307643736300234450ustar00rootroot00000000000000#!/usr/bin/env python # # midi2command.py # """Execute external commands when specific MIDI messages are received. Example configuration (in YAML syntax):: - name: My Backingtracks description: Play audio file with filename matching -playback.mp3 when program change on channel 16 is received status: programchange channel: 16 command: plaympeg %(data1)03i-playback.mp3 - name: My Lead Sheets description: Open PDF with filename matching -sheet.pdf when control change 14 on channel 16 is received status: controllerchange channel: 16 data: 14 command: evince %(data2)03i-sheet.pdf """ import argparse import logging import shlex import subprocess import sys import time from os.path import exists try: from functools import lru_cache except ImportError: # Python < 3.2 try: from backports.functools_lru_cache import lru_cache except ImportError: lru_cache = lambda: lambda func: func import yaml import rtmidi from rtmidi.midiutil import open_midiinput from rtmidi.midiconstants import (CHANNEL_PRESSURE, CONTROLLER_CHANGE, NOTE_ON, NOTE_OFF, PITCH_BEND, POLY_PRESSURE, PROGRAM_CHANGE) log = logging.getLogger('midi2command') BACKEND_MAP = { 'alsa': rtmidi.API_LINUX_ALSA, 'jack': rtmidi.API_UNIX_JACK, 'coremidi': rtmidi.API_MACOSX_CORE, 'windowsmm': rtmidi.API_WINDOWS_MM } STATUS_MAP = { 'noteon': NOTE_ON, 'noteoff': NOTE_OFF, 'programchange': PROGRAM_CHANGE, 'controllerchange': CONTROLLER_CHANGE, 'pitchbend': PITCH_BEND, 'polypressure': POLY_PRESSURE, 'channelpressure': CHANNEL_PRESSURE } class Command(object): def __init__(self, name='', description='', status=0xB0, channel=None, data=None, command=None): self.name = name self.description = description self.status = status self.channel = channel self.command = command if data is None or isinstance(data, int): self.data = data elif hasattr(data, 'split'): self.data = map(int, data.split()) else: raise TypeError("Could not parse 'data' field.") class MidiInputHandler(object): def __init__(self, port, config): self.port = port self._wallclock = time.time() self.commands = dict() self.load_config(config) def __call__(self, event, data=None): event, deltatime = event self._wallclock += deltatime if event[0] < 0xF0: channel = (event[0] & 0xF) + 1 status = event[0] & 0xF0 else: status = event[0] channel = None data1 = data2 = None num_bytes = len(event) if num_bytes >= 2: data1 = event[1] if num_bytes >= 3: data2 = event[2] log.debug("[%s] @%i CH:%2s %02X %s %s", self.port, self._wallclock, channel or '-', status, data1, data2 or '') # Look for matching command definitions cmd = self.lookup_command(status, channel, data1, data2) if cmd: cmdline = cmd.command % dict( channel=channel, data1=data1, data2=data2, status=status) self.do_command(cmdline) @lru_cache() def lookup_command(self, status, channel, data1, data2): for cmd in self.commands.get(status, []): if channel is not None and cmd.channel != channel: continue if (data1 is None and data2 is None) or cmd.data is None: return cmd elif isinstance(cmd.data, int) and cmd.data == data1: return cmd elif (isinstance(cmd.data, (list, tuple)) and cmd.data[0] == data1 and cmd.data[1] == data2): return cmd def do_command(self, cmdline): log.info("Calling external command: %s", cmdline) try: args = shlex.split(cmdline) subprocess.Popen(args) except: log.exception("Error calling external command.") def load_config(self, filename): if not exists(filename): raise IOError("Config file not found: %s" % filename) with open(filename) as patch: data = yaml.load(patch) for cmdspec in data: try: if isinstance(cmdspec, dict) and 'command' in cmdspec: cmd = Command(**cmdspec) elif len(cmdspec) >= 2: cmd = Command(*cmdspec) except (TypeError, ValueError) as exc: log.debug(cmdspec) raise IOError("Invalid command specification: %s" % exc) else: status = STATUS_MAP.get(cmd.status.strip().lower()) if status is None: try: int(cmd.status) except: log.error("Unknown status '%s'. Ignoring command", cmd.status) log.debug("Config: %s\n%s\n", cmd.name, cmd.description) self.commands.setdefault(status, []).append(cmd) def main(args=None): """Main program function. Parses command line (parsed via ``args`` or from ``sys.argv``), detects and optionally lists MIDI input ports, opens given MIDI input port, and attaches MIDI input handler object. """ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) padd = parser.add_argument padd('-b', '--backend', choices=sorted(BACKEND_MAP), help='MIDI backend API (default: OS dependant)') padd('-p', '--port', help='MIDI input port name or number (default: open virtual input)') padd('-v', '--verbose', action="store_true", help='verbose output') padd(dest='config', metavar="CONFIG", help='Configuration file in YAML syntax.') args = parser.parse_args(args if args is not None else sys.argv[1:]) logging.basicConfig(format="%(name)s: %(levelname)s - %(message)s", level=logging.DEBUG if args.verbose else logging.INFO) try: midiin, port_name = open_midiinput( args.port, use_virtual=True, api=BACKEND_MAP.get(args.backend, rtmidi.API_UNSPECIFIED), client_name='midi2command', port_name='MIDI input') except (IOError, ValueError) as exc: return "Could not open MIDI input: %s" % exc except (EOFError, KeyboardInterrupt): return log.debug("Attaching MIDI input callback handler.") midiin.set_callback(MidiInputHandler(port_name, args.config)) log.info("Entering main loop. Press Control-C to exit.") try: # just wait for keyboard interrupt in main thread while True: time.sleep(1) except KeyboardInterrupt: print('') finally: midiin.close_port() del midiin if __name__ == '__main__': sys.exit(main(sys.argv[1:]) or 0) python-rtmidi-1.1.0/examples/midifilter/000077500000000000000000000000001307643736300203025ustar00rootroot00000000000000python-rtmidi-1.1.0/examples/midifilter/__init__.py000066400000000000000000000000001307643736300224010ustar00rootroot00000000000000python-rtmidi-1.1.0/examples/midifilter/__main__.py000066400000000000000000000071741307643736300224050ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # midifilter.py # """Simple MIDI filter / processor.""" from __future__ import absolute_import import argparse import logging import sys import threading import time try: import Queue as queue except ImportError: # Python 3 import queue from rtmidi.midiutil import open_midiport from .filters import MapControllerValue, MonoPressureToCC, Transpose log = logging.getLogger("midifilter") class MidiDispatcher(threading.Thread): def __init__(self, midiin, midiout, *filters): super(MidiDispatcher, self).__init__() self.midiin = midiin self.midiout = midiout self.filters = filters self._wallclock = time.time() self.queue = queue.Queue() def __call__(self, event, data=None): message, deltatime = event self._wallclock += deltatime log.debug("IN: @%0.6f %r", self._wallclock, message) self.queue.put((message, self._wallclock)) def run(self): log.debug("Attaching MIDI input callback handler.") self.midiin.set_callback(self) while True: event = self.queue.get() if event is None: break events = [event] for filter_ in self.filters: events = list(filter_.process(events)) for event in events: log.debug("Out: @%0.6f %r", event[1], event[0]) self.midiout.send_message(event[0]) def stop(self): self.queue.put(None) def main(args=None): parser = argparse.ArgumentParser(prog='midifilter', description=__doc__) padd = parser.add_argument padd('-m', '--mpresstocc', action="store_true", help='Map mono pressure (channel aftertouch) to CC') padd('-r', '--mapccrange', action="store_true", help='Map controller value range to min/max value range') padd('-t', '--transpose', action="store_true", help='Transpose note on/off event note values') padd('-i', '--inport', help='MIDI input port number (default: ask)') padd('-o', '--outport', help='MIDI output port number (default: ask)') padd('-v', '--verbose', action="store_true", help='verbose output') padd('filterargs', nargs="*", type=int, help='MIDI filter argument(s)') args = parser.parse_args(args if args is not None else sys.argv[1:]) logging.basicConfig(format="%(name)s: %(levelname)s - %(message)s", level=logging.DEBUG if args.verbose else logging.INFO) try: midiin, inport_name = open_midiport(args.inport, "input") midiout, outport_name = open_midiport(args.outport, "output") except IOError as exc: print(exc) return 1 except (EOFError, KeyboardInterrupt): return 0 filters = [] #filters = [CCToBankChange(cc=99, channel=15, msb=0, lsb=1, program=99)] if args.transpose: filters.append(Transpose(transpose=args.filterargs[0])) if args.mpresstocc: filters.append(MonoPressureToCC(cc=args.filterargs[0])) if args.mapccrange: filters.append(MapControllerValue(*args.filterargs)) dispatcher = MidiDispatcher(midiin, midiout, *filters) print("Entering main loop. Press Control-C to exit.") try: dispatcher.start() while True: time.sleep(1) except KeyboardInterrupt: dispatcher.stop() dispatcher.join() print('') finally: print("Exit.") midiin.close_port() midiout.close_port() del midiin del midiout return 0 if __name__ == '__main__': sys.exit(main(sys.argv[1:]) or 0) python-rtmidi-1.1.0/examples/midifilter/filters.py000066400000000000000000000060541307643736300223310ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # midifilter.filters.py # """Collection MIDI filter classes.""" from rtmidi.midiconstants import (BANK_SELECT_LSB, BANK_SELECT_MSB, CHANNEL_PRESSURE, CONTROLLER_CHANGE, NOTE_ON, NOTE_OFF, PROGRAM_CHANGE) __all__ = ( 'CCToBankChange', 'MapControllerValue', 'MidiFilter', 'MonoPressureToCC', 'Transpose', ) class MidiFilter(object): """ABC for midi filters.""" event_types = () def __init__(self, *args, **kwargs): self.args = args self.__dict__.update(kwargs) def process(self, events): """Process incoming events. Receives a list of MIDI event tuples (message, timestamp). Must return an iterable of event tuples. """ raise NotImplementedError("Abstract method 'process()'.") def match(self, msg): return msg[0] & 0xF0 in self.event_types class Transpose(MidiFilter): """Transpose note on/off events.""" event_types = (NOTE_ON, NOTE_OFF) def process(self, events): for msg, timestamp in events: if self.match(msg): msg[1] = max(0, min(127, msg[1] + self.transpose)) & 0x7F yield msg, timestamp class MapControllerValue(MidiFilter): """Map controller values to min/max range.""" event_types = (CONTROLLER_CHANGE,) def __init__(self, cc, min_, max_, *args, **kwargs): super(MapControllerValue, self).__init__(*args, **kwargs) self.cc = cc self.min = min_ self.max = max_ def process(self, events): for msg, timestamp in events: # check controller number if self.match(msg) and msg[1] == self.cc: # map controller value msg[2] = int(self._map(msg[2])) yield msg, timestamp def _map(self, value): return value * (self.max - self.min) / 127. + self.min class MonoPressureToCC(MidiFilter): """Change mono pressure events into controller change events.""" event_types = (CHANNEL_PRESSURE,) def process(self, events): for msg, timestamp in events: if self.match(msg): channel = msg[0] & 0xF msg = [CONTROLLER_CHANGE | channel, self.cc, msg[1]] yield msg, timestamp class CCToBankChange(MidiFilter): """Map controller change to a bank select, program change sequence.""" event_types = (CONTROLLER_CHANGE,) def process(self, events): for msg, timestamp in events: channel = msg[0] & 0xF # check controller number & channel if (self.match(msg) and channel == self.channel and msg[1] == self.cc): msb = [CONTROLLER_CHANGE + channel, BANK_SELECT_MSB, self.msb] lsb = [CONTROLLER_CHANGE + channel, BANK_SELECT_LSB, self.lsb] pc = [PROGRAM_CHANGE + channel, self.program] yield msb, timestamp yield lsb, timestamp yield pc, timestamp else: yield msg, timestamp python-rtmidi-1.1.0/examples/sendsysex.py000077500000000000000000000125511307643736300205600ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # sendsysex.py # """Send all system exclusive files given on the command line. The paths given on the command line can also contain directories and all files with a *.syx extension in them will be sent (in alphabetical order). All consecutive sysex messages in each file will be sent to the chosen MIDI output, after confirmation (which can be turned off). """ import argparse import logging import os import sys import time from os.path import basename, exists, isdir, join import rtmidi try: raw_input except NameError: # Python 3 raw_input = input __program__ = 'sendsysex.py' __version__ = '1.1' __author__ = 'Christopher Arndt' __date__ = '$Date$' log = logging.getLogger("sendsysex") SYSTEM_EXCLUSIVE = b'\xF0' END_OF_EXCLUSIVE = b'\xF7' def send_sysex_file(filename, midiout, portname, prompt=True, delay=50): """Send contents of sysex file to given MIDI output. Reads file given by filename and sends all consecutive sysex messages found in it to given midiout after prompt. """ bn = basename(filename) with open(filename, 'rb') as sysex_file: data = sysex_file.read() if data.startswith(SYSTEM_EXCLUSIVE): try: if prompt: yn = raw_input("Send '%s' to %s (y/N)? " % (bn, portname)) except (EOFError, KeyboardInterrupt): print('') raise StopIteration if not prompt or yn.lower() in ('y', 'yes'): sox = 0 i = 0 while sox >= 0: sox = data.find(SYSTEM_EXCLUSIVE, sox) if sox >= 0: eox = data.find(END_OF_EXCLUSIVE, sox) if eox >= 0: sysex_msg = data[sox:eox + 1] # Python 2: convert data into list of integers if isinstance(sysex_msg, str): sysex_msg = [ord(c) for c in sysex_msg] log.info("Sending '%s' message #%03i...", bn, i) midiout.send_message(sysex_msg) time.sleep(0.001 * delay) i += 1 else: break sox = eox + 1 else: log.warning("File '%s' does not start with a sysex message.", bn) def main(args=None): """Main program function. Parses command line (parsed via ``args`` or from ``sys.argv``), detects and optionally lists MIDI output ports, opens given MIDI output port, assembles list of sysex files and calls ``send_sysex_file`` on each of them. """ parser = argparse.ArgumentParser(description=__doc__) padd = parser.add_argument padd(dest='sysexfiles', nargs="*", metavar="SYSEX", help='MIDI system exclusive files or directories to send.') padd('-l', '--list-ports', action="store_true", help='list available MIDI output ports') padd('-p', '--port', dest='port', default=0, type=int, help='MIDI output port number (default: %(default)s)') padd('-d', '--delay', default="50", metavar="MS", type=int, help='delay between sending each Sysex message in milliseconds ' '(default: %(default)s)') padd('-y', '--no-prompt', dest='prompt', action="store_false", help='do not ask for confirmation before sending') padd('-v', '--verbose', action="store_true", help='verbose output') args = parser.parse_args(args if args is not None else sys.argv[1:]) logging.basicConfig(format="%(name)s: %(levelname)s - %(message)s", level=logging.DEBUG if args.verbose else logging.INFO) try: midiout = rtmidi.MidiOut() ports = midiout.get_ports() if ports: if args.list_ports: for i, port in enumerate(ports): print("%i: %s" % (i, port)) return 0 if args.port < len(ports): midiout.open_port(args.port) portname = midiout.get_port_name(args.port) else: log.error("MIDI port number out of range.") log.error("Use '-l' option to list MIDI ports.") return 2 else: log.error("No MIDI output ports found.") return 1 files = [] for path in args.sysexfiles or [os.curdir]: if isdir(path): files.extend(sorted([join(path, fn) for fn in os.listdir(path) if fn.lower().endswith('.syx')])) elif exists(path): files.append(path) else: log.error("File '%s' not found.") if not files: log.warning("No sysex (.syx) files found in given directories or " "working directory.") for filename in files: try: send_sysex_file( filename, midiout, portname, args.prompt, args.delay) except StopIteration: break except Exception as exc: log.error("Error while sending file '%s': %s", (filename, exc)) finally: midiout.close_port() del midiout return 0 if __name__ == '__main__': sys.exit(main(sys.argv[1:]) or 0) python-rtmidi-1.1.0/examples/sequencer/000077500000000000000000000000001307643736300201445ustar00rootroot00000000000000python-rtmidi-1.1.0/examples/sequencer/sequencer.py000066400000000000000000000157151307643736300225210ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # sequencer.py # """Example of using a thread to send out queued-up, timed MIDI messages.""" import logging import threading import time from collections import deque from heapq import heappush, heappop try: range = xrange # noqa except NameError: pass log = logging.getLogger(__name__) class MidiEvent(object): """Container for a MIDI message and a timing tick. Bascially like a two-item named tuple, but we overwrite the comparison operators, so that they (execpt testing for equality) use only on the timing ticks. """ __slots__ = ('tick', 'message') def __init__(self, tick, message): self.tick = tick self.message = message def __repr__(self): return "@ %05i %r" % (self.tick, self.message) def __eq__(self, other): return (self.tick == other.tick and self.message == other.message) def __lt__(self, other): return self.tick < other.tick def __le__(self, other): return self.tick <= other.tick def __gt__(self, other): return self.tick > other.tick def __ge__(self, other): return self.tick >= other.tick class SequencerThread(threading.Thread): def __init__(self, midiout, queue=None, bpm=120.0, ppqn=480): super(SequencerThread, self).__init__() # log.debug("Created sequencer thread.") self.midiout = midiout # inter-thread communication self.queue = queue if queue is None: self.queue = deque() # log.debug("Created queue for MIDI output.") self._stopped = threading.Event() self._finished = threading.Event() # Counts elapsed ticks when sequence is running self._tickcnt = None # Max number of input queue events to get in one loop self._batchsize = 100 # run-time options self.ppqn = ppqn self.bpm = bpm @property def bpm(self): """Return current beats-per-minute value.""" return self._bpm @bpm.setter def bpm(self, value): self._bpm = value self._tick = 60. / value / self.ppqn # log.debug("Changed BPM => %s, tick interval %.2f ms.", # self._bpm, self._tick * 1000) def stop(self, timeout=5): """Set thread stop event, causing it to exit its mainloop.""" self._stopped.set() # log.debug("SequencerThread stop event set.") if self.is_alive(): self._finished.wait(timeout) self.join() def add(self, event, tick=None, delta=0): """Enqueue event for sending to MIDI output.""" if tick is None: tick = self._tickcnt or 0 if not isinstance(event, MidiEvent): event = MidiEvent(tick, event) if not event.tick: event.tick = tick event.tick += delta self.queue.append(event) def get_event(self): """Poll the input queue for events without blocking. Could be overwritten, e.g. if you passed in your own queue instance with a different API. """ try: return self.queue.popleft() except IndexError: return None def handle_event(self, event): """Handle the event by sending it to MIDI out. Could be overwritten, e.g. to handle meta events, like time signature and tick division changes. """ # log.debug("Midi Out: %r", event.message) self.midiout.send_message(event.message) def run(self): """Start the thread's main loop. The thread will watch for events on the input queue and either send them immediately to the MIDI output or queue them for later output, if their timestamp has not been reached yet. """ # busy loop to wait for time when next batch of events needs to # be written to output pending = [] self._tickcnt = 0 try: while not self._stopped.is_set(): due = [] curtime = time.time() # Pop events off the pending queue # if they are due for this tick while True: if not pending or pending[0].tick > self._tickcnt: break evt = heappop(pending) heappush(due, evt) # log.debug("Queued pending event for output: %r", evt) # Pop up to events off the input queue for i in range(self._batchsize): evt = self.get_event() if not evt: break # log.debug("Got event from input queue: %r", evt) # Check whether event should be sent out immediately # or needs to be scheduled if evt.tick <= self._tickcnt: heappush(due, evt) # log.debug("Queued event for output.") else: heappush(pending, evt) # log.debug("Scheduled event in pending queue.") # If this batch contains any due events, # send them to the MIDI output. if due: for i in range(len(due)): self.handle_event(heappop(due)) # loop speed adjustment elapsed = time.time() - curtime if elapsed < self._tick: time.sleep(self._tick - elapsed) self._tickcnt += 1 except KeyboardInterrupt: # log.debug("KeyboardInterrupt / INT signal received.") pass # log.debug("Midi output mainloop exited.") self._finished.set() def _test(): import sys from rtmidi.midiconstants import NOTE_ON, NOTE_OFF from rtmidi.midiutil import open_midiport logging.basicConfig(level=logging.DEBUG, format="%(message)s") try: midiout, port = open_midiport( sys.argv[1] if len(sys.argv) > 1 else None, "output", client_name="RtMidi Sequencer") except (IOError, ValueError) as exc: return "Could not open MIDI input: %s" % exc except (EOFError, KeyboardInterrupt): return seq = SequencerThread(midiout, bpm=100, ppqn=240) def add_quarter(tick, note, vel=100): seq.add((NOTE_ON, note, vel), tick) seq.add((NOTE_OFF, note, 0), tick=tick + seq.ppqn) t = 0 p = seq.ppqn add_quarter(t, 60) add_quarter(t + p, 64) add_quarter(t + p * 2, 67) add_quarter(t + p * 3, 72) t = p * 5 add_quarter(t, 60) add_quarter(t + p, 64) add_quarter(t + p * 2, 67) add_quarter(t + p * 3, 72) try: seq.start() time.sleep(60. / seq.bpm * 4) seq.bpm = 150 time.sleep(60. / seq.bpm * 6) finally: seq.stop() midiout.close_port() if __name__ == '__main__': _test() python-rtmidi-1.1.0/examples/sysexsaver/000077500000000000000000000000001307643736300203665ustar00rootroot00000000000000python-rtmidi-1.1.0/examples/sysexsaver/__init__.py000066400000000000000000000000001307643736300224650ustar00rootroot00000000000000python-rtmidi-1.1.0/examples/sysexsaver/__main__.py000066400000000000000000000154661307643736300224740ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # sysexsaver.py # """Save all revceived sysex messages to given directory.""" import argparse import logging import os import re import sys import time from datetime import datetime from os.path import exists, join from rtmidi.midiconstants import END_OF_EXCLUSIVE, SYSTEM_EXCLUSIVE from rtmidi.midiutil import open_midiport from .manufacturers import manufacturers from .models import models log = logging.getLogger('sysexsaver') def sanitize_name(s, replace='/?*&\\'): s = s.strip() s = re.sub('\s+', '_', s) for c in replace: s = s.replace(c, '_') return s.lower() class SysexMessage(object): @classmethod def fromdata(cls, data): self = cls() if data[0] != SYSTEM_EXCLUSIVE: raise ValueError("Message does not start with 0xF0") if data[-1] != END_OF_EXCLUSIVE: raise ValueError("Message does not end with 0xF7") if len(data) < 5: raise ValueError("Message too short") if data[1] == 0: self.manufacturer_id = (data[1], data[2], data[3]) self.model_id = data[5] self.device_id = data[6] else: self.manufacturer_id = data[1] self.model_id = data[2] self.device_id = data[3] self._data = data return self def __getitem__(self, i): return self._data[i] def __getslice(self, i, j): return self._data[i:j] @property def manufacturer(self): mname = manufacturers.get(self.manufacturer_id) if mname: return mname[0] @property def manufacturer_tag(self): mname = manufacturers.get(self.manufacturer_id, []) if len(mname) >= 2: return mname[1] elif mname: return mname[0] @property def model(self): model_name = models.get(self.manufacturer_id, {}).get(self.model_id) return model_name[0] if model_name else "0x%02X" % self.model_id @property def model_tag(self): model_name = models.get(self.manufacturer_id, {}).get(self.model_id, []) if len(model_name) >= 2: return model_name[1] elif model_name: return model_name[1] else: "0x%02X" % self.model_id def __repr__(self): return "".join(["%02X " % b for b in self._data]) def as_bytes(self): if bytes == str: return "".join([chr(b) for b in self._data]) else: return bytes(self._data) class SysexSaver(object): """MIDI input callback handler object.""" fn_tmpl = "%(manufacturer)s-%(device)s-%(timestamp)s.syx" fn_named_tmpl = "%(manufacturer)s-%(device)s-%(name)s-%(timestamp)s.syx" def __init__(self, portname, directory, debug=False): self.portname = portname self.directory = directory self.debug = debug def __call__(self, event, data=None): try: message, deltatime = event if message[:1] != [SYSTEM_EXCLUSIVE]: return dt = datetime.now() log.debug("[%s: %s] Received sysex msg of %i bytes." % ( self.portname, dt.strftime('%x %X'), len(message))) sysex = SysexMessage.fromdata(message) # XXX: This should be implemented in a subclass # loaded via a plugin infrastructure data = dict(timestamp=dt.strftime('%Y%m%dT%H%M%S.%f')) data['manufacturer'] = sanitize_name( sysex.manufacturer_tag or 'unknown') data['device'] = sanitize_name(sysex.model_tag or 'unknown') if sysex.manufacturer_id == 62 and sysex.model_id == 0x0E: if sysex[4] == 0x10: # sound dump name = "".join(chr(c) for c in sysex[247:263]).rstrip('_') elif sysex[4] == 0x11: # multi dump name = "".join(chr(c) for c in sysex[23:38]).rstrip('_') elif sysex[4] == 0x12: # wave dump if sysex[5] > 1: name = "userwave_%04i" else: name = "romwave_%03i" name = name % ((sysex[5] << 7) | sysex[6]) elif sysex[4] == 0x13: # wave table dump if sysex[6] >= 96: name = "userwavetable_%03i" % (sysex[6] + 1) else: name = "romwavetable_%03i" % (sysex[6] + 1) else: name = "%02X" % sysex[4] data['name'] = sanitize_name(name) outfn = join(self.directory, ( self.fn_named_tmpl if 'name' in data else self.fn_tmpl) % data) if exists(outfn): log.error("Output file already exists, will not overwrite.") else: data = sysex.as_bytes() with open(outfn, 'wb') as outfile: outfile.write(data) log.info("Sysex message of %i bytes written to '%s'.", len(data), outfn) except: msg = "Error handling MIDI message: %s" % sys.exc_info()[1] if self.debug: log.debug(msg, exc_info=True) else: log.error(msg) def main(args=None): """Save revceived sysex message to directory given on command line.""" parser = argparse.ArgumentParser(description=__doc__) padd = parser.add_argument padd('-o', '--outdir', default=os.getcwd(), help="Output directory (default: current working directory).") padd('-p', '--port', help='MIDI output port number (default: ask)') padd('-v', '--verbose', action="store_true", help='verbose output') args = parser.parse_args(args if args is not None else sys.argv[1:]) logging.basicConfig(format="%(name)s: %(levelname)s - %(message)s", level=logging.DEBUG if args.verbose else logging.INFO) try: midiin, port = open_midiport(args.port) except IOError as exc: log.error(exc) return 1 except (EOFError, KeyboardInterrupt): return 0 ss = SysexSaver(port, args.outdir, args.verbose) log.debug("Attaching MIDI input callback handler.") midiin.set_callback(ss) log.debug("Enabling reception of sysex messages.") midiin.ignore_types(sysex=False) log.info("Waiting for sysex reception. Press Control-C to exit.") try: # just wait for keyboard interrupt in main thread while True: time.sleep(1) except KeyboardInterrupt: print('') finally: log.debug("Exit.") midiin.close_port() del midiin return 0 if __name__ == '__main__': sys.exit(main(sys.argv[1:]) or 0) python-rtmidi-1.1.0/examples/sysexsaver/manufacturers.csv000066400000000000000000000263451307643736300237740ustar00rootroot00000000000000"id";"name";"shortname" (0x00, 0x00, 0x07);"Digital Music Corporation"; (0x00, 0x00, 0x0E);"Alesis"; (0x00, 0x00, 0x15);"KAT"; (0x00, 0x00, 0x16);"Opcode"; (0x00, 0x00, 0x1A);"Allen & Heath Brenell"; (0x00, 0x00, 0x1B);"Peavey Electronics"; (0x00, 0x00, 0x1C);"360 Systems"; (0x00, 0x00, 0x20);"Axxes"; (0x00, 0x00, 0x74);"Ta Horng Musical Instrument"; (0x00, 0x00, 0x75);"e-Tek Labs (Forte Tech)"; (0x00, 0x00, 0x76);"Electro-Voice"; (0x00, 0x00, 0x77);"Midisoft Corporation";"Midisoft" (0x00, 0x00, 0x78);"QSound Labs"; (0x00, 0x00, 0x79);"Westrex"; (0x00, 0x00, 0x7A);"Nvidia"; (0x00, 0x00, 0x7B);"ESS Technology"; (0x00, 0x00, 0x7C);"Media Trix Peripherals"; (0x00, 0x00, 0x7D);"Brooktree Corp"; (0x00, 0x00, 0x7E);"Otari Corp"; (0x00, 0x00, 0x7F);"Key Electronics, Inc."; (0x00, 0x01, 0x00);"Shure Incorporated"; (0x00, 0x01, 0x01);"AuraSound"; (0x00, 0x01, 0x02);"Crystal Semiconductor"; (0x00, 0x01, 0x03);"Conexant (Rockwell)";"Conexant" (0x00, 0x01, 0x04);"Silicon Graphics";"SGI" (0x00, 0x01, 0x05);"M-Audio (Midiman)"; (0x00, 0x01, 0x06);"PreSonus"; (0x00, 0x01, 0x08);"Topaz Enterprises"; (0x00, 0x01, 0x09);"Cast Lighting"; (0x00, 0x01, 0x0A);"Microsoft"; (0x00, 0x01, 0x0B);"Sonic Foundry"; (0x00, 0x01, 0x0C);"Line 6 (Fast Forward)";"Line 6" (0x00, 0x01, 0x0D);"Beatnik Inc";"Beatnik" (0x00, 0x01, 0x0E);"Van Koevering Company"; (0x00, 0x01, 0x0F);"Altech Systems"; (0x00, 0x01, 0x10);"S & S Research"; (0x00, 0x01, 0x11);"VLSI Technology"; (0x00, 0x01, 0x12);"Chromatic Research"; (0x00, 0x01, 0x13);"Sapphire"; (0x00, 0x01, 0x14);"IDRC"; (0x00, 0x01, 0x15);"Justonic Tuning"; (0x00, 0x01, 0x16);"TorComp Research Inc."; (0x00, 0x01, 0x17);"Newtek Inc."; (0x00, 0x01, 0x18);"Sound Sculpture"; (0x00, 0x01, 0x19);"Walker Technical"; (0x00, 0x01, 0x1A);"Digital Harmony (PAVO)"; (0x00, 0x01, 0x1B);"InVision Interactive"; (0x00, 0x01, 0x1C);"T-Square Design"; (0x00, 0x01, 0x1D);"Nemesys Music Technology"; (0x00, 0x01, 0x1E);"DBX Professional (Harman Intl)";"DBX" (0x00, 0x01, 0x1F);"Syndyne Corporation"; (0x00, 0x01, 0x20);"Bitheadz"; (0x00, 0x01, 0x21);"Cakewalk Music Software";"Cakewalk" (0x00, 0x01, 0x22);"Analog Devices"; (0x00, 0x01, 0x23);"National Semiconductor"; (0x00, 0x01, 0x24);"Boom Theory / Adinolfi Alternative Percussion"; (0x00, 0x01, 0x25);"Virtual DSP Corporation"; (0x00, 0x01, 0x26);"Antares Systems"; (0x00, 0x01, 0x27);"Angel Software"; (0x00, 0x01, 0x28);"St Louis Music"; (0x00, 0x01, 0x29);"Lyrrus dba G-VOX"; (0x00, 0x01, 0x2A);"Ashley Audio Inc."; (0x00, 0x01, 0x2B);"Vari-Lite Inc."; (0x00, 0x01, 0x2C);"Summit Audio Inc."; (0x00, 0x01, 0x2D);"Aureal Semiconductor Inc."; (0x00, 0x01, 0x2E);"SeaSound LLC"; (0x00, 0x01, 0x2F);"U.S. Robotics"; (0x00, 0x01, 0x30);"Aurisis Research"; (0x00, 0x01, 0x31);"Nearfield Research"; (0x00, 0x01, 0x32);"FM7 Inc"; (0x00, 0x01, 0x33);"Swivel Systems"; (0x00, 0x01, 0x34);"Hyperactive Audio Systems"; (0x00, 0x01, 0x35);"MidiLite (Castle Studios Productions)";"MidiLite" (0x00, 0x01, 0x36);"Radikal Technologies"; (0x00, 0x01, 0x37);"Roger Linn Design"; (0x00, 0x01, 0x38);"TC-Helicon Vocal Technologies"; (0x00, 0x01, 0x39);"Event Electronics"; (0x00, 0x01, 0x3A);"Sonic Network Inc"; (0x00, 0x01, 0x3B);"Realtime Music Solutions"; (0x00, 0x01, 0x3C);"Apogee Digital";"Apogee" (0x00, 0x01, 0x3D);"Classical Organs, Inc."; (0x00, 0x01, 0x3E);"Microtools Inc."; (0x00, 0x01, 0x3F);"Numark Industries"; (0x00, 0x01, 0x40);"Frontier Design Group, LLC"; (0x00, 0x01, 0x41);"Recordare LLC"; (0x00, 0x01, 0x42);"Starr Labs"; (0x00, 0x01, 0x43);"Voyager Sound Inc."; (0x00, 0x01, 0x44);"Manifold Labs"; (0x00, 0x01, 0x45);"Aviom Inc.";"Aviom" (0x00, 0x01, 0x46);"Mixmeister Technology"; (0x00, 0x01, 0x47);"Notation Software"; (0x00, 0x01, 0x48);"Mercurial Communications"; (0x00, 0x01, 0x49);"Wave Arts"; (0x00, 0x01, 0x4A);"Logic Sequencing Devices"; (0x00, 0x01, 0x4B);"Axess Electronics";"Axess" (0x00, 0x01, 0x4C);"Muse Research"; (0x00, 0x01, 0x4D);"Open Labs"; (0x00, 0x01, 0x4E);"Guillemot R&D Inc"; (0x00, 0x01, 0x4F);"Samson Technologies"; (0x00, 0x01, 0x50);"Electronic Theatre Controls"; (0x00, 0x01, 0x51);"Blackberry (RIM)";"Blackberry" (0x00, 0x01, 0x52);"Mobileer"; (0x00, 0x01, 0x53);"Synthogy"; (0x00, 0x01, 0x54);"Lynx Studio Technology Inc."; (0x00, 0x01, 0x55);"Damage Control Engineering LLC"; (0x00, 0x01, 0x56);"Yost Engineering, Inc."; (0x00, 0x01, 0x57);"Brooks & Forsman Designs LLC / DrumLite"; (0x00, 0x01, 0x58);"Infinite Response"; (0x00, 0x01, 0x59);"Garritan Corp"; (0x00, 0x01, 0x5A);"Plogue Art et Technologie, Inc"; (0x00, 0x01, 0x5B);"RJM Music Technology"; (0x00, 0x01, 0x5C);"Custom Solutions Software"; (0x00, 0x01, 0x5D);"Sonarcana LLC"; (0x00, 0x01, 0x5E);"Centrance"; (0x00, 0x01, 0x5F);"Kesumo LLC"; (0x00, 0x01, 0x60);"Stanton (Gibson)"; (0x00, 0x01, 0x61);"Livid Instruments"; (0x00, 0x01, 0x62);"First Act / 745 Media"; (0x00, 0x01, 0x63);"Pygraphics, Inc."; (0x00, 0x01, 0x64);"Panadigm Innovations Ltd"; (0x00, 0x01, 0x65);"Avedis Zildjian Co"; (0x00, 0x01, 0x66);"Auvital Music Corp"; (0x00, 0x01, 0x67);"Inspired Instruments Inc"; (0x00, 0x01, 0x68);"Chris Grigg Designs"; (0x00, 0x01, 0x69);"Slate Digital LLC"; (0x00, 0x01, 0x6A);"Mixware"; (0x00, 0x01, 0x6B);"Social Entropy"; (0x00, 0x01, 0x6C);"Source Audio LLC"; (0x00, 0x01, 0x6D);"Ernie Ball / Music Man"; (0x00, 0x01, 0x6E);"Fishman Transducers"; (0x00, 0x01, 0x6F);"Custom Audio Electronics"; (0x00, 0x01, 0x70);"American Audio/DJ"; (0x00, 0x01, 0x71);"Mega Control Systems"; (0x00, 0x01, 0x72);"Kilpatrick Audio"; (0x00, 0x01, 0x73);"iConnectivity"; (0x00, 0x01, 0x74);"Fractal Audio"; (0x00, 0x01, 0x75);"NetLogic Microsystems"; (0x00, 0x01, 0x76);"Music Computing"; (0x00, 0x01, 0x77);"Nektar Technology Inc"; (0x00, 0x01, 0x78);"Zenph Sound Innovations"; (0x00, 0x01, 0x79);"DJTechTools.com"; (0x00, 0x01, 0x7A);"Rezonance Labs"; (0x00, 0x01, 0x7B);"Decibel Eleven"; (0x00, 0x01, 0x7C);"CNMAT"; (0x00, 0x01, 0x7D);"Media Overkill"; (0x00, 0x01, 0x7E);"Confusionists LLC"; (0x00, 0x20, 0x27);"Acorn Computer"; (0x00, 0x20, 0x29);"Focusrite/Novation";"Novation" (0x00, 0x20, 0x2A);"Samkyung Mechatronics"; (0x00, 0x20, 0x2B);"Medeli Electronics Co."; (0x00, 0x20, 0x2C);"Charlie Lab SRL"; (0x00, 0x20, 0x2D);"Blue Chip Music Technology"; (0x00, 0x20, 0x2E);"BEE OH Corp"; (0x00, 0x20, 0x2F);"LG Semicon America"; (0x00, 0x20, 0x30);"TESI"; (0x00, 0x20, 0x31);"EMAGIC"; (0x00, 0x20, 0x32);"Behringer GmbH";"Behringer" (0x00, 0x20, 0x33);"Access Music Electronics";"Access" (0x00, 0x20, 0x34);"Synoptic"; (0x00, 0x20, 0x35);"Hanmesoft"; (0x00, 0x20, 0x36);"Terratec Electronic GmbH"; (0x00, 0x20, 0x37);"Proel SpA"; (0x00, 0x20, 0x38);"IBK MIDI"; (0x00, 0x20, 0x39);"IRCAM"; (0x00, 0x20, 0x3A);"Propellerhead Software"; (0x00, 0x20, 0x3B);"Red Sound Systems Ltd"; (0x00, 0x20, 0x3C);"Elektron ESI AB"; (0x00, 0x20, 0x3D);"Sintefex Audio"; (0x00, 0x20, 0x3E);"MAM (Music and More)";"MAM" (0x00, 0x20, 0x3F);"Amsaro GmbH"; (0x00, 0x20, 0x40);"CDS Advanced Technology BV"; (0x00, 0x20, 0x41);"Touched By Sound GmbH"; (0x00, 0x20, 0x42);"DSP Arts"; (0x00, 0x20, 0x43);"Phil Rees Music Tech"; (0x00, 0x20, 0x44);"Stamer Musikanlagen GmbH"; (0x00, 0x20, 0x45);"Musical Muntaner S.A. dba Soundart"; (0x00, 0x20, 0x46);"C-Mexx Software";"C-Mexx" (0x00, 0x20, 0x47);"Klavis Technologies"; (0x00, 0x20, 0x48);"Noteheads AB"; (0x00, 0x20, 0x49);"Algorithmix"; (0x00, 0x20, 0x4A);"Skrydstrup R&D"; (0x00, 0x20, 0x4B);"Professional Audio Company"; (0x00, 0x20, 0x4C);"NewWave Labs (MadWaves)"; (0x00, 0x20, 0x4D);"Vermona"; (0x00, 0x20, 0x4E);"Nokia"; (0x00, 0x20, 0x4F);"Wave Idea"; (0x00, 0x20, 0x50);"Hartmann GmbH"; (0x00, 0x20, 0x51);"Lion's Tracs"; (0x00, 0x20, 0x52);"Analogue Systems"; (0x00, 0x20, 0x53);"Focal-JMlab"; (0x00, 0x20, 0x54);"Ringway Electronics (Chang-Zhou) Co Ltd"; (0x00, 0x20, 0x55);"Faith Technologies (Digiplug)"; (0x00, 0x20, 0x56);"Showworks"; (0x00, 0x20, 0x57);"Manikin Electronic";"Manikin" (0x00, 0x20, 0x58);"1 Come Tech"; (0x00, 0x20, 0x59);"Phonic Corp"; (0x00, 0x20, 0x5A);"Dolby Australia (Lake)"; (0x00, 0x20, 0x5B);"Silansys Technologies"; (0x00, 0x20, 0x5C);"Winbond Electronics"; (0x00, 0x20, 0x5D);"Cinetix Medien und Interface GmbH"; (0x00, 0x20, 0x5E);"A&G Soluzioni Digitali"; (0x00, 0x20, 0x5F);"Sequentix Music Systems"; (0x00, 0x20, 0x60);"Oram Pro Audio"; (0x00, 0x20, 0x61);"Be4 Ltd"; (0x00, 0x20, 0x62);"Infection Music"; (0x00, 0x20, 0x63);"Central Music Co. (CME)";"CME" (0x00, 0x20, 0x64);"genoQs Machines GmbH"; (0x00, 0x20, 0x65);"Medialon"; (0x00, 0x20, 0x66);"Waves Audio Ltd"; (0x00, 0x20, 0x67);"Jerash Labs"; (0x00, 0x20, 0x68);"Da Fact"; (0x00, 0x20, 0x69);"Elby Designs"; (0x00, 0x20, 0x6A);"Spectral Audio"; (0x00, 0x20, 0x6B);"Arturia"; (0x00, 0x20, 0x6C);"Vixid"; (0x00, 0x20, 0x6D);"C-Thru Music"; (0x00, 0x20, 0x6E);"Ya Horng Electronic Co LTD"; (0x00, 0x20, 0x6F);"SM Pro Audio"; (0x00, 0x20, 0x70);"OTO MACHINES"; (0x00, 0x20, 0x71);"ELZAB S.A., G LAB"; (0x00, 0x20, 0x72);"Blackstar Amplification Ltd"; (0x00, 0x20, 0x73);"M3i Technologies GmbH"; (0x00, 0x20, 0x74);"Gemalto (from Xiring)"; (0x00, 0x20, 0x75);"Prostage SL"; (0x00, 0x20, 0x76);"Teenage Engineering"; (0x00, 0x20, 0x77);"Tobias Erichsen Consulting"; (0x00, 0x20, 0x78);"Nixer Ltd"; (0x00, 0x20, 0x79);"Hanpin Electron Co Ltd"; (0x00, 0x20, 0x7A);"""MIDI-hardware"" R.Sowa"; (0x00, 0x20, 0x7B);"Beyond Music Industrial Ltd"; (0x00, 0x20, 0x7C);"Kiss Box B.V."; (0x00, 0x20, 0x7D);"Misa Digital Technologies Ltd"; (0x00, 0x20, 0x7E);"AI Musics Technology Inc"; (0x00, 0x20, 0x7F);"Serato Inc LP"; (0x00, 0x21, 0x00);"Limex Music Handles GmbH"; (0x00, 0x21, 0x01);"Kyodday/Tokai"; (0x00, 0x21, 0x02);"Mutable Instruments"; (0x00, 0x21, 0x03);"PreSonus Software Ltd";"PreSonus" (0x00, 0x21, 0x04);"Xiring"; (0x00, 0x21, 0x05);"Fairlight Instruments Pty Ltd";"Fairlight" (0x00, 0x21, 0x06);"Musicom Lab"; (0x00, 0x21, 0x07);"VacoLoco"; (0x00, 0x21, 0x08);"RWA (Hong Kong) Limited"; (0x00, 0x21, 0x09);"Native Instruments";"NI" (0x00, 0x21, 0x0A);"Naonext"; (0x00, 0x21, 0x0B);"MFB"; (0x00, 0x21, 0x0C);"Teknel Research"; (0x00, 0x21, 0x0D);"Ploytec GmbH";"Ploytec" (0x00, 0x21, 0x0E);"Surfin Kangaroo Studio"; (0x00, 0x21, 0x0F);"Philips Electronics HK Ltd";"Philips" (0x00, 0x21, 0x10);"ROLI Ltd"; (0x00, 0x40, 0x00);"Crimson Technology Inc."; (0x00, 0x40, 0x01);"Softbank Mobile Corp"; (0x00, 0x40, 0x03);"D&M Holdings Inc."; 0x01;"Sequential Circuits"; 0x04;"Moog"; 0x06;"Lexicon"; 0x07;"Kurzweil"; 0x0F;"Ensoniq"; 0x10;"Oberheim"; 0x11;"Apple"; 0x18;"Emu"; 0x1A;"ART"; 0x22;"Synthaxe"; 0x24;"Hohner"; 0x29;"PPG"; 0x2B;"SSL"; 0x2F;"Elka / General Music";"GEM" 0x30;"Dynacord"; 0x33;"Clavia (Nord)";"Clavia" 0x36;"Cheetah"; 0x3E;"Waldorf Electronics Gmbh";"Waldorf" 0x40;"Kawai Musical Instruments MFG. CO. Ltd";"Kawai" 0x41;"Roland Corporation";"Roland" 0x42;"Korg Inc.";"Korg" 0x43;"Yamaha Corporation";"Yamaha" 0x44;"Casio Computer Co. Ltd";"Casio" 0x46;"Kamiya Studio Co. Ltd"; 0x47;"Akai Electric Co. Ltd.";"Akai" 0x48;"Victor Company of Japan, Ltd.";"JVC" 0x4B;"Fujitsu Limited";"Fujitsu" 0x4C;"Sony Corporation";"Sony" 0x4E;"Teac Corporation";"Teac" 0x50;"Matsushita Electric Industrial Co. , Ltd";"M-Audio" 0x51;"Fostex Corporation";"Fostex" 0x52;"Zoom Corporation";"Zoom" 0x54;"Matsushita Communication Industrial Co., Ltd."; 0x55;"Suzuki Musical Instruments MFG. Co., Ltd.";"Suzuki" 0x56;"Fuji Sound Corporation Ltd.";"Fuji" 0x57;"Acoustic Technical Laboratory, Inc."; 0x59;"Faith, Inc."; 0x5A;"Internet Corporation"; 0x5C;"Seekers Co. Ltd."; 0x5F;"SD Card Association"; python-rtmidi-1.1.0/examples/sysexsaver/manufacturers.py000066400000000000000000000300611307643736300236170ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Map manufacturer ID to manufacturer name and short name.""" manufacturers = { # id: (name, shortname), 1: ('Sequential Circuits',), 4: ('Moog',), 6: ('Lexicon',), 7: ('Kurzweil',), 15: ('Ensoniq',), 16: ('Oberheim',), 17: ('Apple',), 24: ('Emu',), 26: ('ART',), 34: ('Synthaxe',), 36: ('Hohner',), 41: ('PPG',), 43: ('SSL',), 47: ('Elka / General Music', 'GEM'), 48: ('Dynacord',), 51: ('Clavia (Nord)', 'Clavia'), 54: ('Cheetah',), 62: ('Waldorf Electronics Gmbh', 'Waldorf'), 64: ('Kawai Musical Instruments MFG. CO. Ltd', 'Kawai'), 65: ('Roland Corporation', 'Roland'), 66: ('Korg Inc.', 'Korg'), 67: ('Yamaha Corporation', 'Yamaha'), 68: ('Casio Computer Co. Ltd', 'Casio'), 70: ('Kamiya Studio Co. Ltd', 'Kamiya'), 71: ('Akai Electric Co. Ltd.', 'Akai'), 72: ('Victor Company of Japan, Ltd.', 'JVC'), 75: ('Fujitsu Limited', 'Fujitsu'), 76: ('Sony Corporation', 'Sony'), 78: ('Teac Corporation', 'Teac'), 80: ('Matsushita Electric Industrial Co. , Ltd', 'M-Audio'), 81: ('Fostex Corporation', 'Fostex'), 82: ('Zoom Corporation', 'Zoom'), 84: ('Matsushita Communication Industrial Co., Ltd.', 'Matsushita'), 85: ('Suzuki Musical Instruments MFG. Co., Ltd.', 'Suzuki'), 86: ('Fuji Sound Corporation Ltd.', 'Fuji'), 87: ('Acoustic Technical Laboratory, Inc.', 'ATL'), 89: ('Faith, Inc.', 'Faith'), 90: ('Internet Corporation',), 92: ('Seekers Co. Ltd.', 'Seekers'), 95: ('SD Card Association',), 0x7D: ('Non-commercial',), 0x7E: ('Universal Non-Realtime',), 0x7F: ('Universal Realtime',), (0, 0, 7): ('Digital Music Corporation',), (0, 0, 14): ('Alesis',), (0, 0, 21): ('KAT',), (0, 0, 22): ('Opcode',), (0, 0, 26): ('Allen & Heath Brenell', 'Allen & Heath'), (0, 0, 27): ('Peavey Electronics', 'Peavey'), (0, 0, 28): ('360 Systems',), (0, 0, 32): ('Axxes',), (0, 0, 116): ('Ta Horng Musical Instrument',), (0, 0, 117): ('e-Tek Labs (Forte Tech)', 'e-Tek Labs'), (0, 0, 118): ('Electro-Voice',), (0, 0, 119): ('Midisoft Corporation', 'Midisoft'), (0, 0, 120): ('QSound Labs',), (0, 0, 121): ('Westrex',), (0, 0, 122): ('Nvidia',), (0, 0, 123): ('ESS Technology', 'ESS'), (0, 0, 124): ('Media Trix Peripherals',), (0, 0, 125): ('Brooktree Corp',), (0, 0, 126): ('Otari Corp',), (0, 0, 127): ('Key Electronics, Inc.',), (0, 1, 0): ('Shure Incorporated', 'Shure'), (0, 1, 1): ('AuraSound',), (0, 1, 2): ('Crystal Semiconductor', 'Crystal'), (0, 1, 3): ('Conexant (Rockwell)', 'Conexant'), (0, 1, 4): ('Silicon Graphics', 'SGI'), (0, 1, 5): ('M-Audio (Midiman)', 'M-Audio'), (0, 1, 6): ('PreSonus',), (0, 1, 8): ('Topaz Enterprises', 'Topaz'), (0, 1, 9): ('Cast Lighting',), (0, 1, 10): ('Microsoft',), (0, 1, 11): ('Sonic Foundry',), (0, 1, 12): ('Line 6 (Fast Forward)', 'Line 6'), (0, 1, 13): ('Beatnik Inc', 'Beatnik'), (0, 1, 14): ('Van Koevering Company',), (0, 1, 15): ('Altech Systems',), (0, 1, 16): ('S & S Research',), (0, 1, 17): ('VLSI Technology',), (0, 1, 18): ('Chromatic Research',), (0, 1, 19): ('Sapphire',), (0, 1, 20): ('IDRC',), (0, 1, 21): ('Justonic Tuning',), (0, 1, 22): ('TorComp Research Inc.',), (0, 1, 23): ('Newtek Inc.',), (0, 1, 24): ('Sound Sculpture',), (0, 1, 25): ('Walker Technical',), (0, 1, 26): ('Digital Harmony (PAVO)',), (0, 1, 27): ('InVision Interactive',), (0, 1, 28): ('T-Square Design',), (0, 1, 29): ('Nemesys Music Technology',), (0, 1, 30): ('DBX Professional (Harman Intl)', 'DBX'), (0, 1, 31): ('Syndyne Corporation',), (0, 1, 32): ('Bitheadz',), (0, 1, 33): ('Cakewalk Music Software', 'Cakewalk'), (0, 1, 34): ('Analog Devices',), (0, 1, 35): ('National Semiconductor',), (0, 1, 36): ('Boom Theory / Adinolfi Alternative Percussion',), (0, 1, 37): ('Virtual DSP Corporation',), (0, 1, 38): ('Antares Systems',), (0, 1, 39): ('Angel Software',), (0, 1, 40): ('St Louis Music',), (0, 1, 41): ('Lyrrus dba G-VOX',), (0, 1, 42): ('Ashley Audio Inc.',), (0, 1, 43): ('Vari-Lite Inc.',), (0, 1, 44): ('Summit Audio Inc.',), (0, 1, 45): ('Aureal Semiconductor Inc.',), (0, 1, 46): ('SeaSound LLC',), (0, 1, 47): ('U.S. Robotics',), (0, 1, 48): ('Aurisis Research',), (0, 1, 49): ('Nearfield Research',), (0, 1, 50): ('FM7 Inc',), (0, 1, 51): ('Swivel Systems',), (0, 1, 52): ('Hyperactive Audio Systems',), (0, 1, 53): ('MidiLite (Castle Studios Productions)', 'MidiLite'), (0, 1, 54): ('Radikal Technologies',), (0, 1, 55): ('Roger Linn Design', 'LINN'), (0, 1, 56): ('TC-Helicon Vocal Technologies',), (0, 1, 57): ('Event Electronics',), (0, 1, 58): ('Sonic Network Inc',), (0, 1, 59): ('Realtime Music Solutions',), (0, 1, 60): ('Apogee Digital', 'Apogee'), (0, 1, 61): ('Classical Organs, Inc.',), (0, 1, 62): ('Microtools Inc.',), (0, 1, 63): ('Numark Industries', 'Numark'), (0, 1, 64): ('Frontier Design Group, LLC',), (0, 1, 65): ('Recordare LLC',), (0, 1, 66): ('Starr Labs',), (0, 1, 67): ('Voyager Sound Inc.',), (0, 1, 68): ('Manifold Labs',), (0, 1, 69): ('Aviom Inc.', 'Aviom'), (0, 1, 70): ('Mixmeister Technology',), (0, 1, 71): ('Notation Software',), (0, 1, 72): ('Mercurial Communications',), (0, 1, 73): ('Wave Arts',), (0, 1, 74): ('Logic Sequencing Devices',), (0, 1, 75): ('Axess Electronics', 'Axess'), (0, 1, 76): ('Muse Research',), (0, 1, 77): ('Open Labs',), (0, 1, 78): ('Guillemot R&D Inc',), (0, 1, 79): ('Samson Technologies',), (0, 1, 80): ('Electronic Theatre Controls',), (0, 1, 81): ('Blackberry (RIM)', 'Blackberry'), (0, 1, 82): ('Mobileer',), (0, 1, 83): ('Synthogy',), (0, 1, 84): ('Lynx Studio Technology Inc.',), (0, 1, 85): ('Damage Control Engineering LLC',), (0, 1, 86): ('Yost Engineering, Inc.',), (0, 1, 87): ('Brooks & Forsman Designs LLC / DrumLite',), (0, 1, 88): ('Infinite Response',), (0, 1, 89): ('Garritan Corp', 'Garritan'), (0, 1, 90): ('Plogue Art et Technologie, Inc',), (0, 1, 91): ('RJM Music Technology',), (0, 1, 92): ('Custom Solutions Software',), (0, 1, 93): ('Sonarcana LLC',), (0, 1, 94): ('Centrance',), (0, 1, 95): ('Kesumo LLC',), (0, 1, 96): ('Stanton (Gibson)',), (0, 1, 97): ('Livid Instruments',), (0, 1, 98): ('First Act / 745 Media',), (0, 1, 99): ('Pygraphics, Inc.',), (0, 1, 100): ('Panadigm Innovations Ltd',), (0, 1, 101): ('Avedis Zildjian Co',), (0, 1, 102): ('Auvital Music Corp',), (0, 1, 103): ('Inspired Instruments Inc',), (0, 1, 104): ('Chris Grigg Designs',), (0, 1, 105): ('Slate Digital LLC',), (0, 1, 106): ('Mixware',), (0, 1, 107): ('Social Entropy',), (0, 1, 108): ('Source Audio LLC',), (0, 1, 109): ('Ernie Ball / Music Man', 'Music Man'), (0, 1, 110): ('Fishman Transducers',), (0, 1, 111): ('Custom Audio Electronics',), (0, 1, 112): ('American Audio/DJ',), (0, 1, 113): ('Mega Control Systems',), (0, 1, 114): ('Kilpatrick Audio',), (0, 1, 115): ('iConnectivity',), (0, 1, 116): ('Fractal Audio',), (0, 1, 117): ('NetLogic Microsystems',), (0, 1, 118): ('Music Computing',), (0, 1, 119): ('Nektar Technology Inc',), (0, 1, 120): ('Zenph Sound Innovations',), (0, 1, 121): ('DJTechTools.com',), (0, 1, 122): ('Rezonance Labs',), (0, 1, 123): ('Decibel Eleven',), (0, 1, 124): ('CNMAT',), (0, 1, 125): ('Media Overkill',), (0, 1, 126): ('Confusionists LLC',), (0, 32, 39): ('Acorn Computer',), (0, 32, 41): ('Focusrite/Novation', 'Novation'), (0, 32, 42): ('Samkyung Mechatronics',), (0, 32, 43): ('Medeli Electronics Co.',), (0, 32, 44): ('Charlie Lab SRL',), (0, 32, 45): ('Blue Chip Music Technology',), (0, 32, 46): ('BEE OH Corp',), (0, 32, 47): ('LG Semicon America',), (0, 32, 48): ('TESI',), (0, 32, 49): ('EMAGIC',), (0, 32, 50): ('Behringer GmbH', 'Behringer'), (0, 32, 51): ('Access Music Electronics', 'Access'), (0, 32, 52): ('Synoptic',), (0, 32, 53): ('Hanmesoft',), (0, 32, 54): ('Terratec Electronic GmbH', 'Terratec'), (0, 32, 55): ('Proel SpA',), (0, 32, 56): ('IBK MIDI',), (0, 32, 57): ('IRCAM',), (0, 32, 58): ('Propellerhead Software',), (0, 32, 59): ('Red Sound Systems Ltd',), (0, 32, 60): ('Elektron ESI AB',), (0, 32, 61): ('Sintefex Audio',), (0, 32, 62): ('MAM (Music and More)', 'MAM'), (0, 32, 63): ('Amsaro GmbH',), (0, 32, 64): ('CDS Advanced Technology BV',), (0, 32, 65): ('Touched By Sound GmbH',), (0, 32, 66): ('DSP Arts',), (0, 32, 67): ('Phil Rees Music Tech',), (0, 32, 68): ('Stamer Musikanlagen GmbH',), (0, 32, 69): ('Musical Muntaner S.A. dba Soundart',), (0, 32, 70): ('C-Mexx Software', 'C-Mexx'), (0, 32, 71): ('Klavis Technologies',), (0, 32, 72): ('Noteheads AB',), (0, 32, 73): ('Algorithmix',), (0, 32, 74): ('Skrydstrup R&D',), (0, 32, 75): ('Professional Audio Company',), (0, 32, 76): ('NewWave Labs (MadWaves)',), (0, 32, 77): ('Vermona',), (0, 32, 78): ('Nokia',), (0, 32, 79): ('Wave Idea',), (0, 32, 80): ('Hartmann GmbH', 'Hartmann'), (0, 32, 81): ("Lion's Tracs",), (0, 32, 82): ('Analogue Systems',), (0, 32, 83): ('Focal-JMlab',), (0, 32, 84): ('Ringway Electronics (Chang-Zhou) Co Ltd',), (0, 32, 85): ('Faith Technologies (Digiplug)',), (0, 32, 86): ('Showworks',), (0, 32, 87): ('Manikin Electronic', 'Manikin'), (0, 32, 88): ('1 Come Tech',), (0, 32, 89): ('Phonic Corp',), (0, 32, 90): ('Dolby Australia (Lake)',), (0, 32, 91): ('Silansys Technologies',), (0, 32, 92): ('Winbond Electronics',), (0, 32, 93): ('Cinetix Medien und Interface GmbH',), (0, 32, 94): ('A&G Soluzioni Digitali',), (0, 32, 95): ('Sequentix Music Systems',), (0, 32, 96): ('Oram Pro Audio',), (0, 32, 97): ('Be4 Ltd',), (0, 32, 98): ('Infection Music',), (0, 32, 99): ('Central Music Co. (CME)', 'CME'), (0, 32, 100): ('genoQs Machines GmbH',), (0, 32, 101): ('Medialon',), (0, 32, 102): ('Waves Audio Ltd',), (0, 32, 103): ('Jerash Labs',), (0, 32, 104): ('Da Fact',), (0, 32, 105): ('Elby Designs',), (0, 32, 106): ('Spectral Audio',), (0, 32, 107): ('Arturia',), (0, 32, 108): ('Vixid',), (0, 32, 109): ('C-Thru Music',), (0, 32, 110): ('Ya Horng Electronic Co LTD',), (0, 32, 111): ('SM Pro Audio',), (0, 32, 112): ('OTO MACHINES',), (0, 32, 113): ('ELZAB S.A., G LAB',), (0, 32, 114): ('Blackstar Amplification Ltd', 'Blackstar'), (0, 32, 115): ('M3i Technologies GmbH',), (0, 32, 116): ('Gemalto (from Xiring)',), (0, 32, 117): ('Prostage SL',), (0, 32, 118): ('Teenage Engineering',), (0, 32, 119): ('Tobias Erichsen Consulting',), (0, 32, 120): ('Nixer Ltd',), (0, 32, 121): ('Hanpin Electron Co Ltd',), (0, 32, 122): ('"MIDI-hardware" R.Sowa',), (0, 32, 123): ('Beyond Music Industrial Ltd',), (0, 32, 124): ('Kiss Box B.V.',), (0, 32, 125): ('Misa Digital Technologies Ltd',), (0, 32, 126): ('AI Musics Technology Inc',), (0, 32, 127): ('Serato Inc LP',), (0, 33, 0): ('Limex Music Handles GmbH',), (0, 33, 1): ('Kyodday/Tokai',), (0, 33, 2): ('Mutable Instruments',), (0, 33, 3): ('PreSonus Software Ltd', 'PreSonus'), (0, 33, 4): ('Xiring',), (0, 33, 5): ('Fairlight Instruments Pty Ltd', 'Fairlight'), (0, 33, 6): ('Musicom Lab',), (0, 33, 7): ('VacoLoco',), (0, 33, 8): ('RWA (Hong Kong) Limited',), (0, 33, 9): ('Native Instruments', 'NI'), (0, 33, 10): ('Naonext',), (0, 33, 11): ('MFB',), (0, 33, 12): ('Teknel Research',), (0, 33, 13): ('Ploytec GmbH', 'Ploytec'), (0, 33, 14): ('Surfin Kangaroo Studio',), (0, 33, 15): ('Philips Electronics HK Ltd', 'Philips'), (0, 33, 16): ('ROLI Ltd',), (0, 64, 0): ('Crimson Technology Inc.',), (0, 64, 1): ('Softbank Mobile Corp',), (0, 64, 3): ('D&M Holdings Inc.',) } python-rtmidi-1.1.0/examples/sysexsaver/models.py000066400000000000000000000275631307643736300222400ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Map manufacturer's model ID to name.""" models = { # id: (name, shortname), 1: {}, # Sequential Circuits 4: {}, # Moog 6: {}, # Lexicon 7: {}, # Kurzweil 15: {}, # Ensoniq 16: {}, # Oberheim 17: {}, # Apple 24: {}, # Emu 26: {}, # ART 34: {}, # Synthaxe 36: {}, # Hohner 41: {}, # PPG 43: {}, # SSL 47: {}, # Elka / General Music 48: {}, # Dynacord 51: {}, # Clavia (Nord) 54: {}, # Cheetah # Waldorf Electronics GmbH 62: { 0x0E: ('microWAVE II/XT(k)', 'microwave2'), }, 64: {}, # Kawai Musical Instruments MFG. CO. Ltd 65: {}, # Roland Corporation 66: {}, # Korg Inc. 67: {}, # Yamaha Corporation 68: {}, # Casio Computer Co. Ltd 70: {}, # Kamiya Studio Co. Ltd 71: {}, # Akai Electric Co. Ltd. 72: {}, # Victor Company of Japan, Ltd. 75: {}, # Fujitsu Limited 76: {}, # Sony Corporation 78: {}, # Teac Corporation 80: {}, # Matsushita Electric Industrial Co. , Ltd 81: {}, # Fostex Corporation 82: {}, # Zoom Corporation 84: {}, # Matsushita Communication Industrial Co., Ltd. 85: {}, # Suzuki Musical Instruments MFG. Co., Ltd. 86: {}, # Fuji Sound Corporation Ltd. 87: {}, # Acoustic Technical Laboratory, Inc. 89: {}, # Faith, Inc. 90: {}, # Internet Corporation 92: {}, # Seekers Co. Ltd. 95: {}, # SD Card Association 0x7D: {}, # Non-commercial 0x7E: {}, # Universal Non-Realtime 0x7F: {}, # Universal Realtime (0, 0, 7): {}, # Digital Music Corporation (0, 0, 14): {}, # Alesis (0, 0, 21): {}, # KAT (0, 0, 22): {}, # Opcode (0, 0, 26): {}, # Allen & Heath Brenell (0, 0, 27): {}, # Peavey Electronics (0, 0, 28): {}, # 360 Systems (0, 0, 32): {}, # Axxes (0, 0, 116): {}, # Ta Horng Musical Instrument (0, 0, 117): {}, # e-Tek Labs (Forte Tech) (0, 0, 118): {}, # Electro-Voice (0, 0, 119): {}, # Midisoft Corporation (0, 0, 120): {}, # QSound Labs (0, 0, 121): {}, # Westrex (0, 0, 122): {}, # Nvidia (0, 0, 123): {}, # ESS Technology (0, 0, 124): {}, # Media Trix Peripherals (0, 0, 125): {}, # Brooktree Corp (0, 0, 126): {}, # Otari Corp (0, 0, 127): {}, # Key Electronics, Inc. (0, 1, 0): {}, # Shure Incorporated (0, 1, 1): {}, # AuraSound (0, 1, 2): {}, # Crystal Semiconductor (0, 1, 3): {}, # Conexant (Rockwell) (0, 1, 4): {}, # Silicon Graphics (0, 1, 5): {}, # M-Audio (Midiman) (0, 1, 6): {}, # PreSonus (0, 1, 8): {}, # Topaz Enterprises (0, 1, 9): {}, # Cast Lighting (0, 1, 10): {}, # Microsoft (0, 1, 11): {}, # Sonic Foundry (0, 1, 12): {}, # Line 6 (Fast Forward) (0, 1, 13): {}, # Beatnik Inc (0, 1, 14): {}, # Van Koevering Company (0, 1, 15): {}, # Altech Systems (0, 1, 16): {}, # S & S Research (0, 1, 17): {}, # VLSI Technology (0, 1, 18): {}, # Chromatic Research (0, 1, 19): {}, # Sapphire (0, 1, 20): {}, # IDRC (0, 1, 21): {}, # Justonic Tuning (0, 1, 22): {}, # TorComp Research Inc. (0, 1, 23): {}, # Newtek Inc. (0, 1, 24): {}, # Sound Sculpture (0, 1, 25): {}, # Walker Technical (0, 1, 26): {}, # Digital Harmony (PAVO) (0, 1, 27): {}, # InVision Interactive (0, 1, 28): {}, # T-Square Design (0, 1, 29): {}, # Nemesys Music Technology (0, 1, 30): {}, # DBX Professional (Harman Intl) (0, 1, 31): {}, # Syndyne Corporation (0, 1, 32): {}, # Bitheadz (0, 1, 33): {}, # Cakewalk Music Software (0, 1, 34): {}, # Analog Devices (0, 1, 35): {}, # National Semiconductor (0, 1, 36): {}, # Boom Theory / Adinolfi Alternative Percussion (0, 1, 37): {}, # Virtual DSP Corporation (0, 1, 38): {}, # Antares Systems (0, 1, 39): {}, # Angel Software (0, 1, 40): {}, # St Louis Music (0, 1, 41): {}, # Lyrrus dba G-VOX (0, 1, 42): {}, # Ashley Audio Inc. (0, 1, 43): {}, # Vari-Lite Inc. (0, 1, 44): {}, # Summit Audio Inc. (0, 1, 45): {}, # Aureal Semiconductor Inc. (0, 1, 46): {}, # SeaSound LLC (0, 1, 47): {}, # U.S. Robotics (0, 1, 48): {}, # Aurisis Research (0, 1, 49): {}, # Nearfield Research (0, 1, 50): {}, # FM7 Inc (0, 1, 51): {}, # Swivel Systems (0, 1, 52): {}, # Hyperactive Audio Systems (0, 1, 53): {}, # MidiLite (Castle Studios Productions) (0, 1, 54): {}, # Radikal Technologies (0, 1, 55): {}, # Roger Linn Design (0, 1, 56): {}, # TC-Helicon Vocal Technologies (0, 1, 57): {}, # Event Electronics (0, 1, 58): {}, # Sonic Network Inc (0, 1, 59): {}, # Realtime Music Solutions (0, 1, 60): {}, # Apogee Digital (0, 1, 61): {}, # Classical Organs, Inc. (0, 1, 62): {}, # Microtools Inc. (0, 1, 63): {}, # Numark Industries (0, 1, 64): {}, # Frontier Design Group, LLC (0, 1, 65): {}, # Recordare LLC (0, 1, 66): {}, # Starr Labs (0, 1, 67): {}, # Voyager Sound Inc. (0, 1, 68): {}, # Manifold Labs (0, 1, 69): {}, # Aviom Inc. (0, 1, 70): {}, # Mixmeister Technology (0, 1, 71): {}, # Notation Software (0, 1, 72): {}, # Mercurial Communications (0, 1, 73): {}, # Wave Arts (0, 1, 74): {}, # Logic Sequencing Devices (0, 1, 75): {}, # Axess Electronics (0, 1, 76): {}, # Muse Research (0, 1, 77): {}, # Open Labs (0, 1, 78): {}, # Guillemot R&D Inc (0, 1, 79): {}, # Samson Technologies (0, 1, 80): {}, # Electronic Theatre Controls (0, 1, 81): {}, # Blackberry (RIM) (0, 1, 82): {}, # Mobileer (0, 1, 83): {}, # Synthogy (0, 1, 84): {}, # Lynx Studio Technology Inc. (0, 1, 85): {}, # Damage Control Engineering LLC (0, 1, 86): {}, # Yost Engineering, Inc. (0, 1, 87): {}, # Brooks & Forsman Designs LLC / DrumLite (0, 1, 88): {}, # Infinite Response (0, 1, 89): {}, # Garritan Corp (0, 1, 90): {}, # Plogue Art et Technologie, Inc (0, 1, 91): {}, # RJM Music Technology (0, 1, 92): {}, # Custom Solutions Software (0, 1, 93): {}, # Sonarcana LLC (0, 1, 94): {}, # Centrance (0, 1, 95): {}, # Kesumo LLC (0, 1, 96): {}, # Stanton (Gibson) (0, 1, 97): {}, # Livid Instruments (0, 1, 98): {}, # First Act / 745 Media (0, 1, 99): {}, # Pygraphics, Inc. (0, 1, 100): {}, # Panadigm Innovations Ltd (0, 1, 101): {}, # Avedis Zildjian Co (0, 1, 102): {}, # Auvital Music Corp (0, 1, 103): {}, # Inspired Instruments Inc (0, 1, 104): {}, # Chris Grigg Designs (0, 1, 105): {}, # Slate Digital LLC (0, 1, 106): {}, # Mixware (0, 1, 107): {}, # Social Entropy (0, 1, 108): {}, # Source Audio LLC (0, 1, 109): {}, # Ernie Ball / Music Man (0, 1, 110): {}, # Fishman Transducers (0, 1, 111): {}, # Custom Audio Electronics (0, 1, 112): {}, # American Audio/DJ (0, 1, 113): {}, # Mega Control Systems (0, 1, 114): {}, # Kilpatrick Audio (0, 1, 115): {}, # iConnectivity (0, 1, 116): {}, # Fractal Audio (0, 1, 117): {}, # NetLogic Microsystems (0, 1, 118): {}, # Music Computing (0, 1, 119): {}, # Nektar Technology Inc (0, 1, 120): {}, # Zenph Sound Innovations (0, 1, 121): {}, # DJTechTools.com (0, 1, 122): {}, # Rezonance Labs (0, 1, 123): {}, # Decibel Eleven (0, 1, 124): {}, # CNMAT (0, 1, 125): {}, # Media Overkill (0, 1, 126): {}, # Confusionists LLC (0, 32, 39): {}, # Acorn Computer (0, 32, 41): {}, # Focusrite/Novation (0, 32, 42): {}, # Samkyung Mechatronics (0, 32, 43): {}, # Medeli Electronics Co. (0, 32, 44): {}, # Charlie Lab SRL (0, 32, 45): {}, # Blue Chip Music Technology (0, 32, 46): {}, # BEE OH Corp (0, 32, 47): {}, # LG Semicon America (0, 32, 48): {}, # TESI (0, 32, 49): {}, # EMAGIC (0, 32, 50): {}, # Behringer GmbH (0, 32, 51): {}, # Access Music Electronics (0, 32, 52): {}, # Synoptic (0, 32, 53): {}, # Hanmesoft (0, 32, 54): {}, # Terratec Electronic GmbH (0, 32, 55): {}, # Proel SpA (0, 32, 56): {}, # IBK MIDI (0, 32, 57): {}, # IRCAM (0, 32, 58): {}, # Propellerhead Software (0, 32, 59): {}, # Red Sound Systems Ltd (0, 32, 60): {}, # Elektron ESI AB (0, 32, 61): {}, # Sintefex Audio (0, 32, 62): {}, # MAM (Music and More) (0, 32, 63): {}, # Amsaro GmbH (0, 32, 64): {}, # CDS Advanced Technology BV (0, 32, 65): {}, # Touched By Sound GmbH (0, 32, 66): {}, # DSP Arts (0, 32, 67): {}, # Phil Rees Music Tech (0, 32, 68): {}, # Stamer Musikanlagen GmbH (0, 32, 69): {}, # Musical Muntaner S.A. dba Soundart (0, 32, 70): {}, # C-Mexx Software (0, 32, 71): {}, # Klavis Technologies (0, 32, 72): {}, # Noteheads AB (0, 32, 73): {}, # Algorithmix (0, 32, 74): {}, # Skrydstrup R&D (0, 32, 75): {}, # Professional Audio Company (0, 32, 76): {}, # NewWave Labs (MadWaves) (0, 32, 77): {}, # Vermona (0, 32, 78): {}, # Nokia (0, 32, 79): {}, # Wave Idea (0, 32, 80): {}, # Hartmann GmbH (0, 32, 81): {}, # Lion's Tracs (0, 32, 82): {}, # Analogue Systems (0, 32, 83): {}, # Focal-JMlab (0, 32, 84): {}, # Ringway Electronics (Chang-Zhou) Co Ltd (0, 32, 85): {}, # Faith Technologies (Digiplug) (0, 32, 86): {}, # Showworks (0, 32, 87): {}, # Manikin Electronic (0, 32, 88): {}, # 1 Come Tech (0, 32, 89): {}, # Phonic Corp (0, 32, 90): {}, # Dolby Australia (Lake) (0, 32, 91): {}, # Silansys Technologies (0, 32, 92): {}, # Winbond Electronics (0, 32, 93): {}, # Cinetix Medien und Interface GmbH (0, 32, 94): {}, # A&G Soluzioni Digitali (0, 32, 95): {}, # Sequentix Music Systems (0, 32, 96): {}, # Oram Pro Audio (0, 32, 97): {}, # Be4 Ltd (0, 32, 98): {}, # Infection Music (0, 32, 99): {}, # Central Music Co. (CME) (0, 32, 100): {}, # genoQs Machines GmbH (0, 32, 101): {}, # Medialon (0, 32, 102): {}, # Waves Audio Ltd (0, 32, 103): {}, # Jerash Labs (0, 32, 104): {}, # Da Fact (0, 32, 105): {}, # Elby Designs (0, 32, 106): {}, # Spectral Audio (0, 32, 107): {}, # Arturia (0, 32, 108): {}, # Vixid (0, 32, 109): {}, # C-Thru Music (0, 32, 110): {}, # Ya Horng Electronic Co LTD (0, 32, 111): {}, # SM Pro Audio (0, 32, 112): {}, # OTO MACHINES (0, 32, 113): {}, # ELZAB S.A., G LAB (0, 32, 114): {}, # Blackstar Amplification Ltd (0, 32, 115): {}, # M3i Technologies GmbH (0, 32, 116): {}, # Gemalto (from Xiring) (0, 32, 117): {}, # Prostage SL (0, 32, 118): {}, # Teenage Engineering (0, 32, 119): {}, # Tobias Erichsen Consulting (0, 32, 120): {}, # Nixer Ltd (0, 32, 121): {}, # Hanpin Electron Co Ltd (0, 32, 122): {}, # "MIDI-hardware" R.Sowa (0, 32, 123): {}, # Beyond Music Industrial Ltd (0, 32, 124): {}, # Kiss Box B.V. (0, 32, 125): {}, # Misa Digital Technologies Ltd (0, 32, 126): {}, # AI Musics Technology Inc (0, 32, 127): {}, # Serato Inc LP (0, 33, 0): {}, # Limex Music Handles GmbH (0, 33, 1): {}, # Kyodday/Tokai (0, 33, 2): { 0x02: ("Shruthi-1", "shruthi-1"), }, # Mutable Instruments (0, 33, 3): {}, # PreSonus Software Ltd (0, 33, 4): {}, # Xiring (0, 33, 5): {}, # Fairlight Instruments Pty Ltd (0, 33, 6): {}, # Musicom Lab (0, 33, 7): {}, # VacoLoco (0, 33, 8): {}, # RWA (Hong Kong) Limited (0, 33, 9): {}, # Native Instruments (0, 33, 10): {}, # Naonext (0, 33, 11): {}, # MFB (0, 33, 12): {}, # Teknel Research (0, 33, 13): {}, # Ploytec GmbH (0, 33, 14): {}, # Surfin Kangaroo Studio (0, 33, 15): {}, # Philips Electronics HK Ltd (0, 33, 16): {}, # ROLI Ltd (0, 64, 0): {}, # Crimson Technology Inc. (0, 64, 1): {}, # Softbank Mobile Corp (0, 64, 3): {}, # D&M Holdings Inc. } python-rtmidi-1.1.0/examples/wavetablemodstep.py000066400000000000000000000062571307643736300221040ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # wavetablemodstep.py # """Play a note and step through MIDI Control Change #1 (modulation) values. Optionally allows to send a Control Change #70 first, to set the wavetable of the current sound on a Waldorf Microwave II/XT(k) synthesizer. I use this with a Microwave sound program where the CC #1 is mapped to wavetable position of oscillator 1. This script then allows me to listen to all the waves in a selected wavetable in succession. """ import time import rtmidi from rtmidi.midiconstants import (CONTROL_CHANGE, MODULATION_WHEEL, NOTE_OFF, NOTE_ON, RESET_ALL_CONTROLLERS) CC_SET_WAVETABLE = 70 class Midi(object): """Encapsulate MIDI output.""" def __init__(self, port): self.midi = rtmidi.MidiOut() self.midi.open_port(port) def play_stepping(self, note, cc, dur=0.2, step=1, vel=64, rvel=None, ch=0): """Play given note and step through ctrl values over time.""" # note on note &= 0x7F ch &= 0x0F self.midi.send_message([CONTROL_CHANGE | ch, cc, 0]) time.sleep(0.1) self.midi.send_message([NOTE_ON | ch, note, vel & 0x7F]) # step through modulation controller values for i in range(0, 128, step): self.midi.send_message([CONTROL_CHANGE | ch, cc, i]) time.sleep(dur) # note off self.midi.send_message( [NOTE_OFF | ch, note, (vel if rvel is None else rvel) & 0x7F]) def reset_controllers(self, ch=0): """Reset controllers on given channel.""" self.midi.send_message( [CONTROL_CHANGE | (ch & 0xF), RESET_ALL_CONTROLLERS, 0]) def set_wavetable(self, wt, ch=0): """Set wavetable for current sound to given number.""" self.midi.send_message( [CONTROL_CHANGE | (ch & 0xF), CC_SET_WAVETABLE, wt & 0x7F]) def close(self): """Close MIDI outpurt.""" self.midi.close_port() del self.midi if __name__ == '__main__': import argparse argparser = argparse.ArgumentParser() aadd = argparser.add_argument aadd('-c', '--channel', type=int, default=1, help="MIDI channel (1-based, default: %(default)s)") aadd('-C', '--controller', type=int, default=MODULATION_WHEEL, help="MIDI controller number (default: %(default)s)") aadd('-p', '--port', type=int, default=0, help="MIDI output port (default: %(default)s)") aadd('-l', '--length', type=float, default=0.3, help="Length (in sec.) of each wave (default: %(default)s)") aadd('-n', '--note', type=int, default=60, help="MIDI note number to play (default: %(default)s)") aadd('-w', '--wavetable', type=int, help="Send CC #70 to set wavetable number (1-based, default: none)") args = argparser.parse_args() m = Midi(args.port) ch = max(0, args.channel - 1) if args.wavetable: m.set_wavetable(args.wavetable - 1, ch=args.channel - 1) time.sleep(0.1) try: m.reset_controllers(ch) m.play_stepping(args.note, args.controller, dur=args.length, step=2, ch=ch) finally: m.reset_controllers(ch) m.close() python-rtmidi-1.1.0/fill_template.py000077500000000000000000000043321307643736300175340ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """Custom distutils command to fill placeholders in text files with release meta-data. """ from os.path import join from string import Template try: basestring # noqa except: basestring = str from distutils.core import Command from distutils.dist import DistributionMetadata from distutils.log import error, info from distutils.util import split_quoted DistributionMetadata.templates = None class FillTemplate(Command): """Custom distutils command to fill text templates with release meta data. """ description = "Fill placeholders in documentation text file templates" user_options = [ ('templates=', None, "Template text files to fill") ] def initialize_options(self): self.templates = '' self.template_ext = '.in' def finalize_options(self): if isinstance(self.templates, basestring): self.templates = split_quoted(self.templates) self.templates += getattr(self.distribution.metadata, 'templates', None) or [] for tmpl in self.templates: if not tmpl.endswith(self.template_ext): raise ValueError("Template file '%s' does not have expected " "extension '%s'." % (tmpl, self.template_ext)) def run(self): metadata = self.get_metadata() for infilename in self.templates: try: info("Reading template '%s'...", infilename) with open(infilename) as infile: tmpl = Template(infile.read()) outfilename = infilename.rstrip(self.template_ext) info("Writing filled template to '%s'.", outfilename) with open(outfilename, 'w') as outfile: outfile.write(tmpl.safe_substitute(metadata)) except: error("Could not open template '%s'.", infilename) def get_metadata(self): data = dict() for attr in self.distribution.metadata.__dict__: if not callable(attr): data[attr] = getattr(self.distribution.metadata, attr) data['cpp_info'] = open(join("src", '_rtmidi.cpp')).readline().strip() return data python-rtmidi-1.1.0/requirements-dev.txt000066400000000000000000000006141307643736300203750ustar00rootroot00000000000000alabaster==0.7.9 Babel==2.3.4 coverage==4.2 Cython==0.24.1 docutils==0.12 flake8==3.0.4 imagesize==0.7.1 Jinja2==2.8 MarkupSafe==0.23 mccabe==0.5.2 mock==2.0.0 pbr==1.10.0 pluggy==0.4.0 py==1.4.31 pycodestyle==2.0.0 pydocstyle==1.1.1 pyflakes==1.3.0 Pygments==2.1.3 pytest==3.0.3 pytz==2016.7 six==1.10.0 snowballstemmer==1.2.1 Sphinx==1.4.8 sphinx-rtd-theme==0.1.9 tox==2.3.1 virtualenv==15.0.3 python-rtmidi-1.1.0/rtmidi/000077500000000000000000000000001307643736300156245ustar00rootroot00000000000000python-rtmidi-1.1.0/rtmidi/__init__.py000066400000000000000000000003121307643736300177310ustar00rootroot00000000000000# -*- coding:utf-8 -*- from __future__ import absolute_import from .release import version as __version__ # noqa from ._rtmidi import * # noqa from ._rtmidi import __doc__ # noqa del absolute_import python-rtmidi-1.1.0/rtmidi/midiconstants.py000066400000000000000000000163641307643736300210670ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Definitions of midi events, controller numbers and parameters.""" ################################################### # Midi channel events (the most common events) # Also called "Channel Voice Messages" NOTE_OFF = 0x80 # 1000cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity) NOTE_ON = 0x90 # 1001cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity) POLYPHONIC_PRESSURE = POLY_PRESSURE = 0xA0 # 1010cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity) # see Channel Mode Messages for Controller Numbers CONTROLLER_CHANGE = CONTROL_CHANGE = 0xB0 # 1011cccc 0ccccccc 0vvvvvvv (channel, controller, value) PROGRAM_CHANGE = 0xC0 # 1100cccc 0ppppppp (channel, program) CHANNEL_PRESSURE = MONO_PRESSURE = 0xD0 # 1101cccc 0ppppppp (channel, pressure) PITCH_BEND = 0xE0 # 1110cccc 0vvvvvvv 0wwwwwww (channel, value-lo, value-hi) ################################################### # Channel Mode Messages (Continuous Controller) # All CCs have the same status byte (0xBn). # The controller number is the first data byte # High resolution continuous controllers (MSB) BANK_SELECT = BANK_SELECT_MSB = 0x00 MODULATION_WHEEL = MODULATION_WHEEL_MSB = 0x01 BREATH_CONTROLLER = BREATH_CONTROLLER_MSB = 0x02 FOOT_CONTROLLER = FOOT_CONTROLLER_MSB = 0x04 PORTAMENTO_TIME = PORTAMENTO_TIME_MSB = 0x05 DATA_ENTRY = DATA_ENTRY_MSB = 0x06 CHANNEL_VOLUME = CHANNEL_VOLUME_MSB = 0x07 BALANCE = BALANCE_MSB = 0x08 PAN = PAN_MSB = 0x0A EXPRESSION_CONTROLLER = EXPRESSION_CONTROLLER_MSB = 0x0B EFFECT_CONTROL_1 = EFFECT_CONTROL_1_MSB = 0x0C EFFECT_CONTROL_2 = EFFECT_CONTROL_2_MSB = 0x0D GENERAL_PURPOSE_CONTROLLER_1 = GENERAL_PURPOSE_CONTROLLER_1_MSB = 0x10 GENERAL_PURPOSE_CONTROLLER_2 = GENERAL_PURPOSE_CONTROLLER_2_MSB = 0x11 GENERAL_PURPOSE_CONTROLLER_3 = GENERAL_PURPOSE_CONTROLLER_3_MSB = 0x12 GENERAL_PURPOSE_CONTROLLER_4 = GENERAL_PURPOSE_CONTROLLER_4_MSB = 0x13 # High resolution continuous controllers (LSB) BANK_SELECT_LSB = 0x20 MODULATION_WHEEL_LSB = 0x21 BREATH_CONTROLLER_LSB = 0x22 FOOT_CONTROLLER_LSB = 0x24 PORTAMENTO_TIME_LSB = 0x25 DATA_ENTRY_LSB = 0x26 CHANNEL_VOLUME_LSB = 0x27 BALANCE_LSB = 0x28 PAN_LSB = 0x2A EXPRESSION_CONTROLLER_LSB = 0x2B EFFECT_CONTROL_1_LSB = 0x2C EFFECT_CONTROL_2_LSB = 0x2D GENERAL_PURPOSE_CONTROLLER_1_LSB = 0x30 GENERAL_PURPOSE_CONTROLLER_2_LSB = 0x31 GENERAL_PURPOSE_CONTROLLER_3_LSB = 0x32 GENERAL_PURPOSE_CONTROLLER_4_LSB = 0x33 # Switches # off: value <= 63, on: value >= 64 SUSTAIN = SUSTAIN_ONOFF = 0x40 PORTAMENTO = PORTAMENTO_ONOFF = 0x41 SOSTENUTO = SOSTENUTO_ONOFF = 0x42 SOFT_PEDAL = SOFT_PEDAL_ONOFF = 0x43 LEGATO = LEGATO_ONOFF = 0x44 HOLD_2 = HOLD_2_ONOFF = 0x45 # Low resolution continuous controllers # Sound Variation; FX: Exciter On/Off SOUND_CONTROLLER_1 = 0x46 # Harmonic Content; FX: Compressor On/Off SOUND_CONTROLLER_2 = 0x47 # Release Time; FX: Distortion On/Off SOUND_CONTROLLER_3 = 0x48 # Attack Time; FX: EQ On/Off SOUND_CONTROLLER_4 = 0x49 # Brightness; FX: Expander On/Off SOUND_CONTROLLER_5 = 0x4A # Decay Time; FX: Reverb On/Off SOUND_CONTROLLER_6 = 0x4B # Vibrato Rate; FX: Delay On/Off SOUND_CONTROLLER_7 = 0x4C # Vibrato Depth; FX: Pitch Transpose On/Off SOUND_CONTROLLER_8 = 0x4D # Vibrato Delay; FX: Flange/Chorus On/Off SOUND_CONTROLLER_9 = 0x4E # Undefined; FX: Special Effects On/Off SOUND_CONTROLLER_10 = 0x4F GENERAL_PURPOSE_CONTROLLER_5 = 0x50 GENERAL_PURPOSE_CONTROLLER_6 = 0x51 GENERAL_PURPOSE_CONTROLLER_7 = 0x52 GENERAL_PURPOSE_CONTROLLER_8 = 0x53 # PTC, 0vvvvvvv is the source Note number PORTAMENTO_CONTROL = PTC = 0x54 HIGH_RESOLUTION_VELOCITY_PREFIX = 0x58 # Reverb Send Level, formerly Ext. Effects Depth EFFECTS_1 = EFFECTS_1_DEPTH = 0x5B # formerly Tremelo Depth EFFECTS_2 = EFFECTS_2_DEPTH = 0x5C # Chorus Send Level, formerly Chorus Depth EFFECTS_3 = EFFECTS_3_DEPTH = 0x5D # formerly Celeste(Detune) Depth EFFECTS_4 = EFFECTS_4_DEPTH = 0x5E # formerly Phaser Depth EFFECTS_5 = EFFECTS_5_DEPTH = 0x5F # controller value byte should be 0 DATA_INCREMENT = 0x60 # controller value byte should be 0 DATA_DECREMENT = 0x61 NRPN_LSB = NON_REGISTERED_PARAMETER_NUMBER_LSB = 0x62 NRPN_MSB = NON_REGISTERED_PARAMETER_NUMBER_MSB = 0x63 RPN_LSB = REGISTERED_PARAMETER_NUMBER_LSB = 0x64 RPN_MSB = REGISTERED_PARAMETER_NUMBER_MSB = 0x65 # Channel Mode messages # controller value byte should be 0 ALL_SOUND_OFF = 0x78 # controller value byte should be 0 RESET_ALL_CONTROLLERS = 0x79 # 0 = off, 127 = on LOCAL_CONTROL = LOCAL_CONTROL_ONOFF = 0x7A # controller value byte should be 0 ALL_NOTES_OFF = 0x7B # controller value byte should be 0, also causes ANO OMNI_MODE_OFF = 0x7C # controller value byte should be 0, also causes ANO OMNI_MODE_ON = 0x7D # Mono Mode on / Poly Off; also causes ANO # 1011nnnn 01111110 0000vvvv # vvvv > 0 : Number of channels to use (Omni Off). # vvvv = 0 : Use all available channels (Omni On) MONO_MODE_ON = 0x7E # Poly Mode On / Mono Off # controller value byte should be 0, also causes ANO POLY_MODE_ON = 0x7F ################################################### # System Common Messages, for all channels # 11110000 0iiiiiii 0ddddddd ... 11110111 SYSTEM_EXCLUSIVE = 0xF0 # MIDI Time Code Quarter Frame # 11110001 MIDI_TIME_CODE = MTC = 0xF1 # 11110010 0vvvvvvv 0wwwwwww (lo-position, hi-position) SONG_POSITION_POINTER = 0xF2 # 11110011 0sssssss (songnumber) SONG_SELECT = 0xF3 # 11110100 (0xF4) is undefined # 11110101 (0xF5) is undefined # 11110110 TUNING_REQUEST = TUNE_REQUEST = 0xF6 # 11110111 # End of system exclusive END_OF_EXCLUSIVE = 0xF7 ################################################### # Midifile meta-events SEQUENCE_NUMBER = 0x00 # 00 02 ss ss (seq-number) TEXT = 0x01 # 01 len text... COPYRIGHT = 0x02 # 02 len text... SEQUENCE_NAME = 0x03 # 03 len text... INSTRUMENT_NAME = 0x04 # 04 len text... LYRIC = 0x05 # 05 len text... MARKER = 0x06 # 06 len text... CUEPOINT = 0x07 # 07 len text... PROGRAM_NAME = 0x08 # 08 len text... DEVICE_NAME = 0x09 # 09 len text... MIDI_CH_PREFIX = 0x20 # MIDI channel prefix assignment (deprecated) MIDI_PORT = 0x21 # 21 01 port, deprecated but still used END_OF_TRACK = 0x2F # 2f 00 TEMPO = 0x51 # 51 03 tt tt tt (tempo in µs/quarternote) SMTP_OFFSET = 0x54 # 54 05 hh mm ss ff xx TIME_SIGNATURE = 0x58 # 58 04 nn dd cc bb KEY_SIGNATURE = 0x59 # 59 02 sf mi (sf = number of sharps(+) or flats(-) # mi = major(0) or minor (1)) SPECIFIC = 0x7F # Sequencer specific event ################################################### # System Realtime messages # These should not occur in midi files TIMING_CLOCK = 0xF8 # 0xF9 is undefined SONG_START = 0xFA SONG_CONTINUE = 0xFB SONG_STOP = 0xFC # 0xFD is undefined ACTIVE_SENSING = 0xFE SYSTEM_RESET = 0xFF ################################################### # META EVENT, it is used only in midi files. # In transmitted data it means system reset!!! # 11111111 META_EVENT = 0xFF ESCAPE_SEQUENCE = 0xF7 ################################################### # Misc constants FILE_HEADER = 'MThd' TRACK_HEADER = 'MTrk' # Timecode resolution: frames per second FPS_24 = 0xE8 FPS_25 = 0xE7 FPS_29 = 0xE3 FPS_30 = 0xE2 ################################################### # Helper functions def is_status(byte): """Return True if the given byte is a MIDI status byte, False otherwise.""" return (byte & 0x80) == 0x80 # 1000 0000 python-rtmidi-1.1.0/rtmidi/midiutil.py000066400000000000000000000211371307643736300200220ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # util.py # """Collection of utility functions for handling MIDI I/O and ports. Currently contains functions to list MIDI input/output ports, to get the RtMidi API to use from the environment and to open MIDI ports. """ from __future__ import print_function, unicode_literals import logging import os try: raw_input except NameError: # Python 3 raw_input = input basestring = str import rtmidi __all__ = ( 'get_api_from_environment', 'list_available_ports', 'list_input_ports', 'list_output_ports', 'open_midiinput', 'open_midioutput', 'open_midiport', ) log = logging.getLogger(__name__) def _prompt_for_virtual(type_): """Prompt on the console whether a virtual MIDI port should be opened.""" return raw_input("Do you want to create a virtual MIDI %s port? (y/N) " % type_).strip().lower() in ['y', 'yes'] def get_api_from_environment(api=rtmidi.API_UNSPECIFIED): """Return RtMidi API specified in the environment if any. If the optional api argument is ``rtmidi.API_UNSPECIFIED`` (the default), look in the environment variable ``RTMIDI_API`` for the name of the RtMidi API to use. Valid names are ``LINUX_ALSA``, ``UNIX_JACK``, ``MACOSX_CORE``, ``WINDOWS_MM`` and ``RTMIDI_DUMMY``. If no valid value is found, rtmidi.API_UNSPECIFIED will be used. Returns a ``rtmmidi.API_*`` constant. """ if api == rtmidi.API_UNSPECIFIED and 'RTMIDI_API' in os.environ: try: api_name = os.environ['RTMIDI_API'].upper() api = getattr(rtmidi, 'API_' + api_name) except AttributeError: log.warning("Ignoring unknown API '%s' in environment variable " "RTMIDI_API." % api_name) return api def list_available_ports(ports=None, midiio=None): """List MIDI ports given or available on given MIDI I/O instance.""" if ports is None: ports = midiio.get_ports() type_ = " input" if isinstance(midiio, rtmidi.MidiIn) else " ouput" else: type_ = '' if ports: print("Available MIDI{} ports:\n".format(type_)) for portno, name in enumerate(ports): print("[{}] {}".format(portno, name)) else: print("No MIDI{} ports found.".format(type_)) print() def list_input_ports(api=rtmidi.API_UNSPECIFIED): """List available MIDI input ports. Optionally the RtMidi API can be passed with the ``api`` argument. If not it will be determined via the ``get_api_from_environment`` function. """ midiin = rtmidi.MidiIn(get_api_from_environment(api)) list_available_ports(midiio=midiin) def list_output_ports(api=rtmidi.API_UNSPECIFIED): """List available MIDI output ports. Optionally the RtMidi API can be passed with the ``api`` argument. If not it will be determined via the ``get_api_from_environment`` function. """ midiout = rtmidi.MidiOut(get_api_from_environment(api)) list_available_ports(midiio=midiout) def open_midiport(port=None, type_="input", api=rtmidi.API_UNSPECIFIED, use_virtual=False, interactive=True, client_name=None, port_name=None): """Open MIDI port for input or output and return MidiIn/MidiOut instance. Arguments: ``port`` A MIDI port number or (substring of) a port name or ``None``. Available ports are enumerated starting from zero separately for input and output ports. If only a substring of a port name is given, the first matching port is used. ``type_`` Must be ``"input"`` or ``"output"``. Determines whether a ``MidiIn`` or ``MidiOut`` instance will be created and returned. ``api`` Select the low-level MIDI API to use. Defaults to ``API_UNSPECIFIED``, The specified api will be passed to the ``get_api_from_environment`` function and its return value will be used. If it's ``API_UNSPECIFIED`` the first compiled-in API, which has any input resp. output ports available, will be used. ``use_virtual`` If ``port is ``None``, should a virtual MIDI port be opened? Defaults to ``False``. ``interactive`` If port is ``None`` or no MIDI port matching the port number or name is available, should the user be prompted on the console whether to open a virtual MIDI port (if ``use_virtual`` is ``True``) and/or with a list of available MIDI ports and the option to choose one? Defaults to ``True``. ``client_name`` The name of the MIDI client passed when instantiating a `MidiIn`` or ``MidiOut`` object. See the documentation of the constructor for these classes for the default values and caveats and OS-dependent ideosyncracies regarding the client name. ``port_name`` The name of the MIDI port passed to the ``open_port`` or ``open_virtual_port`` method of the new ``MidiIn`` or ``MidiOut`` instance. See the documentation of the ``open_port`` resp. ``open_virtual_port`` methods for the default values and caveats when wanting to change the port name afterwards. Returns: A two-element tuple of a new ``MidiIn`` or ``MidiOut`` instance and the name of the MIDI port which was opened. Exceptions: ``KeyboardInterrupt, EOFError`` Raised when the user presses Control-C or Control-D during a console prompt. ``IOError`` Raised when no MIDI input or output ports (depending on what was requested) are available. ``ValueError`` Raised when an invalid port number or name is passed and ``interactive`` is ``False``. """ midiclass_ = rtmidi.MidiIn if type_ == "input" else rtmidi.MidiOut log.debug("Creating %s object.", midiclass_.__name__) api = get_api_from_environment(api) midiobj = midiclass_(api, name=client_name) type_ = "input" if isinstance(midiobj, rtmidi.MidiIn) else "output" ports = midiobj.get_ports() if port is None: try: if (midiobj.get_current_api() != rtmidi.API_WINDOWS_MM and (use_virtual or (interactive and _prompt_for_virtual(type_)))): if not port_name: port_name = "Virtual MIDI %s" % type_ log.info("Opening virtual MIDI %s port.", type_) midiobj.open_virtual_port(port_name) return midiobj, port_name except (KeyboardInterrupt, EOFError): del midiobj print('') raise if len(ports) == 0: del midiobj raise IOError("No MIDI %s ports found." % type_) raise IOError("No MIDI %s ports found." % type_) try: port = int(port) except (TypeError, ValueError): if isinstance(port, basestring): portspec = port for portno, name in enumerate(ports): if portspec in name: port = portno break else: log.warning("No port matching '%s' found.", portspec) port = None while interactive and (port is None or (port < 0 or port >= len(ports))): list_available_ports(ports) try: r = raw_input("Select MIDI %s port (Control-C to exit): " % type_) port = int(r) except (KeyboardInterrupt, EOFError): del midiobj print('') raise except (ValueError, TypeError): port = None if port is not None and (port >= 0 and port < len(ports)): if not port_name: port_name = ports[port] log.info("Opening MIDI %s port #%i (%s)." % (type_, port, port_name)) midiobj.open_port(port, port_name) return midiobj, port_name else: raise ValueError("Invalid port.") def open_midiinput(port=None, api=rtmidi.API_UNSPECIFIED, use_virtual=False, interactive=True, client_name=None, port_name=None): """Open a MIDI port for input and return a MidiIn instance. See the ``open_midiport`` functon for information on parameters. """ return open_midiport(port, "input", api, use_virtual, interactive, client_name, port_name) def open_midioutput(port=None, api=rtmidi.API_UNSPECIFIED, use_virtual=False, interactive=True, client_name=None, port_name=None): """Open a MIDI port for output and return a MidiOut instance. See the ``open_midiport`` function for information on parameters. """ return open_midiport(port, "output", api, use_virtual, interactive, client_name, port_name) python-rtmidi-1.1.0/rtmidi/release.py000066400000000000000000000062541307643736300176250ustar00rootroot00000000000000# -*- coding:utf-8 -*- # # release.py - release information for the rtmidi package # """A Python wrapper for the RtMidi C++ library written with Cython. Overview ======== RtMidi_ is a set of C++ classes which provides a concise and simple, cross-platform API (Application Programming Interface) for realtime MIDI input/output across Linux (ALSA & JACK), Macintosh OS X (CoreMIDI & JACK), and Windows (MultiMedia Library) operating systems. python-rtmidi_ is a Python binding for RtMidi implemented with Cython_ and provides a thin wrapper around the RtMidi C++ interface. The API is basically the same as the C++ one but with the naming scheme of classes, methods and parameters adapted to the Python PEP-8 conventions and requirements of the Python package naming structure. **python-rtmidi** supports Python 2 (tested with Python 2.7) and Python 3 (3.3, 3.4, 3.5). Usage example ------------- Here's a quick example of how to use **python-rtmidi** to open the first available MIDI output port and send a middle C note on MIDI channel 1:: import time import rtmidi midiout = rtmidi.MidiOut() available_ports = midiout.get_ports() if available_ports: midiout.open_port(0) else: midiout.open_virtual_port("My virtual output") note_on = [0x90, 60, 112] # channel 1, middle C, velocity 112 note_off = [0x80, 60, 0] midiout.send_message(note_on) time.sleep(0.5) midiout.send_message(note_off) del midiout More usage examples can be found in the ``examples`` directory of the source distribution. The documentation_ provides installation instructions and an API reference. .. _rtmidi: http://www.music.mcgill.ca/~gary/rtmidi/index.html .. _python-rtmidi: %(url)s .. _cython: http://cython.org/ .. _ipython: http://ipython.org/ .. _documentation: https://python-rtmidi.readthedocs.io/ """ name = 'python-rtmidi' version = '1.1.0' description = __doc__.splitlines() keywords = 'rtmidi, midi, music' author = 'Christopher Arndt' author_email = 'chris@chrisarndt.de' url = 'https://chrisarndt.de/projects/%s/' % name repository = 'https://github.com/SpotlightKid/%s.git' % name download_url = 'https://pypi.python.org/pypi/python-rtmidi' license = 'MIT License' platforms = 'POSIX, Windows, MacOS X' long_description = "\n".join(description[2:]) % locals() description = description[0] classifiers = """\ Development Status :: 5 - Production/Stable Environment :: MacOS X Environment :: Win32 (MS Windows) Environment :: Console Intended Audience :: Developers License :: OSI Approved :: MIT License Operating System :: Microsoft :: Windows Operating System :: POSIX Operating System :: MacOS :: MacOS X Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Topic :: Multimedia :: Sound/Audio :: MIDI Topic :: Software Development :: Libraries :: Python Modules """ classifiers = [c.strip() for c in classifiers.splitlines() if c.strip() and not c.startswith('#')] try: # Python 2.x del c # noqa except: pass python-rtmidi-1.1.0/setup.cfg000066400000000000000000000012501307643736300161530ustar00rootroot00000000000000[egg_info] tag_build = .dev #tag_date = true #tag_svn_revision = true [aliases] # A handy alias to build a release (source and egg) release = build filltmpl egg_info -RDb "" sdist --formats=zip,gztar,bztar # A handy alias to upload a release to pypi release_upload = build filltmpl egg_info -RDb "" sdist --formats=zip,gztar,bztar bdist_wheel upload [filltmpl] templates = INSTALL.rst.in [build_sphinx] source-dir = docs/ build-dir = docs/build all_files = 1 [upload_sphinx] upload-dir = docs/build/html [flake8] ignore = E116, E265, E266, E731 max-line-length = 100 exclude = examples/osc2midi/lru_cache.py [pydocstyle] match = (?!test_).*\.pyx? match_dir = (src|rtmidi) python-rtmidi-1.1.0/setup.py000066400000000000000000000113711307643736300160510ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """Setup file for the Cython rtmidi wrapper.""" import sys from ctypes.util import find_library from os.path import exists, join from setuptools import setup # needs to stay before the imports below! from distutils.dist import DistributionMetadata from distutils.extension import Extension from setuptools.command.test import test as TestCommand from fill_template import FillTemplate try: from Cython.Build import cythonize except ImportError: cythonize = None class PyTest(TestCommand): """Custom setup command to run tests via pytest.""" user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] def initialize_options(self): TestCommand.initialize_options(self) self.pytest_args = [] def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): # import here, cause outside the eggs aren't loaded import pytest errno = pytest.main(self.pytest_args) sys.exit(errno) # source package structure SRC_DIR = "src" PKG_DIR = "rtmidi" # Add custom distribution meta-data, avoids warning when running setup DistributionMetadata.repository = None # read meta-data from release.py setup_opts = {} release_info = join(PKG_DIR, 'release.py') exec(compile(open(release_info).read(), release_info, 'exec'), {}, setup_opts) # Add our own custom distutils command to create *.rst files from templates # Template files are listed in setup.cfg setup_opts.setdefault('cmdclass', {})['filltmpl'] = FillTemplate # Add custom test command setup_opts['cmdclass']['test'] = PyTest # Set up options for compiling the _rtmidi Extension if cythonize: sources = [join(SRC_DIR, "_rtmidi.pyx"), join(SRC_DIR, "RtMidi.cpp")] elif exists(join(SRC_DIR, "_rtmidi.cpp")): cythonize = lambda x: x # noqa sources = [join(SRC_DIR, "_rtmidi.cpp"), join(SRC_DIR, "RtMidi.cpp")] else: print("""\ Could not import Cython. Cython >= 0.17 is required to compile the Cython source into the C++ source. Install Cython from https://pypi.python.org/pypi/Cython or use the pre-generated '_rtmidi.cpp' file from the python-rtmidi source distribution. """) sys.exit(1) define_macros = [] include_dirs = [SRC_DIR] libraries = [] library_dirs = [] extra_link_args = [] extra_compile_args = [] alsa = coremidi = jack = winmm = True if '--no-alsa' in sys.argv: alsa = False sys.argv.remove('--no-alsa') if '--no-coremidi' in sys.argv: coremidi = False sys.argv.remove('--no-coremidi') if '--no-jack' in sys.argv: jack = False sys.argv.remove('--no-jack') if '--no-winmm' in sys.argv: winmm = False sys.argv.remove('--no-winmm') if sys.platform.startswith('linux'): if alsa and find_library('asound'): define_macros += [("__LINUX_ALSA__", None)] libraries += ['asound'] if jack and find_library('jack'): define_macros += [('__UNIX_JACK__', None)] libraries += ['jack'] if not find_library('pthread'): print("The 'pthread' library is required to build python-rtmidi on" "Linux. Please install the libc6 development package") sys.exit(1) libraries += ["pthread"] elif sys.platform.startswith('darwin'): if jack and find_library('jack'): define_macros += [('__UNIX_JACK__', None)] libraries += ['jack'] if coremidi: define_macros += [('__MACOSX_CORE__', '')] extra_compile_args += ['-frtti'] extra_link_args += [ '-framework', 'CoreAudio', '-framework', 'CoreMIDI', '-framework', 'CoreFoundation'] elif sys.platform.startswith('win'): extra_compile_args += ['/EHsc'] if winmm: define_macros += [('__WINDOWS_MM__', None)] libraries += ["winmm"] else: print("""\ WARNING: This operating system (%s) is not supported by RtMidi. Linux, Mac OS X (>= 10.5), Windows (XP, Vista, 7/8/10) are supported. Continuing and hoping for the best... """ % sys.platform) # define _rtmidi Extension extensions = [ Extension( PKG_DIR + "._rtmidi", sources=sources, language="c++", define_macros=define_macros, include_dirs=include_dirs, libraries=libraries, library_dirs=library_dirs, extra_compile_args=extra_compile_args, extra_link_args=extra_link_args ) ] # Finally, set up our distribution setup( packages=['rtmidi'], ext_modules=cythonize(extensions), tests_require=['pytest', 'mock'], # On systems without a RTC (e.g. Raspberry Pi), system time will be the # Unix epoch when booted without network connection, which makes zip fail, # because it does not support dates < 1980-01-01. zip_safe=False, **setup_opts ) python-rtmidi-1.1.0/src/000077500000000000000000000000001307643736300151235ustar00rootroot00000000000000python-rtmidi-1.1.0/src/RtMidi.cpp000066400000000000000000002667511307643736300170400ustar00rootroot00000000000000/**********************************************************************/ /*! \class RtMidi \brief An abstract base class for realtime MIDI input/output. This class implements some common functionality for the realtime MIDI input/output subclasses RtMidiIn and RtMidiOut. RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/ RtMidi: realtime MIDI i/o C++ classes Copyright (c) 2003-2016 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is asked to send the modifications to the original developer so that they can be incorporated into the canonical version. This is, however, not a binding provision of this license. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**********************************************************************/ #include "RtMidi.h" #include #if defined(__MACOSX_CORE__) #if TARGET_OS_IPHONE #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos #endif #endif //*********************************************************************// // RtMidi Definitions //*********************************************************************// RtMidi :: RtMidi() : rtapi_(0) { } RtMidi :: ~RtMidi() { if ( rtapi_ ) delete rtapi_; rtapi_ = 0; } std::string RtMidi :: getVersion( void ) throw() { return std::string( RTMIDI_VERSION ); } void RtMidi :: getCompiledApi( std::vector &apis ) throw() { apis.clear(); // The order here will control the order of RtMidi's API search in // the constructor. #if defined(__MACOSX_CORE__) apis.push_back( MACOSX_CORE ); #endif #if defined(__LINUX_ALSA__) apis.push_back( LINUX_ALSA ); #endif #if defined(__UNIX_JACK__) apis.push_back( UNIX_JACK ); #endif #if defined(__WINDOWS_MM__) apis.push_back( WINDOWS_MM ); #endif #if defined(__RTMIDI_DUMMY__) apis.push_back( RTMIDI_DUMMY ); #endif } //*********************************************************************// // RtMidiIn Definitions //*********************************************************************// void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit ) { if ( rtapi_ ) delete rtapi_; rtapi_ = 0; #if defined(__UNIX_JACK__) if ( api == UNIX_JACK ) rtapi_ = new MidiInJack( clientName, queueSizeLimit ); #endif #if defined(__LINUX_ALSA__) if ( api == LINUX_ALSA ) rtapi_ = new MidiInAlsa( clientName, queueSizeLimit ); #endif #if defined(__WINDOWS_MM__) if ( api == WINDOWS_MM ) rtapi_ = new MidiInWinMM( clientName, queueSizeLimit ); #endif #if defined(__MACOSX_CORE__) if ( api == MACOSX_CORE ) rtapi_ = new MidiInCore( clientName, queueSizeLimit ); #endif #if defined(__RTMIDI_DUMMY__) if ( api == RTMIDI_DUMMY ) rtapi_ = new MidiInDummy( clientName, queueSizeLimit ); #endif } RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit ) : RtMidi() { if ( api != UNSPECIFIED ) { // Attempt to open the specified API. openMidiApi( api, clientName, queueSizeLimit ); if ( rtapi_ ) return; // No compiled support for specified API value. Issue a warning // and continue as if no API was specified. std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" << std::endl; } // Iterate through the compiled APIs and return as soon as we find // one with at least one port or we reach the end of the list. std::vector< RtMidi::Api > apis; getCompiledApi( apis ); for ( unsigned int i=0; igetPortCount() ) break; } if ( rtapi_ ) return; // It should not be possible to get here because the preprocessor // definition __RTMIDI_DUMMY__ is automatically defined if no // API-specific definitions are passed to the compiler. But just in // case something weird happens, we'll throw an error. std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!"; throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); } RtMidiIn :: ~RtMidiIn() throw() { } //*********************************************************************// // RtMidiOut Definitions //*********************************************************************// void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string clientName ) { if ( rtapi_ ) delete rtapi_; rtapi_ = 0; #if defined(__UNIX_JACK__) if ( api == UNIX_JACK ) rtapi_ = new MidiOutJack( clientName ); #endif #if defined(__LINUX_ALSA__) if ( api == LINUX_ALSA ) rtapi_ = new MidiOutAlsa( clientName ); #endif #if defined(__WINDOWS_MM__) if ( api == WINDOWS_MM ) rtapi_ = new MidiOutWinMM( clientName ); #endif #if defined(__MACOSX_CORE__) if ( api == MACOSX_CORE ) rtapi_ = new MidiOutCore( clientName ); #endif #if defined(__RTMIDI_DUMMY__) if ( api == RTMIDI_DUMMY ) rtapi_ = new MidiOutDummy( clientName ); #endif } RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string clientName ) { if ( api != UNSPECIFIED ) { // Attempt to open the specified API. openMidiApi( api, clientName ); if ( rtapi_ ) return; // No compiled support for specified API value. Issue a warning // and continue as if no API was specified. std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" << std::endl; } // Iterate through the compiled APIs and return as soon as we find // one with at least one port or we reach the end of the list. std::vector< RtMidi::Api > apis; getCompiledApi( apis ); for ( unsigned int i=0; igetPortCount() ) break; } if ( rtapi_ ) return; // It should not be possible to get here because the preprocessor // definition __RTMIDI_DUMMY__ is automatically defined if no // API-specific definitions are passed to the compiler. But just in // case something weird happens, we'll thrown an error. std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!"; throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); } RtMidiOut :: ~RtMidiOut() throw() { } //*********************************************************************// // Common MidiApi Definitions //*********************************************************************// MidiApi :: MidiApi( void ) : apiData_( 0 ), connected_( false ), errorCallback_(0), firstErrorOccurred_(false), errorCallbackUserData_(0) { } MidiApi :: ~MidiApi( void ) { } void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData = 0 ) { errorCallback_ = errorCallback; errorCallbackUserData_ = userData; } void MidiApi :: error( RtMidiError::Type type, std::string errorString ) { if ( errorCallback_ ) { if ( firstErrorOccurred_ ) return; firstErrorOccurred_ = true; const std::string errorMessage = errorString; errorCallback_( type, errorMessage, errorCallbackUserData_); firstErrorOccurred_ = false; return; } if ( type == RtMidiError::WARNING ) { std::cerr << '\n' << errorString << "\n\n"; } else if ( type == RtMidiError::DEBUG_WARNING ) { #if defined(__RTMIDI_DEBUG__) std::cerr << '\n' << errorString << "\n\n"; #endif } else { std::cerr << '\n' << errorString << "\n\n"; throw RtMidiError( errorString, type ); } } //*********************************************************************// // Common MidiInApi Definitions //*********************************************************************// MidiInApi :: MidiInApi( unsigned int queueSizeLimit ) : MidiApi() { // Allocate the MIDI queue. inputData_.queue.ringSize = queueSizeLimit; if ( inputData_.queue.ringSize > 0 ) inputData_.queue.ring = new MidiMessage[ inputData_.queue.ringSize ]; } MidiInApi :: ~MidiInApi( void ) { // Delete the MIDI queue. if ( inputData_.queue.ringSize > 0 ) delete [] inputData_.queue.ring; } void MidiInApi :: setCallback( RtMidiIn::RtMidiCallback callback, void *userData ) { if ( inputData_.usingCallback ) { errorString_ = "MidiInApi::setCallback: a callback function is already set!"; error( RtMidiError::WARNING, errorString_ ); return; } if ( !callback ) { errorString_ = "RtMidiIn::setCallback: callback function value is invalid!"; error( RtMidiError::WARNING, errorString_ ); return; } inputData_.userCallback = callback; inputData_.userData = userData; inputData_.usingCallback = true; } void MidiInApi :: cancelCallback() { if ( !inputData_.usingCallback ) { errorString_ = "RtMidiIn::cancelCallback: no callback function was set!"; error( RtMidiError::WARNING, errorString_ ); return; } inputData_.userCallback = 0; inputData_.userData = 0; inputData_.usingCallback = false; } void MidiInApi :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { inputData_.ignoreFlags = 0; if ( midiSysex ) inputData_.ignoreFlags = 0x01; if ( midiTime ) inputData_.ignoreFlags |= 0x02; if ( midiSense ) inputData_.ignoreFlags |= 0x04; } double MidiInApi :: getMessage( std::vector *message ) { message->clear(); if ( inputData_.usingCallback ) { errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port."; error( RtMidiError::WARNING, errorString_ ); return 0.0; } if ( inputData_.queue.size == 0 ) return 0.0; // Copy queued message to the vector pointer argument and then "pop" it. std::vector *bytes = &(inputData_.queue.ring[inputData_.queue.front].bytes); message->assign( bytes->begin(), bytes->end() ); double deltaTime = inputData_.queue.ring[inputData_.queue.front].timeStamp; inputData_.queue.size--; inputData_.queue.front++; if ( inputData_.queue.front == inputData_.queue.ringSize ) inputData_.queue.front = 0; return deltaTime; } //*********************************************************************// // Common MidiOutApi Definitions //*********************************************************************// MidiOutApi :: MidiOutApi( void ) : MidiApi() { } MidiOutApi :: ~MidiOutApi( void ) { } // *************************************************** // // // OS/API-specific methods. // // *************************************************** // #if defined(__MACOSX_CORE__) // The CoreMIDI API is based on the use of a callback function for // MIDI input. We convert the system specific time stamps to delta // time values. // OS-X CoreMIDI header files. #include #include #include // A structure to hold variables related to the CoreMIDI API // implementation. struct CoreMidiData { MIDIClientRef client; MIDIPortRef port; MIDIEndpointRef endpoint; MIDIEndpointRef destinationId; unsigned long long lastTime; MIDISysexSendRequest sysexreq; }; //*********************************************************************// // API: OS-X // Class Definitions: MidiInCore //*********************************************************************// static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ ) { MidiInApi::RtMidiInData *data = static_cast (procRef); CoreMidiData *apiData = static_cast (data->apiData); unsigned char status; unsigned short nBytes, iByte, size; unsigned long long time; bool& continueSysex = data->continueSysex; MidiInApi::MidiMessage& message = data->message; const MIDIPacket *packet = &list->packet[0]; for ( unsigned int i=0; inumPackets; ++i ) { // My interpretation of the CoreMIDI documentation: all message // types, except sysex, are complete within a packet and there may // be several of them in a single packet. Sysex messages can be // broken across multiple packets and PacketLists but are bundled // alone within each packet (these packets do not contain other // message types). If sysex messages are split across multiple // MIDIPacketLists, they must be handled by multiple calls to this // function. nBytes = packet->length; if ( nBytes == 0 ) continue; // Calculate time stamp. if ( data->firstMessage ) { message.timeStamp = 0.0; data->firstMessage = false; } else { time = packet->timeStamp; if ( time == 0 ) { // this happens when receiving asynchronous sysex messages time = AudioGetCurrentHostTime(); } time -= apiData->lastTime; time = AudioConvertHostTimeToNanos( time ); if ( !continueSysex ) message.timeStamp = time * 0.000000001; } apiData->lastTime = packet->timeStamp; if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages apiData->lastTime = AudioGetCurrentHostTime(); } //std::cout << "TimeStamp = " << packet->timeStamp << std::endl; iByte = 0; if ( continueSysex ) { // We have a continuing, segmented sysex message. if ( !( data->ignoreFlags & 0x01 ) ) { // If we're not ignoring sysex messages, copy the entire packet. for ( unsigned int j=0; jdata[j] ); } continueSysex = packet->data[nBytes-1] != 0xF7; if ( !( data->ignoreFlags & 0x01 ) && !continueSysex ) { // If not a continuing sysex message, invoke the user callback function or queue the message. if ( data->usingCallback ) { RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; callback( message.timeStamp, &message.bytes, data->userData ); } else { // As long as we haven't reached our queue size limit, push the message. if ( data->queue.size < data->queue.ringSize ) { data->queue.ring[data->queue.back++] = message; if ( data->queue.back == data->queue.ringSize ) data->queue.back = 0; data->queue.size++; } else std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; } message.bytes.clear(); } } else { while ( iByte < nBytes ) { size = 0; // We are expecting that the next byte in the packet is a status byte. status = packet->data[iByte]; if ( !(status & 0x80) ) break; // Determine the number of bytes in the MIDI message. if ( status < 0xC0 ) size = 3; else if ( status < 0xE0 ) size = 2; else if ( status < 0xF0 ) size = 3; else if ( status == 0xF0 ) { // A MIDI sysex if ( data->ignoreFlags & 0x01 ) { size = 0; iByte = nBytes; } else size = nBytes - iByte; continueSysex = packet->data[nBytes-1] != 0xF7; } else if ( status == 0xF1 ) { // A MIDI time code message if ( data->ignoreFlags & 0x02 ) { size = 0; iByte += 2; } else size = 2; } else if ( status == 0xF2 ) size = 3; else if ( status == 0xF3 ) size = 2; else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) { // A MIDI timing tick message and we're ignoring it. size = 0; iByte += 1; } else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) { // A MIDI active sensing message and we're ignoring it. size = 0; iByte += 1; } else size = 1; // Copy the MIDI data to our vector. if ( size ) { message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] ); if ( !continueSysex ) { // If not a continuing sysex message, invoke the user callback function or queue the message. if ( data->usingCallback ) { RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; callback( message.timeStamp, &message.bytes, data->userData ); } else { // As long as we haven't reached our queue size limit, push the message. if ( data->queue.size < data->queue.ringSize ) { data->queue.ring[data->queue.back++] = message; if ( data->queue.back == data->queue.ringSize ) data->queue.back = 0; data->queue.size++; } else std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; } message.bytes.clear(); } iByte += size; } } } packet = MIDIPacketNext(packet); } } MidiInCore :: MidiInCore( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { initialize( clientName ); } MidiInCore :: ~MidiInCore( void ) { // Close a connection if it exists. closePort(); // Cleanup. CoreMidiData *data = static_cast (apiData_); MIDIClientDispose( data->client ); if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); delete data; } void MidiInCore :: initialize( const std::string& clientName ) { // Set up our client. MIDIClientRef client; CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ); OSStatus result = MIDIClientCreate(name, NULL, NULL, &client ); if ( result != noErr ) { std::ostringstream ost; ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ")."; errorString_ = ost.str(); error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Save our api-specific connection information. CoreMidiData *data = (CoreMidiData *) new CoreMidiData; data->client = client; data->endpoint = 0; apiData_ = (void *) data; inputData_.apiData = (void *) data; CFRelease(name); } void MidiInCore :: openPort( unsigned int portNumber, const std::string portName ) { if ( connected_ ) { errorString_ = "MidiInCore::openPort: a valid connection already exists!"; error( RtMidiError::WARNING, errorString_ ); return; } CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); unsigned int nSrc = MIDIGetNumberOfSources(); if (nSrc < 1) { errorString_ = "MidiInCore::openPort: no MIDI input sources found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; } if ( portNumber >= nSrc ) { std::ostringstream ost; ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::INVALID_PARAMETER, errorString_ ); return; } MIDIPortRef port; CoreMidiData *data = static_cast (apiData_); OSStatus result = MIDIInputPortCreate( data->client, CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), midiInputCallback, (void *)&inputData_, &port ); if ( result != noErr ) { MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Get the desired input source identifier. MIDIEndpointRef endpoint = MIDIGetSource( portNumber ); if ( endpoint == 0 ) { MIDIPortDispose( port ); MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error getting MIDI input source reference."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Make the connection. result = MIDIPortConnectSource( port, endpoint, NULL ); if ( result != noErr ) { MIDIPortDispose( port ); MIDIClientDispose( data->client ); errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Save our api-specific port information. data->port = port; connected_ = true; } void MidiInCore :: openVirtualPort( const std::string portName ) { CoreMidiData *data = static_cast (apiData_); // Create a virtual MIDI input destination. MIDIEndpointRef endpoint; OSStatus result = MIDIDestinationCreate( data->client, CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), midiInputCallback, (void *)&inputData_, &endpoint ); if ( result != noErr ) { errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Save our api-specific connection information. data->endpoint = endpoint; } void MidiInCore :: closePort( void ) { CoreMidiData *data = static_cast (apiData_); if ( data->endpoint ) { MIDIEndpointDispose( data->endpoint ); } if ( data->port ) { MIDIPortDispose( data->port ); } connected_ = false; } unsigned int MidiInCore :: getPortCount() { CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); return MIDIGetNumberOfSources(); } // This function was submitted by Douglas Casey Tucker and apparently // derived largely from PortMidi. CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) { CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); CFStringRef str; // Begin with the endpoint's name. str = NULL; MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &str ); if ( str != NULL ) { CFStringAppend( result, str ); CFRelease( str ); } MIDIEntityRef entity = 0; MIDIEndpointGetEntity( endpoint, &entity ); if ( entity == 0 ) // probably virtual return result; if ( CFStringGetLength( result ) == 0 ) { // endpoint name has zero length -- try the entity str = NULL; MIDIObjectGetStringProperty( entity, kMIDIPropertyName, &str ); if ( str != NULL ) { CFStringAppend( result, str ); CFRelease( str ); } } // now consider the device's name MIDIDeviceRef device = 0; MIDIEntityGetDevice( entity, &device ); if ( device == 0 ) return result; str = NULL; MIDIObjectGetStringProperty( device, kMIDIPropertyName, &str ); if ( CFStringGetLength( result ) == 0 ) { CFRelease( result ); return str; } if ( str != NULL ) { // if an external device has only one entity, throw away // the endpoint name and just use the device name if ( isExternal && MIDIDeviceGetNumberOfEntities( device ) < 2 ) { CFRelease( result ); return str; } else { if ( CFStringGetLength( str ) == 0 ) { CFRelease( str ); return result; } // does the entity name already start with the device name? // (some drivers do this though they shouldn't) // if so, do not prepend if ( CFStringCompareWithOptions( result, /* endpoint name */ str /* device name */, CFRangeMake(0, CFStringGetLength( str ) ), 0 ) != kCFCompareEqualTo ) { // prepend the device name to the entity name if ( CFStringGetLength( result ) > 0 ) CFStringInsert( result, 0, CFSTR(" ") ); CFStringInsert( result, 0, str ); } CFRelease( str ); } } return result; } // This function was submitted by Douglas Casey Tucker and apparently // derived largely from PortMidi. static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint ) { CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); CFStringRef str; OSStatus err; int i; // Does the endpoint have connections? CFDataRef connections = NULL; int nConnected = 0; bool anyStrings = false; err = MIDIObjectGetDataProperty( endpoint, kMIDIPropertyConnectionUniqueID, &connections ); if ( connections != NULL ) { // It has connections, follow them // Concatenate the names of all connected devices nConnected = CFDataGetLength( connections ) / sizeof(MIDIUniqueID); if ( nConnected ) { const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); for ( i=0; i= MIDIGetNumberOfSources() ) { std::ostringstream ost; ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::WARNING, errorString_ ); return stringName; } portRef = MIDIGetSource( portNumber ); nameRef = ConnectedEndpointName(portRef); CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding()); CFRelease( nameRef ); return stringName = name; } //*********************************************************************// // API: OS-X // Class Definitions: MidiOutCore //*********************************************************************// MidiOutCore :: MidiOutCore( const std::string clientName ) : MidiOutApi() { initialize( clientName ); } MidiOutCore :: ~MidiOutCore( void ) { // Close a connection if it exists. closePort(); // Cleanup. CoreMidiData *data = static_cast (apiData_); MIDIClientDispose( data->client ); if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); delete data; } void MidiOutCore :: initialize( const std::string& clientName ) { // Set up our client. MIDIClientRef client; CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ); OSStatus result = MIDIClientCreate(name, NULL, NULL, &client ); if ( result != noErr ) { std::ostringstream ost; ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ")."; errorString_ = ost.str(); error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Save our api-specific connection information. CoreMidiData *data = (CoreMidiData *) new CoreMidiData; data->client = client; data->endpoint = 0; apiData_ = (void *) data; CFRelease( name ); } unsigned int MidiOutCore :: getPortCount() { CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); return MIDIGetNumberOfDestinations(); } std::string MidiOutCore :: getPortName( unsigned int portNumber ) { CFStringRef nameRef; MIDIEndpointRef portRef; char name[128]; std::string stringName; CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); if ( portNumber >= MIDIGetNumberOfDestinations() ) { std::ostringstream ost; ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::WARNING, errorString_ ); return stringName; } portRef = MIDIGetDestination( portNumber ); nameRef = ConnectedEndpointName(portRef); CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding()); CFRelease( nameRef ); return stringName = name; } void MidiOutCore :: openPort( unsigned int portNumber, const std::string portName ) { if ( connected_ ) { errorString_ = "MidiOutCore::openPort: a valid connection already exists!"; error( RtMidiError::WARNING, errorString_ ); return; } CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); unsigned int nDest = MIDIGetNumberOfDestinations(); if (nDest < 1) { errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; } if ( portNumber >= nDest ) { std::ostringstream ost; ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::INVALID_PARAMETER, errorString_ ); return; } MIDIPortRef port; CoreMidiData *data = static_cast (apiData_); CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); OSStatus result = MIDIOutputPortCreate( data->client, portNameRef, &port ); CFRelease( portNameRef ); if ( result != noErr ) { MIDIClientDispose( data->client ); errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Get the desired output port identifier. MIDIEndpointRef destination = MIDIGetDestination( portNumber ); if ( destination == 0 ) { MIDIPortDispose( port ); MIDIClientDispose( data->client ); errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Save our api-specific connection information. data->port = port; data->destinationId = destination; connected_ = true; } void MidiOutCore :: closePort( void ) { CoreMidiData *data = static_cast (apiData_); if ( data->endpoint ) { MIDIEndpointDispose( data->endpoint ); } if ( data->port ) { MIDIPortDispose( data->port ); } connected_ = false; } void MidiOutCore :: openVirtualPort( std::string portName ) { CoreMidiData *data = static_cast (apiData_); if ( data->endpoint ) { errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!"; error( RtMidiError::WARNING, errorString_ ); return; } // Create a virtual MIDI output source. MIDIEndpointRef endpoint; OSStatus result = MIDISourceCreate( data->client, CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), &endpoint ); if ( result != noErr ) { errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Save our api-specific connection information. data->endpoint = endpoint; } void MidiOutCore :: sendMessage( std::vector *message ) { // We use the MIDISendSysex() function to asynchronously send sysex // messages. Otherwise, we use a single CoreMidi MIDIPacket. unsigned int nBytes = message->size(); if ( nBytes == 0 ) { errorString_ = "MidiOutCore::sendMessage: no data in message argument!"; error( RtMidiError::WARNING, errorString_ ); return; } MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); CoreMidiData *data = static_cast (apiData_); OSStatus result; if ( message->at(0) != 0xF0 && nBytes > 3 ) { errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?"; error( RtMidiError::WARNING, errorString_ ); return; } Byte buffer[nBytes+(sizeof(MIDIPacketList))]; ByteCount listSize = sizeof(buffer); MIDIPacketList *packetList = (MIDIPacketList*)buffer; MIDIPacket *packet = MIDIPacketListInit( packetList ); ByteCount remainingBytes = nBytes; while (remainingBytes && packet) { ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; // 65535 = maximum size of a MIDIPacket const Byte* dataStartPtr = (const Byte *) &message->at( nBytes - remainingBytes ); packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr); remainingBytes -= bytesForPacket; } if ( !packet ) { errorString_ = "MidiOutCore::sendMessage: could not allocate packet list"; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Send to any destinations that may have connected to us. if ( data->endpoint ) { result = MIDIReceived( data->endpoint, packetList ); if ( result != noErr ) { errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; error( RtMidiError::WARNING, errorString_ ); } } // And send to an explicit destination port if we're connected. if ( connected_ ) { result = MIDISend( data->port, data->destinationId, packetList ); if ( result != noErr ) { errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port."; error( RtMidiError::WARNING, errorString_ ); } } } #endif // __MACOSX_CORE__ //*********************************************************************// // API: LINUX ALSA SEQUENCER //*********************************************************************// // API information found at: // - http://www.alsa-project.org/documentation.php#Library #if defined(__LINUX_ALSA__) // The ALSA Sequencer API is based on the use of a callback function for // MIDI input. // // Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer // time stamps and other assorted fixes!!! // If you don't need timestamping for incoming MIDI events, define the // preprocessor definition AVOID_TIMESTAMPING to save resources // associated with the ALSA sequencer queues. #include #include // ALSA header file. #include // A structure to hold variables related to the ALSA API // implementation. struct AlsaMidiData { snd_seq_t *seq; unsigned int portNum; int vport; snd_seq_port_subscribe_t *subscription; snd_midi_event_t *coder; unsigned int bufferSize; unsigned char *buffer; pthread_t thread; pthread_t dummy_thread_id; unsigned long long lastTime; int queue_id; // an input queue is needed to get timestamped events int trigger_fds[2]; }; #define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits)) //*********************************************************************// // API: LINUX ALSA // Class Definitions: MidiInAlsa //*********************************************************************// static void *alsaMidiHandler( void *ptr ) { MidiInApi::RtMidiInData *data = static_cast (ptr); AlsaMidiData *apiData = static_cast (data->apiData); long nBytes; unsigned long long time, lastTime; bool continueSysex = false; bool doDecode = false; MidiInApi::MidiMessage message; int poll_fd_count; struct pollfd *poll_fds; snd_seq_event_t *ev; int result; apiData->bufferSize = 32; result = snd_midi_event_new( 0, &apiData->coder ); if ( result < 0 ) { data->doInput = false; std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n"; return 0; } unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize ); if ( buffer == NULL ) { data->doInput = false; snd_midi_event_free( apiData->coder ); apiData->coder = 0; std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing buffer memory!\n\n"; return 0; } snd_midi_event_init( apiData->coder ); snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages poll_fd_count = snd_seq_poll_descriptors_count( apiData->seq, POLLIN ) + 1; poll_fds = (struct pollfd*)alloca( poll_fd_count * sizeof( struct pollfd )); snd_seq_poll_descriptors( apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN ); poll_fds[0].fd = apiData->trigger_fds[0]; poll_fds[0].events = POLLIN; while ( data->doInput ) { if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) { // No data pending if ( poll( poll_fds, poll_fd_count, -1) >= 0 ) { if ( poll_fds[0].revents & POLLIN ) { bool dummy; int res = read( poll_fds[0].fd, &dummy, sizeof(dummy) ); (void) res; } } continue; } // If here, there should be data. result = snd_seq_event_input( apiData->seq, &ev ); if ( result == -ENOSPC ) { std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n"; continue; } else if ( result <= 0 ) { std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n"; perror("System reports"); continue; } // This is a bit weird, but we now have to decode an ALSA MIDI // event (back) into MIDI bytes. We'll ignore non-MIDI types. if ( !continueSysex ) message.bytes.clear(); doDecode = false; switch ( ev->type ) { case SND_SEQ_EVENT_PORT_SUBSCRIBED: #if defined(__RTMIDI_DEBUG__) std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n"; #endif break; case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: #if defined(__RTMIDI_DEBUG__) std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n"; std::cout << "sender = " << (int) ev->data.connect.sender.client << ":" << (int) ev->data.connect.sender.port << ", dest = " << (int) ev->data.connect.dest.client << ":" << (int) ev->data.connect.dest.port << std::endl; #endif break; case SND_SEQ_EVENT_QFRAME: // MIDI time code if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; break; case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; break; case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; break; case SND_SEQ_EVENT_SENSING: // Active sensing if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true; break; case SND_SEQ_EVENT_SYSEX: if ( (data->ignoreFlags & 0x01) ) break; if ( ev->data.ext.len > apiData->bufferSize ) { apiData->bufferSize = ev->data.ext.len; free( buffer ); buffer = (unsigned char *) malloc( apiData->bufferSize ); if ( buffer == NULL ) { data->doInput = false; std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n"; break; } } default: doDecode = true; } if ( doDecode ) { nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev ); if ( nBytes > 0 ) { // The ALSA sequencer has a maximum buffer size for MIDI sysex // events of 256 bytes. If a device sends sysex messages larger // than this, they are segmented into 256 byte chunks. So, // we'll watch for this and concatenate sysex chunks into a // single sysex message if necessary. if ( !continueSysex ) message.bytes.assign( buffer, &buffer[nBytes] ); else message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] ); continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) ); if ( !continueSysex ) { // Calculate the time stamp: message.timeStamp = 0.0; // Method 1: Use the system time. //(void)gettimeofday(&tv, (struct timezone *)NULL); //time = (tv.tv_sec * 1000000) + tv.tv_usec; // Method 2: Use the ALSA sequencer event time data. // (thanks to Pedro Lopez-Cabanillas!). time = ( ev->time.time.tv_sec * 1000000 ) + ( ev->time.time.tv_nsec/1000 ); lastTime = time; time -= apiData->lastTime; apiData->lastTime = lastTime; if ( data->firstMessage == true ) data->firstMessage = false; else message.timeStamp = time * 0.000001; } else { #if defined(__RTMIDI_DEBUG__) std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n"; #endif } } } snd_seq_free_event( ev ); if ( message.bytes.size() == 0 || continueSysex ) continue; if ( data->usingCallback ) { RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; callback( message.timeStamp, &message.bytes, data->userData ); } else { // As long as we haven't reached our queue size limit, push the message. if ( data->queue.size < data->queue.ringSize ) { data->queue.ring[data->queue.back++] = message; if ( data->queue.back == data->queue.ringSize ) data->queue.back = 0; data->queue.size++; } else std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n"; } } if ( buffer ) free( buffer ); snd_midi_event_free( apiData->coder ); apiData->coder = 0; apiData->thread = apiData->dummy_thread_id; return 0; } MidiInAlsa :: MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { initialize( clientName ); } MidiInAlsa :: ~MidiInAlsa() { // Close a connection if it exists. closePort(); // Shutdown the input thread. AlsaMidiData *data = static_cast (apiData_); if ( inputData_.doInput ) { inputData_.doInput = false; int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) ); (void) res; if ( !pthread_equal(data->thread, data->dummy_thread_id) ) pthread_join( data->thread, NULL ); } // Cleanup. close ( data->trigger_fds[0] ); close ( data->trigger_fds[1] ); if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); #ifndef AVOID_TIMESTAMPING snd_seq_free_queue( data->seq, data->queue_id ); #endif snd_seq_close( data->seq ); delete data; } void MidiInAlsa :: initialize( const std::string& clientName ) { // Set up the ALSA sequencer client. snd_seq_t *seq; int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); if ( result < 0 ) { errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Set client name. snd_seq_set_client_name( seq, clientName.c_str() ); // Save our api-specific connection information. AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; data->seq = seq; data->portNum = -1; data->vport = -1; data->subscription = 0; data->dummy_thread_id = pthread_self(); data->thread = data->dummy_thread_id; data->trigger_fds[0] = -1; data->trigger_fds[1] = -1; apiData_ = (void *) data; inputData_.apiData = (void *) data; if ( pipe(data->trigger_fds) == -1 ) { errorString_ = "MidiInAlsa::initialize: error creating pipe objects."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Create the input queue #ifndef AVOID_TIMESTAMPING data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue"); // Set arbitrary tempo (mm=100) and resolution (240) snd_seq_queue_tempo_t *qtempo; snd_seq_queue_tempo_alloca(&qtempo); snd_seq_queue_tempo_set_tempo(qtempo, 600000); snd_seq_queue_tempo_set_ppq(qtempo, 240); snd_seq_set_queue_tempo(data->seq, data->queue_id, qtempo); snd_seq_drain_output(data->seq); #endif } // This function is used to count or get the pinfo structure for a given port number. unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber ) { snd_seq_client_info_t *cinfo; int client; int count = 0; snd_seq_client_info_alloca( &cinfo ); snd_seq_client_info_set_client( cinfo, -1 ); while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) { client = snd_seq_client_info_get_client( cinfo ); if ( client == 0 ) continue; // Reset query info snd_seq_port_info_set_client( pinfo, client ); snd_seq_port_info_set_port( pinfo, -1 ); while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) { unsigned int atyp = snd_seq_port_info_get_type( pinfo ); if ( ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) && ( ( atyp & SND_SEQ_PORT_TYPE_SYNTH ) == 0 ) && ( ( atyp & SND_SEQ_PORT_TYPE_APPLICATION ) == 0 ) ) continue; unsigned int caps = snd_seq_port_info_get_capability( pinfo ); if ( ( caps & type ) != type ) continue; if ( count == portNumber ) return 1; ++count; } } // If a negative portNumber was used, return the port count. if ( portNumber < 0 ) return count; return 0; } unsigned int MidiInAlsa :: getPortCount() { snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); AlsaMidiData *data = static_cast (apiData_); return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 ); } std::string MidiInAlsa :: getPortName( unsigned int portNumber ) { snd_seq_client_info_t *cinfo; snd_seq_port_info_t *pinfo; snd_seq_client_info_alloca( &cinfo ); snd_seq_port_info_alloca( &pinfo ); std::string stringName; AlsaMidiData *data = static_cast (apiData_); if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) { int cnum = snd_seq_port_info_get_client( pinfo ); snd_seq_get_any_client_info( data->seq, cnum, cinfo ); std::ostringstream os; os << snd_seq_client_info_get_name( cinfo ); os << ":"; os << snd_seq_port_info_get_name( pinfo ); os << " "; // These lines added to make sure devices are listed os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names os << ":"; os << snd_seq_port_info_get_port( pinfo ); stringName = os.str(); return stringName; } // If we get here, we didn't find a match. errorString_ = "MidiInAlsa::getPortName: error looking for port name!"; error( RtMidiError::WARNING, errorString_ ); return stringName; } void MidiInAlsa :: openPort( unsigned int portNumber, const std::string portName ) { if ( connected_ ) { errorString_ = "MidiInAlsa::openPort: a valid connection already exists!"; error( RtMidiError::WARNING, errorString_ ); return; } unsigned int nSrc = this->getPortCount(); if ( nSrc < 1 ) { errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; } snd_seq_port_info_t *src_pinfo; snd_seq_port_info_alloca( &src_pinfo ); AlsaMidiData *data = static_cast (apiData_); if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) { std::ostringstream ost; ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::INVALID_PARAMETER, errorString_ ); return; } snd_seq_addr_t sender, receiver; sender.client = snd_seq_port_info_get_client( src_pinfo ); sender.port = snd_seq_port_info_get_port( src_pinfo ); receiver.client = snd_seq_client_id( data->seq ); snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); if ( data->vport < 0 ) { snd_seq_port_info_set_client( pinfo, 0 ); snd_seq_port_info_set_port( pinfo, 0 ); snd_seq_port_info_set_capability( pinfo, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE ); snd_seq_port_info_set_type( pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION ); snd_seq_port_info_set_midi_channels(pinfo, 16); #ifndef AVOID_TIMESTAMPING snd_seq_port_info_set_timestamping(pinfo, 1); snd_seq_port_info_set_timestamp_real(pinfo, 1); snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id); #endif snd_seq_port_info_set_name(pinfo, portName.c_str() ); data->vport = snd_seq_create_port(data->seq, pinfo); if ( data->vport < 0 ) { errorString_ = "MidiInAlsa::openPort: ALSA error creating input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } data->vport = snd_seq_port_info_get_port(pinfo); } receiver.port = data->vport; if ( !data->subscription ) { // Make subscription if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) { errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } snd_seq_port_subscribe_set_sender(data->subscription, &sender); snd_seq_port_subscribe_set_dest(data->subscription, &receiver); if ( snd_seq_subscribe_port(data->seq, data->subscription) ) { snd_seq_port_subscribe_free( data->subscription ); data->subscription = 0; errorString_ = "MidiInAlsa::openPort: ALSA error making port connection."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } } if ( inputData_.doInput == false ) { // Start the input queue #ifndef AVOID_TIMESTAMPING snd_seq_start_queue( data->seq, data->queue_id, NULL ); snd_seq_drain_output( data->seq ); #endif // Start our MIDI input thread. pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_attr_setschedpolicy(&attr, SCHED_OTHER); inputData_.doInput = true; int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_); pthread_attr_destroy(&attr); if ( err ) { snd_seq_unsubscribe_port( data->seq, data->subscription ); snd_seq_port_subscribe_free( data->subscription ); data->subscription = 0; inputData_.doInput = false; errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; error( RtMidiError::THREAD_ERROR, errorString_ ); return; } } connected_ = true; } void MidiInAlsa :: openVirtualPort( std::string portName ) { AlsaMidiData *data = static_cast (apiData_); if ( data->vport < 0 ) { snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); snd_seq_port_info_set_capability( pinfo, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE ); snd_seq_port_info_set_type( pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION ); snd_seq_port_info_set_midi_channels(pinfo, 16); #ifndef AVOID_TIMESTAMPING snd_seq_port_info_set_timestamping(pinfo, 1); snd_seq_port_info_set_timestamp_real(pinfo, 1); snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id); #endif snd_seq_port_info_set_name(pinfo, portName.c_str()); data->vport = snd_seq_create_port(data->seq, pinfo); if ( data->vport < 0 ) { errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } data->vport = snd_seq_port_info_get_port(pinfo); } if ( inputData_.doInput == false ) { // Wait for old thread to stop, if still running if ( !pthread_equal(data->thread, data->dummy_thread_id) ) pthread_join( data->thread, NULL ); // Start the input queue #ifndef AVOID_TIMESTAMPING snd_seq_start_queue( data->seq, data->queue_id, NULL ); snd_seq_drain_output( data->seq ); #endif // Start our MIDI input thread. pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_attr_setschedpolicy(&attr, SCHED_OTHER); inputData_.doInput = true; int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_); pthread_attr_destroy(&attr); if ( err ) { if ( data->subscription ) { snd_seq_unsubscribe_port( data->seq, data->subscription ); snd_seq_port_subscribe_free( data->subscription ); data->subscription = 0; } inputData_.doInput = false; errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; error( RtMidiError::THREAD_ERROR, errorString_ ); return; } } } void MidiInAlsa :: closePort( void ) { AlsaMidiData *data = static_cast (apiData_); if ( connected_ ) { if ( data->subscription ) { snd_seq_unsubscribe_port( data->seq, data->subscription ); snd_seq_port_subscribe_free( data->subscription ); data->subscription = 0; } // Stop the input queue #ifndef AVOID_TIMESTAMPING snd_seq_stop_queue( data->seq, data->queue_id, NULL ); snd_seq_drain_output( data->seq ); #endif connected_ = false; } // Stop thread to avoid triggering the callback, while the port is intended to be closed if ( inputData_.doInput ) { inputData_.doInput = false; int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) ); (void) res; if ( !pthread_equal(data->thread, data->dummy_thread_id) ) pthread_join( data->thread, NULL ); } } //*********************************************************************// // API: LINUX ALSA // Class Definitions: MidiOutAlsa //*********************************************************************// MidiOutAlsa :: MidiOutAlsa( const std::string clientName ) : MidiOutApi() { initialize( clientName ); } MidiOutAlsa :: ~MidiOutAlsa() { // Close a connection if it exists. closePort(); // Cleanup. AlsaMidiData *data = static_cast (apiData_); if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); if ( data->coder ) snd_midi_event_free( data->coder ); if ( data->buffer ) free( data->buffer ); snd_seq_close( data->seq ); delete data; } void MidiOutAlsa :: initialize( const std::string& clientName ) { // Set up the ALSA sequencer client. snd_seq_t *seq; int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK ); if ( result1 < 0 ) { errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Set client name. snd_seq_set_client_name( seq, clientName.c_str() ); // Save our api-specific connection information. AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; data->seq = seq; data->portNum = -1; data->vport = -1; data->bufferSize = 32; data->coder = 0; data->buffer = 0; int result = snd_midi_event_new( data->bufferSize, &data->coder ); if ( result < 0 ) { delete data; errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n"; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } data->buffer = (unsigned char *) malloc( data->bufferSize ); if ( data->buffer == NULL ) { delete data; errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; error( RtMidiError::MEMORY_ERROR, errorString_ ); return; } snd_midi_event_init( data->coder ); apiData_ = (void *) data; } unsigned int MidiOutAlsa :: getPortCount() { snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); AlsaMidiData *data = static_cast (apiData_); return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 ); } std::string MidiOutAlsa :: getPortName( unsigned int portNumber ) { snd_seq_client_info_t *cinfo; snd_seq_port_info_t *pinfo; snd_seq_client_info_alloca( &cinfo ); snd_seq_port_info_alloca( &pinfo ); std::string stringName; AlsaMidiData *data = static_cast (apiData_); if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) { int cnum = snd_seq_port_info_get_client(pinfo); snd_seq_get_any_client_info( data->seq, cnum, cinfo ); std::ostringstream os; os << snd_seq_client_info_get_name(cinfo); os << ":"; os << snd_seq_port_info_get_name( pinfo ); os << " "; // These lines added to make sure devices are listed os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names os << ":"; os << snd_seq_port_info_get_port(pinfo); stringName = os.str(); return stringName; } // If we get here, we didn't find a match. errorString_ = "MidiOutAlsa::getPortName: error looking for port name!"; error( RtMidiError::WARNING, errorString_ ); return stringName; } void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string portName ) { if ( connected_ ) { errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!"; error( RtMidiError::WARNING, errorString_ ); return; } unsigned int nSrc = this->getPortCount(); if (nSrc < 1) { errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; } snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca( &pinfo ); AlsaMidiData *data = static_cast (apiData_); if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) { std::ostringstream ost; ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::INVALID_PARAMETER, errorString_ ); return; } snd_seq_addr_t sender, receiver; receiver.client = snd_seq_port_info_get_client( pinfo ); receiver.port = snd_seq_port_info_get_port( pinfo ); sender.client = snd_seq_client_id( data->seq ); if ( data->vport < 0 ) { data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); if ( data->vport < 0 ) { errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } } sender.port = data->vport; // Make subscription if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) { snd_seq_port_subscribe_free( data->subscription ); errorString_ = "MidiOutAlsa::openPort: error allocating port subscription."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } snd_seq_port_subscribe_set_sender(data->subscription, &sender); snd_seq_port_subscribe_set_dest(data->subscription, &receiver); snd_seq_port_subscribe_set_time_update(data->subscription, 1); snd_seq_port_subscribe_set_time_real(data->subscription, 1); if ( snd_seq_subscribe_port(data->seq, data->subscription) ) { snd_seq_port_subscribe_free( data->subscription ); errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } connected_ = true; } void MidiOutAlsa :: closePort( void ) { if ( connected_ ) { AlsaMidiData *data = static_cast (apiData_); snd_seq_unsubscribe_port( data->seq, data->subscription ); snd_seq_port_subscribe_free( data->subscription ); connected_ = false; } } void MidiOutAlsa :: openVirtualPort( std::string portName ) { AlsaMidiData *data = static_cast (apiData_); if ( data->vport < 0 ) { data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); if ( data->vport < 0 ) { errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); } } } void MidiOutAlsa :: sendMessage( std::vector *message ) { int result; AlsaMidiData *data = static_cast (apiData_); unsigned int nBytes = message->size(); if ( nBytes > data->bufferSize ) { data->bufferSize = nBytes; result = snd_midi_event_resize_buffer ( data->coder, nBytes); if ( result != 0 ) { errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } free (data->buffer); data->buffer = (unsigned char *) malloc( data->bufferSize ); if ( data->buffer == NULL ) { errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; error( RtMidiError::MEMORY_ERROR, errorString_ ); return; } } snd_seq_event_t ev; snd_seq_ev_clear(&ev); snd_seq_ev_set_source(&ev, data->vport); snd_seq_ev_set_subs(&ev); snd_seq_ev_set_direct(&ev); for ( unsigned int i=0; ibuffer[i] = message->at(i); result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev ); if ( result < (int)nBytes ) { errorString_ = "MidiOutAlsa::sendMessage: event parsing error!"; error( RtMidiError::WARNING, errorString_ ); return; } // Send the event. result = snd_seq_event_output(data->seq, &ev); if ( result < 0 ) { errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port."; error( RtMidiError::WARNING, errorString_ ); return; } snd_seq_drain_output(data->seq); } #endif // __LINUX_ALSA__ //*********************************************************************// // API: Windows Multimedia Library (MM) //*********************************************************************// // API information deciphered from: // - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp // Thanks to Jean-Baptiste Berruchon for the sysex code. #if defined(__WINDOWS_MM__) // The Windows MM API is based on the use of a callback function for // MIDI input. We convert the system specific time stamps to delta // time values. // Windows MM MIDI header files. #include #include #define RT_SYSEX_BUFFER_SIZE 8192 #define RT_SYSEX_BUFFER_COUNT 4 // A structure to hold variables related to the CoreMIDI API // implementation. struct WinMidiData { HMIDIIN inHandle; // Handle to Midi Input Device HMIDIOUT outHandle; // Handle to Midi Output Device DWORD lastTime; MidiInApi::MidiMessage message; LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT]; CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo }; //*********************************************************************// // API: Windows MM // Class Definitions: MidiInWinMM //*********************************************************************// static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/, UINT inputStatus, DWORD_PTR instancePtr, DWORD_PTR midiMessage, DWORD timestamp ) { if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR ) return; //MidiInApi::RtMidiInData *data = static_cast (instancePtr); MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr; WinMidiData *apiData = static_cast (data->apiData); // Calculate time stamp. if ( data->firstMessage == true ) { apiData->message.timeStamp = 0.0; data->firstMessage = false; } else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001; apiData->lastTime = timestamp; if ( inputStatus == MIM_DATA ) { // Channel or system message // Make sure the first byte is a status byte. unsigned char status = (unsigned char) (midiMessage & 0x000000FF); if ( !(status & 0x80) ) return; // Determine the number of bytes in the MIDI message. unsigned short nBytes = 1; if ( status < 0xC0 ) nBytes = 3; else if ( status < 0xE0 ) nBytes = 2; else if ( status < 0xF0 ) nBytes = 3; else if ( status == 0xF1 ) { if ( data->ignoreFlags & 0x02 ) return; else nBytes = 2; } else if ( status == 0xF2 ) nBytes = 3; else if ( status == 0xF3 ) nBytes = 2; else if ( status == 0xF8 && (data->ignoreFlags & 0x02) ) { // A MIDI timing tick message and we're ignoring it. return; } else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) { // A MIDI active sensing message and we're ignoring it. return; } // Copy bytes to our MIDI message. unsigned char *ptr = (unsigned char *) &midiMessage; for ( int i=0; imessage.bytes.push_back( *ptr++ ); } else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR ) MIDIHDR *sysex = ( MIDIHDR *) midiMessage; if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) { // Sysex message and we're not ignoring it for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i ) apiData->message.bytes.push_back( sysex->lpData[i] ); } // The WinMM API requires that the sysex buffer be requeued after // input of each sysex message. Even if we are ignoring sysex // messages, we still need to requeue the buffer in case the user // decides to not ignore sysex messages in the future. However, // it seems that WinMM calls this function with an empty sysex // buffer when an application closes and in this case, we should // avoid requeueing it, else the computer suddenly reboots after // one or two minutes. if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) { //if ( sysex->dwBytesRecorded > 0 ) { EnterCriticalSection( &(apiData->_mutex) ); MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR) ); LeaveCriticalSection( &(apiData->_mutex) ); if ( result != MMSYSERR_NOERROR ) std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n"; if ( data->ignoreFlags & 0x01 ) return; } else return; } if ( data->usingCallback ) { RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData ); } else { // As long as we haven't reached our queue size limit, push the message. if ( data->queue.size < data->queue.ringSize ) { data->queue.ring[data->queue.back++] = apiData->message; if ( data->queue.back == data->queue.ringSize ) data->queue.back = 0; data->queue.size++; } else std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n"; } // Clear the vector for the next input message. apiData->message.bytes.clear(); } MidiInWinMM :: MidiInWinMM( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { initialize( clientName ); } MidiInWinMM :: ~MidiInWinMM() { // Close a connection if it exists. closePort(); WinMidiData *data = static_cast (apiData_); DeleteCriticalSection( &(data->_mutex) ); // Cleanup. delete data; } void MidiInWinMM :: initialize( const std::string& /*clientName*/ ) { // We'll issue a warning here if no devices are available but not // throw an error since the user can plugin something later. unsigned int nDevices = midiInGetNumDevs(); if ( nDevices == 0 ) { errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available."; error( RtMidiError::WARNING, errorString_ ); } // Save our api-specific connection information. WinMidiData *data = (WinMidiData *) new WinMidiData; apiData_ = (void *) data; inputData_.apiData = (void *) data; data->message.bytes.clear(); // needs to be empty for first input message if ( !InitializeCriticalSectionAndSpinCount(&(data->_mutex), 0x00000400) ) { errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed."; error( RtMidiError::WARNING, errorString_ ); } } void MidiInWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ ) { if ( connected_ ) { errorString_ = "MidiInWinMM::openPort: a valid connection already exists!"; error( RtMidiError::WARNING, errorString_ ); return; } unsigned int nDevices = midiInGetNumDevs(); if (nDevices == 0) { errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; } if ( portNumber >= nDevices ) { std::ostringstream ost; ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::INVALID_PARAMETER, errorString_ ); return; } WinMidiData *data = static_cast (apiData_); MMRESULT result = midiInOpen( &data->inHandle, portNumber, (DWORD_PTR)&midiInputCallback, (DWORD_PTR)&inputData_, CALLBACK_FUNCTION ); if ( result != MMSYSERR_NOERROR ) { errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Allocate and init the sysex buffers. for ( int i=0; isysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ]; data->sysexBuffer[i]->lpData = new char[ RT_SYSEX_BUFFER_SIZE ]; data->sysexBuffer[i]->dwBufferLength = RT_SYSEX_BUFFER_SIZE; data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator data->sysexBuffer[i]->dwFlags = 0; result = midiInPrepareHeader( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) ); if ( result != MMSYSERR_NOERROR ) { midiInClose( data->inHandle ); errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader)."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Register the buffer. result = midiInAddBuffer( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) ); if ( result != MMSYSERR_NOERROR ) { midiInClose( data->inHandle ); errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer)."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } } result = midiInStart( data->inHandle ); if ( result != MMSYSERR_NOERROR ) { midiInClose( data->inHandle ); errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } connected_ = true; } void MidiInWinMM :: openVirtualPort( std::string /*portName*/ ) { // This function cannot be implemented for the Windows MM MIDI API. errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; error( RtMidiError::WARNING, errorString_ ); } void MidiInWinMM :: closePort( void ) { if ( connected_ ) { WinMidiData *data = static_cast (apiData_); EnterCriticalSection( &(data->_mutex) ); midiInReset( data->inHandle ); midiInStop( data->inHandle ); for ( int i=0; iinHandle, data->sysexBuffer[i], sizeof(MIDIHDR)); delete [] data->sysexBuffer[i]->lpData; delete [] data->sysexBuffer[i]; if ( result != MMSYSERR_NOERROR ) { midiInClose( data->inHandle ); errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader)."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } } midiInClose( data->inHandle ); connected_ = false; LeaveCriticalSection( &(data->_mutex) ); } } unsigned int MidiInWinMM :: getPortCount() { return midiInGetNumDevs(); } std::string MidiInWinMM :: getPortName( unsigned int portNumber ) { std::string stringName; unsigned int nDevices = midiInGetNumDevs(); if ( portNumber >= nDevices ) { std::ostringstream ost; ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::WARNING, errorString_ ); return stringName; } MIDIINCAPS deviceCaps; midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS)); #if defined( UNICODE ) || defined( _UNICODE ) int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1; stringName.assign( length, 0 ); length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL); #else stringName = std::string( deviceCaps.szPname ); #endif // Next lines added to add the portNumber to the name so that // the device's names are sure to be listed with individual names // even when they have the same brand name std::ostringstream os; os << " "; os << portNumber; stringName += os.str(); return stringName; } //*********************************************************************// // API: Windows MM // Class Definitions: MidiOutWinMM //*********************************************************************// MidiOutWinMM :: MidiOutWinMM( const std::string clientName ) : MidiOutApi() { initialize( clientName ); } MidiOutWinMM :: ~MidiOutWinMM() { // Close a connection if it exists. closePort(); // Cleanup. WinMidiData *data = static_cast (apiData_); delete data; } void MidiOutWinMM :: initialize( const std::string& /*clientName*/ ) { // We'll issue a warning here if no devices are available but not // throw an error since the user can plug something in later. unsigned int nDevices = midiOutGetNumDevs(); if ( nDevices == 0 ) { errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available."; error( RtMidiError::WARNING, errorString_ ); } // Save our api-specific connection information. WinMidiData *data = (WinMidiData *) new WinMidiData; apiData_ = (void *) data; } unsigned int MidiOutWinMM :: getPortCount() { return midiOutGetNumDevs(); } std::string MidiOutWinMM :: getPortName( unsigned int portNumber ) { std::string stringName; unsigned int nDevices = midiOutGetNumDevs(); if ( portNumber >= nDevices ) { std::ostringstream ost; ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::WARNING, errorString_ ); return stringName; } MIDIOUTCAPS deviceCaps; midiOutGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIOUTCAPS)); #if defined( UNICODE ) || defined( _UNICODE ) int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1; stringName.assign( length, 0 ); length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL); #else stringName = std::string( deviceCaps.szPname ); #endif // Next lines added to add the portNumber to the name so that // the device's names are sure to be listed with individual names // even when they have the same brand name std::ostringstream os; os << " "; os << portNumber; stringName += os.str(); return stringName; } void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ ) { if ( connected_ ) { errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!"; error( RtMidiError::WARNING, errorString_ ); return; } unsigned int nDevices = midiOutGetNumDevs(); if (nDevices < 1) { errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!"; error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); return; } if ( portNumber >= nDevices ) { std::ostringstream ost; ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::INVALID_PARAMETER, errorString_ ); return; } WinMidiData *data = static_cast (apiData_); MMRESULT result = midiOutOpen( &data->outHandle, portNumber, (DWORD)NULL, (DWORD)NULL, CALLBACK_NULL ); if ( result != MMSYSERR_NOERROR ) { errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } connected_ = true; } void MidiOutWinMM :: closePort( void ) { if ( connected_ ) { WinMidiData *data = static_cast (apiData_); midiOutReset( data->outHandle ); midiOutClose( data->outHandle ); connected_ = false; } } void MidiOutWinMM :: openVirtualPort( std::string /*portName*/ ) { // This function cannot be implemented for the Windows MM MIDI API. errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; error( RtMidiError::WARNING, errorString_ ); } void MidiOutWinMM :: sendMessage( std::vector *message ) { if ( !connected_ ) return; unsigned int nBytes = static_cast(message->size()); if ( nBytes == 0 ) { errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!"; error( RtMidiError::WARNING, errorString_ ); return; } MMRESULT result; WinMidiData *data = static_cast (apiData_); if ( message->at(0) == 0xF0 ) { // Sysex message // Allocate buffer for sysex data. char *buffer = (char *) malloc( nBytes ); if ( buffer == NULL ) { errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!"; error( RtMidiError::MEMORY_ERROR, errorString_ ); return; } // Copy data to buffer. for ( unsigned int i=0; iat(i); // Create and prepare MIDIHDR structure. MIDIHDR sysex; sysex.lpData = (LPSTR) buffer; sysex.dwBufferLength = nBytes; sysex.dwFlags = 0; result = midiOutPrepareHeader( data->outHandle, &sysex, sizeof(MIDIHDR) ); if ( result != MMSYSERR_NOERROR ) { free( buffer ); errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Send the message. result = midiOutLongMsg( data->outHandle, &sysex, sizeof(MIDIHDR) ); if ( result != MMSYSERR_NOERROR ) { free( buffer ); errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message."; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Unprepare the buffer and MIDIHDR. while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof (MIDIHDR) ) ) Sleep( 1 ); free( buffer ); } else { // Channel or system message. // Make sure the message size isn't too big. if ( nBytes > 3 ) { errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!"; error( RtMidiError::WARNING, errorString_ ); return; } // Pack MIDI bytes into double word. DWORD packet; unsigned char *ptr = (unsigned char *) &packet; for ( unsigned int i=0; iat(i); ++ptr; } // Send the message immediately. result = midiOutShortMsg( data->outHandle, packet ); if ( result != MMSYSERR_NOERROR ) { errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message."; error( RtMidiError::DRIVER_ERROR, errorString_ ); } } } #endif // __WINDOWS_MM__ //*********************************************************************// // API: UNIX JACK // // Written primarily by Alexander Svetalkin, with updates for delta // time by Gary Scavone, April 2011. // // *********************************************************************// #if defined(__UNIX_JACK__) // JACK header files #include #include #include #define JACK_RINGBUFFER_SIZE 16384 // Default size for ringbuffer struct JackMidiData { jack_client_t *client; jack_port_t *port; jack_ringbuffer_t *buffSize; jack_ringbuffer_t *buffMessage; jack_time_t lastTime; MidiInApi :: RtMidiInData *rtMidiIn; }; //*********************************************************************// // API: JACK // Class Definitions: MidiInJack //*********************************************************************// static int jackProcessIn( jack_nframes_t nframes, void *arg ) { JackMidiData *jData = (JackMidiData *) arg; MidiInApi :: RtMidiInData *rtData = jData->rtMidiIn; jack_midi_event_t event; jack_time_t time; // Is port created? if ( jData->port == NULL ) return 0; void *buff = jack_port_get_buffer( jData->port, nframes ); // We have midi events in buffer int evCount = jack_midi_get_event_count( buff ); for (int j = 0; j < evCount; j++) { MidiInApi::MidiMessage message; message.bytes.clear(); jack_midi_event_get( &event, buff, j ); for ( unsigned int i = 0; i < event.size; i++ ) message.bytes.push_back( event.buffer[i] ); // Compute the delta time. time = jack_get_time(); if ( rtData->firstMessage == true ) rtData->firstMessage = false; else message.timeStamp = ( time - jData->lastTime ) * 0.000001; jData->lastTime = time; if ( !rtData->continueSysex ) { if ( rtData->usingCallback ) { RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) rtData->userCallback; callback( message.timeStamp, &message.bytes, rtData->userData ); } else { // As long as we haven't reached our queue size limit, push the message. if ( rtData->queue.size < rtData->queue.ringSize ) { rtData->queue.ring[rtData->queue.back++] = message; if ( rtData->queue.back == rtData->queue.ringSize ) rtData->queue.back = 0; rtData->queue.size++; } else std::cerr << "\nMidiInJack: message queue limit reached!!\n\n"; } } } return 0; } MidiInJack :: MidiInJack( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { initialize( clientName ); } void MidiInJack :: initialize( const std::string& clientName ) { JackMidiData *data = new JackMidiData; apiData_ = (void *) data; data->rtMidiIn = &inputData_; data->port = NULL; data->client = NULL; this->clientName = clientName; connect(); } void MidiInJack :: connect() { JackMidiData *data = static_cast (apiData_); if ( data->client ) return; // Initialize JACK client if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { errorString_ = "MidiInJack::initialize: JACK server not running?"; error( RtMidiError::WARNING, errorString_ ); return; } jack_set_process_callback( data->client, jackProcessIn, data ); jack_activate( data->client ); } MidiInJack :: ~MidiInJack() { JackMidiData *data = static_cast (apiData_); closePort(); if ( data->client ) jack_client_close( data->client ); delete data; } void MidiInJack :: openPort( unsigned int portNumber, const std::string portName ) { JackMidiData *data = static_cast (apiData_); connect(); // Creating new port if ( data->port == NULL) data->port = jack_port_register( data->client, portName.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); if ( data->port == NULL) { errorString_ = "MidiInJack::openPort: JACK error creating port"; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Connecting to the output std::string name = getPortName( portNumber ); jack_connect( data->client, name.c_str(), jack_port_name( data->port ) ); } void MidiInJack :: openVirtualPort( const std::string portName ) { JackMidiData *data = static_cast (apiData_); connect(); if ( data->port == NULL ) data->port = jack_port_register( data->client, portName.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); if ( data->port == NULL ) { errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port"; error( RtMidiError::DRIVER_ERROR, errorString_ ); } } unsigned int MidiInJack :: getPortCount() { int count = 0; JackMidiData *data = static_cast (apiData_); connect(); if ( !data->client ) return 0; // List of available ports const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); if ( ports == NULL ) return 0; while ( ports[count] != NULL ) count++; free( ports ); return count; } std::string MidiInJack :: getPortName( unsigned int portNumber ) { JackMidiData *data = static_cast (apiData_); std::string retStr(""); connect(); // List of available ports const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); // Check port validity if ( ports == NULL ) { errorString_ = "MidiInJack::getPortName: no ports available!"; error( RtMidiError::WARNING, errorString_ ); return retStr; } if ( ports[portNumber] == NULL ) { std::ostringstream ost; ost << "MidiInJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::WARNING, errorString_ ); } else retStr.assign( ports[portNumber] ); free( ports ); return retStr; } void MidiInJack :: closePort() { JackMidiData *data = static_cast (apiData_); if ( data->port == NULL ) return; jack_port_unregister( data->client, data->port ); data->port = NULL; } //*********************************************************************// // API: JACK // Class Definitions: MidiOutJack //*********************************************************************// // Jack process callback static int jackProcessOut( jack_nframes_t nframes, void *arg ) { JackMidiData *data = (JackMidiData *) arg; jack_midi_data_t *midiData; int space; // Is port created? if ( data->port == NULL ) return 0; void *buff = jack_port_get_buffer( data->port, nframes ); jack_midi_clear_buffer( buff ); while ( jack_ringbuffer_read_space( data->buffSize ) > 0 ) { jack_ringbuffer_read( data->buffSize, (char *) &space, (size_t) sizeof(space) ); midiData = jack_midi_event_reserve( buff, 0, space ); jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) space ); } return 0; } MidiOutJack :: MidiOutJack( const std::string clientName ) : MidiOutApi() { initialize( clientName ); } void MidiOutJack :: initialize( const std::string& clientName ) { JackMidiData *data = new JackMidiData; apiData_ = (void *) data; data->port = NULL; data->client = NULL; this->clientName = clientName; connect(); } void MidiOutJack :: connect() { JackMidiData *data = static_cast (apiData_); if ( data->client ) return; // Initialize output ringbuffers data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); // Initialize JACK client if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { errorString_ = "MidiOutJack::initialize: JACK server not running?"; error( RtMidiError::WARNING, errorString_ ); return; } jack_set_process_callback( data->client, jackProcessOut, data ); jack_activate( data->client ); } MidiOutJack :: ~MidiOutJack() { JackMidiData *data = static_cast (apiData_); closePort(); // Cleanup jack_ringbuffer_free( data->buffSize ); jack_ringbuffer_free( data->buffMessage ); if ( data->client ) { jack_client_close( data->client ); } delete data; } void MidiOutJack :: openPort( unsigned int portNumber, const std::string portName ) { JackMidiData *data = static_cast (apiData_); connect(); // Creating new port if ( data->port == NULL ) data->port = jack_port_register( data->client, portName.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); if ( data->port == NULL ) { errorString_ = "MidiOutJack::openPort: JACK error creating port"; error( RtMidiError::DRIVER_ERROR, errorString_ ); return; } // Connecting to the output std::string name = getPortName( portNumber ); jack_connect( data->client, jack_port_name( data->port ), name.c_str() ); } void MidiOutJack :: openVirtualPort( const std::string portName ) { JackMidiData *data = static_cast (apiData_); connect(); if ( data->port == NULL ) data->port = jack_port_register( data->client, portName.c_str(), JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); if ( data->port == NULL ) { errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port"; error( RtMidiError::DRIVER_ERROR, errorString_ ); } } unsigned int MidiOutJack :: getPortCount() { int count = 0; JackMidiData *data = static_cast (apiData_); connect(); if ( !data->client ) return 0; // List of available ports const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); if ( ports == NULL ) return 0; while ( ports[count] != NULL ) count++; free( ports ); return count; } std::string MidiOutJack :: getPortName( unsigned int portNumber ) { JackMidiData *data = static_cast (apiData_); std::string retStr(""); connect(); // List of available ports const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); // Check port validity if ( ports == NULL) { errorString_ = "MidiOutJack::getPortName: no ports available!"; error( RtMidiError::WARNING, errorString_ ); return retStr; } if ( ports[portNumber] == NULL) { std::ostringstream ost; ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; errorString_ = ost.str(); error( RtMidiError::WARNING, errorString_ ); } else retStr.assign( ports[portNumber] ); free( ports ); return retStr; } void MidiOutJack :: closePort() { JackMidiData *data = static_cast (apiData_); if ( data->port == NULL ) return; jack_port_unregister( data->client, data->port ); data->port = NULL; } void MidiOutJack :: sendMessage( std::vector *message ) { int nBytes = message->size(); JackMidiData *data = static_cast (apiData_); // Write full message to buffer jack_ringbuffer_write( data->buffMessage, ( const char * ) &( *message )[0], message->size() ); jack_ringbuffer_write( data->buffSize, ( char * ) &nBytes, sizeof( nBytes ) ); } #endif // __UNIX_JACK__ python-rtmidi-1.1.0/src/RtMidi.h000066400000000000000000000704731307643736300164770ustar00rootroot00000000000000/**********************************************************************/ /*! \class RtMidi \brief An abstract base class for realtime MIDI input/output. This class implements some common functionality for the realtime MIDI input/output subclasses RtMidiIn and RtMidiOut. RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/ RtMidi: realtime MIDI i/o C++ classes Copyright (c) 2003-2016 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is asked to send the modifications to the original developer so that they can be incorporated into the canonical version. This is, however, not a binding provision of this license. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**********************************************************************/ /*! \file RtMidi.h */ #ifndef RTMIDI_H #define RTMIDI_H #define RTMIDI_VERSION "2.1.1" #include #include #include #include /************************************************************************/ /*! \class RtMidiError \brief Exception handling class for RtMidi. The RtMidiError class is quite simple but it does allow errors to be "caught" by RtMidiError::Type. See the RtMidi documentation to know which methods can throw an RtMidiError. */ /************************************************************************/ class RtMidiError : public std::exception { public: //! Defined RtMidiError types. enum Type { WARNING, /*!< A non-critical error. */ DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ UNSPECIFIED, /*!< The default, unspecified error type. */ NO_DEVICES_FOUND, /*!< No devices found on system. */ INVALID_DEVICE, /*!< An invalid device ID was specified. */ MEMORY_ERROR, /*!< An error occured during memory allocation. */ INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ INVALID_USE, /*!< The function was called incorrectly. */ DRIVER_ERROR, /*!< A system driver error occured. */ SYSTEM_ERROR, /*!< A system error occured. */ THREAD_ERROR /*!< A thread error occured. */ }; //! The constructor. RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw() : message_(message), type_(type) {} //! The destructor. virtual ~RtMidiError( void ) throw() {} //! Prints thrown error message to stderr. virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; } //! Returns the thrown error message type. virtual const Type& getType(void) const throw() { return type_; } //! Returns the thrown error message string. virtual const std::string& getMessage(void) const throw() { return message_; } //! Returns the thrown error message as a c-style string. virtual const char* what( void ) const throw() { return message_.c_str(); } protected: std::string message_; Type type_; }; //! RtMidi error callback function prototype. /*! \param type Type of error. \param errorText Error description. Note that class behaviour is undefined after a critical error (not a warning) is reported. */ typedef void (*RtMidiErrorCallback)( RtMidiError::Type type, const std::string &errorText, void *userData ); class MidiApi; class RtMidi { public: //! MIDI API specifier arguments. enum Api { UNSPECIFIED, /*!< Search for a working compiled API. */ MACOSX_CORE, /*!< Macintosh OS-X Core Midi API. */ LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ UNIX_JACK, /*!< The JACK Low-Latency MIDI Server API. */ WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */ RTMIDI_DUMMY /*!< A compilable but non-functional API. */ }; //! A static function to determine the current RtMidi version. static std::string getVersion( void ) throw(); //! A static function to determine the available compiled MIDI APIs. /*! The values returned in the std::vector can be compared against the enumerated list values. Note that there can be more than one API compiled for certain operating systems. */ static void getCompiledApi( std::vector &apis ) throw(); //! Pure virtual openPort() function. virtual void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi" ) ) = 0; //! Pure virtual openVirtualPort() function. virtual void openVirtualPort( const std::string portName = std::string( "RtMidi" ) ) = 0; //! Pure virtual getPortCount() function. virtual unsigned int getPortCount() = 0; //! Pure virtual getPortName() function. virtual std::string getPortName( unsigned int portNumber = 0 ) = 0; //! Pure virtual closePort() function. virtual void closePort( void ) = 0; //! Returns true if a port is open and false if not. virtual bool isPortOpen( void ) const = 0; //! Set an error callback function to be invoked when an error has occured. /*! The callback function will be called whenever an error has occured. It is best to set the error callback function before opening a port. */ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ) = 0; protected: RtMidi(); virtual ~RtMidi(); MidiApi *rtapi_; }; /**********************************************************************/ /*! \class RtMidiIn \brief A realtime MIDI input class. This class provides a common, platform-independent API for realtime MIDI input. It allows access to a single MIDI input port. Incoming MIDI messages are either saved to a queue for retrieval using the getMessage() function or immediately passed to a user-specified callback function. Create multiple instances of this class to connect to more than one MIDI device at the same time. With the OS-X, Linux ALSA, and JACK MIDI APIs, it is also possible to open a virtual input port to which other MIDI software clients can connect. by Gary P. Scavone, 2003-2014. */ /**********************************************************************/ // **************************************************************** // // // RtMidiIn and RtMidiOut class declarations. // // RtMidiIn / RtMidiOut are "controllers" used to select an available // MIDI input or output interface. They present common APIs for the // user to call but all functionality is implemented by the classes // MidiInApi, MidiOutApi and their subclasses. RtMidiIn and RtMidiOut // each create an instance of a MidiInApi or MidiOutApi subclass based // on the user's API choice. If no choice is made, they attempt to // make a "logical" API selection. // // **************************************************************** // class RtMidiIn : public RtMidi { public: //! User callback function type definition. typedef void (*RtMidiCallback)( double timeStamp, std::vector *message, void *userData); //! Default constructor that allows an optional api, client name and queue size. /*! An exception will be thrown if a MIDI system initialization error occurs. The queue size defines the maximum number of messages that can be held in the MIDI queue (when not using a callback function). If the queue size limit is reached, incoming messages will be ignored. If no API argument is specified and multiple API support has been compiled, the default order of use is ALSA, JACK (Linux) and CORE, JACK (OS-X). \param api An optional API id can be specified. \param clientName An optional client name can be specified. This will be used to group the ports that are created by the application. \param queueSizeLimit An optional size of the MIDI input queue can be specified. */ RtMidiIn( RtMidi::Api api=UNSPECIFIED, const std::string clientName = std::string( "RtMidi Input Client"), unsigned int queueSizeLimit = 100 ); //! If a MIDI connection is still open, it will be closed by the destructor. ~RtMidiIn ( void ) throw(); //! Returns the MIDI API specifier for the current instance of RtMidiIn. RtMidi::Api getCurrentApi( void ) throw(); //! Open a MIDI input connection given by enumeration number. /*! \param portNumber An optional port number greater than 0 can be specified. Otherwise, the default or first port found is opened. \param portName An optional name for the application port that is used to connect to portId can be specified. */ void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi Input" ) ); //! Create a virtual input port, with optional name, to allow software connections (OS X, JACK and ALSA only). /*! This function creates a virtual MIDI input port to which other software applications can connect. This type of functionality is currently only supported by the Macintosh OS-X, any JACK, and Linux ALSA APIs (the function returns an error for the other APIs). \param portName An optional name for the application port that is used to connect to portId can be specified. */ void openVirtualPort( const std::string portName = std::string( "RtMidi Input" ) ); //! Set a callback function to be invoked for incoming MIDI messages. /*! The callback function will be called whenever an incoming MIDI message is received. While not absolutely necessary, it is best to set the callback function before opening a MIDI port to avoid leaving some messages in the queue. \param callback A callback function must be given. \param userData Optionally, a pointer to additional data can be passed to the callback function whenever it is called. */ void setCallback( RtMidiCallback callback, void *userData = 0 ); //! Cancel use of the current callback function (if one exists). /*! Subsequent incoming MIDI messages will be written to the queue and can be retrieved with the \e getMessage function. */ void cancelCallback(); //! Close an open MIDI connection (if one exists). void closePort( void ); //! Returns true if a port is open and false if not. virtual bool isPortOpen() const; //! Return the number of available MIDI input ports. /*! \return This function returns the number of MIDI ports of the selected API. */ unsigned int getPortCount(); //! Return a string identifier for the specified MIDI input port number. /*! \return The name of the port with the given Id is returned. \retval An empty string is returned if an invalid port specifier is provided. */ std::string getPortName( unsigned int portNumber = 0 ); //! Specify whether certain MIDI message types should be queued or ignored during input. /*! By default, MIDI timing and active sensing messages are ignored during message input because of their relative high data rates. MIDI sysex messages are ignored by default as well. Variable values of "true" imply that the respective message type will be ignored. */ void ignoreTypes( bool midiSysex = true, bool midiTime = true, bool midiSense = true ); //! Fill the user-provided vector with the data bytes for the next available MIDI message in the input queue and return the event delta-time in seconds. /*! This function returns immediately whether a new message is available or not. A valid message is indicated by a non-zero vector size. An exception is thrown if an error occurs during message retrieval or an input connection was not previously established. */ double getMessage( std::vector *message ); //! Set an error callback function to be invoked when an error has occured. /*! The callback function will be called whenever an error has occured. It is best to set the error callback function before opening a port. */ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); protected: void openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit ); }; /**********************************************************************/ /*! \class RtMidiOut \brief A realtime MIDI output class. This class provides a common, platform-independent API for MIDI output. It allows one to probe available MIDI output ports, to connect to one such port, and to send MIDI bytes immediately over the connection. Create multiple instances of this class to connect to more than one MIDI device at the same time. With the OS-X, Linux ALSA and JACK MIDI APIs, it is also possible to open a virtual port to which other MIDI software clients can connect. by Gary P. Scavone, 2003-2014. */ /**********************************************************************/ class RtMidiOut : public RtMidi { public: //! Default constructor that allows an optional client name. /*! An exception will be thrown if a MIDI system initialization error occurs. If no API argument is specified and multiple API support has been compiled, the default order of use is ALSA, JACK (Linux) and CORE, JACK (OS-X). */ RtMidiOut( RtMidi::Api api=UNSPECIFIED, const std::string clientName = std::string( "RtMidi Output Client") ); //! The destructor closes any open MIDI connections. ~RtMidiOut( void ) throw(); //! Returns the MIDI API specifier for the current instance of RtMidiOut. RtMidi::Api getCurrentApi( void ) throw(); //! Open a MIDI output connection. /*! An optional port number greater than 0 can be specified. Otherwise, the default or first port found is opened. An exception is thrown if an error occurs while attempting to make the port connection. */ void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi Output" ) ); //! Close an open MIDI connection (if one exists). void closePort( void ); //! Returns true if a port is open and false if not. virtual bool isPortOpen() const; //! Create a virtual output port, with optional name, to allow software connections (OS X, JACK and ALSA only). /*! This function creates a virtual MIDI output port to which other software applications can connect. This type of functionality is currently only supported by the Macintosh OS-X, Linux ALSA and JACK APIs (the function does nothing with the other APIs). An exception is thrown if an error occurs while attempting to create the virtual port. */ void openVirtualPort( const std::string portName = std::string( "RtMidi Output" ) ); //! Return the number of available MIDI output ports. unsigned int getPortCount( void ); //! Return a string identifier for the specified MIDI port type and number. /*! An empty string is returned if an invalid port specifier is provided. */ std::string getPortName( unsigned int portNumber = 0 ); //! Immediately send a single message out an open MIDI output port. /*! An exception is thrown if an error occurs during output or an output connection was not previously established. */ void sendMessage( std::vector *message ); //! Set an error callback function to be invoked when an error has occured. /*! The callback function will be called whenever an error has occured. It is best to set the error callback function before opening a port. */ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); protected: void openMidiApi( RtMidi::Api api, const std::string clientName ); }; // **************************************************************** // // // MidiInApi / MidiOutApi class declarations. // // Subclasses of MidiInApi and MidiOutApi contain all API- and // OS-specific code necessary to fully implement the RtMidi API. // // Note that MidiInApi and MidiOutApi are abstract base classes and // cannot be explicitly instantiated. RtMidiIn and RtMidiOut will // create instances of a MidiInApi or MidiOutApi subclass. // // **************************************************************** // class MidiApi { public: MidiApi(); virtual ~MidiApi(); virtual RtMidi::Api getCurrentApi( void ) = 0; virtual void openPort( unsigned int portNumber, const std::string portName ) = 0; virtual void openVirtualPort( const std::string portName ) = 0; virtual void closePort( void ) = 0; virtual unsigned int getPortCount( void ) = 0; virtual std::string getPortName( unsigned int portNumber ) = 0; inline bool isPortOpen() const { return connected_; } void setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ); //! A basic error reporting function for RtMidi classes. void error( RtMidiError::Type type, std::string errorString ); protected: virtual void initialize( const std::string& clientName ) = 0; void *apiData_; bool connected_; std::string errorString_; RtMidiErrorCallback errorCallback_; bool firstErrorOccurred_; void *errorCallbackUserData_; }; class MidiInApi : public MidiApi { public: MidiInApi( unsigned int queueSizeLimit ); virtual ~MidiInApi( void ); void setCallback( RtMidiIn::RtMidiCallback callback, void *userData ); void cancelCallback( void ); virtual void ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ); double getMessage( std::vector *message ); // A MIDI structure used internally by the class to store incoming // messages. Each message represents one and only one MIDI message. struct MidiMessage { std::vector bytes; double timeStamp; // Default constructor. MidiMessage() :bytes(0), timeStamp(0.0) {} }; struct MidiQueue { unsigned int front; unsigned int back; unsigned int size; unsigned int ringSize; MidiMessage *ring; // Default constructor. MidiQueue() :front(0), back(0), size(0), ringSize(0) {} }; // The RtMidiInData structure is used to pass private class data to // the MIDI input handling function or thread. struct RtMidiInData { MidiQueue queue; MidiMessage message; unsigned char ignoreFlags; bool doInput; bool firstMessage; void *apiData; bool usingCallback; RtMidiIn::RtMidiCallback userCallback; void *userData; bool continueSysex; // Default constructor. RtMidiInData() : ignoreFlags(7), doInput(false), firstMessage(true), apiData(0), usingCallback(false), userCallback(0), userData(0), continueSysex(false) {} }; protected: RtMidiInData inputData_; }; class MidiOutApi : public MidiApi { public: MidiOutApi( void ); virtual ~MidiOutApi( void ); virtual void sendMessage( std::vector *message ) = 0; }; // **************************************************************** // // // Inline RtMidiIn and RtMidiOut definitions. // // **************************************************************** // inline RtMidi::Api RtMidiIn :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } inline void RtMidiIn :: openPort( unsigned int portNumber, const std::string portName ) { rtapi_->openPort( portNumber, portName ); } inline void RtMidiIn :: openVirtualPort( const std::string portName ) { rtapi_->openVirtualPort( portName ); } inline void RtMidiIn :: closePort( void ) { rtapi_->closePort(); } inline bool RtMidiIn :: isPortOpen() const { return rtapi_->isPortOpen(); } inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { ((MidiInApi *)rtapi_)->setCallback( callback, userData ); } inline void RtMidiIn :: cancelCallback( void ) { ((MidiInApi *)rtapi_)->cancelCallback(); } inline unsigned int RtMidiIn :: getPortCount( void ) { return rtapi_->getPortCount(); } inline std::string RtMidiIn :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); } inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { ((MidiInApi *)rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); } inline double RtMidiIn :: getMessage( std::vector *message ) { return ((MidiInApi *)rtapi_)->getMessage( message ); } inline void RtMidiIn :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); } inline RtMidi::Api RtMidiOut :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } inline void RtMidiOut :: openPort( unsigned int portNumber, const std::string portName ) { rtapi_->openPort( portNumber, portName ); } inline void RtMidiOut :: openVirtualPort( const std::string portName ) { rtapi_->openVirtualPort( portName ); } inline void RtMidiOut :: closePort( void ) { rtapi_->closePort(); } inline bool RtMidiOut :: isPortOpen() const { return rtapi_->isPortOpen(); } inline unsigned int RtMidiOut :: getPortCount( void ) { return rtapi_->getPortCount(); } inline std::string RtMidiOut :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); } inline void RtMidiOut :: sendMessage( std::vector *message ) { ((MidiOutApi *)rtapi_)->sendMessage( message ); } inline void RtMidiOut :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); } // **************************************************************** // // // MidiInApi and MidiOutApi subclass prototypes. // // **************************************************************** // #if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) #define __RTMIDI_DUMMY__ #endif #if defined(__MACOSX_CORE__) class MidiInCore: public MidiInApi { public: MidiInCore( const std::string clientName, unsigned int queueSizeLimit ); ~MidiInCore( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; void openPort( unsigned int portNumber, const std::string portName ); void openVirtualPort( const std::string portName ); void closePort( void ); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); protected: void initialize( const std::string& clientName ); }; class MidiOutCore: public MidiOutApi { public: MidiOutCore( const std::string clientName ); ~MidiOutCore( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; void openPort( unsigned int portNumber, const std::string portName ); void openVirtualPort( const std::string portName ); void closePort( void ); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); void sendMessage( std::vector *message ); protected: void initialize( const std::string& clientName ); }; #endif #if defined(__UNIX_JACK__) class MidiInJack: public MidiInApi { public: MidiInJack( const std::string clientName, unsigned int queueSizeLimit ); ~MidiInJack( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; void openPort( unsigned int portNumber, const std::string portName ); void openVirtualPort( const std::string portName ); void closePort( void ); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); protected: std::string clientName; void connect( void ); void initialize( const std::string& clientName ); }; class MidiOutJack: public MidiOutApi { public: MidiOutJack( const std::string clientName ); ~MidiOutJack( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; void openPort( unsigned int portNumber, const std::string portName ); void openVirtualPort( const std::string portName ); void closePort( void ); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); void sendMessage( std::vector *message ); protected: std::string clientName; void connect( void ); void initialize( const std::string& clientName ); }; #endif #if defined(__LINUX_ALSA__) class MidiInAlsa: public MidiInApi { public: MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit ); ~MidiInAlsa( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; void openPort( unsigned int portNumber, const std::string portName ); void openVirtualPort( const std::string portName ); void closePort( void ); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); protected: void initialize( const std::string& clientName ); }; class MidiOutAlsa: public MidiOutApi { public: MidiOutAlsa( const std::string clientName ); ~MidiOutAlsa( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; void openPort( unsigned int portNumber, const std::string portName ); void openVirtualPort( const std::string portName ); void closePort( void ); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); void sendMessage( std::vector *message ); protected: void initialize( const std::string& clientName ); }; #endif #if defined(__WINDOWS_MM__) class MidiInWinMM: public MidiInApi { public: MidiInWinMM( const std::string clientName, unsigned int queueSizeLimit ); ~MidiInWinMM( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; void openPort( unsigned int portNumber, const std::string portName ); void openVirtualPort( const std::string portName ); void closePort( void ); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); protected: void initialize( const std::string& clientName ); }; class MidiOutWinMM: public MidiOutApi { public: MidiOutWinMM( const std::string clientName ); ~MidiOutWinMM( void ); RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; void openPort( unsigned int portNumber, const std::string portName ); void openVirtualPort( const std::string portName ); void closePort( void ); unsigned int getPortCount( void ); std::string getPortName( unsigned int portNumber ); void sendMessage( std::vector *message ); protected: void initialize( const std::string& clientName ); }; #endif #if defined(__RTMIDI_DUMMY__) class MidiInDummy: public MidiInApi { public: MidiInDummy( const std::string /*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } void openPort( unsigned int /*portNumber*/, const std::string /*portName*/ ) {} void openVirtualPort( const std::string /*portName*/ ) {} void closePort( void ) {} unsigned int getPortCount( void ) { return 0; } std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } protected: void initialize( const std::string& /*clientName*/ ) {} }; class MidiOutDummy: public MidiOutApi { public: MidiOutDummy( const std::string /*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } void openPort( unsigned int /*portNumber*/, const std::string /*portName*/ ) {} void openVirtualPort( const std::string /*portName*/ ) {} void closePort( void ) {} unsigned int getPortCount( void ) { return 0; } std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } void sendMessage( std::vector * /*message*/ ) {} protected: void initialize( const std::string& /*clientName*/ ) {} }; #endif #endif python-rtmidi-1.1.0/src/_rtmidi.pyx000066400000000000000000000637501307643736300173270ustar00rootroot00000000000000# -*- encoding: utf-8 -*- #cython: embedsignature=True # # rtmidi.pyx # """A Python wrapper for the RtMidi C++ library written with Cython. Overview ======== **RtMidi** is a set of C++ classes which provides a concise and simple, cross-platform API (Application Programming Interface) for realtime MIDI input/output across Linux (ALSA & JACK), Macintosh OS X (CoreMIDI & JACK), and Windows (Multimedia Library) operating systems. **python-rtmidi** is a Python binding for RtMidi implemented with Cython and provides a thin wrapper around the RtMidi C++ interface. The API is basically the same as the C++ one but with the naming scheme of classes, methods and parameters adapted to the Python PEP-8 conventions and requirements of the Python package naming structure. **python-rtmidi** supports Python 2 (tested with Python 2.7) and Python 3 (3.3, 3.4). Public API ========== See the docstrings of each function and class and their methods for more information. Functions --------- ``get_compiled_api`` Return a list of low-level MIDI backend APIs this module supports. Classes ------- ``MidiIn(rtapi=API_UNSPECIFIED, name="RtMidi Client", queue_size_limit=1024)`` Midi input client interface. ``MidiOut(rtapi=API_UNSPECIFIED, name="RtMidi Client")`` Midi output client interface. Constants --------- These constants are returned by the ``get_compiled_api`` function and the ``MidiIn.get_current_api`` resp. ``MidiOut.get_current_api`` methods and are used to specify the low-level MIDI backend API to use when creating a ``MidiIn`` or `MidiOut`` instance. ``API_UNSPECIFIED`` Use first compiled-in API, which has any input resp. output ports ``API_MACOSX_CORE`` OS X CoreMIDI ``API_LINUX_ALSA`` Linux ALSA ``API_UNIX_JACK`` Jack Client ``API_WINDOWS_MM`` Windows MultiMedia ``API_RTMIDI_DUMMY`` RtMidi Dummy API (used when no suitable API was found) Exceptions ---------- ``RtMidiError`` General RtMidi error. Raised, for example, when opening a (virtual) MIDI port fails. Usage example ============= Here's a short example of how to use **python-rtmidi** to open the first available MIDI output port and send a middle C note on MIDI channel 1:: import time import rtmidi midiout = rtmidi.MidiOut() available_ports = midiout.get_ports() if available_ports: midiout.open_port(0) else: midiout.open_virtual_port("My virtual output") note_on = [0x90, 60, 112] # channel 1, middle C, velocity 112 note_off = [0x80, 60, 0] midiout.send_message(note_on) time.sleep(0.5) midiout.send_message(note_off) del midiout """ import sys from libcpp cimport bool from libcpp.string cimport string from libcpp.vector cimport vector __all__ = ( 'API_UNSPECIFIED', 'API_MACOSX_CORE', 'API_LINUX_ALSA', 'API_UNIX_JACK', 'API_WINDOWS_MM', 'API_RTMIDI_DUMMY', 'ERRORTYPE_WARNING', 'ERRORTYPE_DEBUG_WARNING', 'ERRORTYPE_UNSPECIFIED', 'ERRORTYPE_NO_DEVICES_FOUND', 'ERRORTYPE_INVALID_DEVICE', 'ERRORTYPE_MEMORY_ERROR', 'ERRORTYPE_INVALID_PARAMETER', 'ERRORTYPE_INVALID_USE', 'ERRORTYPE_DRIVER_ERROR', 'ERRORTYPE_SYSTEM_ERROR', 'ERRORTYPE_THREAD_ERROR', 'MidiIn', 'MidiOut', 'RtMidiError', 'get_compiled_api' ) if bytes is str: string_types = (str, unicode) else: string_types = (str,) # Init Python threads and GIL, because RtMidi calls Python from native threads. # See http://permalink.gmane.org/gmane.comp.python.cython.user/5837 cdef extern from "Python.h": void PyEval_InitThreads() PyEval_InitThreads() # Declarations for RtMidi C++ classes and their methods we use cdef extern from "RtMidi.h": # Enums nested in classes are apparently not supported by Cython yet # therefore we need to use the following work-around. # https://groups.google.com/d/msg/cython-users/monwJxJCb-g/k_h1rcU-3TgJ cdef enum Api "RtMidi::Api": UNSPECIFIED "RtMidi::UNSPECIFIED" MACOSX_CORE "RtMidi::MACOSX_CORE" LINUX_ALSA "RtMidi::LINUX_ALSA" UNIX_JACK "RtMidi::UNIX_JACK" WINDOWS_MM "RtMidi::WINDOWS_MM" RTMIDI_DUMMY "RtMidi::RTMIDI_DUMMY" cdef enum ErrorType "RtMidiError::Type": ERR_WARNING "RtMidiError::WARNING" ERR_DEBUG_WARNING "RtMidiError::DEBUG_WARNING" ERR_UNSPECIFIED "RtMidiError::UNSPECIFIED" ERR_NO_DEVICES_FOUND "RtMidiError::NO_DEVICES_FOUND" ERR_INVALID_DEVICE "RtMidiError::INVALID_DEVICE" ERR_MEMORY_ERROR "RtMidiError::MEMORY_ERROR" ERR_INVALID_PARAMETER "RtMidiError::INVALID_PARAMETER" ERR_INVALID_USE "RtMidiError::INVALID_USE" ERR_DRIVER_ERROR "RtMidiError::DRIVER_ERROR" ERR_SYSTEM_ERROR "RtMidiError::SYSTEM_ERROR" ERR_THREAD_ERROR "RtMidiError::THREAD_ERROR" # Another work-around for calling a C++ static method: cdef void RtMidi_getCompiledApi "RtMidi::getCompiledApi"(vector[Api] &apis) ctypedef void (*RtMidiCallback)(double timeStamp, vector[unsigned char] *message, void *userData) ctypedef void (*RtMidiErrorCallback)(ErrorType errorType, const string errorText, void *userData) cdef cppclass RtMidi: unsigned int getPortCount() string getPortName(unsigned int portNumber) void openPort(unsigned int portNumber, string portName) except + void openVirtualPort(string portName) except + void closePort() void setErrorCallback(RtMidiErrorCallback callback, void *userData) cdef cppclass RtMidiIn(RtMidi): Api RtMidiIn() except + Api RtMidiIn(Api rtapi, string clientName, unsigned int queueSizeLimit) except + Api getCurrentApi() void cancelCallback() double getMessage(vector[unsigned char] *message) void ignoreTypes(bool midiSysex, bool midiTime, bool midiSense) void setCallback(RtMidiCallback callback, void *data) except + cdef cppclass RtMidiOut(RtMidi): Api RtMidiOut() except + Api RtMidiOut(Api rtapi, string clientName) except + Api getCurrentApi() void sendMessage(vector[unsigned char] *message) except + # internal functions cdef void _cb_func(double delta_time, vector[unsigned char] *msg_v, void *cb_info) with gil: """Wrapper for a Python callback function for MIDI input.""" func, data = ( cb_info) message = [msg_v.at(i) for i in range(msg_v.size())] func((message, delta_time), data) cdef void _cb_error_func(ErrorType errorType, const string &errorText, void *cb_info) with gil: """Wrapper for a Python callback function for errors.""" func, data, decoder = ( cb_info) func(errorType, decoder(errorText), data) def _to_bytes(name): """Convert a unicode (Python 2) or str (Python 3) object into bytes.""" # 'bytes' == 'str' in Python 2 but a separate type in Python 3 if isinstance(name, string_types): try: name = bytes(name, 'utf-8') # Python 3 except TypeError: name = name.encode('utf-8') # Python 2 if not isinstance(name, bytes): raise TypeError("name must be bytes or (unicode) string.") return name # Public API # export Api enum values to Python API_UNSPECIFIED = UNSPECIFIED API_MACOSX_CORE = MACOSX_CORE API_LINUX_ALSA = LINUX_ALSA API_UNIX_JACK = UNIX_JACK API_WINDOWS_MM = WINDOWS_MM API_RTMIDI_DUMMY = RTMIDI_DUMMY # export error values to Python ERRORTYPE_WARNING = ERR_WARNING ERRORTYPE_DEBUG_WARNING = ERR_DEBUG_WARNING ERRORTYPE_UNSPECIFIED = ERR_UNSPECIFIED ERRORTYPE_NO_DEVICES_FOUND = ERR_NO_DEVICES_FOUND ERRORTYPE_INVALID_DEVICE = ERR_INVALID_DEVICE ERRORTYPE_MEMORY_ERROR = ERR_MEMORY_ERROR ERRORTYPE_INVALID_PARAMETER = ERR_INVALID_PARAMETER ERRORTYPE_INVALID_USE = ERR_INVALID_USE ERRORTYPE_DRIVER_ERROR = ERR_DRIVER_ERROR ERRORTYPE_SYSTEM_ERROR = ERR_SYSTEM_ERROR ERRORTYPE_THREAD_ERROR = ERR_THREAD_ERROR def get_compiled_api(): """Return a list of low-level MIDI backend APIs this module supports. Check for support for a particular API by using the ``API_*`` constants in the module namespace, i.e.:: if rtmidi.API_UNIX_JACK in rtmidi.get_compiled_api(): ... """ cdef vector[Api] api_v RtMidi_getCompiledApi(api_v) return [api_v[i] for i in range(api_v.size())] class RtMidiError(Exception): """Base general RtMidi error.""" cdef class MidiBase: cdef object _port cdef object _error_callback cdef RtMidi* baseptr(self): return NULL # context management def __enter__(self): return self def __exit__(self, *exc_info): self.close_port() def _check_port(self): inout = "input" if isinstance(self, MidiIn) else "output" if self._port == -1: raise RtMidiError("%r already opened virtual %s port." % (self, inout)) elif self._port is not None: raise RtMidiError("%r already opened %s port %i." % (self, inout, self._port)) return inout def _decode_string(self, s, encoding='auto'): """Decode given byte string with given encoding.""" if encoding == 'auto': if sys.platform.startswith('win'): encoding = 'latin1' elif (self.get_current_api() == API_MACOSX_CORE and sys.platform == 'darwin'): encoding = 'macroman' else: encoding = 'utf-8' return s.decode(encoding, "ignore") def get_port_count(self): """Return the number of available MIDI input or output ports.""" return self.baseptr().getPortCount() def get_port_name(self, unsigned int port, encoding='auto'): """Return the name of the MIDI input or output port with given number. Ports are numbered from zero, separately for input and output ports. The number of available ports is returned by the ``get_port_count`` method. The port name is decoded to a (unicode) string with the encoding given by ``encoding``. If ``encoding`` is ``"auto"`` (the default), then an appropriate encoding is chosen based on the system and the used backend API. If ``encoding`` is ``None``, the name is returned un-decoded, i.e. as type ``str`` in Python 2 or ``bytes`` in Python 3. """ cdef string name = self.baseptr().getPortName(port) if len(name): return self._decode_string(name, encoding) if encoding else name def get_ports(self, encoding='auto'): """Return a list of names of available MIDI input or output ports. The list index of each port name corresponds to its port number. The port names are decoded to (unicode) strings with the encoding given by ``encoding``. If ``encoding`` is ``"auto"`` (the default), then an appropriate encoding is chosen based on the system and the used backend API. If ``encoding`` is ``None``, the names are returned un-decoded, i.e. as type ``str`` in Python 2 or ``bytes`` in Python 3. """ return [self.get_port_name(p, encoding=encoding) for p in range(self.get_port_count())] def is_port_open(self): """Return True if a port has been opened and False if not. .. note:: The ``isPortOpen`` method of the RtMidi C++ library does not return True when a virtual port has been openend. The python-rtmidi implementation, on the other hand, does. """ return self._port is not None def open_port(self, unsigned int port=0, name=None): """Open the MIDI input or output port with the given port number. Only one port can be opened per ``MidiIn`` or ``MidiOut`` instance. An ``RtMidiError`` exception is raised if an attempt is made to open a port on a ``MidiIn`` or ``MidiOut`` instance, which already opened a (virtual) port. You can optionally pass a name for the RtMidi port with the ``name`` keyword or the second positional argument. Names with non-ASCII characters in them have to be passed as unicode or utf-8 encoded strings in Python 2. The default name is "RtMidi input" resp. "RtMidi output". .. note:: Closing a port and opening it again with a different name does not change the port name. To change the port name, delete its instance, create a new one and open the port again giving a different name. Exceptions: ``RtMidiError`` Raised when trying to open a MIDI port when a (virtual) port has already been opened by this instance. """ inout = self._check_port() self.baseptr().openPort(port, _to_bytes(("RtMidi %s" % inout) if name is None else name)) self._port = port return self def open_virtual_port(self, name=None): """Open a virtual MIDI input or output port. Only one port can be opened per ``MidiIn`` or ``MidiOut`` instance. An ``RtMidiError`` exception is raised if an attempt is made to open a port on a ``MidiIn`` or ``MidiOut`` instance, which already opened a (virtual) port. A virtual port is not connected to a physical MIDI device or system port when first opened. You can connect it to another MIDI output with the OS-dependent tools provided by the low-level MIDI framework, e.g. ``aconnect`` for ALSA, ``jack_connect`` for JACK, or the Audio & MIDI settings dialog for CoreMIDI. .. note:: Virtual ports are not supported by some backend APIs, namely the Windows MultiMedia API. You can use special MIDI drivers like `MIDI Yoke`_ or loopMIDI_ to provide hardware-independent virtual MIDI ports as an alternative. You can optionally pass a name for the RtMidi port with the ``name`` keyword or the second positional argument. Names with non-ASCII characters in them have to be passed as unicode or utf-8 encoded strings in Python 2. The default name is "RtMidi virtual input" resp. "RtMidi virtual output". .. note:: Closing a port and opening it again with a different name does not change the port name. To change the port name, delete its instance, create a new one and open the port again giving a different name. Also, to close a virtual input port, you have to delete its ``MidiIn`` or ``MidiOut`` instance. Exceptions: ``NotImplementedError`` Raised when trying to open a virtual MIDI port with the Windows MultiMedia API, which doesn't support virtual ports. ``RtMidiError`` Raised when trying to open a virtual port when a (virtual) port has already been opened by this instance. .. _midi yoke: http://www.midiox.com/myoke.htm .. _loopmidi: http://www.tobias-erichsen.de/software/loopmidi.html """ if self.get_current_api() == API_WINDOWS_MM: raise NotImplementedError("Virtual ports are not supported " "by the Windows MultiMedia API.") inout = self._check_port() self.baseptr().openVirtualPort(_to_bytes(("RtMidi virtual %s" % inout) if name is None else name)) self._port = -1 return self def close_port(self): """Close the MIDI input or output port opened via ``open_port``. It is safe to call this method repeatedly or if no port has been opened (yet) by this instance. Also cancels a callback function set with ``set_callback``. To close a virtual port opened via ``open_virtual_port``, you have to delete its ``MidiIn`` or ``MidiOut`` instance. """ if self._port != -1: self._port = None self.baseptr().closePort() def set_error_callback(self, func, data=None): """Register a callback function for errors. The callback function is called when an error occurs and must take three arguments. The first argument is a member of enum ``RtMidiError::Type``, represented by one of the ERRORTYPE_* constants. The second argument is an error message. The third argument is the value of the ``data`` argument passed to this function when the callback is registered. Registering an error callback function replaces any previously registered error callback. """ self._error_callback = (func, data, self._decode_string) self.baseptr().setErrorCallback(&_cb_error_func, self._error_callback) def cancel_error_callback(self): """Remove the registered callback function for errors. This can be safely called even when no callback function has been registered. """ self.baseptr().setErrorCallback(NULL, NULL) cdef class MidiIn(MidiBase): """Midi input client interface. You can specify the low-level MIDI backend API to use via the ``rtapi`` keyword or the first positional argument, passing one of the module-level ``API_*`` constants. You can get a list of compiled-in APIs with the module-level ``get_compiled_api`` function. If you pass ``API_UNSPECIFIED`` (the default), the first compiled-in API, which has any input ports available, will be used. You can optionally pass a name for the MIDI client with the ``name`` keyword or the second positional argument. Names with non-ASCII characters in them have to be passed as unicode or utf-8 encoded strings in Python 2. The default name is ``"RtMidiIn Client"``. .. note:: With some backend APIs (e.g. ALSA), the client name is set by the first ``MidiIn`` *or* ``MidiOut`` created by your program and does not change until *all* ``MidiIn`` and ``MidiOut`` instances are deleted and then a new one is created. The ``queue_size_limit`` argument specifies the size of the internal ring buffer in which incoming MIDI events are placed until retrieved via the ``get_message`` method (i.e. when no callback function is registered). The default value is ``1024`` (overriding the default value ``100`` of the underlying C++ RtMidi library). """ cdef RtMidiIn *thisptr cdef object _callback cdef RtMidi* baseptr(self): return self.thisptr def __cinit__(self, Api rtapi=UNSPECIFIED, name=None, unsigned int queue_size_limit=1024): """Create a new client instance for MIDI input. See the class docstring for a description of the constructor arguments. """ self.thisptr = new RtMidiIn( rtapi, _to_bytes("RtMidiIn Client" if name is None else name), queue_size_limit) self._callback = None self._port = None def get_current_api(self): """Return the low-level MIDI backend API used by this instance. Use this by comparing the returned value to the module-level ``API_*`` constants, e.g.:: midiin = rtmidi.MidiIn() if midiin.get_current_api() == rtmidi.API_UNIX_JACK: print("Using JACK API for MIDI input.") """ return self.thisptr.getCurrentApi() def __dealloc__(self): """De-allocate pointer to C++ class instance.""" del self.thisptr def close_port(self): self.cancel_callback() MidiBase.close_port(self) close_port.__doc__ == MidiBase.close_port.__doc__ def ignore_types(self, sysex=True, timing=True, active_sense=True): """Enable/Disable input filtering of certain types of MIDI events. By default System Exclusive (aka sysex), MIDI Clock and Active Sensing messages are filtered from the MIDI input and never reach your code, because they can fill up input buffers very quickly. To receive them, you can selectively disable the filtering of these event types. To enable reception - i.e. turn off the default filtering - of sysex messages, pass ``sysex = False``. To enable reception of MIDI Clock, pass ``timing = False``. To enable reception of Active Sensing, pass ``active_sensing = False``. These arguments can of course be combined in one call, and they all default to ``True``. If you enable reception of any of these event types, be sure to either use an input callback function, which returns quickly or poll for MIDI input often. Otherwise you might lose MIDI input because the input buffer overflows. **Windows note:** the Windows Multi Media API uses fixed size buffers for the reception of sysex messages, whose number and size is set at compile time. Sysex messages longer than the buffer size can not be received properly when using the Windows Multi Media API. The default distribution of python-rtmidi sets the number of sysex buffers to four and the size of each to 8192 bytes. To change these values, edit the ``RT_SYSEX_BUFFER_COUNT`` and ``RT_SYSEX_BUFFER_SIZE`` preprocessor defines in ``RtMidi.cpp`` and recompile. """ self.thisptr.ignoreTypes(sysex, timing, active_sense) def get_message(self): """Poll for MIDI input. Checks whether a MIDI event is available in the input buffer and returns a two-element tuple with the MIDI message and a delta time. The MIDI message is a list of integers representing the data bytes of the message, the delta time is a float representing the time in seconds elapsed since the reception of the previous MIDI event. The function does not block. When no MIDI message is available, it returns ``None``. """ cdef vector[unsigned char] msg_v cdef double delta_time = self.thisptr.getMessage(&msg_v) if not msg_v.empty(): message = [msg_v.at(i) for i in range(msg_v.size())] return (message, delta_time) def set_callback(self, func, data=None): """Register a callback function for MIDI input. The callback function is called whenever a MIDI message is received and must take two arguments. The first argument is a two-element tuple with the MIDI message and a delta time, like the one returned by the ``get_message`` method and the second argument is value of the ``data`` argument passed to this function when the callback is registered. Registering a callback function replaces any previously registered callback. The callback function is safely removed when the input port is closed or the ``MidiIn`` instance is deleted. """ if self._callback: self.cancel_callback() self._callback = (func, data) self.thisptr.setCallback(&_cb_func, self._callback) def cancel_callback(self): """Remove the registered callback function for MIDI input. This can be safely called even when no callback function has been registered. """ if self._callback: self.thisptr.cancelCallback() self._callback = None cdef class MidiOut(MidiBase): """Midi output client interface. You can specify the low-level MIDI backend API to use via the ``rtapi`` keyword or the first positional argument, passing one of the module-level ``API_*`` constants. You can get a list of compiled-in APIs with the module-level ``get_compiled_api`` function. If you pass ``API_UNSPECIFIED`` (the default), the first compiled-in API, which has any input ports available, will be used. You can optionally pass a name for the MIDI client with the ``name`` keyword or the second positional argument. Names with non-ASCII characters in them have to be passed as unicode or utf-8 encoded strings in Python 2. The default name is ``"RtMidiOut Client"``. .. note:: With some APIs (e.g. ALSA), the client name is set by the first ``MidiIn`` *or* ``MidiOut`` created by your program and does not change until *all* ``MidiIn`` and ``MidiOut`` instances are deleted and then a new one is created. """ cdef RtMidiOut *thisptr cdef RtMidi* baseptr(self): return self.thisptr def __cinit__(self, Api rtapi=UNSPECIFIED, name=None): """Create a new client instance for MIDI output. See the class docstring for a description of the constructor arguments. """ self.thisptr = new RtMidiOut( rtapi, _to_bytes("RtMidiOut Client" if name is None else name)) self._port = None def __dealloc__(self): """De-allocate pointer to C++ class instance.""" del self.thisptr def get_current_api(self): """Return the low-level MIDI backend API used by this instance. Use this by comparing the returned value to the module-level ``API_*`` constants, e.g.:: midiout = rtmidi.MidiOut() if midiout.get_current_api() == rtmidi.API_UNIX_JACK: print("Using JACK API for MIDI output.") """ return self.thisptr.getCurrentApi() def send_message(self, message): """Send a MIDI message to the output port. The message must be passed as an iterable of integers, each element representing one byte of the MIDI message. Normal MIDI messages have a length of one to three bytes, but you can also send system exclusive messages, which can be arbitrarily long, via this method. No check is made whether the passed data constitutes a valid MIDI message. """ cdef vector[unsigned char] msg_v for c in message: msg_v.push_back(c) self.thisptr.sendMessage(&msg_v) python-rtmidi-1.1.0/tests/000077500000000000000000000000001307643736300154765ustar00rootroot00000000000000python-rtmidi-1.1.0/tests/test_errorcallback.py000066400000000000000000000036071307643736300217230ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """Tests for the error callback""" import unittest try: from unittest import mock except ImportError: import mock import rtmidi class TestErrorCallback(unittest.TestCase): INVALID_PORT_NUMBER = 9999 MIDI_OUT_ERROR_USER_DATA = 'midiOutError' MIDI_IN_ERROR_USER_DATA = 'midiInError' def setUp(self): self.midi_out = rtmidi.MidiOut() self.midi_in = rtmidi.MidiIn() def test_midiout_error_callback(self): errcb = mock.Mock() self.midi_out.set_error_callback(errcb, self.MIDI_OUT_ERROR_USER_DATA) self.midi_out.open_port(self.INVALID_PORT_NUMBER) errcb.assert_called_with(rtmidi.ERRORTYPE_INVALID_PARAMETER, mock.ANY, self.MIDI_OUT_ERROR_USER_DATA) def test_midiin_error_callback(self): errcb = mock.Mock() self.midi_in.set_error_callback(errcb, self.MIDI_IN_ERROR_USER_DATA) self.midi_in.open_port(self.INVALID_PORT_NUMBER) errcb.assert_called_with(rtmidi.ERRORTYPE_INVALID_PARAMETER, mock.ANY, self.MIDI_IN_ERROR_USER_DATA) def test_error_callback_without_user_data(self): errcb = mock.Mock() self.midi_out.set_error_callback(errcb) self.midi_out.open_port(self.INVALID_PORT_NUMBER) errcb.assert_called_with(rtmidi.ERRORTYPE_INVALID_PARAMETER, mock.ANY, None) def test_cancel_error_callback(self): errcb = mock.Mock() self.midi_out.set_error_callback(errcb) self.midi_out.cancel_error_callback() try: self.midi_out.open_port(self.INVALID_PORT_NUMBER) except RuntimeError: pass errcb.assert_not_called() if __name__ == '__main__': unittest.main() python-rtmidi-1.1.0/tests/test_errors.py000066400000000000000000000043121307643736300204230ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """Tests for the error conditions""" import unittest import rtmidi class TestErrors(unittest.TestCase): INVALID_PORT_NUMBER = 9999 INVALID_NAME_TYPES = (0, 666, 3.141, object) def setUp(self): self.midi_out = rtmidi.MidiOut() self.midi_in = rtmidi.MidiIn() def test_midiout_invalid_client_name_type(self): for name in self.INVALID_NAME_TYPES: with self.assertRaises(TypeError): rtmidi.MidiOut(rtapi=rtmidi.API_RTMIDI_DUMMY, name=name) def test_midiin_invalid_client_name_type(self): for name in self.INVALID_NAME_TYPES: with self.assertRaises(TypeError): rtmidi.MidiIn(rtapi=rtmidi.API_RTMIDI_DUMMY, name=name) def test_midiout_open_invalid_port(self): with self.assertRaises(RuntimeError): self.midi_out.open_port(self.INVALID_PORT_NUMBER) def test_midiin_open_invalid_port(self): with self.assertRaises(RuntimeError): self.midi_in.open_port(self.INVALID_PORT_NUMBER) def test_midiout_open_invalid_port_name_type(self): for name in self.INVALID_NAME_TYPES: with self.assertRaises(TypeError): self.midi_out.open_port(name=name) def test_midiin_open_invalid_port_name_type(self): for name in self.INVALID_NAME_TYPES: with self.assertRaises(TypeError): self.midi_in.open_port(name=name) def test_midiout_double_open_port(self): self.midi_out.open_port() with self.assertRaises(rtmidi.RtMidiError): self.midi_out.open_port() def test_midiin_double_open_port(self): self.midi_in.open_port() with self.assertRaises(rtmidi.RtMidiError): self.midi_in.open_port() def test_midiout_double_open_virtual_port(self): self.midi_out.open_virtual_port() with self.assertRaises(rtmidi.RtMidiError): self.midi_out.open_virtual_port() def test_midiin_double_open_virtual_port(self): self.midi_in.open_virtual_port() with self.assertRaises(rtmidi.RtMidiError): self.midi_in.open_virtual_port() if __name__ == '__main__': unittest.main() python-rtmidi-1.1.0/tests/test_rtmidi.py000066400000000000000000000053121307643736300204000ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """Unit tests for the rtmidi module.""" import time import unittest import rtmidi class RtMidiTestCase(unittest.TestCase): NOTE_ON = [0x90, 48, 100] NOTE_OFF = [0x80, 48, 16] TEST_PORT_NAME = 'rtmidi test' DELAY = 0.1 def setUp(self): self.midi_in = rtmidi.MidiIn() self.midi_out = rtmidi.MidiOut() # TODO: hack-ish strategy to find out the port number of the virtual # output port, which should should work on ALSA too # See: https://github.com/thestk/rtmidi/issues/88 ports_before = self.midi_in.get_ports() self.midi_out.open_virtual_port() ports_after = self.midi_in.get_ports() port_name = list(set(ports_after).difference(ports_before))[0] for portnum, port in enumerate(ports_after): if port == port_name: self.midi_in.open_port(portnum) break else: raise IOError("Could not find MIDI output port.") def tearDown(self): self.midi_in.close_port() del self.midi_in self.midi_out.close_port() del self.midi_out def test_is_port_open(self): assert self.midi_in.is_port_open() self.midi_in.close_port() assert not self.midi_in.is_port_open() # virtual ports can't be closed assert self.midi_out.is_port_open() self.midi_out.close_port() assert self.midi_out.is_port_open() midi_out2 = rtmidi.MidiOut() assert not midi_out2.is_port_open() midi_out2.open_virtual_port() assert midi_out2.is_port_open() del midi_out2 def test_send_and_get_message(self): self.midi_out.send_message(self.NOTE_ON) self.midi_out.send_message(self.NOTE_OFF) time.sleep(self.DELAY) message_1, _ = self.midi_in.get_message() message_2, _ = self.midi_in.get_message() self.assertEqual(message_1, self.NOTE_ON) self.assertEqual(message_2, self.NOTE_OFF) def test_callback(self): messages = [] def callback(event, data): messages.append((event[0], data)) self.midi_in.set_callback(callback, data=42) self.midi_out.send_message(self.NOTE_ON) self.midi_out.send_message(self.NOTE_OFF) time.sleep(self.DELAY) self.assertEqual(messages[0], (self.NOTE_ON, 42)) self.assertEqual(messages[1], (self.NOTE_OFF, 42)) self.midi_in.cancel_callback() messages = [] self.midi_out.send_message(self.NOTE_ON) self.midi_out.send_message(self.NOTE_OFF) time.sleep(self.DELAY) self.assertEqual(messages, []) if __name__ == '__main__': unittest.main() python-rtmidi-1.1.0/tox.ini000066400000000000000000000004261307643736300156510ustar00rootroot00000000000000[tox] envlist = flake8, py27, py33, py34, py35 skip_missing_interpreters = True [testenv] commands = py.test -v tests deps = -r{toxinidir}/requirements-dev.txt [testenv:flake8] commands = flake8 setup.py {toxinidir}/rtmidi {toxinidir}/examples {toxinidir}/tests deps = flake8 python-rtmidi-1.1.0/update-docs.sh000077500000000000000000000005061307643736300171040ustar00rootroot00000000000000#!/bin/bash # # update-docs.sh - Build documentation, update gh-pages branch and push changes git checkout master git pull make docs git checkout gh-pages rsync -av docs/_build/html/ . git add -A COMMIT="$(git log -n 1 --pretty=format:"%h" master)" git commit -m "Update docs for commit $COMMIT" git push git checkout master