././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2633297 mido-1.2.10/0000775000175000017500000000000000000000000012744 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608250925.0 mido-1.2.10/.flake80000664000175000017500000000016500000000000014121 0ustar00olembolemb00000000000000[flake8] ignore = F401, C901, F901 exclude = .git,__pycache__,docs/source/conf.py,old,build,dist max-complexity = 10 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608567398.0 mido-1.2.10/LICENSE0000664000175000017500000000206600000000000013755 0ustar00olembolemb00000000000000The MIT License Copyright (c) Ole Martin Bjørndalen 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1609342087.0 mido-1.2.10/MANIFEST.in0000664000175000017500000000073100000000000014503 0ustar00olembolemb00000000000000include README.rst LICENSE tox.ini .flake8 recursive-include tests *.py recursive-include examples *.py recursive-include extras *.py include docs/_static/PLACEHOLDER recursive-include docs *.bat recursive-include docs *.py recursive-include docs *.rst recursive-include docs Makefile recursive-include examples *.py recursive-include examples *.sh recursive-include extras *.rst recursive-include mido *.py recursive-include docs/images *.svg prune docs/_build prune logo ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2633297 mido-1.2.10/PKG-INFO0000664000175000017500000001003100000000000014034 0ustar00olembolemb00000000000000Metadata-Version: 2.1 Name: mido Version: 1.2.10 Summary: MIDI Objects for Python Home-page: https://mido.readthedocs.io/ Author: Ole Martin Bjorndalen Author-email: ombdalen@gmail.com License: MIT Description: Mido - MIDI Objects for Python ============================== .. image:: https://travis-ci.org/mido/mido.svg?branch=master :target: https://travis-ci.org/mido/mido .. image:: https://github.com/mido/mido/workflows/Test/badge.svg :target: https://github.com/mido/mido/actions Mido is a library for working with MIDI messages and ports: .. code-block:: python >>> import mido >>> msg = mido.Message('note_on', note=60) >>> msg.type 'note_on' >>> msg.note 60 >>> msg.bytes() [144, 60, 64] >>> msg.copy(channel=2) Message('note_on', channel=2, note=60, velocity=64, time=0) .. code-block:: python port = mido.open_output('Port Name') port.send(msg) .. code-block:: python with mido.open_input() as inport: for msg in inport: print(msg) .. code-block:: python mid = mido.MidiFile('song.mid') for msg in mid.play(): port.send(msg) Full documentation at https://mido.readthedocs.io/ Main Features ------------- * works in Python 2 and 3. * convenient message objects. * supports RtMidi, PortMidi and Pygame. New backends are easy to write. * full support for all 18 messages defined by the MIDI standard. * standard port API allows all kinds of input and output ports to be used interchangeably. New port types can be written by subclassing and overriding a few methods. * includes a reusable MIDI stream parser. * full support for MIDI files (read, write, create and play) with complete access to every message in the file, including all common meta messages. * can read and write SYX files (binary and plain text). * implements (somewhat experimental) MIDI over TCP/IP with socket ports. This allows for example wireless MIDI between two computers. * includes programs for playing MIDI files, listing ports and serving and forwarding ports over a network. Status ------ 1.2 is the third stable release. Requirements ------------ Mido targets Python 3.6 and 2.7. Installing ---------- :: pip install mido If you want to use ports:: pip install python-rtmidi See ``docs/backends/`` for other backends. Source Code ----------- https://github.com/mido/mido/ License ------- Mido is released under the terms of the `MIT license `_. Questions and suggestions ------------------------- For questions and proposals which may not fit into issues or pull requests, we recommend to ask and discuss on `Discussions `_. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.6 Provides-Extra: dev Provides-Extra: ports ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608567398.0 mido-1.2.10/README.rst0000664000175000017500000000475300000000000014444 0ustar00olembolemb00000000000000Mido - MIDI Objects for Python ============================== .. image:: https://travis-ci.org/mido/mido.svg?branch=master :target: https://travis-ci.org/mido/mido .. image:: https://github.com/mido/mido/workflows/Test/badge.svg :target: https://github.com/mido/mido/actions Mido is a library for working with MIDI messages and ports: .. code-block:: python >>> import mido >>> msg = mido.Message('note_on', note=60) >>> msg.type 'note_on' >>> msg.note 60 >>> msg.bytes() [144, 60, 64] >>> msg.copy(channel=2) Message('note_on', channel=2, note=60, velocity=64, time=0) .. code-block:: python port = mido.open_output('Port Name') port.send(msg) .. code-block:: python with mido.open_input() as inport: for msg in inport: print(msg) .. code-block:: python mid = mido.MidiFile('song.mid') for msg in mid.play(): port.send(msg) Full documentation at https://mido.readthedocs.io/ Main Features ------------- * works in Python 2 and 3. * convenient message objects. * supports RtMidi, PortMidi and Pygame. New backends are easy to write. * full support for all 18 messages defined by the MIDI standard. * standard port API allows all kinds of input and output ports to be used interchangeably. New port types can be written by subclassing and overriding a few methods. * includes a reusable MIDI stream parser. * full support for MIDI files (read, write, create and play) with complete access to every message in the file, including all common meta messages. * can read and write SYX files (binary and plain text). * implements (somewhat experimental) MIDI over TCP/IP with socket ports. This allows for example wireless MIDI between two computers. * includes programs for playing MIDI files, listing ports and serving and forwarding ports over a network. Status ------ 1.2 is the third stable release. Requirements ------------ Mido targets Python 3.6 and 2.7. Installing ---------- :: pip install mido If you want to use ports:: pip install python-rtmidi See ``docs/backends/`` for other backends. Source Code ----------- https://github.com/mido/mido/ License ------- Mido is released under the terms of the `MIT license `_. Questions and suggestions ------------------------- For questions and proposals which may not fit into issues or pull requests, we recommend to ask and discuss on `Discussions `_. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2353299 mido-1.2.10/bin/0000775000175000017500000000000000000000000013514 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/bin/mido-connect0000755000175000017500000000153100000000000016017 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Forward all messages from one or more ports to server. """ import argparse import mido def parse_args(): parser = argparse.ArgumentParser(description=__doc__) arg = parser.add_argument arg('address', metavar='ADDRESS', help='host:port to connect to') arg('ports', metavar='PORT', nargs='+', help='input ports to listen to') return parser.parse_args() args = parse_args() try: hostname, port = mido.sockets.parse_address(args.address) ports = [mido.open_input(name) for name in args.ports] with mido.sockets.connect(hostname, port) as server_port: print('Connected.') for message in mido.ports.multi_receive(ports): print('Sending {}'.format(message)) server_port.send(message) except KeyboardInterrupt: pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608482251.0 mido-1.2.10/bin/mido-play0000775000175000017500000000423200000000000015336 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Play MIDI file on output port. Example: mido-play some_file.mid Todo: - add option for printing messages """ from __future__ import print_function, division import sys import argparse import mido from mido import MidiFile, Message, tempo2bpm def parse_args(): parser = argparse.ArgumentParser(description=__doc__) arg = parser.add_argument arg('-o', '--output-port', help='Mido port to send output to') arg('-m', '--print-messages', dest='print_messages', action='store_true', default=False, help='Print messages as they are played back') arg('-q', '--quiet', dest='quiet', action='store_true', default=False, help='print nothing') arg('files', metavar='FILE', nargs='+', help='MIDI file to play') return parser.parse_args() def play_file(output, filename, print_messages): midi_file = MidiFile(filename) print('Playing {}.'.format(midi_file.filename)) length = midi_file.length print('Song length: {} minutes, {} seconds.'.format( int(length / 60), int(length % 60))) print('Tracks:') for i, track in enumerate(midi_file.tracks): print(' {:2d}: {!r}'.format(i, track.name.strip())) for message in midi_file.play(meta_messages=True): if print_messages: sys.stdout.write(repr(message) + '\n') sys.stdout.flush() if isinstance(message, Message): output.send(message) elif message.type == 'set_tempo': print('Tempo changed to {:.1f} BPM.'.format( tempo2bpm(message.tempo))) print() def main(): try: with mido.open_output(args.output_port) as output: print('Using output {!r}.'.format(output.name)) output.reset() try: for filename in args.files: play_file(output, filename, args.print_messages) finally: print() output.reset() except KeyboardInterrupt: pass args = parse_args() if args.quiet: def print(*args): pass main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608250925.0 mido-1.2.10/bin/mido-ports0000775000175000017500000000137600000000000015546 0ustar00olembolemb00000000000000#!/usr/bin/env python """ List available PortMidi ports. """ from __future__ import print_function import os import sys import mido def print_ports(heading, port_names): print(heading) for name in port_names: print(" '{}'".format(name)) print() print() print_ports('Available input Ports:', mido.get_input_names()) print_ports('Available output Ports:', mido.get_output_names()) for name in ['MIDO_DEFAULT_INPUT', 'MIDO_DEFAULT_OUTPUT', 'MIDO_DEFAULT_IOPORT', 'MIDO_BACKEND']: try: value = os.environ[name] print('{}={!r}'.format(name, value)) except LookupError: print('{} not set.'.format(name)) print() print('Using backend {}.'.format(mido.backend.name)) print() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/bin/mido-serve0000755000175000017500000000162400000000000015515 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Serve one or more output ports. Every message received on any of the connected sockets will be sent to every output port. """ import argparse import mido from mido import sockets from mido.ports import MultiPort def parse_args(): parser = argparse.ArgumentParser(description=__doc__) arg = parser.add_argument arg('address', metavar='ADDRESS', help='host:port to serve on') arg('ports', metavar='PORT', nargs='+', help='output port to serve') return parser.parse_args() args = parse_args() try: out = MultiPort([mido.open_output(name) for name in args.ports]) (hostname, port) = sockets.parse_address(args.address) with sockets.PortServer(hostname, port) as server: for message in server: print('Received {}'.format(message)) out.send(message) except KeyboardInterrupt: pass ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2433298 mido-1.2.10/docs/0000775000175000017500000000000000000000000013674 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/Makefile0000644000175000017500000001266400000000000015343 0ustar00olembolemb00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # 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 " 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 " 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/Mido.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Mido.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/Mido" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Mido" @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." 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." ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2433298 mido-1.2.10/docs/_static/0000775000175000017500000000000000000000000015322 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/_static/PLACEHOLDER0000644000175000017500000000007000000000000016762 0ustar00olembolemb00000000000000This file is needed for git to include this directory. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/about_midi.rst0000644000175000017500000000562200000000000016545 0ustar00olembolemb00000000000000About MIDI ========== A Short Introduction To MIDI ---------------------------- MIDI is a simple binary protocol for communicating with synthesizers and other electronic music equipment. It was developed in 1981 by Dave Smith and Chet Wood of Sequential Systems. MIDI was quickly embraced by all the major synth manufacturers and led to developments such as microcomputer sequencers, and with them the electronic home studio. Although many attempts have been made to replace it, it is still the industry standard. MIDI was designed for the 8-bit micro controllers found in synthesizers at the beginning of the 80's. As such, it is a very minimal byte-oriented protocol. The message for turning a note on is only three bytes long (here shown in hexadecimal):: 92 3C 64 This message consists of:: 92 -- 9 == message type note on 2 == channel 2 3C -- note 60 (middle C) 64 -- velocity (how hard the note is hit) The first byte is called a status byte. It has the upper bit set, which is how you can tell it apart from the following data bytes. Data bytes are thus only 7 bits (0..127). Each message type has a given number of data bytes, the exception being the System Exclusive message which has a start and a stop byte and any number of data bytes in-between these two:: F0 ... F7 Messages can be divided into four groups: * Channel messages. These are used to turn notes on and off, to change patches, and change controllers (pitch bend, modulation wheel, pedal and many others). There are 16 channels, and the channel number is encoded in the lower 4 bits of the status byte. Each synth can choose which channel (or channels) it responds to. This can typically be configured. * System common messages. * System real time messages, the include start, stop, continue, song position (for playback of songs) and reset. * System Exclusive messages (often called Sysex messages). These are used for sending and receiving device specific such as patch data. Some Examples of Messages ------------------------- :: # Turn on middle C on channel 2: 92 3C 64 # Turn it back off: 82 3C 64 # Change to program (sound) number 4 on channel 2: C2 04 # Continue (Starts a song that has been paused): FB # Sysex data request for the Roland SH-201 synthesizer: F0 41 10 00 00 16 11 20 00 00 00 00 00 00 21 3F F7 Further Reading --------------- * `An Introduction to MIDI `_ * `MIDI Basics `_ (by Yamaha) * `Wikipedia's page on MIDI `_ * `MIDI Manufacturers Association `_ * `A full table of MIDI messages `_ * `Essentials of the MIDI protocol `_ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/acknowledgements.rst0000644000175000017500000000035700000000000017763 0ustar00olembolemb00000000000000Acknowledgments =============== Thanks to /u/tialpoy/ on Reddit for extensive code review and helpful suggestions. Thanks to everyone who has sent bug reports and patches. The PortMidi wrapper is based on portmidizero by Grant Yoshida. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608482251.0 mido-1.2.10/docs/authors.rst0000664000175000017500000000037600000000000016121 0ustar00olembolemb00000000000000Authors ======= Ole Martin Bjørndalen (lead programmer) and many other contributors. Many people have contributed to Mido over the years, but this page has not been updated to include them. The :doc:`/changes` page includes names of all contributors. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2433298 mido-1.2.10/docs/backends/0000775000175000017500000000000000000000000015446 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/backends/amidi.rst0000644000175000017500000000176600000000000017273 0ustar00olembolemb00000000000000amidi (Experimental) -------------------- Name: ``mido.backends.amidi`` Features: * Linux only. * very basic implementation. * no callbacks * can only access physical ports. (Devices that are plugged in.) * high overhead when sending since it runs the ``amidi`` command for each messages. * known bug: is one behind when receiving messages. See below. The ``amidi`` command (a part of ALSA) is used for I/O:: * ``amidi -l`` to list messages (in ``get_input_names()`` etc.) * ``amidi -d -p DEVICE`` to receive messages. ``amidi`` prints these out one on each line as hex bytes. Unfortunately it puts the newline at the beginning of the line which flushes the buffer before the message instead of after. This causes problems with non-blocking receiption using ``select.poll()`` which means messages are received one behind. This needs to be looked into. * ``amidi --send-hex MESSAGE_IN_HEX -p DEVICE`` to send messages. Since this is called for every messages the overhead is very high. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/backends/index.rst0000644000175000017500000000426600000000000017315 0ustar00olembolemb00000000000000Backends ======== .. toctree:: :maxdepth: 1 rtmidi portmidi pygame rtmidi_python amidi Choosing a Backend ------------------ Mido comes with five backends: * :doc:`RtMidi ` is the recommended backends. It has all the features of the other ones and more and is usually easier to install. * :doc:`PortMidi ` was the default backend up until 1.2. It uses the ``portmidi`` shared library and can be difficult to install on some systems. * :doc:`Pygame ` uses the ``pygame.midi``. * :doc:`rtmidi-python ` uses the ``rtmidi_python`` package, an alternative wrapper for PortMidi. It is currently very basic but easier to install on some Windows systems. * :doc:`Amidi ` is an experimental backend for Linux/ALSA that uses the command ``amidi`` to send and receive messages. If you want to use another than the RtMidi you can override this with the ``MIDO_BACKEND`` environment variable, for example:: $ MIDO_BACKEND=mido.backends.portmidi ./program.py Alternatively, you can set the backend from within your program:: >>> mido.set_backend('mido.backends.portmidi') >>> mido.backend This will override the environment variable. If you want to use more than one backend at a time, you can do:: rtmidi = mido.Backend('mido.backends.rtmidi') portmidi = mido.Backend('mido.backends.portmidi') input = rtmidi.open_input() output = portmidi.open_output() for message in input: output.send(message) The backend will not be loaded until you call one of the ``open_`` or ``get_`` methods. You can pass ``load=True`` to have it loaded right away. If you pass ``use_environ=True`` the module will use the environment variables ``MIDO_DEFAULT_INPUT`` etc. for default ports. Environment Variables --------------------- You can override the backend's choice of default ports with these three environment variables:: MIDO_DEFAULT_INPUT MIDO_DEFAULT_OUTPUT MIDO_DEFAULT_IOPORT For example:: $ MIDO_DEFAULT_INPUT='SH-201' python program.py or:: $ export MIDO_DEFAULT_OUTPUT='Integra-7' $ python program1.py $ python program2.py ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/backends/portmidi.rst0000644000175000017500000000163000000000000020025 0ustar00olembolemb00000000000000PortMidi -------- Name: ``mido.backends.portmidi`` Installing ^^^^^^^^^^ The PortMidi backend requires the ``portmidi`` shared library. `Ubuntu `_:: apt install libportmidi-dev `Homebrew `_:: brew install portmidi `MacPorts `_:: port install portmidi The backend will look for:: portmidi.so (Linux) portmidi.dll (Windows) portmidi.dynlib (macOS) Features ^^^^^^^^ Can send but doesn't receive ``active_sensing`` messages. PortMidi has no callback mechanism, so callbacks are implemented in Python with threads. Each port with a callback has a dedicated thread doing blocking reads from the device. Due to limitations in PortMidi the port list will not be up-to-date if there are any ports open. (The refresh is implemented by re-initalizing PortMidi which would break any open ports.) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/backends/pygame.rst0000644000175000017500000000033700000000000017463 0ustar00olembolemb00000000000000Pygame ------ Name: ``mido.backends.pygame`` The Pygame backend uses ``pygame.midi`` for I/O. Doesn't receive ``active_sensing``. Callbacks are currently not implemented. Pygame.midi is implemented on top of PortMidi. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/backends/rtmidi.rst0000644000175000017500000000706300000000000017474 0ustar00olembolemb00000000000000RtMidi (Default, Recommended) ----------------------------- Name: ``mido.backends.rtmidi`` The RtMidi backend is a thin wrapper around `python-rtmidi `_ Features: * callbacks * true blocking ``receive()`` in Python 3 (using a callback and a queue) * virtual ports * ports can be opened multiple times, each will receive a copy of each message * client name can be specified when opening a virtual port * sends but doesn't receive active sensing * port list is always up to date * all methods but ``close()`` are thread safe Port Names (Linux/ALSA ^^^^^^^^^^^^^^^^^^^^^^ When you're using Linux/ALSA the port names include client name and ALSA client and port numbers, for example: .. code-block:: python >>> mido.get_output_names() ['TiMidity:TiMidity port 0 128:0'] The ALSA client and port numbers ("128:0" in this case) can change from session to session, making it hard to hard code port names or use them in config files. To get around this the RtMidi backend allows you to leave out the the port number of port number and client names. These lines will all open the port above: .. code-block:: python mido.open_output('TiMidity port 0') .. code-block:: python mido.open_output('TiMidity:TiMidity port 0') .. code-block:: python mido.open_output('TiMidity:TiMidity port 0 128:0') There is currently no way to list ports without port number or client name. This can be added in a future version of there is demand for it and a suitable API is found. Virtual Ports ^^^^^^^^^^^^^ RtMidi is the only backend that can create virtual ports: .. code-block:: python >>> port = mido.open_input('New Port', virtual=True) >>> port Other applications can now connect to this port. (One oddity is that, at least in Linux, RtMidi can't see its own virtual ports, while PortMidi can see them.) Client Name ^^^^^^^^^^^ You can specify a client name for the port: (New in 1.2.0.) .. code-block:: python >>> port = mido.open_input('New Port', client_name='My Client') This requires python-rtmidi >= 1.0rc1. If ``client_name`` is passed the port will be a virtal port. .. note:: Unfortunately, at least with ALSA, opening two ports with the same ``client_name`` creates two clients with the same name instead of one client with two ports. There are a couple of problems with port names in Linux. First, RtMidi can't see some software ports such as ``amSynth MIDI IN``. PortMidi uses the same ALSA sequencer API, so this is problem in RtMidi. Second, in some versions of RtMidi ports are named inconsistently. For example, the input port 'Midi Through 14:0' has a corresponding output named 'Midi Through:0'. Unless this was intended, it is a bug in RtMidi's ALSA implementation. Choosing API ^^^^^^^^^^^^ The RtMidi library can be compiled with support for more than one API. You can select API by adding it after the module name, either in the environment variable:: $ export MIDO_BACKEND=mido.backends.rtmidi/LINUX_ALSA $ export MIDO_BACKEND=mido.backends.rtmidi/UNIX_JACK or in one of these:: >>> mido.set_backend('mido.backends.rtmidi/LINUX_ALSA') >>> mido.backend >>> mido.Backend('mido.backends.rtmidi/UNIX_JACK') This allows you to, for example, use both ALSA and JACK ports in the same program. To get a list of available APIs:: >>> mido.backend.module.get_api_names() ['LINUX_ALSA', 'UNIX_JACK'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/backends/rtmidi_python.rst0000644000175000017500000000076100000000000021073 0ustar00olembolemb00000000000000rtmidi_python ------------- Name: ``mido.backends.rtmidi_python`` Installing ^^^^^^^^^^ :: pip install rtmidi-python Features ^^^^^^^^ * uses the ``rtmidi_python`` package rather than ``python_rtmidi`` * supports callbacks * limited support for virtual ports (no client name) * no true blocking * sends but doesn't receive active sensing Since the API of ``rtmidi_python`` and ``python_rtmidi`` are almost identical it would make sense to refactor so they share most of the code. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/bin.rst0000644000175000017500000000252500000000000015200 0ustar00olembolemb00000000000000Included Programs ================= These are installed with Mido. mido-play --------- Plays back one or more MIDI files:: $ mido-play song1.mid song2.mid mido-ports ---------- Lists available input and output ports and shows environment variables and the current backend module. mido-serve ---------- Serves one or more ports over the network, for example:: $ mido-serve :9080 'Integra-7' You can now connect to this port with ``mido-forward`` (or use ``mido.sockets.connect()`` and send messages to it. The messages will be forwarded to every port you listed (in this case 'Integra-7'). mido-connect ------------ Forwards all messages that arrive on one or more ports to a server. For example, to use the SH-201 keyboard connected to this computer to play sounds on the Integra-7 on a computer named ``mac.local`` (which runs the server as above), you can do:: $ mido-connect mac.local:9080 'SH-201' Note that you may experience latency and jitter, so this may not be very useful for live playing or for playing back songs. There is also no security built in, so you should only use this on a trusted network. (Anyone can connect and send anything, including harmful sysex messages.) ``mido-serve`` and ``mido-connect`` are only included as fun programs to play with, but may in the future be expanded into something more usable. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1620661179.0 mido-1.2.10/docs/changes.rst0000664000175000017500000005714700000000000016054 0ustar00olembolemb00000000000000Changes ======= (See :doc:`roadmap` for future plans.) Release History --------------- 1.2.10 (2021-05-10) ^^^^^^^^^^^^^^^^^^^ * New ``repr()`` format for messages, tracks and MIDI file objects. (Implemented by John Belmonte, pull request #164.) * added new example ``midifiles/show_midifile.py`` based on the new ``repr()`` format. * Added ``msg.is_cc()`` method. Checks if message is a control change. Can also be used to check for a specific control change number, for example ``msg.is_cc(7)``. * Fixed memory leaks in RtMidi backend (issue #256, fix by The Other Days, pull request #264.) * clip now works with sysex messages (Fix by Avatar Timo Stüber, pull request #229.) * Improved docs and error message for time attribute in a message. (tomerv, pull request #249.) * Improved MidiFile.play to avoid time drift. (Implemented by John Belmonte, pull request #161.) * bugfix: MIDO_DEFAULT_INPUT was misspelled in mido-ports causing it to be show as 'not set' even though it was set. (Fix by Bernhard Wagner, pull request #192.) * Now only copies ports once in ports.multi_receive() (Tom Ritchford, pull request #191.) * Ports lists returned from ``get_input_names()`` and friends are no longer sorted. (Suggested and implemented by Ryan McCampbell, issue #298.) * Updated linke in docs to point to the new home github.com/mido/ (Fixed by Joshua Mayers, pull request #177.) * thanks to Christopher Arndt, Kathryn DiPippo and Timo Stüber for fixing flake8 issues. 1.2.9 (2018-10-05) ^^^^^^^^^^^^^^^^^^ * rewrote ``Parser`` class around a MIDI tokenizer. Should lead to slight speedup and much cleaner code. * bugfix: `data` attribute was missing for `UnknownMetaMessage` objects. This caused `AttributeError` when the messages were printed or saved to a file. Also, the documentation incorrectly listed the attribute as `_data` instead of `data`. (Reported by Groowy.) * bugfix: UnknownMetaMessage encoding was broken causing crashes when saving a file with unknown meta messages. (Reported by exeex, issue #159.) * bugfix: inputs and outputs were switched around when opening named ports with PortMidi backend. (Reproduced by Predrag Radovic, issue #108, fix by Juan Antonio Aldea, pull request #109.) * bugfix: time signature meta messages had wrong default value of 2/4. The default value is now 4/4. (Fix by Sebastian Böck, pull request #104.) * bugfix: ``msg.copy()`` didn't handle generators for sysex data. ``msg.copy(data=(i for i in range(3)))`` would give ``data=()`` instead of ``data=(0,1,2)``. (The code should be refactored so this is handled by the same function everywhere, such as in ``__init__()``, in ``copy()`` and in ``parser.feed()``.) * bugfix: ``MultiPort._receive()`` ignored the ``block`` parameter. (Fix by Tom Swirly, pull request #135.) * bugfix: sequencer number meta message was incorrectly limited to range 0..255 instead of 0..65335. (Reported by muranyia, issue #144.) * now using Tox for testing. (Implemented by Chris Apple, pull request #123.) * Travis integration up by Carl Thomé and Chris Apple. 1.2.8 (2017-06-30) ^^^^^^^^^^^^^^^^^^ * bugfix: nonblocking receive was broken for RtMidi IO ports. (Reported by Chris Apple, issue #99.) * bugfix: ``IOPort.poll()`` would block if another thread was waiting for ``receive()``. Fixed the problem by removing the lock, which was never needed in the first place as the embedded input port does its own locking. 1.2.7 (2017-05-31) ^^^^^^^^^^^^^^^^^^ * added max length when reading message from a MIDI file. This prevents Python from running out of memory when reading a corrupt file. Instead it will now raise an ``IOError`` with a descriptive error message. (Implemented by Curtis Hawthorne, pull request #95.) * removed dependency on ``python-rtmidi`` from tests. (Reported by Josue Ortega, issue #96.) 1.2.6 (2017-05-04) ^^^^^^^^^^^^^^^^^^ * bugfix: Sending sysex with Pygame in Python 3 failed with ``"TypeError: array() argument 1 must be a unicode character, not byte"``. (Reported by Harry Williamson.) * now handles ``sequence_number`` and ``midi_port`` messages with 0 data bytes. These are incorrect but can occur in rare cases. See ``mido/midifiles/test_midifiles.py`` for more. (Reported by Gilthans (issue #42) and hyst329 (issue #93)). 1.2.5 (2017-04-28) ^^^^^^^^^^^^^^^^^^ * bugfix: RtMidi backend ignored ``api`` argument. (Fix by Tom Feist, pull request #91.) 1.2.4 (2017-03-19) ^^^^^^^^^^^^^^^^^^ * fixed outdated python-rtmidi install instructions. (Reported by Christopher Arndt, issue #87.) 1.2.3 (2017-03-14) ^^^^^^^^^^^^^^^^^^ * typo and incorrect links in docs fixed by Michael (miketwo) (pull requests #84 and #85). 1.2.2 (2017-03-14) ^^^^^^^^^^^^^^^^^^ * bugfix: sysex data was broken in string format encoding and decoding. The data was encoded with spaces ('data=(1, 2, 3)') instead of as one word ('data=(1,2,3)'). * added some tests for string format. * bugfix: ``BaseOutput.send()`` raised string instead of ``ValueError``. 1.2.1 (2017-03-10) ^^^^^^^^^^^^^^^^^^ * bugfix: IO port never received anything when used with RtMidi backend. (Reported by dagargo, issue #83.) This was caused by a very old bug introduced in 1.0.3. IOPort mistakenly called the inner method ``self.input._receive()`` instead of ``self.input.receive()``. This happens to work for ports that override ``_receive()`` but not for the new RtMidi backend which overrides ``receive()``. (The default implementation of ``_receive()`` just drops the message on the floor.) * bugfix: PortMidi backend was broken due to missing import (``ctypes.byref``). (Introduced in 1.2.0.) 1.2.0 (2017-03-07) ^^^^^^^^^^^^^^^^^^^ New implementation of messages and parser: * completely reimplemented messages. The code is now much simpler, clearer and easier to work with. * new contructors ``Message.from_bytes()``, ``Message.from_hex()``, ``Message.from_str()``. * new message attributes ``is_meta`` and ``is_realtime``. Frozen (immutable) messages: * added ``FrozenMessage`` and ``FrozenMetaMessage``. These are immutable versions of ``Message`` and ``MetaMessage`` that are hashable and thus can be used as dictionary keys. These are available in ``mido.frozen``. (Requested by Jasper Lyons, issue #36.) RtMidi is now the default backend: * switched default backend from PortMidi to RtMidi. RtMidi is easier to install on most systems and better in every way. If you want to stick to PortMidi you can either set the environment variable ``$MIDO_BACKEND=mido.backends.portmidi`` or call ``mido.set_backend('mido.backends.portmidi')`` in your program. * refactored the RtMidi backend to have a single ``Port`` class instead of inheriting from base ports. It was getting hard to keep track of it all. The code is now a lot easier to reason about. * you can now pass ``client_name`` when opening RtMidi ports: ``open_output('Test', client_name='My Client')``. When ``client_name`` is passed the port will automatically be a virtual port. * with ``LINUX_ALSA`` you can now omit client name and ALSA client/port number when opening ports, allowing you to do ``mido.open_output('TiMidity port 0')`` instead of ``mido.open_output('TiMidity:TiMidity port 0 128:0')``. (See RtMidi backend docs for more.) Changes to the port API: * ports now have ``is_input`` and ``is_output`` attributes. * new functions ``tick2second()`` and ``second2tick()``. (By Carl Thomé, pull request #71.) * added ``_locking`` attribute to ``BasePort``. You can set this to ``False`` in a subclass to do your own locking. * ``_receive()`` is now allowed to return a messages. This makes the API more consistent and makes it easier to implement thread safe ports. * ``pending()`` is gone. This had to be done to allow for the new ``_receive()`` behavior. * improved MIDI file documentation. (Written by Carl Thomé.) Other changes: * bugfix: if a port inherited from both ``BaseInput`` and ``BaseOutput`` this would cause ``BasePort.__init__()`` to be called twice, which means ``self._open()`` was also called twice. As a workaround ``BasePort.__init__()`` will check if ``self.closed`` exists. * added ``mido.version_info``. * ``mido.set_backend()`` can now be called with ``load=True``. * added ``multi_send()``. * ``MIN_PITCHWHEEL``, ``MAX_PITCHWHEEL``, ``MIN_SONGPOS`` and ``MAX_SONGPOS`` are now available in the top level module (for example ``mido.MIN_PITCHWHEEL``). * added experimental new backend ``mido.backends.amidi``. This uses the ALSA ``amidi`` command to send and receive messages, which makes it very inefficient but possibly useful for sysex transfer. * added new backend ``mido.backends.rtmidi_python`` (previously available in the examples folder.) This uses the ``rtmidi-python`` package instead of ``python-rtmidi``. For now it lacks some of features of the ``rtmidi`` backend, but can still be useful on systems where ``python-rtmidi`` is not available. (Requested by netchose, issue #55.) 1.1.24 (2017-02-16) ^^^^^^^^^^^^^^^^^^^ * bugfix: PortMidi backend was broken on macOS due to a typo. (Fix by Sylvain Le Groux, pull request #81.) 1.1.23 (2017-01-31) ^^^^^^^^^^^^^^^^^^^ * bugfix: ``read_syx_file()`` didn't handle '\n' in text format file causing it to crash. (Reported by Paul Forgey, issue #80.) 1.1.22 (2017-01-27) ^^^^^^^^^^^^^^^^^^^ * the bugfix in 1.1.20 broke blocking receive() for RtMidi. Reverting the changes. This will need some more investigation. 1.1.21 (2017-01-26) ^^^^^^^^^^^^^^^^^^^ * bugfix: MidiFile save was broken in 1.1.20 due to a missing import. 1.1.20 (2017-01-26) ^^^^^^^^^^^^^^^^^^^ * bugfix: close() would sometimes hang for RtMidi input ports. (The bug was introduced in 1.1.18 when the backend was rewritten to support true blocking.) * Numpy numbers can now be used for all message attributes. (Based on implementation by Henry Mao, pull request #78.) The code checks against numbers.Integral and numbers.Real (for the time attribute) so values can be any subclass of these. 1.1.19 (2017-01-25) ^^^^^^^^^^^^^^^^^^^ * Pygame backend can now receive sysex messages. (Fix by Box of Stops.) * bugfix: ``libportmidi.dylib`` was not found when using MacPorts. (Fix by yam655, issue #77.) * bugfix: ``SocketPort.__init()`` was not calling ``IOPort.__init__()`` which means it didn't get a ``self._lock``. (Fixed by K Lars Lohn, pull request #72. Also reported by John J. Foerch, issue #79.) * fixed typo in intro example (README and index.rst). Fix by Antonio Ospite (pull request #70), James McDermott (pull request #73) and Zdravko Bozakov (pull request #74). * fixed typo in virtual ports example (Zdravko Bozakov, pull request #75.) 1.1.18 (2016-10-22) ^^^^^^^^^^^^^^^^^^^ * ``time`` is included in message comparison. ``msg1 == msg2`` will now give the same result as ``str(msg1) == str(msg2)`` and ``repr(msg1)`` == ``repr(msg2)``. This means you can now compare tracks wihout any trickery, for example: ``mid1.tracks == mid2.tracks``. If you need to leave out time the easiest was is ``msg1.bytes() == msg2.bytes()``. This may in rare cases break code. * bugfix: ``end_of_track`` messages in MIDI files were not handled correctly. (Reported by Colin Raffel, issue #62). * bugfix: ``merge_tracks()`` dropped messages after the first ``end_of_track`` message. The new implementation removes all ``end_of_track`` messages and adds one at the end, making sure to adjust the delta times of the remaining messages. * refactored MIDI file code. * ``mido-play`` now has a new option ``-m / --print-messages`` which prints messages as they are played back. * renamed ``parser._parsed_messages`` to ``parser.messages``. ``BaseInput`` and ``SocketPort`` use it so it should be public. * ``Parser()`` now takes an option argument ``data`` which is passed to ``feed()``. 1.1.17 (2016-10-06) ^^^^^^^^^^^^^^^^^^^ * RtMidi now supports true blocking ``receive()`` in Python 3. This should result in better performance and lower latency. (Thanks to Adam Roberts for helping research queue behavior. See issue #49 for more.) * bugfix: ``MidiTrack.copy()`` (Python 3 only) returned ``list``. * fixed example ``queue_port.py`` which broke when locks where added. 1.1.16 (2016-09-27) ^^^^^^^^^^^^^^^^^^^ * bugfix: ``MidiTrack`` crashed instead of returning a message on ``track[index]``. Fix by Colin Raffel (pull request #61). * added ``__add__()`` and ``__mul__()`` to ``MidiTrack`` so ``+`` and ``*`` will return tracks instead of lists. * added ``poll()`` method to input ports as a shortcut for ``receive(block=False)``. * added example ``rtmidi_python_backend.py``, a backend for the rtmidi-python package (which is different from the python-rtmidi backend that Mido currently uses.) This may at some point be added to the package but for now it's in the examples folder. (Requested by netchose, issue #55.) * removed custom ``_import_module()``. Its only function was to make import errors more informative by showing the full module path, such as ``ImportError: mido.backends.rtmidi`` instead of just ``ImportError: rtmidi``. Unfortunately it ended up masking import errors in the backend module, causing confusion. It turns ``importlib.import_module()`` can be called with the full path, and on Python 3 it will also display the full path in the ``ImportError`` message. 1.1.15 (2016-08-24) ^^^^^^^^^^^^^^^^^^^ * Sending and receiving messages is now thread safe. (Initial implementation by Adam Roberts.) * Bugfix: ``PortServer`` called ``__init__`` from the wrong class. (Fix by Nathan Hurst.) * Changes to ``MidiTrack``: * ``MidiTrack()`` now takes a as a parameter an iterable of messages. Examples: .. code-block:: python MidiTrack(messages) MidiTrack(port.iter_pending()) MidiTrack(msg for msg in some_generator) * Slicing a ``MidiTrack`` returns a ``MidiTrack``. (It used to return a ``list``.) Example: .. code-block:: python track[1:10] * Added the ability to use file objects as well as filenames when reading, writing and saving MIDI files. This allows you to create a MIDI file dynamically, possibly *not* using mido, save it to an io.BytesIO, and then play that in-memory file, without having to create an intermediate external file. Of course the memory file (and/or the MidiFile) can still be saved to an external file. (Implemented by Brian O'Neill.) * PortMidi backend now uses pm.lib.Pm_GetHostErrorText() to get host error messages instead of just the generic "PortMidi: \`Host error\'". (Implemented by Tom Manderson.) Thanks to Richard Vogl and Tim Cook for reporting errors in the docs. 1.1.14 (2015-06-09) ^^^^^^^^^^^^^^^^^^^ * bugfix: merge_tracks() concatenated the tracks instead of merging them. This caused tracks to be played back one by one. (Issue #28, reported by Charles Gillingham.) * added support for running status when writing MIDI files. (Implemented by John Benediktsson.) * rewrote the callback system in response to issues #23 and #25. * there was no way to set a callback function if the port was opened without one. (Issue#25, reported by Nils Werner.) Callbacks can now be set and cleared at any time by either passing one to ``open_input()`` or updating the ``callback`` attribute. This causes some slight changes to the behavior of the port when using callbacks. Previously if you opened the port with a callback and then set ``port.callback = None`` the callback thread would keep running but drop any incoming messages. If you do the same now the callback thread will stop and the port will return normal non-callback behavior. If you want the callback thread to drop messages you can set ``port.callback = lambda message: None``. Also, ``receive()`` no longer checks ``self.callback``. This was inconsistent as it was the only method to do so. It also allows ports that don't support callbacks to omit the ``callback`` attribute. * bugfix: closing a port would sometimes cause a segfault when using callbacks. (Issue #24, reported by Francesco Ceruti.) * bugfix: Pygame ports were broken due to a faulty check for ``virtual=True``. * now raises ``ValueError`` instead of ``IOError`` if you pass ``virtual`` or ``callback`` while opening a port and the backend doesn't support them. (An unsupported argument is not an IO error.) * fixed some errors in backend documentation. (Pull request #23 by velolala.) * ``MultiPort`` now has a ``yield_port`` argument just like ``multi_receive()``. 1.1.13 (2015-02-07) ^^^^^^^^^^^^^^^^^^^ * the PortMidi backend will now return refresh the port list when you ask for port names are open a new port, which means you will see devices that you plug in after loading the backend. (Due to limitations in PortMidi the list will only be refreshed if there are no open ports.) * bugfix: ``tempo2bpm()`` was broken and returned the wrong value for anything but 500000 microseconds per beat (120 BPM). (Reported and fixed by Jorge Herrera, issue #21) * bugfix: ``merge_tracks()`` didn't work with empty list of tracks. * added proper keyword arguments and doc strings to open functions. 1.1.12 (2014-12-02) ^^^^^^^^^^^^^^^^^^^ * raises IOError if you try to open a virtual port with PortMidi or Pygame. (They are not supported by these backends.) * added ``merge_tracks()``. * removed undocumented method ``MidiFile.get_messages()``. (Replaced by ``merge_tracks(mid.tracks)``.) * bugfix: ``receive()`` checked ``self.callback`` which didn't exist for all ports, causing an ``AttributeError``. 1.1.11 (2014-10-15) ^^^^^^^^^^^^^^^^^^^ * added ``bpm2tempo()`` and ``tempo2bpm()``. * fixed error in documentation (patch by Michael Silver). * added notes about channel numbers to documentation (reported by ludwig404 / leonh, issue #18). 1.1.10 (2014-10-09) ^^^^^^^^^^^^^^^^^^^ * bugfix: MidiFile.length was computer incorrectly. * bugfix: tempo changes caused timing problems in MIDI file playback. (Reported by Michelle Thompson.) * mido-ports now prints port names in single ticks. * MidiFile.__iter__() now yields end_of_track. This means playback will end there instead of at the preceding message. 1.1.9 (2014-10-06) ^^^^^^^^^^^^^^^^^^ * bugfix: _compute_tick_time() was not renamed to _compute_seconds_per_tick() everywhere. * bugfix: sleep time in play() was sometimes negative. 1.1.8 (2014-09-29) ^^^^^^^^^^^^^^^^^^ * bugfix: timing in MIDI playback was broken from 1.1.7 on. Current time was subtracted before time stamps were converted from ticks to seconds, leading to absurdly large delta times. (Reported by Michelle Thompson.) * bugfix: ``read_syx_file()`` didn't handle empty file. 1.1.7 (2014-08-12) ^^^^^^^^^^^^^^^^^^ * some classes and functions have been moved to more accessible locations:: from mido import MidiFile, MidiTrack, MetaMessage from mido.midifiles import MetaSpec, add_meta_spec * you can now iterate over a MIDI file. This will generate all MIDI messages in playback order. The ``time`` attribute of each message is the number of seconds since the last message or the start of the file. (Based on suggestion by trushkin in issue #16.) * added get_sleep_time() to complement set_sleep_time(). * the Backend object no longer looks for the backend module exists on startup, but will instead just import the module when you call one of the ``open_*()`` or ``get_*()`` functions. This test didn't work when the library was packaged in a zip file or executable. This means that Mido can now be installed as Python egg and frozen with tools like PyInstaller and py2exe. See "Freezing Mido Programs" for more on this. (Issue #17 reported by edauenhauer and issue #14 reported by netchose.) * switched to pytest for unit tests. 1.1.6 (2014-06-21) ^^^^^^^^^^^^^^^^^^ * bugfix: package didn't work with easy_install. (Issue #14, reported by netchose.) * bugfix: 100% memory consumption when calling blocking receive() on a PortMidi input. (Issue #15, reported by Francesco Ceruti.) * added wheel support: http://pythonwheels.com/ 1.1.5 (2014-04-18) ^^^^^^^^^^^^^^^^^^ * removed the 'mode' attribute from key_signature messages. Minor keys now have an 'm' appended, for example 'Cm'. * bugfix: sysex was broken in MIDI files. * bugfix: didn't handle MIDI files without track headers. * bugfix: MIDI files didn't handle channel prefix > 15 * bugfix: MIDI files didn't handle SMPTE offset with frames > 29 1.1.4 (2014-10-04) ^^^^^^^^^^^^^^^^^^ * bugfix: files with key signatures Cb, Db and Gb failed due to faulty error handling. * bugfix: when reading some MIDI files Mido crashed with the message "ValueError: attribute must be in range 0..255". The reason was that Meta messages set running status, which caused the next statusless message to be falsely interpreted as a meta message. (Reported by Domino Marama). * fixed a typo in MidiFile._read_track(). Sysex continuation should work now. * rewrote tests to make them more readable. 1.1.3 (2013-10-14) ^^^^^^^^^^^^^^^^^^ * messages are now copied on send. This allows the sender to modify the message and send it to another port while the two ports receive their own personal copies that they can modify without any side effects. 1.1.2 (2013-10-05) ^^^^^^^^^^^^^^^^^^ * bugfix: non-ASCII character caused trouble with installation when LC_ALL=C. (Reported by Gene De Lisa) * bugfix: used old exception handling syntax in rtmidi backend which broke in 3.3 * fixed broken link in 1.1.1 (2013-10-04) ^^^^^^^^^^^^^^^^^^ * bugfix: mido.backends package was not included in distribution. 1.1.0 (2013-10-01) ^^^^^^^^^^^^^^^^^^ * added support for selectable backends (with MIDO_BACKEND) and included python-rtmidi and pygame backends in the official library (as mido.backend.rtmidi and mido.backend.pygame). * added full support for MIDI files (read, write playback) * added MIDI over TCP/IP (socket ports) * added utility programs mido-play, mido-ports, mido-serve and mido-forward. * added support for SMPTE time code quarter frames. * port constructors and ``open_*()`` functions can now take keyword arguments. * output ports now have reset() and panic() methods. * new environment variables MIDO_DEFAULT_INPUT, MIDO_DEFAULT_OUTPUT and MIDO_DEFAULT_IOPORT. If these are set, the open_*() functions will use them instead of the backend's default ports. * added new meta ports MultiPort and EchoPort. * added new examples and updated the old ones. * format_as_string() now takes an include_time argument (defaults to True) so you can leave out the time attribute. * sleep time inside sockets can now be changed. * Message() no longer accepts a status byte as its first argument. (This was only meant to be used internally.) * added callbacks for input ports (PortMidi and python-rtmidi) * PortMidi and pygame input ports now actually block on the device instead of polling and waiting. * removed commas from repr() format of Message and MetaMessage to make them more consistent with other classes. 1.0.4 (2013-08-15) ^^^^^^^^^^^^^^^^^^ * rewrote parser 1.0.3 (2013-07-12) ^^^^^^^^^^^^^^^^^^ * bugfix: __exit__() didn't close port. * changed repr format of message to start with "message". * removed support for undefined messages. (0xf4, 0xf5, 0xf7, 0xf9 and 0xfd.) * default value of velocity is now 64 (0x40). (This is the recommended default for devices that don't support velocity.) 1.0.2 (2013-07-31) ^^^^^^^^^^^^^^^^^^ * fixed some errors in the documentation. 1.0.1 (2013-07-31) ^^^^^^^^^^^^^^^^^^ * multi_receive() and multi_iter_pending() had wrong implementation. They were supposed to yield only messages by default. 1.0.0 (2013-07-20) ^^^^^^^^^^^^^^^^^^ Initial release. Basic functionality: messages, ports and parser. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/docs/conf.py0000664000175000017500000001730500000000000015201 0ustar00olembolemb00000000000000# -*- coding: utf-8 -*- # # Mido documentation build configuration file, created by # sphinx-quickstart on Wed Jun 26 16:58:08 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, 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('..')) import mido from mido import __version__ # -- 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.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Mido' copyright = u'Ole Martin Bjørndalen' # 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 = __version__ # The full version, including alpha/beta/rc tags. release = __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 = [] # -- 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 = 'default' # 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 = 'Midodoc' # -- 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', 'Mido.tex', u'Mido Documentation', u'Ole Martin Bjørndalen', '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', 'mido', u'Mido Documentation', [u'Ole Martin Bjørndalen'], 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', 'Mido', u'Mido Documentation', u'Ole Martin Bjørndalen', 'Mido', 'MIDI Objects for Python', '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' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1620661093.0 mido-1.2.10/docs/contributing.rst0000664000175000017500000000432300000000000017137 0ustar00olembolemb00000000000000Contributing ============ Questions --------- If you have questions about contributing code or suggestions for how to make contributing easier, please write at https://github.com/mido/mido/discussions. Installing for developers ------------------------- To install the dev dependencies, you can run the command:: pip install -e .[dev] This will install all needed dependencies for testing and documentation. Testing ------- `pytest `_ is used for unit testing. The tests are found in `mido/test_*.py`. Tests can be run using the command:: py.test Before submission, it is required that the tox tests run and pass. Run the tox tests using:: tox It is required to test on at least 2.7 and 3.5 before submission. Any other passes are nice to have You can also set up a commit hook:: echo "tox" >.git/hooks/pre-commit chmod +x .git/hooks/pre-commit This will run tests when you commit and cancel the commit if any tests fail. Testing MIDI file support ------------------------- Test Files ^^^^^^^^^^ The `Lakh MIDI Dataset `_ is a great resource for testing the MIDI file parser. Publishing (Release Checklist) ------------------------------ I am currently the only one with access to publishing on PyPI and readthedocs. This will hopefully change in the future. Bump Version ^^^^^^^^^^^^ X.Y.Z is the version, for example 1.1.18 or 1.2.0. * update version and date in `docs/changes.rst` * update version in `mido/__about__.py` * `git commit -a -c "Bumped version to X.Y.Z."` Publish on PyPI ^^^^^^^^^^^^^^^ I like to do this before I push to GitHub. This way if the package fails to upload I can roll back and fix it before I push my changes. :: rm -rf dist/* python setup.py bdist_wheel --universal python setup.py sdist twine upload dist/* Push to GitHub ^^^^^^^^^^^^^^ :: git tag X.Y.Z git push git push --tags Update the stable branch (if this is a stable release): :: git checkout stable git pull . master git push git checkout master Update Read the Docs ^^^^^^^^^^^^^^^^^^^^ Log into readthedocs.org and build the latest documentation. This is set up to use the stable branch. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1607910294.0 mido-1.2.10/docs/freezing.rst0000664000175000017500000000216600000000000016244 0ustar00olembolemb00000000000000Freezing to EXE File ==================== PyInstaller ----------- When you build an executable with PyInstaller and run it you may get import errors like this one:: ImportError: No module named mido.backends.portmidi The reason is that Mido uses ``import_module()`` to import the backend modules, while PyInstaller looks for ``import`` statements. The easiest fix is to import the module at the top of the program:: import mido import mido.backends.portmidi # The backend you want to use. print(mido.get_input_names()) and then run ``pyinstaller`` like usual:: $ pyinstaller --onefile midotest.py $ ./dist/midotest [u'Midi Through Port-0'] If you don't want to change the program, you can instead declare the backend module as a `hidden import `_. bbFreeze, py2exe, cx_Freeze, py2app, etc. ----------------------------------------- I suspect the same is true for these, but I have not had a chance to try it out yet. Adding the explicit ``import`` statement should always work, though, since Mido backends are just normal Python modules. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/frozen_messages.rst0000644000175000017500000000255000000000000017620 0ustar00olembolemb00000000000000Frozen Messages --------------- (New in 1.2.) Since Mido messages are mutable (can change) they can not be hashed or put in dictionaries. This makes it hard to use them for things like Markov chains. In these situations you can use frozen messages: .. code-block:: python from mido.frozen import FrozenMessage msg = FrozenMessage('note_on') d = {msg: 'interesting'} Frozen messages are used and behave in exactly the same way as normal messages with one exception: attributes are not settable. There are also variants for meta messages (``FrozenMetaMessage`` and ``FrozenUnknownMetaMessage``). You can freeze and thaw messages with: .. code-block:: python from mido.frozen import freeze_message, thaw_message frozen = freeze_message(msg) thawed = thaw_message(frozen) ``thaw_message()`` will always return a copy. Passing a frozen message to ``freeze_message()`` will return the original message. Both functions return ``None`` if you pass ``None`` which is handy for things like: .. code-block:: python msg = freeze_message(port.receive()) # Python 3 only: for msg in map(freeze_message, port): ... # Python 2 and 3: for msg in (freeze_message(msg) for msg in port): ... To check if a message is frozen: .. code-block:: python from mido.frozen import is_frozen if is_frozen(msg): ... ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2433298 mido-1.2.10/docs/images/0000775000175000017500000000000000000000000015141 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/images/midi_time.svg0000644000175000017500000004754700000000000017641 0ustar00olembolemb00000000000000 image/svg+xml Minutes Beats Ticks 4 beats per minute (BPM) 3 ticks per beat ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/implementing_backends.rst0000644000175000017500000000230400000000000020745 0ustar00olembolemb00000000000000Writing a New Backend ===================== A backend is a Python module with one or more of these:: Input -- an input port class Output -- an output port class IOPort -- an I/O port class get_devices() -- returns a list of devices Once written, the backend can be used by setting the environment variable ``MIDO_BACKEND`` or by calling ``mido.set_backend()``. In both cases, the path of the module is used. ``Input`` And input class for ``open_input()``. This is only required if the backend supports input. ``Output`` And output class for ``open_output()``. This is only required if the backend supports output. ``IOPort`` An I/O port class for ``open_ioport()``. If this is not found, ``open_ioport()`` will return ``mido.ports.IOPort(Input(), Output())``. ``get_devices(**kwargs)`` Returns a list of devices, where each device is dictionary with at least these three values:: { 'name': 'Some MIDI Input Port', 'is_input': True, 'is_output': False, } These are used to build return values for ``get_input_names()`` etc.. This function will also be available to the user directly. For examples, see ``mido/backends/``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/implementing_ports.rst0000644000175000017500000001460500000000000020351 0ustar00olembolemb00000000000000Writing a New Port ================== The Mido port API allows you to write new ports to do practically anything. A new port type can be defined by subclassing one of the base classes and overriding one or more methods. Here's an example:: from mido.ports import BaseOutput class PrintPort(BaseOutput): def _send(message): print(message) >>> port = PrintPort() >>> port.send(msg) note_on channel=0 note=0 velocity=64 time=0 ``_send()`` will be called by ``send()``, and is responsible for actually sending the message somewhere (or in this case print it out). Overridable Methods ------------------- There are four overridable methods (all of them default to doing nothing):: ``_open(self, **kwargs)`` Should do whatever is necessary to initialize the port (for example opening a MIDI device.) Called by ``__init__()``. The ``name`` attribute is already set when ``_open()`` is called, but you will get the rest of the keyword arguments. If your port takes a different set of arguments or has other special needs, you can override ``__init__()`` instead. ``_close(self)`` Should clean up whatever resources the port has allocated (such as closing a MIDI device). Called by ``close()`` if the port is not already closed. ``_send(self, message)`` (Output ports only.) Should send the message (or do whatever else that makes sense). Called by ``send()`` if the port is open and the message is a Mido message. (You don't need any type checking here.) Raise IOError if something goes wrong. ``_receive(self, block=True)`` (Input ports only.) Should return a message if there is one available. If ``block=True`` it should block until a message is available and then return it. If ``block=False`` it should return a message or ``None`` if there is no message yet. If you return ``None`` the enclosing ``pending()`` method will check ``self._messages`` and return one from there. .. note:: ``Prior to 1.2.0 ``_receive()`` would put messages in ``self._messages`` (usually via the parser) and rely on ``receive()`` to return them to the user. Since this was not thread safe the API was changed in 1.2.0 to allow the ``_receive()`` to return a message. The old behavior is still supported, so old code will work as before. Raise IOError if something goes wrong. Each method corresponds to the public method of the same name, and will be called by that method. The outer method will take care of many things, so the inner method only needs to do the very minimum. The outer method also provides the doc string, so you don't have to worry about that. The base classes are ``BaseInput``, ``BaseOutput`` and ``BaseIOPort`` (which is a subclass of the other two.) Locking ------- The calls to ``_receive()`` and ``_send()`` will are protected by a lock, ``left.lock``. As a result all send and receive will be thread safe. .. note:: If your ``_receive()`` function actually blocks instead of letting the parent class handle it ``poll()`` will not work. The two functions are protected by the same lock, so when ``receive()`` blocks it will also block other threads calling ``poll()``. In this case you need to implement your own locking. If you want to implement your own thread safety you can set the ``_locking`` attribute in your class:: class MyInput(ports.BaseInput): _locking = False ... An example of this is ``mido.backends.rtmidi`` where the callback is used to feed an internal queue that ``receive()`` reads from. Examples -------- An full example of a device port for the imaginary MIDI library ``fjopp``:: import fjopp from mido.ports import BaseIOPort # This defines an I/O port. class FjoppPort(BaseIOPort): def _open(self, **kwargs): self._device = fjopp.open_device(self.name) def _close(self): self._device.close() def _send(self, message): self.device.write(message.bytes()) def _receive(self, block=True): while True: data = self.device.read() if data: self._parser.feed(data) else: return If ``fjopp`` supports blocking read, you can do this to actually block on the device instead of letting ``receive()`` and friends poll and wait for you:: def _receive(self, block=True): if block: # Actually block on the device. # (``read_blocking()`` will always return some data.) while not ``self._messages``: data = self._device.read_blocking() self._parser.feed(data) else: # Non-blocking read like above. while True: data = self.device.read() if data: self._parser.feed(data) This can be used for any kind of port that wants to block on a pipe, an socket or another input source. Note that Mido will still use polling and waiting when receiving from multiple ports (for example in a ``MultiPort``). If you want separate input and output classes, but the ``_open()`` and ``_close()`` methods have a lot in common, you can implement this using a mix-in. Sometimes it's useful to know inside the methods whether the port supports input or output. The way to do this is to check for the methods ```send()`` and ``receive()``, for example:: def _open(self, **kwargs): if hasattr(self, 'send'): # This is an output port. if hasattr(self, 'receive'): # This is an input port. if hasattr(self, 'send') and hasattr(self, 'receive'): # This is an I/O port. Attributes ---------- A port has some attributes that can be useful inside your methods. ``name`` The name of the port. The value is device specific and does not have to be unique. It can have any value, but must be a string or ``None``. This is set by ``__init__()``. ``closed`` True if the port is closed. You don't have to worry about this inside your methods. ``_messages`` This is a ``collections.deque`` of messages that have been read and are ready to be received. This is a shortcut to ``_parser.messages``. ``_device_type`` (Optional.) If this attribute exists, it's a string which will be used in ``__repr__()``. If it doesn't exist, the class name will be used instead. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608496479.0 mido-1.2.10/docs/index.rst0000664000175000017500000000334200000000000015537 0ustar00olembolemb00000000000000.. Mido documentation master file, created by sphinx-quickstart on Wed Jun 26 16:58:08 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Mido - MIDI Objects for Python ============================== Version |version| Mido is a library for working with MIDI messages and ports: .. code-block:: python >>> import mido >>> msg = mido.Message('note_on', note=60) >>> msg.type 'note_on' >>> msg.note 60 >>> msg.bytes() [144, 60, 64] >>> msg.copy(channel=2) Message('note_on', channel=2, note=60, velocity=64, time=0) .. code-block:: python port = mido.open_output('Port Name') port.send(msg) .. code-block:: python with mido.open_input() as inport: for msg in inport: print(msg) .. code-block:: python mid = mido.MidiFile('song.mid') for msg in mid.play(): port.send(msg) Mido is short for MIDI objects. Source code ----------- https://github.com/mido/mido/ About This Document ------------------- This document is available at https://mido.readthedocs.io/ To build locally:: python setup.py docs This requires Sphinx. The resulting files can be found in ``docs/_build/``. Contents -------- .. toctree:: :maxdepth: 2 changes roadmap installing backends/index contributing intro messages frozen_messages ports midi_files syx parsing string_encoding socket_ports bin implementing_ports implementing_backends freezing about_midi message_types meta_message_types lib resources license authors acknowledgements Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608567398.0 mido-1.2.10/docs/installing.rst0000664000175000017500000000136600000000000016600 0ustar00olembolemb00000000000000Installing Mido =============== Requirements ------------ Mido targets Python 3.6 and 2.7. There are no external dependencies unless you want to use the port backends, which are loaded on demand. Mido comes with backends for RtMidi (`python-rtmidi `_ or `rtmidi_python `_), `PortMidi `_ and `Pygame `_. See :doc:`backends/index` for help choosing a backend. Installing ---------- To install:: pip install mido If you want to use ports:: pip install python-rtmidi See :doc:`backends/index` for installation instructions for other backends. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608496479.0 mido-1.2.10/docs/intro.rst0000664000175000017500000001201500000000000015560 0ustar00olembolemb00000000000000Introduction (Basic Concepts) ============================= Mido is all about messages and ports. Messages -------- Mido allows you to work with MIDI messages as Python objects. To create a new message:: >>> from mido import Message >>> msg = Message('note_on', note=60) >>> msg Message('note_on', channel=0, note=60, velocity=64, time=0) .. note:: Mido numbers channels 0 to 15 instead of 1 to 16. This makes them easier to work with in Python but you may want to add and subtract 1 when communicating with the user. A list of all supported message types and their parameters can be found in :doc:`message_types`. The values can now be accessed as attributes:: >>> msg.type 'note_on' >>> msg.note 60 >>> msg.velocity 64 Attributes are also settable but this should be avoided. It's better to use ``msg.copy()``:: >>> msg.copy(note=100, velocity=127) Message('note_on', channel=0, note=100, velocity=127, time=0) Type and value checks are done when you pass parameters or assign to attributes, and the appropriate exceptions are raised. This ensures that the message is always valid. For more about messages, see :doc:`messages`. Type and Value Checking ----------------------- Mido messages come with type and value checking built in:: >>> import mido >>> mido.Message('note_on', channel=2092389483249829834) Traceback (most recent call last): File "", line 1, in File "/home/olemb/src/mido/mido/messages/messages.py", line 89, in __init__ check_msgdict(msgdict) File "/home/olemb/src/mido/mido/messages/checks.py", line 100, in check_msgdict check_value(name, value) File "/home/olemb/src/mido/mido/messages/checks.py", line 87, in check_value _CHECKS[name](value) File "/home/olemb/src/mido/mido/messages/checks.py", line 17, in check_channel raise ValueError('channel must be in range 0..15') ValueError: channel must be in range 0..15 This means that the message object is always a valid MIDI message. Ports ----- To create an output port and send a message:: >>> outport = mido.open_output() >>> outport.send(msg) To create an input port and receive a message:: >>> inport = mido.open_input() >>> msg = inport.receive() .. note:: Multiple threads can safely send and receive notes on the same port. This will give you the default output and input ports. If you want to open a specific port, you will need its name. To get a list of all available input ports:: >>> mido.get_input_names() ['Midi Through Port-0', 'SH-201', 'Integra-7'] >>> inport = mido.open_input('SH-201') All Mido ports can be used with the ``with`` statement, which will close the port for you:: with mido.open_input('SH-201') as inport: ... To iterate through all incoming messages:: for msg in inport: ... You can also receive and iterate over messages in a non-blocking way. For more about ports, see :doc:`ports`. All Ports are Ports ------------------- The input and output ports used above are device ports, which communicate with a (physical or virtual) MIDI device. Other port types include: * ``MultiPort``, which wraps around a set of ports and allow you to send to all of them or receive from all of them as if they were one. * ``SocketPort``, which communicates with another port over a TCP/IP (network) connection. * ``IOPort``, which wraps around an input and an output port and allows you to send and receive messages as if the two were the same port. Ports of all types look and behave the same way, so they can be used interchangeably. It's easy to write new port types. See :doc:`implementing_ports`. Virtual Ports ------------- Virtual ports allow you to create new ports that other applications can connect to:: with mido.open_input('New Port', virtual=True) as inport: for message in inport: print(message) The port should now appear to other applications as "New Port". Unfortunately virtual ports are not supported by PortMidi and Pygame so this only works with RtMidi. Parsing MIDI Bytes ------------------ Mido comes with a parser that allows you to turn bytes into messages. You can create a new parser:: >>> p = mido.Parser() >>> p.feed([0x90, 0x40]) >>> p.feed_byte(0x60) You can then fetch messages out of the parser:: >>> p.pending() 1 >>> for message in p: ... print(message) ... note_on channel=0 note=64 velocity=96 time=0 For more on parsers and parsing see :doc:`parsing`. You can also create a message from bytes using class methods (new in 1.2): .. code-block:: python msg1 = mido.Message.from_bytes([0x90, 0x40, 0x60]) msg2 = mido.Message.from_hex('90, 40 60') The bytes must contain exactly one complete message. If not ``ValueError`` is raised. Backends -------- Mido comes with backends for RtMidi and PortMidi and Pygame. The default is RtMidi. You can select another backend or even use multiple backends at the same time. For more on this, see :doc:`backends/index`. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/lib.rst0000644000175000017500000000445100000000000015176 0ustar00olembolemb00000000000000.. _api: Library Reference ================= Messages -------- .. module:: mido .. autoclass:: Message :members: :inherited-members: :undoc-members: Ports ----- .. autofunction:: open_input .. autofunction:: open_output .. autofunction:: open_ioport .. autofunction:: get_input_names .. autofunction:: get_output_names .. autofunction:: get_ioport_names Backends -------- .. autofunction:: set_backend .. autoclass:: Backend :members: :inherited-members: :undoc-members: Parsing ------- .. autofunction:: parse .. autofunction:: parse_all .. autoclass:: Parser :members: :inherited-members: :undoc-members: MIDI Files ----------- .. autoclass:: MidiFile :members: :inherited-members: :undoc-members: .. autoclass:: MidiTrack :members: :inherited-members: :undoc-members: .. autoclass:: MetaMessage :members: :inherited-members: :undoc-members: .. autofunction:: tick2second .. autofunction:: second2tick .. autofunction:: bpm2tempo .. autofunction:: tempo2bpm .. autofunction:: merge_tracks SYX Files --------- .. autofunction:: read_syx_file .. autofunction:: write_syx_file Port Classes and Functions -------------------------- .. module:: mido.ports .. autoclass:: BaseInput :members: :inherited-members: :undoc-members: .. autoclass:: BaseOutput :members: :inherited-members: :undoc-members: .. autoclass:: IOPort :members: :inherited-members: :undoc-members: .. autoclass:: MultiPort :members: :inherited-members: :undoc-members: .. autofunction:: multi_receive .. autofunction:: multi_iter_pending .. autofunction:: multi_send .. autofunction:: sleep .. autofunction:: set_sleep_time .. autofunction:: get_sleep_time .. autofunction:: panic_messages .. autofunction:: reset_messages Socket Ports ------------ .. module:: mido.sockets .. autoclass:: PortServer :members: :inherited-members: :undoc-members: .. autoclass:: SocketPort :members: :inherited-members: :undoc-members: .. autofunction:: parse_address Frozen Messages --------------- .. module:: mido.frozen .. autofunction:: freeze_message .. autofunction:: thaw_message .. autofunction:: is_frozen .. autoclass:: Frozen .. autoclass:: FrozenMessage .. autoclass:: FrozenMetaMessage .. autoclass:: FrozenUnknownMetaMessage ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/license.rst0000644000175000017500000000213300000000000016045 0ustar00olembolemb00000000000000License ======= The MIT License (MIT) Copyright (c) 2013-infinity Ole Martin Bjørndalen 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/make.bat0000644000175000017500000001174400000000000015306 0ustar00olembolemb00000000000000@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. 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 ) 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\Mido.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Mido.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" == "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 ) :end ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608311148.0 mido-1.2.10/docs/message_types.rst0000664000175000017500000000454000000000000017301 0ustar00olembolemb00000000000000Message Types ============= Supported Messages ------------------ ============== ============================== Name Keyword Arguments / Attributes ============== ============================== note_off channel note velocity note_on channel note velocity polytouch channel note value control_change channel control value program_change channel program aftertouch channel value pitchwheel channel pitch sysex data quarter_frame frame_type frame_value songpos pos song_select song tune_request clock start continue stop active_sensing reset ============== ============================== ``quarter_frame`` is used for SMPTE time codes. See: http://www.electronics.dit.ie/staff/tscarff/Music_technology/midi/MTC.htm Parameter Types --------------- =========== ====================== ================ Name Valid Range Default Value =========== ====================== ================ channel 0..15 0 frame_type 0..7 0 frame_value 0..15 0 control 0..127 0 note 0..127 0 program 0..127 0 song 0..127 0 value 0..127 0 velocity 0..127 64 data (0..127, 0..127, ...) () (empty tuple) pitch -8192..8191 0 pos 0..16383 0 time any integer or float 0 =========== ====================== ================ .. note:: Mido numbers channels 0 to 15 instead of 1 to 16. This makes them easier to work with in Python but you may want to add and subtract 1 when communicating with the user. ``velocity`` is how fast the note was struck or released. It defaults to 64 so that if you don't set it, you will still get a reasonable value. (64 is the recommended default for devices that don't support it attack or release velocity.) The ``time`` is used in MIDI files as delta time. The ``data`` parameter accepts any iterable that generates numbers in 0..127. This includes:: mido.Message('sysex', data=[1, 2, 3]) mido.Message('sysex', data=range(10)) mido.Message('sysex', data=(i for i in range(10) if i % 2 == 0)) For details about the binary encoding of a MIDI message, see: http://www.midi.org/techspecs/midimessages.php ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608496479.0 mido-1.2.10/docs/messages.rst0000664000175000017500000001040300000000000016233 0ustar00olembolemb00000000000000Messages ======== A Mido message is a Python object with methods and attributes. The attributes will vary depending on message type. To create a new message:: >>> mido.Message('note_on') Message('note_on', channel=0, note=0, velocity=64, time=0) You can pass attributes as keyword arguments:: >>> mido.Message('note_on', note=100, velocity=3, time=6.2) Message('note_on', channel=0, note=100, velocity=3, time=6.2) All attributes will default to 0. The exceptions are ``velocity``, which defaults to 64 (middle velocity) and ``data`` which defaults to ``()``. You can set and get attributes as you would expect:: >>> msg = mido.Message('note_on') >>> msg.note 0 The ``type`` attribute can be used to determine message type:: >>> msg.type 'note_on' Attributes are also settable but it's always better to use ``msg.copy()``:: >>> msg.copy(note=99, time=100.0) Message('note_on', channel=0, note=99, velocity=64, time=100.0) .. note:: Mido always makes a copy of messages instead of modifying them so if you do the same you have immutable messages in practice. (Third party libraries may not follow the same rule.) .. note:: :doc:`/frozen_messages` are a variant of messages that are hashable and can be used as dictionary keys. They are also safe from tampering by third party libraries. You can freely convert between the two and use frozen messages wherever normal messages are allowed. Mido supports all message types defined by the MIDI standard. For a full list of messages and their attributes, see :doc:`/message_types`. Control Changes --------------- .. code-block:: python if msg.is_cc(): print('Control change message received') if msg.is_cc(7): print('Volume changed to', msg.value) Converting To Bytes ------------------- You can convert a message to MIDI bytes with one of these methods: >>> msg = mido.Message('note_on') >>> msg Message('note_on', channel=0, note=0, velocity=64, time=0) >>> msg.bytes() [144, 0, 64] >>> msg.bin() bytearray(b'\x90\x00@') >>> msg.hex() '90 00 40' Converting From Bytes --------------------- You can turn bytes back into messages with the :doc:`/parser `. You can also create a message from bytes using class methods (new in 1.2): .. code-block:: python msg1 = mido.Message.from_bytes([0x90, 0x40, 0x60]) msg2 = mido.Message.from_hex('90, 40 60') The bytes must contain exactly one complete message. If not ``ValueError`` is raised. The Time Attribute ------------------ Each message has a ``time`` attribute, which can be set to any value of type ``int`` or ``float`` (and in Python 2 also ``long``). Some parts of Mido use the attribute for special purposes. In MIDI file tracks, it is used as delta time (in ticks), and it must be a non-negative integer. In other parts of Mido, this value is ignored. .. note:: Before 1.1.18 the ``time`` attribute was not included in comparisons. If you want the old behavior the easies way is ``msg1.bytes()`` == ``msg2.bytes()``. To sort messages on time you can do:: messages.sort(key=lambda message: message.time) or:: import operator messages.sort(key=operator.attrgetter('time')) System Exclusive Messages ------------------------- System Exclusive (SysEx) messages are used to send device specific data. The ``data`` attribute is a tuple of data bytes which serves as the payload of the message:: >>> msg = Message('sysex', data=[1, 2, 3]) >>> msg Message('sysex', data=(1, 2, 3), time=0) >>> msg.hex() 'F0 01 02 03 F7' You can also extend the existing data:: >>> msg = Message('sysex', data=[1, 2, 3]) >>> msg.data += [4, 5] >>> msg.data += [6, 7, 8] >>> msg Message('sysex', data=(1, 2, 3, 4, 5, 6, 7, 8), time=0) Any sequence of integers is allowed, and type and range checking is applied to each data byte. These are all valid:: (65, 66, 67) [65, 66, 67] (i + 65 for i in range(3)) (ord(c) for c in 'ABC') bytearray(b'ABC') b'ABC' # Python 3 only. For example:: >>> msg = Message('sysex', data=bytearray(b'ABC')) >>> msg.data += bytearray(b'DEF') >>> msg Message('sysex', data=(65, 66, 67, 68, 69, 70), time=0) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608496479.0 mido-1.2.10/docs/meta_message_types.rst0000664000175000017500000002157500000000000020316 0ustar00olembolemb00000000000000Meta Message Types ================== Supported Messages ------------------ sequence_number (0x00) ^^^^^^^^^^^^^^^^^^^^^^ =============== ============ ======== Attribute Values Default =============== ============ ======== number 0..65535 0 =============== ============ ======== Sequence number in type 0 and 1 MIDI files; pattern number in type 2 MIDI files. text (0x01) ^^^^^^^^^^^ ============== ============== ======== Attribute Values Default ============== ============== ======== text string '' ============== ============== ======== General "Text" Meta Message. Can be used for any text based data. copyright (0x02) ^^^^^^^^^^^^^^^^ ============== ============== ======== Attribute Values Default ============== ============== ======== text string '' ============== ============== ======== Provides information about a MIDI file's copyright. track_name (0x03) ^^^^^^^^^^^^^^^^^ ============== ============== ======== Attribute Values Default ============== ============== ======== name string '' ============== ============== ======== Stores a MIDI track's name. instrument_name (0x04) ^^^^^^^^^^^^^^^^^^^^^^ ============== ============== ======== Attribute Values Default ============== ============== ======== name string '' ============== ============== ======== Stores an instrument's name. lyrics (0x05) ^^^^^^^^^^^^^ ============== ============== ======== Attribute Values Default ============== ============== ======== text string '' ============== ============== ======== Stores the lyrics of a song. Typically one syllable per Meta Message. marker (0x06) ^^^^^^^^^^^^^ ============== ============== ======== Attribute Values Default ============== ============== ======== text string '' ============== ============== ======== Marks a point of interest in a MIDI file. Can be used as the marker for the beginning of a verse, solo, etc. cue_marker (0x07) ^^^^^^^^^^^^^^^^^ ============== ============== ======== Attribute Values Default ============== ============== ======== text string '' ============== ============== ======== Marks a cue. IE: 'Cue performer 1', etc device_name (0x09) ^^^^^^^^^^^^^^^^^^ ============== ============== ======== Attribute Values Default ============== ============== ======== name string '' ============== ============== ======== Gives the name of the device. channel_prefix (0x20) ^^^^^^^^^^^^^^^^^^^^^ ============== ============== ======== Attribute Values Default ============== ============== ======== channel 0..255 0 ============== ============== ======== Gives the prefix for the channel on which events are played. midi_port (0x21) ^^^^^^^^^^^^^^^^ ============== ============== ======== Attribute Values Default ============== ============== ======== port 0..255 0 ============== ============== ======== Gives the MIDI Port on which events are played. end_of_track (0x2f) ^^^^^^^^^^^^^^^^^^^ ============== ============== ======== Attribute Values Default ============== ============== ======== n/a n/a n/a ============== ============== ======== An empty Meta Message that marks the end of a track. set_tempo (0x51) ^^^^^^^^^^^^^^^^ ============== ============== ======== Attribute Values Default ============== ============== ======== tempo 0..16777215 500000 ============== ============== ======== Tempo is in microseconds per beat (quarter note). You can use :py:func:`bpm2tempo` and :py:func:`tempo2bpm` to convert to and from beats per minute. Note that :py:func:`tempo2bpm` may return a floating point number. smpte_offset (0x54) ^^^^^^^^^^^^^^^^^^^ ============== ================= ======== Attribute Values Default ============== ================= ======== frame_rate 24, 25, 29.97, 30 24 hours 0..255 0 minutes 0..59 0 seconds 0..59 0 frames 0..255 0 sub_frames 0..99 0 ============== ================= ======== time_signature (0x58) ^^^^^^^^^^^^^^^^^^^^^ ============================ =============== ======== Attribute Values Default ============================ =============== ======== numerator 0..255 4 denominator 1..2**255 4 clocks_per_click 0..255 24 notated_32nd_notes_per_beat 0..255 8 ============================ =============== ======== Time signature of: 4/4 : MetaMessage('time_signature', numerator=4, denominator=4) 3/8 : MetaMessage('time_signature', numerator=3, denominator=8) .. note:: From 1.2.9 time signature message have the correct default value of 4/4. In earlier versions the default value was 2/4 due to a typo in the code. key_signature (0x59) ^^^^^^^^^^^^^^^^^^^^ ========= ================== ======== Attribute Values Default ========= ================== ======== key 'C', 'F#m', ... 'C' ========= ================== ======== Valid values: A A#m Ab Abm Am B Bb Bbm Bm C C# C#m Cb Cm D D#m Db Dm E Eb Ebm Em F F# F#m Fm G G#m Gb Gm Note: the mode attribute was removed in 1.1.5. Instead, an 'm' is appended to minor keys. sequencer_specific (0x7f) ^^^^^^^^^^^^^^^^^^^^^^^^^ ============== ============== ======== Attribute Values Default ============== ============== ======== data [..] [] ============== ============== ======== An unprocessed sequencer specific message containing raw data. Unknown Meta Messages --------------------- Unknown meta messages will be returned as ``UnknownMetaMessage`` objects, with ``type`` set to ``unknown_meta``. The messages are saved back to the file exactly as they came out. Code that depends on ``UnknownMetaMessage`` may break if the message in question is ever implemented, so it's best to only use these to learn about the format of the new message and then implement it as described below. ``UnknownMetaMessage`` have two attributes:: ``type_byte`` - a byte which uniquely identifies this message type ``data`` - the message data as a list of bytes These are also visible in the ``repr()`` string:: UnknownMetaMessage(type_byte=251, data=(1, 2, 3), time=0> Implementing New Meta Messages ------------------------------ If you come across a meta message which is not implemented, or you want to use a custom meta message, you can add it by writing a new meta message spec:: from mido.midifiles.meta import MetaSpec, add_meta_spec class MetaSpec_light_color(MetaSpec): type_byte = 0xf0 attributes = ['r', 'g', 'b'] defaults = [0, 0, 0] def decode(self, message, data): # Interpret the data bytes and assign them to attributes. (message.r, message.g, message.b) = data def encode(self, message): # Encode attributes to data bytes and # return them as a list of ints. return [message.r, message.g, message.b] def check(self, name, value): # (Optional) # This is called when the user assigns # to an attribute. You can use this for # type and value checking. (Name checking # is already done. # # If this method is left out, no type and # value checking will be done. if not isinstance(value, int): raise TypeError('{} must be an integer'.format(name)) if not 0 <= value <= 255: raise TypeError('{} must be in range 0..255'.format(name)) Then you can add your new message type with:: add_meta_spec(MetaSpec_light_color) and create messages in the usual way:: >>> from mido import MetaMessage >>> MetaMessage('light_color', r=120, g=60, b=10) MetaMessage('light_color', r=120, g=60, b=10, time=0) and the new message type will now work when reading and writing MIDI files. Some additional functions are available:: encode_string(unicode_string) decode_string(byte_list) These convert between a Unicode string and a list of bytes using the current character set in the file. If your message contains only one string with the attribute name ``text`` or ``name``, you can subclass from one of the existing messages with these attributes, for example:: class MetaSpec_copyright(MetaSpec_text): type_byte = 0x02 class MetaSpec_instrument_name(MetaSpec_track_name): type_byte = 0x04 This allows you to skip everything but ``type_byte``, since the rest is inherited. See the existing MetaSpec classes for further examples. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608496479.0 mido-1.2.10/docs/midi_files.rst0000664000175000017500000001572400000000000016543 0ustar00olembolemb00000000000000MIDI Files ========== MidiFile objects can be used to read, write and play back MIDI files. Opening a File -------------- You can open a file with:: from mido import MidiFile mid = MidiFile('song.mid') .. note:: Sysex dumps such as patch data are often stored in SYX files rather than MIDI files. If you get "MThd not found. Probably not a MIDI file" try ``mido.read_syx_file()``. (See :doc:`syx` for more.) The ``tracks`` attribute is a list of tracks. Each track is a list of messages and meta messages, with the ``time`` attribute of each messages set to its delta time (in ticks). (See Tempo and Beat Resolution below for more on delta times.) To print out all messages in the file, you can do:: for i, track in enumerate(mid.tracks): print('Track {}: {}'.format(i, track.name)) for msg in track: print(msg) The entire file is read into memory. Thus you can freely modify tracks and messages, and save the file back by calling the ``save()`` method. (More on this below.) Iterating Over Messages ----------------------- Iterating over a ``MidiFile`` object will generate all MIDI messages in the file in playback order. The ``time`` attribute of each message is the number of seconds since the last message or the start of the file. Meta messages will also be included. If you want to filter them out, you can do:: if msg.is_meta: ... This makes it easy to play back a MIDI file on a port (though this simple implementation is subject to time drift):: for msg in MidiFile('song.mid'): time.sleep(msg.time) if not msg.is_meta: port.send(msg) This is so useful that there's a method for it:: for msg in MidiFile('song.mid').play(): port.send(msg) This does the sleeping and filtering for you (while avoiding drift). If you pass ``meta_messages=True`` you will also get meta messages. These can not be sent on ports, which is why they are off by default. Creating a New File ------------------- You can create a new file by calling MidiFile without the ``filename`` argument. The file can then be saved by calling the ``save()`` method:: from mido import Message, MidiFile, MidiTrack mid = MidiFile() track = MidiTrack() mid.tracks.append(track) track.append(Message('program_change', program=12, time=0)) track.append(Message('note_on', note=64, velocity=64, time=32)) track.append(Message('note_off', note=64, velocity=127, time=32)) mid.save('new_song.mid') The ``MidiTrack`` class is a subclass of list, so you can use all the usual methods. All messages must be tagged with delta time (in ticks). (A delta time is how long to wait before the next message.) If there is no 'end_of_track' message at the end of a track, one will be written anyway. A complete example can be found in ``examples/midifiles/``. The ``save`` method takes either a filename (``str``) or, using the ``file`` keyword parameter, a file object such as an in-memory binary file (an ``io.BytesIO``). If you pass a file object, ``save`` does not close it. Similarly, the ``MidiFile`` constructor can take either a filename, or a file object by using the ``file`` keyword parameter. if you pass a file object to ``MidiFile`` as a context manager, the file is not closed when the context manager exits. Examples can be found in ``test_midifiles2.py``. File Types ---------- There are three types of MIDI files: * type 0 (single track): all messages are saved in one track * type 1 (synchronous): all tracks start at the same time * type 2 (asynchronous): each track is independent of the others When creating a new file, you can select type by passing the ``type`` keyword argument, or by setting the ``type`` attribute:: mid = MidiFile(type=2) mid.type = 1 Type 0 files must have exactly one track. A ``ValueError`` is raised if you attempt to save a file with no tracks or with more than one track. Playback Length --------------- You can get the total playback time in seconds by accessing the ``length`` property:: mid.length This is only supported for type 0 and 1 files. Accessing ``length`` on a type 2 file will raise ``ValueError``, since it is impossible to compute the playback time of an asynchronous file. Meta Messages ------------- Meta messages behave like normal messages and can be created in the usual way, for example:: >>> from mido import MetaMessage >>> MetaMessage('key_signature', key='C#', mode='major') MetaMessage('key_signature', key='C#', mode='major', time=0) You can tell meta messages apart from normal messages with:: if msg.is_meta: ... or if you know the message type you can use the ``type`` attribute:: if msg.type == 'key_signature': ... elif msg.type == 'note_on': ... Meta messages can not be sent on ports. For a list of supported meta messages and their attributes, and also how to implement new meta messages, see :doc:`meta_message_types`. About the Time Attribute ------------------------ The ``time`` attribute is used in several different ways: * inside a track, it is delta time in ticks. This must be an integer. * in messages yielded from ``play()``, it is delta time in seconds (time elapsed since the last yielded message) * (only important to implementers) inside certain methods it is used for absolute time in ticks or seconds Tempo and Beat Resolution ------------------------- .. image:: images/midi_time.svg Timing in MIDI files is centered around ticks and beats. A beat is the same as a quarter note. Beats are divided into ticks, the smallest unit of time in MIDI. Each message in a MIDI file has a delta time, which tells how many ticks have passed since the last message. The length of a tick is defined in ticks per beat. This value is stored as ``ticks_per_beat`` in MidiFile objects and remains fixed throughout the song. MIDI Tempo vs. BPM ^^^^^^^^^^^^^^^^^^ Unlike music, tempo in MIDI is not given as beats per minute, but rather in microseconds per beat. The default tempo is 500000 microseconds per beat, which is 120 beats per minute. The meta message 'set_tempo' can be used to change tempo during a song. You can use :py:func:`bpm2tempo` and :py:func:`tempo2bpm` to convert to and from beats per minute. Note that :py:func:`tempo2bpm` may return a floating point number. Converting Between Ticks and Seconds ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To convert from MIDI time to absolute time in seconds, the number of beats per minute (BPM) and ticks per beat (often called pulses per quarter note or PPQ, for short) have to be decided upon. You can use :py:func:`tick2second` and :py:func:`second2tick` to convert to and from seconds and ticks. Note that integer rounding of the result might be necessary because MIDI files require ticks to be integers. If you have a lot of rounding errors you should increase the time resolution with more ticks per beat, by setting MidiFile.ticks_per_beat to a large number. Typical values range from 96 to 480 but some use even more ticks per beat. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608496479.0 mido-1.2.10/docs/parsing.rst0000664000175000017500000000407100000000000016073 0ustar00olembolemb00000000000000Parsing MIDI Bytes ================== MIDI is a binary protocol. Each each message is encoded as a status byte followed by up to three data bytes. (Sysex messages can have any number of data bytes and use a stop byte instead.) .. note:: To parse a single message you can use the class methods ``mido.Message.from_bytes()`` and ``mido.Message.from_hex()`` (new in 1.2). Mido comes with a parser that turns MIDI bytes into messages. You can create a parser object, or call one of the utility functions:: >>> mido.parse([0x92, 0x10, 0x20]) Message('note_on', channel=2, note=16, velocity=32, time=0) >>> mido.parse_all([0x92, 0x10, 0x20, 0x82, 0x10, 0x20]) [Message('note_on', channel=2, note=16, velocity=32, time=0), Message('note_off', channel=2, note=16, velocity=32, time=0)] These functions are just shortcuts for the full ``Parser`` class. This is the parser used inside input ports to parse incoming messages. Here are a few examples of how it can be used:: >>> p = mido.Parser() >>> p.feed([0x90, 0x10, 0x20]) >>> p.pending() 1 >>> p.get_message() Message('note_on', channel=0, note=16, velocity=32, time=0) >>> p.feed_byte(0x90) >>> p.feed_byte(0x10) >>> p.feed_byte(0x20) >>> p.feed([0x80, 0x10, 0x20]) Message('note_on', channel=0, note=16, velocity=32, time=0) ``feed()`` accepts any iterable that generates integers in 0..255. The parser will skip and stray status bytes or data bytes, so you can safely feed it random data and see what comes out the other end. ``get_message()`` will return ``None`` if there are no messages ready to be gotten. You can also fetch parsed messages out of the parser by iterating over it:: >>> p.feed([0x92, 0x10, 0x20, 0x82, 0x10, 0x20]) >>> for message in p: ... print(message) note_on channel=2 note=16 velocity=32 time=0 note_off channel=2 note=16 velocity=32 time=0 The messages are available in `p.messages` (a `collections.deque`). For the full table of MIDI binary encoding, see: ``_ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/ports.rst0000644000175000017500000001627300000000000015604 0ustar00olembolemb00000000000000Ports ===== A Mido port is an object that can send or receive messages (or both). You can open a port by calling one of the open methods, for example:: >>> inport = mido.open_input('SH-201') >>> outport = mido.open_output('Integra-7') Now you can receive messages on the input port and send messages on the output port:: >>> msg = inport.receive() >>> outport.send(msg) The message is copied by ``send()``, so you can safely modify your original message without causing breakage in other parts of the system. In this case, the ports are device ports, and are connected to some sort of (physical or virtual) MIDI device, but a port can be anything. For example, you can use a ``MultiPort`` receive messages from multiple ports as if they were one:: from mido.ports import MultiPort ... multi = MultiPort([inport1, inport2, inport3]) for msg in multi: print(msg) This will receive messages from all ports and print them out. Another example is a socket port, which is a wrapper around a TCP/IP socket. No matter how the port is implemented internally or what it does, it will look and behave like any other Mido port, so all kinds of ports can be used interchangeably. .. note:: Sending and receiving messages is thread safe. Opening and closing ports and listing port names are not. Common Things ------------- How to open a port depends on the port type. Device ports (PortMidi, RtMidi and others defined in backends) are opened with the open functions, for example:: port = mido.open_output() Input and I/O ports (which support both input and output) are opened with ``open_input()`` and ``open_ioport()`` respectively. If you call these without a port name like above, you will get the (system specific) default port. You can override this by setting the ``MIDO_DEFAULT_OUTPUT`` etc. environment variables. To get a list of available ports, you can do:: >>> mido.get_output_names() ['SH-201', 'Integra-7'] and then:: >>> port = mido.open_output('Integra-7') There are corresponding function for input and I/O ports. To learn how to open other kinds of ports, see the documentation for the port type in question. The port name is available in ``port.name``. To close a port, call:: port.close() or use the ``with`` statement to have the port closed automatically:: with mido.open_input() as port: for message in port: do_something_with(message) You can check if the port is closed with:: if port.closed: print("Yup, it's closed.") If the port is already closed, calling ``close()`` will simply do nothing. Output Ports ------------ Output ports basically have only one method:: outport.send(message) This will send the message immediately. (Well, the port can choose to do whatever it wants with the message, but at least it's sent.) There are also a couple of utility methods:: outport.reset() This will send "all notes off" and "reset all controllers" on every channel. This is used to reset everything to the default state, for example after playing back a song or messing around with controllers. If you pass ``autoreset=True`` to the constructor, ``reset()`` will be called when the port closes:: with mido.open_output('Integra-7') as outport: for msg in inport: outport.send(msg) # reset() is called here outport.close() # or here Sometimes notes hang because a ``note_off`` has not been sent. To (abruptly) stop all sounding notes, you can call:: outport.panic() This will not reset controllers. Unlike ``reset()``, the notes will not be turned off gracefully, but will stop immediately with no regard to decay time. Input Ports ----------- To iterate over incoming messages::: for msg in port: print(msg) This will iterate over messages as they arrive on the port until the port closes. (So far only socket ports actually close by themselves. This happens if the other end disconnects.) You can also do non-blocking iteration:: for msg in port.iter_pending(): print(msg) This will iterate over all messages that have already arrived. It is typically used in main loops where you want to do something else while you wait for messages:: while True: for msg in port.iter_pending(): print(msg) do_other_stuff() In an event based system like a GUI where you don't write the main loop you can install a handler that's called periodically. Here's an example for GTK:: def callback(self): for msg in self.inport: print(msg) gobject.timeout_add_seconds(timeout, callback) To get a bit more control you can receive messages one at a time:: msg = port.receive() This will block until a message arrives. To get a message only if one is available, you can use `poll()`:: msg = port.poll() This will return ``None`` if no message is available. .. note:: There used to be a ``pending()`` method which returned the number of pending messages. It was removed in 1.2.0 for three reasons: * with ``poll()`` and ``iter_pending()`` it is no longer necessary * it was unreliable when multithreading and for some ports it doesn't even make sense * it made the internal method API confusing. `_send()` sends a message so `_receive()` should receive a message. Callbacks --------- Instead of reading from the port you can install a callback function which will be called for every message that arrives. Here's a simple callback function:: def print_message(message): print(message) To install the callback you can either pass it when you create the port or later by setting the ``callback`` attribute:: port = mido.open_input(callback=print_message) port.callback = print_message ... port.callback = another_function .. note:: Since the callback runs in a different thread you may need to use locks or other synchronization mechanisms to keep your main program and the callback from stepping on each other's toes. Calling ``receive()``, ``__iter__()``, or ``iter_pending()`` on a port with a callback will raise an exception:: ValueError: a callback is set for this port To clear the callback:: port.callback = None This will return the port to normal. Port API -------- Common Methods and Attributes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``close()`` Close the port. If the port is already closed this will simply do nothing. ``name`` Name of the port or None. ``closed`` True if the port is closed. Output Port Methods ^^^^^^^^^^^^^^^^^^^ ``send(message)`` Send a message. ``reset()`` Sends "all notes off" and "reset all controllers on all channels. ``panic()`` Sends "all sounds off" on all channels. This will abruptly end all sounding notes. Input Port Methods ^^^^^^^^^^^^^^^^^^ ``receive(block=True)`` Receive a message. This will block until it returns a message. If ``block=True`` is passed it will instead return ``None`` if there is no message. ``poll()`` Returns a message, or ``None`` if there are no pending messages. ``iter_pending()`` Iterates through pending messages. ``__iter__()`` Iterates through messages as they arrive on the port until the port closes. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608496479.0 mido-1.2.10/docs/resources.rst0000664000175000017500000000315600000000000016445 0ustar00olembolemb00000000000000Resources ========= * `MIDI Association `_ (midi.org) * `Table of MIDI Messages `_ (midi.org) * `Tech Specs & Info `_ (midi.org) * `MIDI `_ (Wikipedia) * `Essentials of the MIDI Protocol `_ (Craig Stuart Sapp, CCRMA) * `Outline of the Standard MIDI File Structure `_ (Craig Stuart Sapp, CCRMA) * `Active Sense `_ (About the active sensing message.) * `Active Sensing `_ (Sweetwater) * `MIDI Technical/Programming Docs `_ (Jeff Glatt) * `Standard MIDI Files `_ (cnx.org) * `MIDI File Parsing `_ (Course assignment in `Music 253 `_ at Stanford University) * `MIDI File Format `_ (The Sonic Spot) * `Delta time and running status `_ (mic at recordingblogs.com) * `MIDI meta messages `_ (recordingblog.com) * `Meta Message `_ (Sound On Sound) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1609342087.0 mido-1.2.10/docs/roadmap.rst0000664000175000017500000001336400000000000016060 0ustar00olembolemb00000000000000Roadmap ======= This will be developed into a proper roadmap but for now it's more of a list of ideas. Various Improvements to MIDI File Parsing ----------------------------------------- * add ``mido.exceptions.MidiParseError``. * add better error handling to MIDI file parser as discussed in `issue #63 `_. * support RIFF MIDI files (`issue #43 `_) * support MIDI files that end in empty meta message (`issue #42 `_) Better Support for Concurrency and Multithreading ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Mido was not originally designed for multithreading. Locks were added to the port base classes as an attempt to get around this but it is a crude solution that has created numerous problems and headaches. The RtMido backend has abandoned locking in favor of using RtMidi's user callback to feed a queue. If you write your port so that ``send()``, ``receive()`` and ``poll()`` are thread safe the rest of the rest of the API will be as well. For ports that do actual I/O (MIDI devices, sockets, files, pipes etc.) it is always best for the port itself to ensure thread safety. It's less clear what to do for utility ports like ``MultiPort``. Mido is currently not very good at multiplexing input. You can use ``MultiPort`` and ``multi_receive()``, but since it can't actually block on more than one port it uses poll and wait. This uses more resources and adds latency. The alternative is to use callbacks, but only a few backends support (and some like PortMidi fake them with a thread that polls and waits, taking you back to square one). Programming with callbacks also forces you to deal with multithreading (since the callback runs in a different thread) and to break your program flow up into callback handlers. This is not always desirable. In Go the solution would be to use channels instead of ports. Each port would then have its own channel with a goroutine reading from the device and feeding the channel, and the language would take care of the multiplexing. I am not sure how one would achieve something like this in Python. Making Messages Immutable ^^^^^^^^^^^^^^^^^^^^^^^^^ See: https://github.com/mido/mido/issues/36 The current workaround is frozen messages (``mido.freeze``). In any case, the documentation should be updated to encourage copying over mutation. Native Backends (ALSA, JACK, CoreMIDI, Windows MIDI) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ See https://github.com/mido/mido-native-backends No Default Backend? ^^^^^^^^^^^^^^^^^^^ Currently one backend is chosen as the default. Perhaps it would be better to require the user to specify the backend with ``$MIDO_BACKEND`` or ``mido.set_backend()``. New API for Creating New Port Types ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The current system uses multiple inheritance making the code very hard to follow and reason about: * too much magic and too much you need to keep in your head * attributes like ``self.name`` and ``self.closed`` appear in your name space and you have to dig around in the base classes to see where they come from * there is a ``self._parser`` in every port even if you don't need it * ``self._parser`` is not thread safe * ``BaseInput.receive()`` needs to account for all the numerous current and historical behaviors of ``self._receive()``. * blocking and nonblocking receive can not be protected by the same lock (since a call to ``receive()`` would then block a call to ``poll()``), which means ports that due true blocking need to do their own locking. * if you want to do our own locking you need to remember to set ``_locking=False``. This will replace the lock with a dummy lock, which while doing nothing still adds a bit of overhead. A good principle is for any part of the code to know as little as possible about the rest of the code. For example a backend port should only need to worry about: * opening and closing the device * reading and writing data (blocking and nonblocking) It should not have to worry about things like ``autoreset``, ``closed=True/False`` and iteration. Also, as long as its ``send()`` and ``receive()/poll()`` methods are thread safe the rest of the API will be as well. Some alternatives to subclassing: * embedding: write a basic port and wrap it in a ``FancyPort`` which provides the rest of the API * mixins: write a basic port and use mixins (``PortMethods``, ``InputMethods``, ``OutputMethods``) to import the rest of the API. Maybe ----- * add a way to convert between MIDI file types as suggested in `issue #92 `_. * RtMidi backend: allow user to list ports without client name and ALSA port numbers. * Add native backends? See https://github.com/mido/mido-native-backends * Currently all backends ignore ``active_sensing`` messages because they create a lot of noise and are usually not very useful. Should this be changed (perhaps as an option)? Filtering can be turned off with: * rtmidi: ``self._rt.ignore_types(False, False, False)`` * portmidi: ``pm.lib.Pa_SetFilter(self._stream, 0)`` * rtmidi_python: ``self._rt.ignore_types(False, False, False)`` * pygame: (is there a way to configure this?) * amidi: (not sure if this receives ``active_sensing`` already) * Refactor ``rtmidi`` and ``rtmidi_python`` backends to avoid code duplication. This would give ``rtmidi_python`` all of the features of ``rtmidi`` (as long as they are supported in the package). * Add more fine grained error types, for example ``PortNotFound`` instead of just ``IOError``. (This should be a subclass so old code still works.) One problem here is that new code that uses ``PortNotFound`` would not work with backends that raise ``IOError``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/socket_ports.rst0000644000175000017500000000742100000000000017147 0ustar00olembolemb00000000000000Socket Ports - MIDI over TCP/IP =============================== About Socket Ports ------------------ Socket ports allow you to send MIDI messages over a computer network. The protocol is standard MIDI bytes over a TCP stream. Caveats ------- The data is sent over an unencrypted channel. Also, the default server allows connections from any host and also accepts arbitrary sysex messages, which could allow anyone to for example overwrite patches on your synths (or worse). Use only on trusted networks. If you need more security, you can build a custom server with a white list of clients that are allowed to connect. If timing is critical, latency and jitter (especially on wireless networks) may make socket ports unusable. Sending Messages to a Server ---------------------------- First, let's import some things:: from mido.sockets import PortServer, connect After that, a simple server is only two lines:: for message in PortServer('localhost', 8080): print(message) You can then connect to the server and send it messages:: output = connect('localhost', 8080): output.send(message) Each end of the connection behaves like a normal Mido I/O port, with all the usual methods. The host may be a DNS host name or IP address (as a string). It may also be '', in which case connections are accepted on any ip address on the computer. Turning Things on their Head ---------------------------- If you want the server to send messages the client, you can instead do:: server = PortServer('localhost', 8080): while True: server.send(message) ... and then on the client side:: for message in connect('localhost', 8080): print(message) The client will now print any message that the server sends. Each message that the server sends will be received by all connected clients. Under the Hood -------------- The examples above use the server and client ports as normal I/O ports. This makes it easy to write simple servers, but you don't have any control connections and the way messages are sent and received. To get more control, you can ignore all the other methods of the ``PortServer`` object and use only ``accept()``. Here's a simple server implemented this way:: with PortServer('localhost', 8080) as server: while True: client = server.accept() for message in client: print(message) ``accept()`` waits for a client to connect, and returns a SocketPort object which is connected to the SocketPort object returned by ``connect()`` at the other end. The server above has one weakness: it allows only one connection at a time. You can get around this by using ``accept(block=False)``. This will return a SocketPort if there is a connection waiting and None if there is connection yet. Using this, you can write the server any way you like, for example:: with PortServer('localhost', 8080) as server: clients = [] while True: # Handle connections. client = server.accept(block=False) if client: print('Connection from {}'.format(client.name)) clients.append(client) for i, client in reversed(enumerate(clients)): if client.closed: print('{} disconnected'.format(client.name)) del clients[i] # Receive messages. for client in clients: for message in client.iter_pending() print('Received {} from {}'.format(message, client)) # Do other things ... Possible Future Additions ------------------------- Optional HTTP-style headers could be added. As long as these are 7-bit ASCII, they will be counted as data bytes and ignored by clients or servers who don't expect them. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608496479.0 mido-1.2.10/docs/string_encoding.rst0000664000175000017500000000624700000000000017613 0ustar00olembolemb00000000000000String Encoding =============== Mido messages can be serialized to a text format, which can be used to safely store messages in text files, send them across sockets or embed them in JSON, among other things. To encode a message, simply call ``str()`` on it:: >>> cc = control_change(channel=9, control=1, value=122, time=60) >>> str(cc) 'control_change channel=9 control=1 value=122 time=60' To convert the other way (new method in 1.2):: >>> mido.Message.from_str('control_change control=1 value=122') Message('control_change', channel=0, control=1, value=122, time=0) Alternatively, you can call the ``format_as_string`` function directly: >>> mido.format_as_string(cc) 'control_change channel=9 control=1 value=122 time=60' If you don't need the time attribute or you want to store it elsewhere, you can pass ``include_time=False``:: >>> mido.format_as_string(cc) 'control_change channel=9 control=1 value=122' (This option is also available in ``mido.Message.from_str()``.) Format ------ The format is simple:: MESSAGE_TYPE [PARAMETER=VALUE ...] These are the same as the arguments to ``mido.Message()``. The order of parameters doesn't matter, but each one can only appear once. Only these character will ever occur in a string encoded Mido message:: [a-z][0-9][ =_.+()] or written out:: 'abcdefghijklmnopqrstuvwxyz0123456789 =_.+()' This means the message can be embedded in most text formats without any form of escaping. Parsing ------- To parse a message, you can use ``mido.parse_string()``:: >>> parse_string('control_change control=1 value=122 time=0.5') Message('control_change', channel=0, control=1, value=122, time=0.5) Parameters that are left out are set to their default values. ``ValueError`` is raised if the message could not be parsed. Extra whitespace is ignored:: >>> parse_string(' control_change control=1 value=122') Message('control_change', channel=0, control=1, value=122, time=0) To parse messages from a stream, you can use ``mido.messages.parse_string_stream()``:: for (message, error) in parse_string_stream(open('some_music.text')): if error: print(error) else: do_something_with(message) This will return every valid message in the stream. If a message could not be parsed, ``message`` will be ``None`` and ``error`` will be an error message describing what went wrong, as well as the line number where the error occurred. The argument to ``parse_string_stream()`` can be any object that generates strings when iterated over, such as a file or a list. ``parse_string_stream()`` will ignore blank lines and comments (which start with a # and go to the end of the line). An example of valid input:: # A very short song with an embedded sysex message. note_on channel=9 note=60 velocity=120 time=0 # Send some data sysex data=(1,2,3) time=0.5 pitchwheel pitch=4000 # bend the not a little time=0.7 note_off channel=9 note=60 velocity=60 time=1.0 Examples -------- And example of messages embedded in JSON:: {'messages': [ '0.0 note_on channel=9 note=60 velocity=120', '0.5 sysex data=(1,2,3)', ... ]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/docs/syx.rst0000644000175000017500000000154000000000000015247 0ustar00olembolemb00000000000000SYX Files ========= SYX files are used to store SysEx messages, usually for patch data. Reading and Writing ------------------- To read a SYX file:: messages = mido.read_syx_file('patch.syx') To write a SYX file:: mido.write_syx_file('patch.syx', messages) Non-sysex messages will be ignored. Plain Text Format ----------------- Mido also supports plain text SYX files. These are read in exactly the same way:: messages = mido.read_syx_file('patch.txt') ``read_syx_file()`` determines which format the file is by looking at the first byte. It Raises ValueError if file is plain text and byte is not a 2-digit hex number. To write plain text:: mido.write_syx_file('patch.txt', messages, plaintext=True) This will write the messages as hex encoded bytes with one message per line:: F0 00 01 5D 02 00 F7 F0 00 01 5D 03 00 F7 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2473297 mido-1.2.10/examples/0000775000175000017500000000000000000000000014562 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2473297 mido-1.2.10/examples/backends/0000775000175000017500000000000000000000000016334 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/backends/printer.py0000664000175000017500000000110000000000000020361 0ustar00olembolemb00000000000000""" A simple custom backend with an output port type which prints messages to stdout. """ from mido.ports import BaseOutput def get_devices(): return [{'name': 'The Print Port', 'is_input': False, 'is_output': True}] class Output(BaseOutput): def _open(self, **kwargs): device = get_devices()[0] if self.name is None: self.name = device['name'] elif self.name != device['name']: raise ValueError('unknown port {!r}'.format(self.name)) def _send(self, message): print(message) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/backends/rtm.py0000664000175000017500000000576100000000000017521 0ustar00olembolemb00000000000000""" Experimental backend for rtmidi-python: http://github.com/superquadratic/rtmidi-python - Doesn't work with Python 3.3: File "rtmidi_python.pyx", line 61, in rtmidi_python.MidiIn.__cinit__ (rtmidi_ python.cpp:1214) TypeError: expected bytes, str found - Virtual ports don't show up, so other programs can't connect to them. - There is no way to select API. Other than that, it works exactly like the included python-rtmidi backend. """ from __future__ import absolute_import import rtmidi_python as rtmidi from mido.ports import BaseInput, BaseOutput def get_devices(): devices = [] input_names = set(rtmidi.MidiIn().ports) output_names = set(rtmidi.MidiOut().ports) for name in sorted(input_names | output_names): devices.append({ 'name': name, 'is_input': name in input_names, 'is_output': name in output_names, }) return devices class PortCommon(object): def _open(self, virtual=False, callback=None): opening_input = hasattr(self, 'receive') if opening_input: self._rt = rtmidi.MidiIn() self._rt.ignore_types(False, False, False) if callback: def callback_wrapper(message, delta_time): self._parser.feed(message) for message in self._parser: callback(message) self._rt.callback = self._callback = callback_wrapper self._has_callback = True else: self._has_callback = False else: self._rt = rtmidi.MidiOut() # Turn of ignore of sysex, time and active_sensing. ports = self._rt.ports if virtual: if self.name is None: raise IOError('virtual port must have a name') self._rt.open_virtual_port(self.name) else: if self.name is None: # Todo: this could fail if list is empty. # In RtMidi, the default port is the first port. try: self.name = ports[0] except IndexError: raise IOError('no ports available') try: port_id = ports.index(self.name) except ValueError: raise IOError('unknown port {!r}'.format(self.name)) self._rt.open_port(port_id) self._device_type = 'rtmidi_python' def _close(self): self._rt.close_port() class Input(PortCommon, BaseInput): # Todo: sysex messages do not arrive here. def _receive(self, block=True): if self._has_callback: raise IOError('a callback is currently set for this port') while True: (message, delta_time) = self._rt.get_message() if message is None: break else: self._parser.feed(message) class Output(PortCommon, BaseOutput): def _send(self, message): self._rt.send_message(message.bytes()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/backends/use_printer.py0000664000175000017500000000103100000000000021240 0ustar00olembolemb00000000000000""" Try out the new printer port backend. It also works with MIDO_BACKEND, so you can do: $ MIDO_BACKEND=printer python >>> import mido >>> mido.get_output_names() ['The Printer Port'] """ import mido mido.set_backend('printer') print('Available outputs: {!r}'.format(mido.get_output_names())) with mido.open_output() as port: print('Using {}.'.format(port)) port.send(mido.Message('program_change', program=10)) for i in [1, 2, 3]: port.send(mido.Message('control_change', control=1, value=i)) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2473297 mido-1.2.10/examples/midifiles/0000775000175000017500000000000000000000000016527 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/midifiles/create_midi_file.py0000775000175000017500000000153600000000000022355 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Create a new MIDI file with some random notes. The file is saved to test.mid. """ from __future__ import division import random import sys from mido import Message, MidiFile, MidiTrack, MAX_PITCHWHEEL notes = [64, 64+7, 64+12] outfile = MidiFile() track = MidiTrack() outfile.tracks.append(track) track.append(Message('program_change', program=12)) delta = 300 ticks_per_expr = int(sys.argv[1]) if len(sys.argv) > 1 else 20 for i in range(4): note = random.choice(notes) track.append(Message('note_on', note=note, velocity=100, time=delta)) for j in range(delta // ticks_per_expr): pitch = MAX_PITCHWHEEL * j * ticks_per_expr // delta track.append(Message('pitchwheel', pitch=pitch, time=ticks_per_expr)) track.append(Message('note_off', note=note, velocity=100, time=0)) outfile.save('test.mid') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/midifiles/midifile_to_json.py0000664000175000017500000000053300000000000022417 0ustar00olembolemb00000000000000import sys import json import mido def midifile_to_dict(mid): tracks = [] for track in mid.tracks: tracks.append([vars(msg).copy() for msg in track]) return { 'ticks_per_beat': mid.ticks_per_beat, 'tracks': tracks, } mid = mido.MidiFile(sys.argv[1]) print(json.dumps(midifile_to_dict(mid), indent=2)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/midifiles/play_midi_file.py0000775000175000017500000000127300000000000022055 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Play MIDI file on output port. Run with (for example): ./play_midi_file.py 'SH-201 MIDI 1' 'test.mid' """ import sys import mido import time from mido import MidiFile filename = sys.argv[1] if len(sys.argv) == 3: portname = sys.argv[2] else: portname = None with mido.open_output(portname) as output: try: midifile = MidiFile(filename) t0 = time.time() for message in midifile.play(): print(message) output.send(message) print('play time: {:.2f} s (expected {:.2f})'.format( time.time() - t0, midifile.length)) except KeyboardInterrupt: print() output.reset() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/midifiles/print_midi_file.py0000775000175000017500000000070400000000000022242 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Open a MIDI file and print every message in every track. Support for MIDI files is still experimental. """ import sys from mido import MidiFile if __name__ == '__main__': filename = sys.argv[1] midi_file = MidiFile(filename) for i, track in enumerate(midi_file.tracks): sys.stdout.write('=== Track {}\n'.format(i)) for message in track: sys.stdout.write(' {!r}\n'.format(message)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608567398.0 mido-1.2.10/examples/midifiles/show_midifile.py0000775000175000017500000000042700000000000021731 0ustar00olembolemb00000000000000#!/usr/bin/env python import sys import argparse import mido def parse_args(): parser = argparse.ArgumentParser() arg = parser.add_argument arg('midifile', nargs=1) return parser.parse_args() args = parse_args() print(repr(mido.MidiFile(args.midifile[0]))) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/examples/midifiles/test.sh0000755000175000017500000000020700000000000020042 0ustar00olembolemb00000000000000#!/bin/bash function play { # mido-play test.mid pmidi -p 20:0 -d 0 test.mid } ./create_midi_file.py && xxd test.mid && play ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2513297 mido-1.2.10/examples/ports/0000775000175000017500000000000000000000000015731 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/ports/input_filter.py0000775000175000017500000000153700000000000021020 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Receive messages on the input port and print messages with a specific channel (defaults to 0). Usage: python example_input_filter.py portname [CHANNEL] [...] """ from __future__ import print_function import sys import mido def accept_notes(port): """Only let note_on and note_off messages through.""" for message in port: if message.type in ('note_on', 'note_off'): yield message if len(sys.argv) > 1: portname = sys.argv[1] else: portname = None # Use default port try: with mido.open_input(portname) as port: print('Using {}'.format(port)) print("Ignoring everything but 'note_on' and 'note_off'.") print('Waiting for notes...') for message in accept_notes(port): print('Received {}'.format(message)) except KeyboardInterrupt: pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/ports/list_ports.py0000775000175000017500000000061500000000000020512 0ustar00olembolemb00000000000000#!/usr/bin/env python """ List available ports. This is a simple version of mido-ports. """ from __future__ import print_function import mido def print_ports(heading, port_names): print(heading) for name in port_names: print(" '{}'".format(name)) print() print() print_ports('Input Ports:', mido.get_input_names()) print_ports('Output Ports:', mido.get_output_names()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/ports/multi_receive.py0000775000175000017500000000065400000000000021147 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Receive messages from multiple ports. """ import mido from mido.ports import multi_receive # Open all available inputs. ports = [mido.open_input(name) for name in mido.get_input_names()] for port in ports: print('Using {}'.format(port)) print('Waiting for messages...') try: for message in multi_receive(ports): print('Received {}'.format(message)) except KeyboardInterrupt: pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/ports/nonblocking_receive.py0000775000175000017500000000110500000000000022310 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Example of non-blocking reception from input port. """ from __future__ import print_function import sys import time import mido if len(sys.argv) > 1: portname = sys.argv[1] else: portname = None # Use default port try: with mido.open_input(portname) as port: print('Using {}'.format(port)) while True: for message in port.iter_pending(): print('Received {}'.format(message)) print('Doing something else for a while...') time.sleep(0.5) except KeyboardInterrupt: pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/ports/queue_port.py0000664000175000017500000000152100000000000020472 0ustar00olembolemb00000000000000"""A port interface around a queue. This allows you to create internal ports to send messages between threads. """ import mido from mido import ports try: import queue except ImportError: # Python 2. import Queue as queue class QueuePort(ports.BaseIOPort): # We don't need locking since the queue takes care of that. _locking = False def _open(self): self.queue = queue.Queue() def _send(self, msg): self.queue.put(msg) def _receive(self, block=True): try: return self.queue.get(block=block) except queue.Empty: return None def _device_type(self): return 'Queue' def main(): msg = mido.Message('clock') port = QueuePort() print(port.poll()) port.send(msg) print(port.receive()) if __name__ == '__main__': main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/ports/receive.py0000775000175000017500000000100600000000000017725 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Receive messages from the input port and print them out. """ from __future__ import print_function import sys import mido if len(sys.argv) > 1: portname = sys.argv[1] else: portname = None # Use default port try: with mido.open_input(portname) as port: print('Using {}'.format(port)) print('Waiting for messages...') for message in port: print('Received {}'.format(message)) sys.stdout.flush() except KeyboardInterrupt: pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/ports/send.py0000775000175000017500000000150600000000000017241 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Send random notes to the output port. """ from __future__ import print_function import sys import time import random import mido from mido import Message if len(sys.argv) > 1: portname = sys.argv[1] else: portname = None # Use default port # A pentatonic scale notes = [60, 62, 64, 67, 69, 72] try: with mido.open_output(portname, autoreset=True) as port: print('Using {}'.format(port)) while True: note = random.choice(notes) on = Message('note_on', note=note) print('Sending {}'.format(on)) port.send(on) time.sleep(0.05) off = Message('note_off', note=note) print('Sending {}'.format(off)) port.send(off) time.sleep(0.1) except KeyboardInterrupt: pass print() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2513297 mido-1.2.10/examples/sockets/0000775000175000017500000000000000000000000016235 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/sockets/forward_ports.py0000775000175000017500000000076400000000000021514 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Forward all messages from one or more ports to server. Example: python forward_ports.py localhost:8080 'Keyboard MIDI 1' """ import sys import mido host, port = mido.sockets.parse_address(sys.argv[1]) ports = [mido.open_input(name) for name in sys.argv[2:]] with mido.sockets.connect(host, port) as server_port: print('Connected.') for message in mido.ports.multi_receive(ports): print('Sending {}'.format(message)) server_port.send(message) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/sockets/serve_ports.py0000775000175000017500000000146600000000000021174 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Serve one or more output ports. Every message received on any of the connected sockets will be sent to every output port. Example: python serve_ports.py :8080 'SH-201' 'SD-20 Part A' This simply iterates through all incoming messages. More advanced and flexible servers can be written by calling the ``accept()`` and ``accept(block=False) methods directly. See PortServer.__init__() for an example. """ import sys import mido from mido import sockets from mido.ports import MultiPort # Todo: do this with a argument parser. out = MultiPort([mido.open_output(name) for name in sys.argv[2:]]) (host, port) = sockets.parse_address(sys.argv[1]) with sockets.PortServer(host, port) as server: for message in server: print('Received {}'.format(message)) out.send(message) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/sockets/simple_client.py0000775000175000017500000000160400000000000021442 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Simple client that sends program_change messages to server at timed intervals. Example: python simple_client.py localhost:8080 """ import sys import time import random import mido if sys.argv[1:]: address = sys.argv[1] else: address = 'localhost:9080' host, port = mido.sockets.parse_address(address) notes = [60, 67, 72, 79, 84, 79, 72, 67, 60] on = mido.Message('note_on', velocity=100) off = mido.Message('note_off', velocity=100) base = random.randrange(12) print('Connecting to {}'.format(address)) with mido.sockets.connect(host, port) as server_port: try: message = mido.Message('program_change') for note in notes: on.note = off.note = base + note server_port.send(on) time.sleep(0.05) server_port.send(off) time.sleep(0.1) finally: server_port.reset() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/sockets/simple_server.py0000775000175000017500000000077000000000000021475 0ustar00olembolemb00000000000000#!/usr/bin/env python """ Simple server that just prints every message it receives. python simple_server.py localhost:8080 """ import sys from mido import sockets if sys.argv[1:]: address = sys.argv[1] else: address = 'localhost:9080' try: (hostname, portno) = sockets.parse_address(address) print('Serving on {}'.format(address)) with sockets.PortServer(hostname, portno) as server: for message in server: print(message) except KeyboardInterrupt: pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/examples/using_rtmidi_directly.py0000664000175000017500000000124200000000000021527 0ustar00olembolemb00000000000000""" First example from here modified to use Mido messages: http://pypi.python.org/pypi/python-rtmidi/ """ import time import mido 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") # Original example: # note_on = [0x99, 60, 112] # channel 10, middle C, velocity 112 # note_off = [0x89, 60, 0] note_on = mido.Message('note_on', channel=9, note=60, velocity=112).bytes() note_off = mido.Message('note_off', channel=9, note=60, velocity=0).bytes() midiout.send_message(note_on) time.sleep(0.5) midiout.send_message(note_off) del midiout ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2513297 mido-1.2.10/extras/0000775000175000017500000000000000000000000014252 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/extras/README.rst0000644000175000017500000000042700000000000015742 0ustar00olembolemb00000000000000Disclaimer =========== These are experimental versions of functionality that may be rolled into Mido some time in the future. Until the, proceed at your own risk. (Where risk means the danger the code will crash or that the API will change once you had just gotten used to it.) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/extras/hid_joystick.py0000664000175000017500000001524400000000000017315 0ustar00olembolemb00000000000000"""Read from /dev/input/js0 and return as dictionaries. If you have pygame it is easier and more portable to do something like:: import pygame.joystick from pygame.event import event_name pygame.init() pygame.joystick.init() js = pygame.joystick.Joystick(0) js.init() while True: for event in pygame.event.get(): if event.axis == 0: print(event) Init: 8 = init? Time stamp | (ms since boot) | --+--+--+-- | -- Button number f0 fb 37 09 00 00 81 00 f0 fb 37 09 00 00 81 01 f0 fb 37 09 00 00 81 02 f0 fb 37 09 00 00 81 03 f0 fb 37 09 00 00 81 04 f0 fb 37 09 00 00 81 05 f0 fb 37 09 00 00 81 06 f0 fb 37 09 00 00 81 07 f0 fb 37 09 00 00 81 08 f0 fb 37 09 00 00 81 09 f0 fb 37 09 00 00 81 0a f0 fb 37 09 00 00 81 0b f0 fb 37 09 00 00 82 00 f0 fb 37 09 00 00 82 01 f0 fb 37 09 00 00 82 02 f0 fb 37 09 00 00 82 03 f0 fb 37 09 00 00 82 04 f0 fb 37 09 00 00 82 05 --+-- | | 1 = button, 2 = | value (little endian unsigned) button down | 98 f0 2f 09 01 00 01 00 1 down 08 fa 2f 09 00 00 01 00 1 up 2c 6a 31 09 01 00 01 01 2 down 04 73 31 09 00 00 01 01 2 up 48 bf 32 09 01 00 01 02 3 down f8 c4 32 09 00 00 01 02 3 up Logitech PS2-style gamepad: axis 0 == left stick -left / right (left is negative) axis 1 == left stick -up / down (up is negative) axis 2 == right stick -left / right axis 3 == right stick -up / down axis 4 == plus stick -left / right (when mode is off), values min/0/max axis 5 == plus stick -up / down (when mode is off, values min/0/max The + stick has two modes. When the mode light is off, it sends axis 4/5. When mode is on, it sends axis 0/1. The values are -32767, 0, and 32767. Other axis have values from -32767 to 32767 as well. """ import struct import select JS_EVENT_BUTTON = 0x1 JS_EVENT_AXIS = 0x2 JS_EVENT_INIT = 0x80 def read_event(device): data = device.read(8) event = {} (event['time'], event['value'], event['type'], event['number']) = struct.unpack('IhBB', data) event['init'] = bool(event['type'] & JS_EVENT_INIT) event['type'] &= 0x7f # Strip away the flag bits (JS_EVENT_INIT etc.) if event['type'] != JS_EVENT_BUTTON: event['normalized_value'] = \ float(event['value']) / 0x7fff # Normalize to -1..1 event['type'] = {1: 'button', 2: 'axis'}[event['type']] return event def read_events(device_name): with open(device_name, 'rb') as device: while True: yield read_event(device) def panic(port): """ Send "All Notes Off" and "Reset All Controllers" on all channels. """ for channel in range(16): for control in [121, 123]: message = mido.Message('control_change', channel=channel, control=control, value=0) print(message) port.send(message) class Monophonic(object): # Todo: this assumes everything is on channel 0! def __init__(self, output, channel=0): self.output = output self.notes = set() self.current_note = None self.channel = channel def send(self, message): if message.type not in ['note_on', 'note_off'] or \ message.channel != self.channel: self.output.send(message) return if message.type == 'note_on': self.notes.add(message.note) elif message.type == 'note_off': if message.note in self.notes: self.notes.remove(message.note) print(self.notes) try: note = min(self.notes) except ValueError: note = None if note == self.current_note: return # Same note as before, no change. if self.current_note is not None: off = mido.Message('note_off', note=self.current_note, velocity=message.velocity) print(off) self.output.send(off) self.current_note = None if note is not None: on = mido.Message('note_on', note=note, velocity=message.velocity) print(on) self.output.send(on) self.current_note = note def play_scale(dev, out): # out = Monophonic(out, channel=0) # Major scale. scale = [0, 2, 4, 5, 7, 9, 11, 12, 14, 16, 17, 19] # program = 16 # Organ program = 74 out.send(mido.Message('program_change', program=program)) while True: event = read_event(dev) if event['init']: continue # Skip init events. if event['type'] == 'button': # Convert to D-major scale starting at middle D. note = 62 + 12 + scale[event['number']] if event['value']: type_ = 'note_on' else: type_ = 'note_off' message = mido.Message(type_, note=note, velocity=64) out.send(message) # elif event['type'] == 'axis': # if event['number'] == 0: # pitch_scale = mido.messages.MAX_PITCHWHEEL # pitch = int(event['normalized_value'] * pitch_scale) # out.send(mido.Message('pitchwheel', pitch=pitch)) def play_drums(dev, out): # http://www.midi.org/techspecs/gm1sound.php note_mapping = { 2: 35, # Acoustic Bass Drum 6: 38, # Acoustic Snare 1: 41, # Low Floor Tom 4: 47, # Low Mid Tom 3: 50, # High Tom 8: 51, # Ride Cymbal 5: 42, # Closed Hi Hat 7: 46, # Open Hi Hat 9: 52, # Chinese Cymbal 10: 55, # Splash Cymbal } while True: event = read_event(dev) if event['init']: continue if event['type'] == 'button': print(event) button = event['number'] + 1 # Number buttons starting with 1. if button not in note_mapping: continue if event['value']: type_ = 'note_on' else: type_ = 'note_off' note = note_mapping[button] message = mido.Message(type_, channel=9, note=note, velocity=64) print(message) out.send(message) if __name__ == '__main__': import sys import mido with open('/dev/input/js0') as dev: with mido.open_output('SD-20 Part A') as out: try: # play_drums(dev, out) play_scale(dev, out) finally: panic(out) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2553298 mido-1.2.10/mido/0000775000175000017500000000000000000000000013674 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1620661216.0 mido-1.2.10/mido/__about__.py0000664000175000017500000000024100000000000016151 0ustar00olembolemb00000000000000__version__ = '1.2.10' __author__ = 'Ole Martin Bjorndalen' __author_email__ = 'ombdalen@gmail.com' __url__ = 'https://mido.readthedocs.io/' __license__ = 'MIT' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608573042.0 mido-1.2.10/mido/__init__.py0000664000175000017500000001032600000000000016007 0ustar00olembolemb00000000000000# -*- coding: utf-8 -*- """ MIDI Objects for Python Mido is a library for working with MIDI messages and ports. It's designed to be as straight forward and Pythonic as possible. Creating messages: Message(type, **parameters) -- create a new message MetaMessage(type, **parameters) -- create a new meta message UnknownMetaMessage(type_byte, data=None, time=0) Ports: open_input(name=None, virtual=False, callback=None) -- open an input port open_output(name=None, virtual=False, -- open an output port autoreset=False) open_ioport(name=None, virtual=False, -- open an I/O port (capable callback=None, autoreset=False) of both input and output) get_input_names() -- return a list of names of available input ports get_output_names() -- return a list of names of available output ports get_ioport_names() -- return a list of names of available I/O ports MIDI files: MidiFile(filename, **kwargs) -- open a MIDI file MidiTrack() -- a MIDI track bpm2tempo() -- convert beats per minute to MIDI file tempo tempo2bpm() -- convert MIDI file tempo to beats per minute merge_tracks(tracks) -- merge tracks into one track SYX files: read_syx_file(filename) -- read a SYX file write_syx_file(filename, messages, plaintext=False) -- write a SYX file Parsing MIDI streams: parse(bytes) -- parse a single message bytes (any iterable that generates integers in 0..127) parse_all(bytes) -- parse all messages bytes Parser -- MIDI parser class Parsing objects serialized with str(message): parse_string(string) -- parse a string containing a message parse_string_stream(iterable) -- parse strings from an iterable and generate messages Sub modules: ports -- useful tools for working with ports For more on MIDI, see: http://www.midi.org/ Getting started: >>> import mido >>> m = mido.Message('note_on', note=60, velocity=64) >>> m >>> m.type 'note_on' >>> m.channel = 6 >>> m.note = 19 >>> m.copy(velocity=120) >>> s = mido.Message('sysex', data=[byte for byte in range(5)]) >>> s.data (0, 1, 2, 3, 4) >>> s.hex() 'F0 00 01 02 03 04 F7' >>> len(s) 7 >>> default_input = mido.open_input() >>> default_input.name 'MPK mini MIDI 1' >>> output = mido.open_output('SD-20 Part A') >>> >>> for message in default_input: ... output.send(message) >>> get_input_names() ['MPK mini MIDI 1', 'SH-201'] """ from __future__ import absolute_import import os from .backends.backend import Backend from . import ports, sockets from .messages import (Message, parse_string, parse_string_stream, format_as_string, MIN_PITCHWHEEL, MAX_PITCHWHEEL, MIN_SONGPOS, MAX_SONGPOS) from .parser import Parser, parse, parse_all from .midifiles import (MidiFile, MidiTrack, merge_tracks, MetaMessage, UnknownMetaMessage, bpm2tempo, tempo2bpm, tick2second, second2tick, KeySignatureError) from .syx import read_syx_file, write_syx_file from .version import version_info from .__about__ import (__version__, __author__, __author_email__, __url__, __license__) # Prevent splat import. __all__ = [] def set_backend(name=None, load=False): """Set current backend. name can be a module name like 'mido.backends.rtmidi' or a Backend object. If no name is passed, the default backend will be used. This will replace all the open_*() and get_*_name() functions in top level mido module. The module will be loaded the first time one of those functions is called.""" glob = globals() if isinstance(name, Backend): backend = name else: backend = Backend(name, load=load, use_environ=True) glob['backend'] = backend for name in dir(backend): if name.split('_')[0] in ['open', 'get']: glob[name] = getattr(backend, name) set_backend() del os, absolute_import ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2553298 mido-1.2.10/mido/backends/0000775000175000017500000000000000000000000015446 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/mido/backends/__init__.py0000644000175000017500000000000000000000000017543 0ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/backends/_parser_queue.py0000664000175000017500000000407100000000000020661 0ustar00olembolemb00000000000000import time from .. import ports from ..parser import Parser from ..py2 import PY2 from threading import RLock if PY2: import Queue as queue else: import queue class ParserQueue: """ Thread safe message queue with built in MIDI parser. This should be avaiable to other backend implementations and perhaps also in the public API, but the API needs a bit of review. (Ideally This would replace the parser.) q = ParserQueue() q.put(msg) q.put_bytes([0xf8, 0, 0]) msg = q.get() msg = q.poll() """ def __init__(self): self._queue = queue.Queue() self._parser = Parser() self._parser_lock = RLock() def put(self, msg): self._queue.put(msg) def put_bytes(self, msg_bytes): with self._parser_lock: self._parser.feed(msg_bytes) for msg in self._parser: self.put(msg) def _get_py2(self): # In Python 2 queue.get() doesn't respond to CTRL-C. A workaroud is # to call queue.get(timeout=100) (very high timeout) in a loop, but all # that does is poll with a timeout of 50 milliseconds. This results in # much too high latency. # # It's better to do our own polling with a shorter sleep time. # # See Issue #49 and https://bugs.python.org/issue8844 sleep_time = ports.get_sleep_time() while True: try: return self._queue.get_nowait() except queue.Empty: time.sleep(sleep_time) continue # TODO: add timeout? def get(self): if PY2: return self._get_py2() else: return self._queue.get() def poll(self): try: return self._queue.get_nowait() except queue.Empty: return None def __iter__(self): while True: return self.get() def iterpoll(self): while True: msg = self.poll() if msg is None: return else: yield msg ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/backends/amidi.py0000664000175000017500000000602100000000000017102 0ustar00olembolemb00000000000000"""Mido amidi backend Very experimental backend using amidi to access the ALSA rawmidi interface. TODO: * use parser instead of from_hex()? * default port name * do sysex messages work? * starting amidi for every message sent is costly """ import os import select import threading import subprocess from ..messages import Message from ._common import PortMethods, InputMethods, OutputMethods """ Dir Device Name IO hw:1,0,0 UM-1 MIDI 1 IO hw:2,0,0 nanoKONTROL2 MIDI 1 IO hw:2,0,0 MPK mini MIDI 1 """ def get_devices(): devices = [] lines = os.popen('amidi -l').read().splitlines() for line in lines[1:]: mode, device, name = line.strip().split(None, 2) devices.append({'name': name.strip(), 'device': device, 'is_input': 'I' in mode, 'is_output': 'O' in mode, }) return devices def _get_device(name, mode): for dev in get_devices(): if name == dev['name'] and dev[mode]: return dev else: raise IOError('unknown port {!r}'.format(name)) class Input(PortMethods, InputMethods): def __init__(self, name=None, **kwargs): self.name = name self.closed = False self._proc = None self._poller = select.poll() self._lock = threading.RLock() dev = _get_device(self.name, 'is_input') self._proc = subprocess.Popen(['amidi', '-d', '-p', dev['device']], stdout=subprocess.PIPE) self._poller.register(self._proc.stdout, select.POLLIN) def _read_message(self): line = self._proc.stdout.readline().strip().decode('ascii') if line: return Message.from_hex(line) else: # The first line is sometimes blank. return None def receive(self, block=True): if not block: return self.poll() while True: msg = self.poll() if msg: return msg # Wait for message. self._poller.poll() def poll(self): with self._lock: while self._poller.poll(0): msg = self._read_message() if msg is not None: return msg def close(self): if not self.closed: if self._proc: self._proc.kill() self._proc = None self.closed = True class Output(PortMethods, OutputMethods): def __init__(self, name=None, autoreset=False, **kwargs): self.name = name self.autoreset = autoreset self.closed = False self._dev = _get_device(self.name, 'is_output') def send(self, msg): proc = subprocess.Popen(['amidi', '--send-hex', msg.hex(), '-p', self._dev['device']]) proc.wait() def close(self): if not self.closed: if self.autoreset: self.reset() self.closed = True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1620660558.0 mido-1.2.10/mido/backends/backend.py0000664000175000017500000001505400000000000017414 0ustar00olembolemb00000000000000import os import importlib from .. import ports DEFAULT_BACKEND = 'mido.backends.rtmidi' class Backend(object): """ Wrapper for backend module. A backend module implements classes for input and output ports for a specific MIDI library. The Backend object wraps around the object and provides convenient 'open_*()' and 'get_*_names()' functions. """ def __init__(self, name=None, api=None, load=False, use_environ=True): self.name = name or os.environ.get('MIDO_BACKEND', DEFAULT_BACKEND) self.api = api self.use_environ = use_environ self._module = None # Split out api (if present). if api: self.api = api elif self.name and '/' in self.name: self.name, self.api = self.name.split('/', 1) else: self.api = None if load: self.load() @property def module(self): """A reference module implementing the backend. This will always be a valid reference to a module. Accessing this property will load the module. Use .loaded to check if the module is loaded. """ self.load() return self._module @property def loaded(self): """Return True if the module is loaded.""" return self._module is not None def load(self): """Load the module. Does nothing if the module is already loaded. This function will be called if you access the 'module' property.""" if not self.loaded: self._module = importlib.import_module(self.name) def _env(self, name): if self.use_environ: return os.environ.get(name) else: return None def _add_api(self, kwargs): if self.api and 'api' not in kwargs: kwargs['api'] = self.api return kwargs def open_input(self, name=None, virtual=False, callback=None, **kwargs): """Open an input port. If the environment variable MIDO_DEFAULT_INPUT is set, if will override the default port. virtual=False Passing True opens a new port that other applications can connect to. Raises IOError if not supported by the backend. callback=None A callback function to be called when a new message arrives. The function should take one argument (the message). Raises IOError if not supported by the backend. """ kwargs.update(dict(virtual=virtual, callback=callback)) if name is None: name = self._env('MIDO_DEFAULT_INPUT') return self.module.Input(name, **self._add_api(kwargs)) def open_output(self, name=None, virtual=False, autoreset=False, **kwargs): """Open an output port. If the environment variable MIDO_DEFAULT_OUTPUT is set, if will override the default port. virtual=False Passing True opens a new port that other applications can connect to. Raises IOError if not supported by the backend. autoreset=False Automatically send all_notes_off and reset_all_controllers on all channels. This is the same as calling `port.reset()`. """ kwargs.update(dict(virtual=virtual, autoreset=autoreset)) if name is None: name = self._env('MIDO_DEFAULT_OUTPUT') return self.module.Output(name, **self._add_api(kwargs)) def open_ioport(self, name=None, virtual=False, callback=None, autoreset=False, **kwargs): """Open a port for input and output. If the environment variable MIDO_DEFAULT_IOPORT is set, if will override the default port. virtual=False Passing True opens a new port that other applications can connect to. Raises IOError if not supported by the backend. callback=None A callback function to be called when a new message arrives. The function should take one argument (the message). Raises IOError if not supported by the backend. autoreset=False Automatically send all_notes_off and reset_all_controllers on all channels. This is the same as calling `port.reset()`. """ kwargs.update(dict(virtual=virtual, callback=callback, autoreset=autoreset)) if name is None: name = self._env('MIDO_DEFAULT_IOPORT') or None if hasattr(self.module, 'IOPort'): # Backend has a native IOPort. Use it. return self.module.IOPort(name, **self._add_api(kwargs)) else: # Backend has no native IOPort. Use the IOPort wrapper # in midi.ports. # # We need an input and an output name. # MIDO_DEFAULT_IOPORT overrides the other two variables. if name: input_name = output_name = name else: input_name = self._env('MIDO_DEFAULT_INPUT') output_name = self._env('MIDO_DEFAULT_OUTPUT') kwargs = self._add_api(kwargs) return ports.IOPort(self.module.Input(input_name, **kwargs), self.module.Output(output_name, **kwargs)) def _get_devices(self, **kwargs): if hasattr(self.module, 'get_devices'): return self.module.get_devices(**self._add_api(kwargs)) else: return [] def get_input_names(self, **kwargs): """Return a list of all input port names.""" devices = self._get_devices(**self._add_api(kwargs)) names = [device['name'] for device in devices if device['is_input']] return names def get_output_names(self, **kwargs): """Return a list of all output port names.""" devices = self._get_devices(**self._add_api(kwargs)) names = [device['name'] for device in devices if device['is_output']] return names def get_ioport_names(self, **kwargs): """Return a list of all I/O port names.""" devices = self._get_devices(**self._add_api(kwargs)) inputs = [device['name'] for device in devices if device['is_input']] outputs = set( [device['name'] for device in devices if device['is_output']]) return [name for name in inputs if name in outputs] def __repr__(self): if self.loaded: status = 'loaded' else: status = 'not loaded' if self.api: name = '{}/{}'.format(self.name, self.api) else: name = self.name return ''.format(name, status) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/backends/portmidi.py0000664000175000017500000002127500000000000017656 0ustar00olembolemb00000000000000""" Input and Output ports for PortMidi. There is no need to use this module directly. All you need is available in the toplevel module. PortMidi documentation: http://portmedia.sourceforge.net/portmidi/doxygen/ """ import threading from ..ports import BaseInput, BaseOutput, sleep from . import portmidi_init as pm _state = {'port_count': 0} def _refresh_port_list(): if _state['port_count'] == 0: # If no ports are open we can reboot PortMidi # to refresh the port list. This is a hack, but it's # the only way to get an up-to-date list. pm.lib.Pm_Terminate() pm.lib.Pm_Initialize() def _check_error(return_value): """Raise IOError if return_value < 0. The exception will be raised with the error message from PortMidi. """ if return_value == pm.pmHostError: raise IOError('PortMidi Host Error: ' '{}'.format(pm.get_host_error_message())) elif return_value < 0: raise IOError(pm.lib.Pm_GetErrorText(return_value)) def _get_device(device_id): info_pointer = pm.lib.Pm_GetDeviceInfo(device_id) if not info_pointer: raise IOError('PortMidi device with id={} not found'.format( device_id)) info = info_pointer.contents return { 'id': device_id, 'interface': info.interface.decode('utf-8'), 'name': info.name.decode('utf-8'), 'is_input': info.is_input, 'is_output': info.is_output, 'opened': bool(info.opened), } def _get_default_device(get_input): if get_input: device_id = pm.lib.Pm_GetDefaultInputDeviceID() else: device_id = pm.lib.Pm_GetDefaultOutputDeviceID() if device_id < 0: raise IOError('no default port found') return _get_device(device_id) def _get_named_device(name, get_input): # Look for the device by name and type (input / output) for device in get_devices(): if device['name'] != name: continue # Skip if device is the wrong type if get_input: if not device['is_input']: continue else: if not device['is_output']: continue if device['opened']: raise IOError('port already opened: {!r}'.format(name)) return device else: raise IOError('unknown port {!r}'.format(name)) def get_devices(**kwargs): """Return a list of devices as dictionaries.""" _refresh_port_list() return [_get_device(i) for i in range(pm.lib.Pm_CountDevices())] class PortCommon(object): """ Mixin with common things for input and output ports. """ def _open(self, **kwargs): _refresh_port_list() if kwargs.get('virtual'): raise ValueError('virtual ports are not supported' ' by the PortMidi backend') self._stream = pm.PortMidiStreamPtr() if self.name is None: device = _get_default_device(self.is_input) self.name = device['name'] else: device = _get_named_device(self.name, self.is_input) if device['opened']: if self.is_input: devtype = 'input' else: devtype = 'output' raise IOError('{} port {!r} is already open'.format(devtype, self.name)) if self.is_input: _check_error(pm.lib.Pm_OpenInput( pm.byref(self._stream), device['id'], # Input device pm.null, # Input driver info 1000, # Buffer size pm.NullTimeProcPtr, # Time callback pm.null)) # Time info else: _check_error(pm.lib.Pm_OpenOutput( pm.byref(self._stream), device['id'], # Output device pm.null, # Output diver info 0, # Buffer size # (ignored when latency == 0?) pm.NullTimeProcPtr, # Default to internal clock pm.null, # Time info 0)) # Latency # This is set when we return, but the callback thread needs # it to be False now (or it will just return right away.) self.closed = False _state['port_count'] += 1 if self.is_input: self._thread = None self.callback = kwargs.get('callback') self._device_type = 'PortMidi/{}'.format(device['interface']) def _close(self): self.callback = None _check_error(pm.lib.Pm_Close(self._stream)) _state['port_count'] -= 1 class Input(PortCommon, BaseInput): """ PortMidi Input port """ def _receive(self, block=True): # Since there is no blocking read in PortMidi, the block # flag is ignored and the enclosing receive() takes care # of blocking. # Allocate buffer. # I get hanging notes if MAX_EVENTS > 1, so I'll have to # resort to calling Pm_Read() in a loop until there are no # more pending events. max_events = 1 BufferType = pm.PmEvent * max_events read_buffer = BufferType() # Read available data from the stream and feed it to the parser. while pm.lib.Pm_Poll(self._stream): # TODO: this should be allocated once # Read one message. Should return 1. # If num_events < 0, an error occured. length = 1 # Buffer length num_events = pm.lib.Pm_Read(self._stream, read_buffer, length) _check_error(num_events) # Get the event event = read_buffer[0] # print('Received: {:x}'.format(event.message)) # The bytes of the message are stored like this: # 0x00201090 -> (0x90, 0x10, 0x10) # (TODO: not sure if this is correct.) packed_message = event.message & 0xffffffff for i in range(4): byte = packed_message & 0xff self._parser.feed_byte(byte) packed_message >>= 8 @property def callback(self): return self._callback @callback.setter def callback(self, func): self._callback = func if func is None: self._stop_thread() else: self._start_thread() def _start_thread(self): """Start callback thread if not already running.""" if not self._thread: self._stop_event = None self._thread = threading.Thread( target=self._thread_main) self._thread.daemon = True self._thread.start() def _stop_thread(self): """Stop callback thread if running.""" if self._thread: # Ask callback thread to stop. self._stop_event = threading.Event() self._stop_event.wait() self._thread = None def _thread_main(self): # TODO: exceptions do not propagate to the main thread, so if # something goes wrong here there is no way to detect it, and # there is no warning. (An unknown variable, for example, will # just make the thread stop silently.) # Receive messages from port until it's closed # or some exception occurs. try: while not self._stop_event: self._receive() for message in self._parser: if self.callback: self.callback(message) sleep() finally: # Inform parent thread that we are done. if self._stop_event: self._stop_event.set() def _close(self): self._stop_thread() PortCommon._close(self) class Output(PortCommon, BaseOutput): """ PortMidi output port """ def _send(self, message): if message.type == 'sysex': # Sysex messages are written as a string. string = pm.c_char_p(bytes(message.bin())) timestamp = 0 # Ignored when latency = 0 _check_error(pm.lib.Pm_WriteSysEx(self._stream, timestamp, string)) else: # The bytes of a message as packed into a 32 bit integer. packed_message = 0 for byte in reversed(message.bytes()): packed_message <<= 8 packed_message |= byte timestamp = 0 # Ignored when latency = 0 _check_error(pm.lib.Pm_WriteShort(self._stream, timestamp, packed_message)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1618776401.0 mido-1.2.10/mido/backends/portmidi_init.py0000664000175000017500000001026200000000000020673 0ustar00olembolemb00000000000000""" Low-level wrapper for PortMidi library Copied straight from Grant Yoshida's portmidizero, with slight modifications. """ import sys from ctypes import (CDLL, CFUNCTYPE, POINTER, Structure, c_char_p, c_int, c_long, c_uint, c_void_p, cast, create_string_buffer) import ctypes.util dll_name = '' if sys.platform == 'darwin': dll_name = ctypes.util.find_library('libportmidi.dylib') elif sys.platform in ('win32', 'cygwin'): dll_name = 'portmidi.dll' else: dll_name = 'libportmidi.so' lib = CDLL(dll_name) null = None false = 0 true = 1 # portmidi.h # From portmidi.h PM_HOST_ERROR_MSG_LEN = 256 def get_host_error_message(): """Return host error message.""" buf = create_string_buffer(PM_HOST_ERROR_MSG_LEN) lib.Pm_GetHostErrorText(buf, PM_HOST_ERROR_MSG_LEN) return buf.raw.decode().rstrip('\0') PmError = c_int # PmError enum pmNoError = 0 pmHostError = -10000 pmInvalidDeviceId = -9999 pmInsufficientMemory = -9989 pmBufferTooSmall = -9979 pmBufferOverflow = -9969 pmBadPtr = -9959 pmBadData = -9994 pmInternalError = -9993 pmBufferMaxSize = -9992 lib.Pm_Initialize.restype = PmError lib.Pm_Terminate.restype = PmError PmDeviceID = c_int PortMidiStreamPtr = c_void_p PmStreamPtr = PortMidiStreamPtr PortMidiStreamPtrPtr = POINTER(PortMidiStreamPtr) lib.Pm_HasHostError.restype = c_int lib.Pm_HasHostError.argtypes = [PortMidiStreamPtr] lib.Pm_GetErrorText.restype = c_char_p lib.Pm_GetErrorText.argtypes = [PmError] lib.Pm_GetHostErrorText.argtypes = [c_char_p, c_uint] pmNoDevice = -1 class PmDeviceInfo(Structure): _fields_ = [("structVersion", c_int), ("interface", c_char_p), ("name", c_char_p), ("is_input", c_int), ("is_output", c_int), ("opened", c_int)] PmDeviceInfoPtr = POINTER(PmDeviceInfo) lib.Pm_CountDevices.restype = c_int lib.Pm_GetDefaultOutputDeviceID.restype = PmDeviceID lib.Pm_GetDefaultInputDeviceID.restype = PmDeviceID PmTimestamp = c_long PmTimeProcPtr = CFUNCTYPE(PmTimestamp, c_void_p) NullTimeProcPtr = cast(null, PmTimeProcPtr) # PmBefore is not defined lib.Pm_GetDeviceInfo.argtypes = [PmDeviceID] lib.Pm_GetDeviceInfo.restype = PmDeviceInfoPtr lib.Pm_OpenInput.restype = PmError lib.Pm_OpenInput.argtypes = [PortMidiStreamPtrPtr, PmDeviceID, c_void_p, c_long, PmTimeProcPtr, c_void_p] lib.Pm_OpenOutput.restype = PmError lib.Pm_OpenOutput.argtypes = [PortMidiStreamPtrPtr, PmDeviceID, c_void_p, c_long, PmTimeProcPtr, c_void_p, c_long] lib.Pm_SetFilter.restype = PmError lib.Pm_SetFilter.argtypes = [PortMidiStreamPtr, c_long] lib.Pm_SetChannelMask.restype = PmError lib.Pm_SetChannelMask.argtypes = [PortMidiStreamPtr, c_int] lib.Pm_Abort.restype = PmError lib.Pm_Abort.argtypes = [PortMidiStreamPtr] lib.Pm_Close.restype = PmError lib.Pm_Close.argtypes = [PortMidiStreamPtr] PmMessage = c_long class PmEvent(Structure): _fields_ = [("message", PmMessage), ("timestamp", PmTimestamp)] PmEventPtr = POINTER(PmEvent) lib.Pm_Read.restype = PmError lib.Pm_Read.argtypes = [PortMidiStreamPtr, PmEventPtr, c_long] lib.Pm_Poll.restype = PmError lib.Pm_Poll.argtypes = [PortMidiStreamPtr] lib.Pm_Write.restype = PmError lib.Pm_Write.argtypes = [PortMidiStreamPtr, PmEventPtr, c_long] lib.Pm_WriteShort.restype = PmError lib.Pm_WriteShort.argtypes = [PortMidiStreamPtr, PmTimestamp, c_long] lib.Pm_WriteSysEx.restype = PmError lib.Pm_WriteSysEx.argtypes = [PortMidiStreamPtr, PmTimestamp, c_char_p] # porttime.h # PtError enum PtError = c_int ptNoError = 0 ptHostError = -10000 ptAlreadyStarted = -9999 ptAlreadyStopped = -9998 ptInsufficientMemory = -9997 PtTimestamp = c_long PtCallback = CFUNCTYPE(PmTimestamp, c_void_p) lib.Pt_Start.restype = PtError lib.Pt_Start.argtypes = [c_int, PtCallback, c_void_p] lib.Pt_Stop.restype = PtError lib.Pt_Started.restype = c_int lib.Pt_Time.restype = PtTimestamp ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/backends/pygame.py0000664000175000017500000000717100000000000017310 0ustar00olembolemb00000000000000""" Mido ports for pygame.midi. Pygame uses PortMidi, so this is perhaps not very useful. http://www.pygame.org/docs/ref/midi.html """ from __future__ import absolute_import from pygame import midi from ..ports import BaseInput, BaseOutput def _get_device(device_id): keys = ['interface', 'name', 'is_input', 'is_output', 'opened'] info = dict(zip(keys, midi.get_device_info(device_id))) # TODO: correct encoding? info['name'] = info['name'].decode('utf-8') info['id'] = device_id return info def _get_default_device(get_input): if get_input: device_id = midi.get_default_input_id() else: device_id = midi.get_default_output_id() if device_id < 0: raise IOError('no default port found') return _get_device(device_id) def _get_named_device(name, get_input): # Look for the device by name and type (input / output) for device in get_devices(): if device['name'] != name: continue # Skip if device is the wrong type if get_input: if device['is_output']: continue else: if device['is_input']: continue if device['opened']: raise IOError('port already opened: {!r}'.format(name)) return device else: raise IOError('unknown port: {!r}'.format(name)) def get_devices(**kwargs): midi.init() return [_get_device(device_id) for device_id in range(midi.get_count())] class PortCommon(object): """ Mixin with common things for input and output ports. """ def _open(self, **kwargs): if kwargs.get('virtual'): raise ValueError('virtual ports are not supported' ' by the Pygame backend') elif kwargs.get('callback'): raise ValueError('callbacks are not supported' ' by the Pygame backend') midi.init() if self.name is None: device = _get_default_device(self.is_input) self.name = device['name'] else: device = _get_named_device(self.name, self.is_input) if device['opened']: if self.is_input: devtype = 'input' else: devtype = 'output' raise IOError('{} port {!r} is already open'.format(devtype, self.name)) if self.is_input: self._port = midi.Input(device['id']) else: self._port = midi.Output(device['id']) self._device_type = 'Pygame/{}'.format(device['interface']) def _close(self): self._port.close() class Input(PortCommon, BaseInput): """ PortMidi Input port """ def _receive(self, block=True): # I get hanging notes if MAX_EVENTS > 1, so I'll have to # resort to calling Pm_Read() in a loop until there are no # more pending events. while self._port.poll(): bytes, time = self._port.read(1)[0] self._parser.feed(bytes) class Output(PortCommon, BaseOutput): """ PortMidi output port """ def _send(self, message): if message.type == 'sysex': # Python 2 version of Pygame accepts a bytes or list here # while Python 3 version requires bytes. # According to the docs it should accept both so this may be # a bug in Pygame: # https://www.pygame.org/docs/ref/midi.html#pygame.midi.Output.write_sys_ex self._port.write_sys_ex(midi.time(), bytes(message.bin())) else: self._port.write_short(*message.bytes()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1620659452.0 mido-1.2.10/mido/backends/rtmidi.py0000664000175000017500000001242200000000000017311 0ustar00olembolemb00000000000000"""Backend for python-rtmidi: http://pypi.python.org/pypi/python-rtmidi/ """ from __future__ import absolute_import import threading import rtmidi from .. import ports from ..messages import Message from ._parser_queue import ParserQueue from .rtmidi_utils import expand_alsa_port_name def _get_api_lookup(): api_to_name = {} name_to_api = {} for name in dir(rtmidi): if name.startswith('API_'): value = getattr(rtmidi, name) name = name.replace('API_', '') name_to_api[name] = value api_to_name[value] = name return api_to_name, name_to_api _api_to_name, _name_to_api = _get_api_lookup() def _get_api_id(name=None): if name is None: return rtmidi.API_UNSPECIFIED try: api = _name_to_api[name] except KeyError: raise ValueError('unknown API {}'.format(name)) if name in get_api_names(): return api else: raise ValueError('API {} not compiled in'.format(name)) def get_devices(api=None, **kwargs): devices = [] rtapi = _get_api_id(api) mi = rtmidi.MidiIn(rtapi=rtapi) mo = rtmidi.MidiOut(rtapi=rtapi) input_names = mi.get_ports() output_names = mo.get_ports() for name in input_names + output_names: devices.append({'name': name, 'is_input': name in input_names, 'is_output': name in output_names, }) mi.delete() mo.delete() return devices def get_api_names(): return [_api_to_name[n] for n in rtmidi.get_compiled_api()] def _open_port(rt, name=None, client_name=None, virtual=False, api=None): if api == 'LINUX_ALSA': name = expand_alsa_port_name(rt.get_ports(), name) if client_name is not None: virtual = True if virtual: if name is None: raise IOError('virtual port must have a name') rt.open_virtual_port(name) return name port_names = rt.get_ports() if len(port_names) == 0: raise IOError('no ports available') if name is None: name = port_names[0] port_id = 0 elif name in port_names: port_id = port_names.index(name) else: raise IOError('unknown port {!r}'.format(name)) try: rt.open_port(port_id) except RuntimeError as err: raise IOError(*err.args) return name class PortCommon(object): def _close(self): self._rt.close_port() self._rt.delete() class Input(PortCommon, ports.BaseInput): _locking = False def _open(self, client_name=None, virtual=False, api=None, callback=None, **kwargs): self.closed = True self._callback_lock = threading.RLock() self._queue = ParserQueue() rtapi = _get_api_id(api) self._rt = rtmidi.MidiIn(name=client_name, rtapi=rtapi) self.api = _api_to_name[self._rt.get_current_api()] self._device_type = 'RtMidi/{}'.format(self.api) self.name = _open_port(self._rt, self.name, client_name=client_name, virtual=virtual, api=self.api) self._rt.ignore_types(False, False, True) self.closed = False # We need to do this last when everything is set up. self.callback = callback # We override receive() and poll() instead of _receive() and # _poll() to bypass locking. def receive(self, block=True): if block: return self._queue.get() else: return self._queue.poll() def poll(self): return self._queue.poll() receive.__doc__ = ports.BaseInput.receive.__doc__ poll.__doc__ = ports.BaseInput.poll.__doc__ @property def callback(self): return self._callback @callback.setter def callback(self, func): with self._callback_lock: # Make sure the callback doesn't run while were swapping it out. self._rt.cancel_callback() if func: # Make sure the callback gets all the queued messages. for msg in self._queue.iterpoll(): func(msg) self._callback = func self._rt.set_callback(self._callback_wrapper) def _callback_wrapper(self, msg_data, data): try: msg = Message.from_bytes(msg_data[0]) except ValueError: # Ignore invalid message. return (self._callback or self._queue.put)(msg) class Output(PortCommon, ports.BaseOutput): _locking = False def _open(self, client_name=None, virtual=False, api=None, callback=None, **kwargs): self.closed = True self._send_lock = threading.RLock() rtapi = _get_api_id(api) self._rt = rtmidi.MidiOut(name=client_name, rtapi=rtapi) self.api = _api_to_name[self._rt.get_current_api()] self._device_type = 'RtMidi/{}'.format(self.api) self.name = _open_port(self._rt, self.name, client_name=client_name, virtual=virtual, api=self.api) self.closed = False # We override send() instead of _send() to bypass locking. def send(self, msg): """Send a message on the port.""" with self._send_lock: self._rt.send_message(msg.bytes()) send.__doc__ = ports.BaseOutput.send.__doc__ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1620659452.0 mido-1.2.10/mido/backends/rtmidi_python.py0000664000175000017500000000740200000000000020714 0ustar00olembolemb00000000000000"""Backend for rtmidi-python: https://pypi.python.org/pypi/rtmidi-python To use this backend copy (or link) it to somewhere in your Python path and call: mido.set_backend('mido.backends.rtmidi_python') or set shell variable $MIDO_BACKEND to mido.backends.rtmidi_python TODO: * add support for APIs. * active_sensing is still filtered. (The same is true for mido.backends.rtmidi.)There may be a way to remove this filtering. """ from __future__ import absolute_import import rtmidi_python as rtmidi # TODO: change this to a relative import if the backend is included in # the package. from ..ports import BaseInput, BaseOutput from ..py2 import PY2 if PY2: import Queue as queue else: import queue def get_devices(api=None, **kwargs): devices = [] input_names = rtmidi.MidiIn().ports output_names = rtmidi.MidiOut().ports for name in input_names + output_names: devices.append({'name': name, 'is_input': name in input_names, 'is_output': name in output_names, }) return devices class PortCommon(object): def _open(self, virtual=False, **kwargs): self._queue = queue.Queue() self._callback = None # rtapi = _get_api_id(api) opening_input = hasattr(self, 'receive') if opening_input: self._rt = rtmidi.MidiIn() self._rt.ignore_types(False, False, True) self.callback = kwargs.get('callback') else: self._rt = rtmidi.MidiOut() # rtapi=rtapi) # Turn of ignore of sysex, time and active_sensing. ports = self._rt.ports if virtual: if self.name is None: raise IOError('virtual port must have a name') self._rt.open_virtual_port(self.name) else: if self.name is None: # TODO: this could fail if list is empty. # In RtMidi, the default port is the first port. try: self.name = ports[0] except IndexError: raise IOError('no ports available') try: port_id = ports.index(self.name) except ValueError: raise IOError('unknown port {!r}'.format(self.name)) try: self._rt.open_port(port_id) except RuntimeError as err: raise IOError(*err.args) # api = _api_to_name[self._rt.get_current_api()] api = '' self._device_type = 'RtMidi/{}'.format(api) if virtual: self._device_type = 'virtual {}'.format(self._device_type) @property def callback(self): return self._callback @callback.setter def callback(self, func): self._callback = func if func is None: self._rt.callback = None else: self._rt.callback = self._callback_wrapper def _callback_wrapper(self, msg_bytes, timestamp): self._parser.feed(msg_bytes) for message in self._parser: if self.callback: self.callback(message) def _close(self): self._rt.close_port() del self._rt # Virtual ports are closed when this is deleted. class Input(PortCommon, BaseInput): def _receive(self, block=True): # Since there is no blocking read in RtMidi, the block # flag is ignored and the enclosing receive() takes care # of blocking. while True: message_data, timestamp = self._rt.get_message() if message_data is None: break else: self._parser.feed(message_data) class Output(PortCommon, BaseOutput): def _send(self, message): self._rt.send_message(message.bytes()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608652691.0 mido-1.2.10/mido/backends/rtmidi_utils.py0000664000175000017500000000250700000000000020534 0ustar00olembolemb00000000000000"""Utility functions for RtMidi backend. These are in a separate file so they can be tested without the `python-rtmidi` package. """ def expand_alsa_port_name(port_names, name): """Expand ALSA port name. RtMidi/ALSA includes client name and client:port number in the port name, for example: TiMidity:TiMidity port 0 128:0 This allows you to specify only port name or client:port name when opening a port. It will compare the name to each name in port_names (typically returned from get_*_names()) and try these three variants in turn: TiMidity:TiMidity port 0 128:0 TiMidity:TiMidity port 0 TiMidity port 0 It returns the first match. If no match is found it returns the passed name so the caller can deal with it. """ if name is None: return None for port_name in port_names: if name == port_name: return name # Try without client and port number (for example 128:0). without_numbers = port_name.rsplit(None, 1)[0] if name == without_numbers: return port_name if ':' in without_numbers: without_client = without_numbers.split(':', 1)[1] if name == without_client: return port_name else: # Let caller deal with it. return name ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/frozen.py0000664000175000017500000000437700000000000015564 0ustar00olembolemb00000000000000from .messages import Message from .midifiles import MetaMessage, UnknownMetaMessage class Frozen(object): def __setattr__(self, *_): raise ValueError('frozen message is immutable') def __hash__(self): return hash(tuple(sorted(vars(self).items()))) class FrozenMessage(Frozen, Message): pass class FrozenMetaMessage(Frozen, MetaMessage): pass class FrozenUnknownMetaMessage(Frozen, UnknownMetaMessage): def __repr__(self): return 'Frozen' + UnknownMetaMessage.__repr__(self) def is_frozen(msg): """Return True if message is frozen, otherwise False.""" return isinstance(msg, Frozen) # TODO: these two functions are almost the same except inverted. There # should be a way to refactor them to lessen code duplication. def freeze_message(msg): """Freeze message. Returns a frozen version of the message. Frozen messages are immutable, hashable and can be used as dictionary keys. Will return None if called with None. This allows you to do things like:: msg = freeze_message(port.poll()) """ if isinstance(msg, Frozen): # Already frozen. return msg elif isinstance(msg, Message): class_ = FrozenMessage elif isinstance(msg, UnknownMetaMessage): class_ = FrozenUnknownMetaMessage elif isinstance(msg, MetaMessage): class_ = FrozenMetaMessage elif msg is None: return None else: raise ValueError('first argument must be a message or None') frozen = class_.__new__(class_) vars(frozen).update(vars(msg)) return frozen def thaw_message(msg): """Thaw message. Returns a mutable version of a frozen message. Will return None if called with None. """ if not isinstance(msg, Frozen): # Already thawed, just return a copy. return msg.copy() elif isinstance(msg, FrozenMessage): class_ = Message elif isinstance(msg, FrozenUnknownMetaMessage): class_ = UnknownMetaMessage elif isinstance(msg, FrozenMetaMessage): class_ = MetaMessage elif msg is None: return None else: raise ValueError('first argument must be a message or None') thawed = class_.__new__(class_) vars(thawed).update(vars(msg)) return thawed ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2553298 mido-1.2.10/mido/messages/0000775000175000017500000000000000000000000015503 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/messages/__init__.py0000664000175000017500000000044500000000000017617 0ustar00olembolemb00000000000000from .checks import check_time from .specs import (SPEC_LOOKUP, SPEC_BY_TYPE, SPEC_BY_STATUS, MIN_PITCHWHEEL, MAX_PITCHWHEEL, MIN_SONGPOS, MAX_SONGPOS) from .messages import (BaseMessage, Message, parse_string, format_as_string, parse_string_stream) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/messages/checks.py0000664000175000017500000000547700000000000017332 0ustar00olembolemb00000000000000from numbers import Integral, Real from .specs import (SPEC_BY_TYPE, MIN_SONGPOS, MAX_SONGPOS, MIN_PITCHWHEEL, MAX_PITCHWHEEL) from ..py2 import convert_py2_bytes def check_type(type_): if type_ not in SPEC_BY_TYPE: raise ValueError('invalid message type {!r}'.format(type_)) def check_channel(channel): if not isinstance(channel, Integral): raise TypeError('channel must be int') elif not 0 <= channel <= 15: raise ValueError('channel must be in range 0..15') def check_pos(pos): if not isinstance(pos, Integral): raise TypeError('song pos must be int') elif not MIN_SONGPOS <= pos <= MAX_SONGPOS: raise ValueError('song pos must be in range {}..{}'.format( MIN_SONGPOS, MAX_SONGPOS)) def check_pitch(pitch): if not isinstance(pitch, Integral): raise TypeError('pichwheel value must be int') elif not MIN_PITCHWHEEL <= pitch <= MAX_PITCHWHEEL: raise ValueError('pitchwheel value must be in range {}..{}'.format( MIN_PITCHWHEEL, MAX_PITCHWHEEL)) def check_data(data_bytes): for byte in convert_py2_bytes(data_bytes): check_data_byte(byte) def check_frame_type(value): if not isinstance(value, Integral): raise TypeError('frame_type must be int') elif not 0 <= value <= 7: raise ValueError('frame_type must be in range 0..7') def check_frame_value(value): if not isinstance(value, Integral): raise TypeError('frame_value must be int') elif not 0 <= value <= 15: raise ValueError('frame_value must be in range 0..15') def check_data_byte(value): if not isinstance(value, Integral): raise TypeError('data byte must be int') elif not 0 <= value <= 127: raise ValueError('data byte must be in range 0..127') def check_time(time): if not isinstance(time, Real): raise TypeError('time must be int or float') _CHECKS = { 'type': check_type, 'data': check_data, 'channel': check_channel, 'control': check_data_byte, 'data': check_data, 'frame_type': check_frame_type, 'frame_value': check_frame_value, 'note': check_data_byte, 'pitch': check_pitch, 'pos': check_pos, 'program': check_data_byte, 'song': check_data_byte, 'value': check_data_byte, 'velocity': check_data_byte, 'time': check_time, } def check_value(name, value): _CHECKS[name](value) def check_msgdict(msgdict): spec = SPEC_BY_TYPE.get(msgdict['type']) if spec is None: raise ValueError('unknown message type {!r}'.format(msgdict['type'])) for name, value in msgdict.items(): if name not in spec['attribute_names']: raise ValueError( '{} message has no attribute {}'.format(spec['type'], name)) check_value(name, value) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/messages/decode.py0000664000175000017500000000531200000000000017301 0ustar00olembolemb00000000000000from .specs import (SYSEX_START, SYSEX_END, SPEC_BY_STATUS, CHANNEL_MESSAGES, MIN_PITCHWHEEL) from .checks import check_data from ..py2 import convert_py2_bytes def _decode_sysex_data(data): return {'data': tuple(data)} def _decode_quarter_frame_data(data): return {'frame_type': data[0] >> 4, 'frame_value': data[0] & 15} def _decode_songpos_data(data): return {'pos': data[0] | (data[1] << 7)} def _decode_pitchwheel_data(data): return {'pitch': data[0] | ((data[1] << 7) + MIN_PITCHWHEEL)} def _make_special_cases(): cases = { 0xe0: _decode_pitchwheel_data, 0xf0: _decode_sysex_data, 0xf1: _decode_quarter_frame_data, 0xf2: _decode_songpos_data, } for i in range(16): cases[0xe0 | i] = _decode_pitchwheel_data return cases _SPECIAL_CASES = _make_special_cases() def _decode_data_bytes(status_byte, data, spec): # Subtract 1 for status byte. if len(data) != (spec['length'] - 1): raise ValueError( 'wrong number of bytes for {} message'.format(spec['type'])) # TODO: better name than args? names = [name for name in spec['value_names'] if name != 'channel'] args = {name: value for name, value in zip(names, data)} if status_byte in CHANNEL_MESSAGES: # Channel is stored in the lower nibble of the status byte. args['channel'] = status_byte & 0x0f return args def decode_message(msg_bytes, time=0, check=True): """Decode message bytes and return messages as a dictionary. Raises ValueError if the bytes are out of range or the message is invalid. This is not a part of the public API. """ # TODO: this function is getting long. msg_bytes = convert_py2_bytes(msg_bytes) if len(msg_bytes) == 0: raise ValueError('message is 0 bytes long') status_byte = msg_bytes[0] data = msg_bytes[1:] try: spec = SPEC_BY_STATUS[status_byte] except KeyError: raise ValueError('invalid status byte {!r}'.format(status_byte)) msg = { 'type': spec['type'], 'time': time, } # Sysex. if status_byte == SYSEX_START: if len(data) < 1: raise ValueError('sysex without end byte') end = data[-1] data = data[:-1] if end != SYSEX_END: raise ValueError('invalid sysex end byte {!r}'.format(end)) if check: check_data(data) if status_byte in _SPECIAL_CASES: if status_byte in CHANNEL_MESSAGES: msg['channel'] = status_byte & 0x0f msg.update(_SPECIAL_CASES[status_byte](data)) else: msg.update(_decode_data_bytes(status_byte, data, spec)) return msg ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/messages/encode.py0000664000175000017500000000324400000000000017315 0ustar00olembolemb00000000000000from .specs import CHANNEL_MESSAGES, SPEC_BY_TYPE, MIN_PITCHWHEEL def _encode_pitchwheel(msg): pitch = msg['pitch'] - MIN_PITCHWHEEL return [0xe0 | msg['channel'], pitch & 0x7f, pitch >> 7] def _encode_sysex(msg): return [0xf0] + list(msg['data']) + [0xf7] def _encode_quarter_frame(msg): return [0xf1, msg['frame_type'] << 4 | msg['frame_value']] def _encode_songpos(data): pos = data['pos'] return [0xf2, pos & 0x7f, pos >> 7] def _encode_note_off(msg): return [0x80 | msg['channel'], msg['note'], msg['velocity']] def _encode_note_on(msg): return [0x90 | msg['channel'], msg['note'], msg['velocity']] def _encode_control_change(msg): return [0xb0 | msg['channel'], msg['control'], msg['value']] _SPECIAL_CASES = { 'pitchwheel': _encode_pitchwheel, 'sysex': _encode_sysex, 'quarter_frame': _encode_quarter_frame, 'songpos': _encode_songpos, # These are so common that they get special cases to speed things up. 'note_off': _encode_note_off, 'note_on': _encode_note_on, 'control_change': _encode_control_change, } def encode_message(msg): """Encode msg dict as a list of bytes. TODO: Add type and value checking. (Can be turned off with keyword argument.) This is not a part of the public API. """ encode = _SPECIAL_CASES.get(msg['type']) if encode: return encode(msg) else: spec = SPEC_BY_TYPE[msg['type']] status_byte = spec['status_byte'] if status_byte in CHANNEL_MESSAGES: status_byte |= msg['channel'] data = [msg[name] for name in spec['value_names'] if name != 'channel'] return [status_byte] + data ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608647494.0 mido-1.2.10/mido/messages/messages.py0000664000175000017500000001732000000000000017667 0ustar00olembolemb00000000000000import re from .specs import make_msgdict, SPEC_BY_TYPE, REALTIME_TYPES from .checks import check_msgdict, check_value, check_data from .decode import decode_message from .encode import encode_message from .strings import msg2str, str2msg from ..py2 import convert_py2_bytes class BaseMessage(object): """Abstract base class for messages.""" is_meta = False def copy(self): raise NotImplementedError def bytes(self): raise NotImplementedError def bin(self): """Encode message and return as a bytearray. This can be used to write the message to a file. """ return bytearray(self.bytes()) def hex(self, sep=' '): """Encode message and return as a string of hex numbers, Each number is separated by the string sep. """ return sep.join('{:02X}'.format(byte) for byte in self.bytes()) def dict(self): """Returns a dictionary containing the attributes of the message. Example: {'type': 'sysex', 'data': [1, 2], 'time': 0} Sysex data will be returned as a list. """ data = vars(self).copy() if data['type'] == 'sysex': # Make sure we return a list instead of a SysexData object. data['data'] = list(data['data']) return data @classmethod def from_dict(cl, data): """Create a message from a dictionary. Only "type" is required. The other will be set to default values. """ return cl(**data) def _get_value_names(self): # This is overriden by MetaMessage. return list(SPEC_BY_TYPE[self.type]['value_names']) + ['time'] def __repr__(self): items = [repr(self.type)] for name in self._get_value_names(): items.append('{}={!r}'.format(name, getattr(self, name))) return '{}({})'.format(type(self).__name__, ', '.join(items)) @property def is_realtime(self): """True if the message is a system realtime message.""" return self.type in REALTIME_TYPES def is_cc(self, control=None): """Return True if the message is of type 'control_change'. The optional control argument can be used to test for a specific control number, for example: if msg.is_cc(7): # Message is control change 7 (channel volume). """ if self.type != 'control_change': return False elif control is None: return True else: return self.control == control def __delattr__(self, name): raise AttributeError('attribute cannot be deleted') def __setattr__(self, name, value): raise AttributeError('message is immutable') def __eq__(self, other): if not isinstance(other, BaseMessage): raise TypeError('can\'t compare message to {}'.format(type(other))) # This includes time in comparison. return vars(self) == vars(other) class SysexData(tuple): """Special kind of tuple accepts and converts any sequence in +=.""" def __iadd__(self, other): check_data(other) return self + SysexData(convert_py2_bytes(other)) class Message(BaseMessage): def __init__(self, type, **args): msgdict = make_msgdict(type, args) if type == 'sysex': msgdict['data'] = SysexData(convert_py2_bytes(msgdict['data'])) check_msgdict(msgdict) vars(self).update(msgdict) def copy(self, **overrides): """Return a copy of the message. Attributes will be overridden by the passed keyword arguments. Only message specific attributes can be overridden. The message type can not be changed. """ if not overrides: # Bypass all checks. msg = self.__class__.__new__(self.__class__) vars(msg).update(vars(self)) return msg if 'type' in overrides and overrides['type'] != self.type: raise ValueError('copy must be same message type') if 'data' in overrides: overrides['data'] = bytearray(overrides['data']) msgdict = vars(self).copy() msgdict.update(overrides) check_msgdict(msgdict) return self.__class__(**msgdict) @classmethod def from_bytes(cl, data, time=0): """Parse a byte encoded message. Accepts a byte string or any iterable of integers. This is the reverse of msg.bytes() or msg.bin(). """ msg = cl.__new__(cl) msgdict = decode_message(data, time=time) if 'data' in msgdict: msgdict['data'] = SysexData(msgdict['data']) vars(msg).update(msgdict) return msg @classmethod def from_hex(cl, text, time=0, sep=None): """Parse a hex encoded message. This is the reverse of msg.hex(). """ # bytearray.fromhex() is a bit picky about its input # so we need to replace all whitespace characters with spaces. text = re.sub(r'\s', ' ', text) if sep is not None: # We also replace the separator with spaces making sure # the string length remains the same so char positions will # be correct in bytearray.fromhex() error messages. text = text.replace(sep, ' ' * len(sep)) return cl.from_bytes(bytearray.fromhex(text), time=time) @classmethod def from_str(cl, text): """Parse a string encoded message. This is the reverse of str(msg). """ return cl(**str2msg(text)) def __len__(self): if self.type == 'sysex': return 2 + len(self.data) else: return SPEC_BY_TYPE[self.type]['length'] def __str__(self): return msg2str(vars(self)) def _setattr(self, name, value): if name == 'type': raise AttributeError('type attribute is read only') elif name not in vars(self): raise AttributeError('{} message has no ' 'attribute {}'.format(self.type, name)) else: check_value(name, value) if name == 'data': vars(self)['data'] = SysexData(value) else: vars(self)[name] = value __setattr__ = _setattr def bytes(self): """Encode message and return as a list of integers.""" return encode_message(vars(self)) def parse_string(text): """Parse a string of text and return a message. The string can span multiple lines, but must contain one full message. Raises ValueError if the string could not be parsed. """ return Message.from_str(text) def parse_string_stream(stream): """Parse a stram of messages and yield (message, error_message) stream can be any iterable that generates text strings, where each string is a string encoded message. If a string can be parsed, (message, None) is returned. If it can't be parsed (None, error_message) is returned. The error message containes the line number where the error occurred. """ line_number = 1 for line in stream: try: line = line.split('#')[0].strip() if line: yield parse_string(line), None except ValueError as exception: error_message = 'line {line_number}: {msg}'.format( line_number=line_number, msg=exception.args[0]) yield None, error_message line_number += 1 def format_as_string(msg, include_time=True): """Format a message and return as a string. This is equivalent to str(message). To leave out the time attribute, pass include_time=False. """ return msg2str(vars(msg), include_time=include_time) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/messages/specs.py0000664000175000017500000000646000000000000017200 0ustar00olembolemb00000000000000"""Definitions and lookup tables for MIDI messages. TODO: * add lookup functions for messages definitions by type and status byte. """ # TODO: these include undefined messages. CHANNEL_MESSAGES = set(range(0x80, 0xf0)) COMMON_MESSAGES = set(range(0xf0, 0xf8)) REALTIME_MESSAGES = set(range(0xf8, 0x100)) SYSEX_START = 0xf0 SYSEX_END = 0xf7 # Pitchwheel is a 14 bit signed integer MIN_PITCHWHEEL = -8192 MAX_PITCHWHEEL = 8191 # Song pos is a 14 bit unsigned integer MIN_SONGPOS = 0 MAX_SONGPOS = 16383 def _defmsg(status_byte, type_, value_names, length): return { 'status_byte': status_byte, 'type': type_, 'value_names': value_names, 'attribute_names': set(value_names) | {'type', 'time'}, 'length': length, } SPECS = [ _defmsg(0x80, 'note_off', ('channel', 'note', 'velocity'), 3), _defmsg(0x90, 'note_on', ('channel', 'note', 'velocity'), 3), _defmsg(0xa0, 'polytouch', ('channel', 'note', 'value'), 3), _defmsg(0xb0, 'control_change', ('channel', 'control', 'value'), 3), _defmsg(0xc0, 'program_change', ('channel', 'program',), 2), _defmsg(0xd0, 'aftertouch', ('channel', 'value',), 2), _defmsg(0xe0, 'pitchwheel', ('channel', 'pitch',), 3), # System common messages. # 0xf4 and 0xf5 are undefined. _defmsg(0xf0, 'sysex', ('data',), float('inf')), _defmsg(0xf1, 'quarter_frame', ('frame_type', 'frame_value'), 2), _defmsg(0xf2, 'songpos', ('pos',), 3), _defmsg(0xf3, 'song_select', ('song',), 2), _defmsg(0xf6, 'tune_request', (), 1), # System real time messages. # 0xf9 and 0xfd are undefined. _defmsg(0xf8, 'clock', (), 1), _defmsg(0xfa, 'start', (), 1), _defmsg(0xfb, 'continue', (), 1), _defmsg(0xfc, 'stop', (), 1), _defmsg(0xfe, 'active_sensing', (), 1), _defmsg(0xff, 'reset', (), 1), ] def _make_spec_lookups(specs): lookup = {} by_status = {} by_type = {} for spec in specs: type_ = spec['type'] status_byte = spec['status_byte'] by_type[type_] = spec if status_byte in CHANNEL_MESSAGES: for channel in range(16): by_status[status_byte | channel] = spec else: by_status[status_byte] = spec lookup.update(by_status) lookup.update(by_type) return lookup, by_status, by_type SPEC_LOOKUP, SPEC_BY_STATUS, SPEC_BY_TYPE = _make_spec_lookups(SPECS) REALTIME_TYPES = {'tune_request', 'clock', 'start', 'continue', 'stop'} DEFAULT_VALUES = { 'channel': 0, 'control': 0, 'data': (), 'frame_type': 0, 'frame_value': 0, 'note': 0, 'pitch': 0, 'pos': 0, 'program': 0, 'song': 0, 'value': 0, 'velocity': 64, 'time': 0, } # TODO: should this be in decode.py? def make_msgdict(type_, overrides): """Return a new message. Returns a dictionary representing a message. Message values can be overriden. No type or value checking is done. The caller is responsible for calling check_msgdict(). """ if type_ in SPEC_BY_TYPE: spec = SPEC_BY_TYPE[type_] else: raise LookupError('Unknown message type {!r}'.format(type_)) msg = {'type': type_, 'time': DEFAULT_VALUES['time']} for name in spec['value_names']: msg[name] = DEFAULT_VALUES[name] msg.update(overrides) return msg ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/messages/strings.py0000664000175000017500000000310600000000000017546 0ustar00olembolemb00000000000000from .specs import SPEC_BY_TYPE, make_msgdict def msg2str(msg, include_time=True): type_ = msg['type'] spec = SPEC_BY_TYPE[type_] words = [type_] for name in spec['value_names']: value = msg[name] if name == 'data': value = '({})'.format(','.join(str(byte) for byte in value)) words.append('{}={}'.format(name, value)) if include_time: words.append('time={}'.format(msg['time'])) return str.join(' ', words) def _parse_time(value): # Convert to int if possible. try: return int(value) except ValueError: pass try: return float(value) except ValueError: pass raise ValueError('invalid time {!r}'.format(value)) def _parse_data(value): if not value.startswith('(') and value.endswith(')'): raise ValueError('missing parentheses in data message') try: return [int(byte) for byte in value[1:-1].split(',')] except ValueError: raise ValueError('unable to parse data bytes') def str2msg(text): """Parse str format and return message dict. No type or value checking is done. The caller is responsible for calling check_msgdict(). """ words = text.split() type_ = words[0] args = words[1:] msg = {} for arg in args: name, value = arg.split('=', 1) if name == 'time': value = _parse_time(value) elif name == 'data': value = _parse_data(value) else: value = int(value) msg[name] = value return make_msgdict(type_, msg) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2593298 mido-1.2.10/mido/midifiles/0000775000175000017500000000000000000000000015641 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/mido/midifiles/__init__.py0000644000175000017500000000032300000000000017746 0ustar00olembolemb00000000000000from .meta import MetaMessage, UnknownMetaMessage, KeySignatureError from .units import tick2second, second2tick, bpm2tempo, tempo2bpm from .tracks import MidiTrack, merge_tracks from .midifiles import MidiFile ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/midifiles/meta.py0000664000175000017500000003616300000000000017152 0ustar00olembolemb00000000000000""" Meta messages for MIDI files. TODO: - what if an unknown meta message is implemented and someone depends on the 'data' attribute? - is 'type_byte' a good name? - 'values' is not a good name for a dictionary. - type and value safety? - copy(). - expose _key_signature_encode/decode? """ from __future__ import print_function, division import math import struct from numbers import Integral from contextlib import contextmanager from ..messages import BaseMessage, check_time from ..py2 import PY2 _charset = 'latin1' class KeySignatureError(Exception): """ Raised when key cannot be converted from key/mode to key letter """ pass def _reverse_table(table): """Return value: key for dictionary.""" return {value: key for (key, value) in table.items()} _key_signature_decode = {(-7, 0): 'Cb', (-6, 0): 'Gb', (-5, 0): 'Db', (-4, 0): 'Ab', (-3, 0): 'Eb', (-2, 0): 'Bb', (-1, 0): 'F', (0, 0): 'C', (1, 0): 'G', (2, 0): 'D', (3, 0): 'A', (4, 0): 'E', (5, 0): 'B', (6, 0): 'F#', (7, 0): 'C#', (-7, 1): 'Abm', (-6, 1): 'Ebm', (-5, 1): 'Bbm', (-4, 1): 'Fm', (-3, 1): 'Cm', (-2, 1): 'Gm', (-1, 1): 'Dm', (0, 1): 'Am', (1, 1): 'Em', (2, 1): 'Bm', (3, 1): 'F#m', (4, 1): 'C#m', (5, 1): 'G#m', (6, 1): 'D#m', (7, 1): 'A#m', } _key_signature_encode = _reverse_table(_key_signature_decode) _smpte_framerate_decode = {0: 24, 1: 25, 2: 29.97, 3: 30, } _smpte_framerate_encode = _reverse_table(_smpte_framerate_decode) def signed(to_type, n): formats = {'byte': 'Bb', 'short': 'Hh', 'long': 'Ll', 'ubyte': 'bB', 'ushort': 'hH', 'ulong': 'lL' } try: pack_format, unpack_format = formats[to_type] except KeyError: raise ValueError('invalid integer type {}'.format(to_type)) try: packed = struct.pack(pack_format, n) return struct.unpack(unpack_format, packed)[0] except struct.error as err: raise ValueError(*err.args) def unsigned(to_type, n): return signed('u{}'.format(to_type), n) def encode_variable_int(value): """Encode variable length integer. Returns the integer as a list of bytes, where the last byte is < 128. This is used for delta times and meta message payload length. """ if not isinstance(value, Integral) or value < 0: raise ValueError('variable int must be a non-negative integer') bytes = [] while value: bytes.append(value & 0x7f) value >>= 7 if bytes: bytes.reverse() # Set high bit in every byte but the last. for i in range(len(bytes) - 1): bytes[i] |= 0x80 return bytes else: return [0] def encode_string(string): return list(bytearray(string.encode(_charset))) def decode_string(data): return bytearray(data).decode(_charset) @contextmanager def meta_charset(tmp_charset): global _charset old = _charset _charset = tmp_charset yield _charset = old def check_int(value, low, high): if not isinstance(value, Integral): raise TypeError('attribute must be an integer') elif not low <= value <= high: raise ValueError('attribute must be in range {}..{}'.format(low, high)) if PY2: _STRING_TYPE = (str, unicode) # noqa: F821 else: _STRING_TYPE = str def check_str(value): if not isinstance(value, _STRING_TYPE): raise TypeError('attribute must be a string') class MetaSpec(object): # The default is to do no checks. def check(self, name, value): pass class MetaSpec_sequence_number(MetaSpec): type_byte = 0x00 attributes = ['number'] defaults = [0] def decode(self, message, data): if len(data) == 0: # Message with length 0 can occur in some files. # (See issues 42 and 93.) message.number = 0 else: message.number = (data[0] << 8) | data[1] def encode(self, message): return [message.number >> 8, message.number & 0xff] def check(self, name, value): check_int(value, 0, 0xffff) class MetaSpec_text(MetaSpec): type_byte = 0x01 attributes = ['text'] defaults = [''] def decode(self, message, data): message.text = decode_string(data) def encode(self, message): return encode_string(message.text) def check(self, name, value): check_str(value) class MetaSpec_copyright(MetaSpec_text): type_byte = 0x02 class MetaSpec_track_name(MetaSpec_text): type_byte = 0x03 attributes = ['name'] defaults = [''] def decode(self, message, data): message.name = decode_string(data) def encode(self, message): return encode_string(message.name) class MetaSpec_instrument_name(MetaSpec_track_name): type_byte = 0x04 class MetaSpec_lyrics(MetaSpec_text): type_byte = 0x05 class MetaSpec_marker(MetaSpec_text): type_byte = 0x06 class MetaSpec_cue_marker(MetaSpec_text): type_byte = 0x07 class MetaSpec_device_name(MetaSpec_track_name): type_byte = 0x09 class MetaSpec_channel_prefix(MetaSpec): type_byte = 0x20 attributes = ['channel'] defaults = [0] def decode(self, message, data): message.channel = data[0] def encode(self, message): return [message.channel] def check(self, name, value): check_int(value, 0, 0xff) class MetaSpec_midi_port(MetaSpec): type_byte = 0x21 attributes = ['port'] defaults = [0] def decode(self, message, data): if len(data) == 0: # Message with length 0 can occur in some files. # (See issues 42 and 93.) message.port = 0 else: message.port = data[0] def encode(self, message): return [message.port] def check(self, name, value): check_int(value, 0, 255) class MetaSpec_end_of_track(MetaSpec): type_byte = 0x2f attributes = [] defaults = [] def decode(self, message, data): pass def encode(self, message): return [] class MetaSpec_set_tempo(MetaSpec): type_byte = 0x51 attributes = ['tempo'] defaults = [500000] def decode(self, message, data): message.tempo = (data[0] << 16) | (data[1] << 8) | (data[2]) def encode(self, message): tempo = message.tempo return [tempo >> 16, tempo >> 8 & 0xff, tempo & 0xff] def check(self, name, value): check_int(value, 0, 0xffffff) class MetaSpec_smpte_offset(MetaSpec): type_byte = 0x54 attributes = ['frame_rate', 'hours', 'minutes', 'seconds', 'frames', 'sub_frames' ] # TODO: What are some good defaults? defaults = [24, 0, 0, 0, 0, 0] def decode(self, message, data): message.frame_rate = _smpte_framerate_decode[(data[0] >> 6)] message.hours = (data[0] & 0x3f) message.minutes = data[1] message.seconds = data[2] message.frames = data[3] message.sub_frames = data[4] def encode(self, message): frame_rate_lookup = _smpte_framerate_encode[message.frame_rate] << 6 return [frame_rate_lookup | message.hours, message.minutes, message.seconds, message.frames, message.sub_frames] def check(self, name, value): if name == 'frame_rate': if value not in _smpte_framerate_encode: valid = ', '.join(sorted(_smpte_framerate_encode.keys())) raise ValueError('frame_rate must be one of {}'.format(valid)) elif name == 'hours': check_int(value, 0, 255) elif name in ['minutes', 'seconds']: check_int(value, 0, 59) elif name == 'frames': check_int(value, 0, 255) elif name == 'sub_frames': check_int(value, 0, 99) class MetaSpec_time_signature(MetaSpec): type_byte = 0x58 # TODO: these need more sensible names. attributes = ['numerator', 'denominator', 'clocks_per_click', 'notated_32nd_notes_per_beat'] defaults = [4, 4, 24, 8] def decode(self, message, data): message.numerator = data[0] message.denominator = 2 ** data[1] message.clocks_per_click = data[2] message.notated_32nd_notes_per_beat = data[3] def encode(self, message): return [message.numerator, int(math.log(message.denominator, 2)), message.clocks_per_click, message.notated_32nd_notes_per_beat, ] def check(self, name, value): if name == 'denominator': # This allows for the ridiculous time signature of # 4/57896044618658097711785492504343953926634... # 992332820282019728792003956564819968 check_int(value, 1, 2 ** 255) encoded = math.log(value, 2) encoded_int = int(encoded) if encoded != encoded_int: raise ValueError('denominator must be a power of 2') else: check_int(value, 0, 255) class MetaSpec_key_signature(MetaSpec): type_byte = 0x59 attributes = ['key'] defaults = ['C'] def decode(self, message, data): key = signed('byte', data[0]) mode = data[1] try: message.key = _key_signature_decode[(key, mode)] except KeyError: if key < 7: msg = ('Could not decode key with {} ' 'flats and mode {}'.format(abs(key), mode)) else: msg = ('Could not decode key with {} ' 'sharps and mode {}'.format(key, mode)) raise KeySignatureError(msg) def encode(self, message): key, mode = _key_signature_encode[message.key] return [unsigned('byte', key), mode] def check(self, name, value): if value not in _key_signature_encode: raise ValueError('invalid key {!r}'.format(value)) class MetaSpec_sequencer_specific(MetaSpec): type_byte = 0x7f attributes = ['data'] defaults = [[]] def decode(self, message, data): message.data = tuple(data) def encode(self, message): return list(message.data) def add_meta_spec(klass): spec = klass() if not hasattr(spec, 'type'): name = klass.__name__.replace('MetaSpec_', '') spec.type = name # This is used by copy(). spec.settable_attributes = set(spec.attributes) | {'time'} _META_SPECS[spec.type_byte] = spec _META_SPECS[spec.type] = spec _META_SPEC_BY_TYPE[spec.type] = spec _META_SPECS = {} _META_SPEC_BY_TYPE = {} def _add_builtin_meta_specs(): for name, spec in globals().items(): if name.startswith('MetaSpec_'): add_meta_spec(spec) _add_builtin_meta_specs() def build_meta_message(meta_type, data, delta=0): # TODO: handle unknown type. try: spec = _META_SPECS[meta_type] except KeyError: return UnknownMetaMessage(meta_type, data) else: msg = MetaMessage(spec.type, time=delta) # This adds attributes to msg: spec.decode(msg, data) return msg class MetaMessage(BaseMessage): is_meta = True def __init__(self, type, **kwargs): # TODO: handle unknown type? spec = _META_SPEC_BY_TYPE[type] self_vars = vars(self) self_vars['type'] = type for name in kwargs: if name not in spec.settable_attributes: raise ValueError( '{} is not a valid argument for this message type'.format( name)) for name, value in zip(spec.attributes, spec.defaults): self_vars[name] = value self_vars['time'] = 0 for name, value in kwargs.items(): # Using setattr here because we want type and value checks. self._setattr(name, value) def copy(self, **overrides): """Return a copy of the message Attributes will be overridden by the passed keyword arguments. Only message specific attributes can be overridden. The message type can not be changed. """ if not overrides: # Bypass all checks. msg = self.__class__.__new__(self.__class__) vars(msg).update(vars(self)) return msg if 'type' in overrides and overrides['type'] != self.type: raise ValueError('copy must be same message type') attrs = vars(self).copy() attrs.update(overrides) return self.__class__(**attrs) # FrozenMetaMessage overrides __setattr__() but we still need to # set attributes in __init__(). def _setattr(self, name, value): spec = _META_SPEC_BY_TYPE[self.type] self_vars = vars(self) if name in spec.settable_attributes: if name == 'time': check_time(value) else: spec.check(name, value) self_vars[name] = value elif name in self_vars: raise AttributeError('{} attribute is read only'.format(name)) else: raise AttributeError( '{} message has no attribute {}'.format(self.type, name)) __setattr__ = _setattr def bytes(self): spec = _META_SPEC_BY_TYPE[self.type] data = spec.encode(self) return ([0xff, spec.type_byte] + encode_variable_int(len(data)) + data) def _get_value_names(self): """Used by BaseMessage.__repr__().""" spec = _META_SPEC_BY_TYPE[self.type] return spec.attributes + ['time'] class UnknownMetaMessage(MetaMessage): def __init__(self, type_byte, data=None, time=0): if data is None: data = () else: data = tuple(data) vars(self).update({ 'type': 'unknown_meta', 'type_byte': type_byte, 'data': data, 'time': time}) def __repr__(self): fmt = 'UnknownMetaMessage(type_byte={}, data={}, time={})' return fmt.format(self.type_byte, self.data, self.time) def __setattr__(self, name, value): # This doesn't do any checking. # It probably should. vars(self)[name] = value def bytes(self): length = encode_variable_int(len(self.data)) return ([0xff, self.type_byte] + length + list(self.data)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1609443238.0 mido-1.2.10/mido/midifiles/midifiles.py0000664000175000017500000003420400000000000020163 0ustar00olembolemb00000000000000""" MIDI file reading and playback. References: http://home.roadrunner.com/~jgglatt/ http://home.roadrunner.com/~jgglatt/tech/miditech.htm http://home.roadrunner.com/~jgglatt/tech/midifile.htm http://www.sonicspot.com/guide/midifiles.html http://www.ccarh.org/courses/253/assignment/midifile/ https://code.google.com/p/binasc/wiki/mainpage http://stackoverflow.com/questions/2984608/midi-delta-time http://www.recordingblogs.com/sa/tabid/82/EntryId/44/MIDI-Part-XIII-Delta-time-a http://www.sonicspot.com/guide/midifiles.html """ from __future__ import print_function, division import io import time import string import struct from numbers import Integral from ..messages import Message, SPEC_BY_STATUS from .meta import (MetaMessage, build_meta_message, meta_charset, encode_variable_int) from .tracks import MidiTrack, merge_tracks, fix_end_of_track from .units import tick2second # The default tempo is 120 BPM. # (500000 microseconds per beat (quarter note).) DEFAULT_TEMPO = 500000 DEFAULT_TICKS_PER_BEAT = 480 # Maximum message length to attempt to read. MAX_MESSAGE_LENGTH = 1000000 def print_byte(byte, pos=0): char = chr(byte) if char.isspace() or char not in string.printable: char = '.' print(' {:06x}: {:02x} {}'.format(pos, byte, char)) class DebugFileWrapper(object): def __init__(self, file): self.file = file def read(self, size): data = self.file.read(size) for byte in data: # Iterating gives us byte strings instead of ints in # Python 2. if isinstance(byte, str): byte = ord(byte) print_byte(byte, self.file.tell()) return data def tell(self): return self.file.tell() def read_byte(self): byte = self.read(1) if byte == b'': raise EOFError else: return ord(byte) def read_bytes(infile, size): if size > MAX_MESSAGE_LENGTH: raise IOError('Message length {} exceeds maximum length {}'.format( size, MAX_MESSAGE_LENGTH)) return [read_byte(infile) for _ in range(size)] def _dbg(text=''): print(text) # We can't use the chunk module for two reasons: # # 1. we may have mixed big and little endian chunk sizes. (RIFF is # little endian while MTrk is big endian.) # # 2. the chunk module assumes that chunks are padded to the nearest # multiple of 2. This is not true of MIDI files. def read_chunk_header(infile): header = infile.read(8) if len(header) < 8: raise EOFError # TODO: check for b'RIFF' and switch endian? return struct.unpack('>4sL', header) def read_file_header(infile): name, size = read_chunk_header(infile) if name != b'MThd': raise IOError('MThd not found. Probably not a MIDI file') else: data = infile.read(size) if len(data) < 6: raise EOFError return struct.unpack('>hhh', data[:6]) def read_message(infile, status_byte, peek_data, delta, clip=False): try: spec = SPEC_BY_STATUS[status_byte] except LookupError: raise IOError('undefined status byte 0x{:02x}'.format(status_byte)) # Subtract 1 for status byte. size = spec['length'] - 1 - len(peek_data) data_bytes = peek_data + read_bytes(infile, size) if clip: data_bytes = [byte if byte < 127 else 127 for byte in data_bytes] else: for byte in data_bytes: if byte > 127: raise IOError('data byte must be in range 0..127') return Message.from_bytes([status_byte] + data_bytes, time=delta) def read_sysex(infile, delta, clip=False): length = read_variable_int(infile) data = read_bytes(infile, length) # Strip start and end bytes. # TODO: is this necessary? if data and data[0] == 0xf0: data = data[1:] if data and data[-1] == 0xf7: data = data[:-1] if clip: data = [byte if byte < 127 else 127 for byte in data] return Message('sysex', data=data, time=delta) def read_variable_int(infile): delta = 0 while True: byte = read_byte(infile) delta = (delta << 7) | (byte & 0x7f) if byte < 0x80: return delta def read_meta_message(infile, delta): meta_type = read_byte(infile) length = read_variable_int(infile) data = read_bytes(infile, length) return build_meta_message(meta_type, data, delta) def read_track(infile, debug=False, clip=False): track = MidiTrack() name, size = read_chunk_header(infile) if name != b'MTrk': raise IOError('no MTrk header at start of track') if debug: _dbg('-> size={}'.format(size)) _dbg() start = infile.tell() last_status = None while True: # End of track reached. if infile.tell() - start == size: break if debug: _dbg('Message:') delta = read_variable_int(infile) if debug: _dbg('-> delta={}'.format(delta)) status_byte = read_byte(infile) if status_byte < 0x80: if last_status is None: raise IOError('running status without last_status') peek_data = [status_byte] status_byte = last_status else: if status_byte != 0xff: # Meta messages don't set running status. last_status = status_byte peek_data = [] if status_byte == 0xff: msg = read_meta_message(infile, delta) elif status_byte in [0xf0, 0xf7]: # TODO: I'm not quite clear on the difference between # f0 and f7 events. msg = read_sysex(infile, delta, clip) else: msg = read_message(infile, status_byte, peek_data, delta, clip) track.append(msg) if debug: _dbg('-> {!r}'.format(msg)) _dbg() return track def write_chunk(outfile, name, data): """Write an IFF chunk to the file. `name` must be a bytestring.""" outfile.write(name) outfile.write(struct.pack('>L', len(data))) outfile.write(data) def write_track(outfile, track): data = bytearray() running_status_byte = None for msg in fix_end_of_track(track): if not isinstance(msg.time, Integral): raise ValueError('message time must be int in MIDI file') if msg.time < 0: raise ValueError('message time must be non-negative in MIDI file') if msg.is_realtime: raise ValueError('realtime messages are not allowed in MIDI files') data.extend(encode_variable_int(msg.time)) if msg.is_meta: data.extend(msg.bytes()) running_status_byte = None elif msg.type == 'sysex': data.append(0xf0) # length (+ 1 for end byte (0xf7)) data.extend(encode_variable_int(len(msg.data) + 1)) data.extend(msg.data) data.append(0xf7) running_status_byte = None else: msg_bytes = msg.bytes() status_byte = msg_bytes[0] if status_byte == running_status_byte: data.extend(msg_bytes[1:]) else: data.extend(msg_bytes) if status_byte < 0xf0: running_status_byte = status_byte else: running_status_byte = None write_chunk(outfile, b'MTrk', data) def get_seconds_per_tick(tempo, ticks_per_beat): # Tempo is given in microseconds per beat (default 500000). # At this tempo there are (500000 / 1000000) == 0.5 seconds # per beat. At the default resolution of 480 ticks per beat # this is: # # (500000 / 1000000) / 480 == 0.5 / 480 == 0.0010417 # return (tempo / 1000000.0) / ticks_per_beat class MidiFile(object): def __init__(self, filename=None, file=None, type=1, ticks_per_beat=DEFAULT_TICKS_PER_BEAT, charset='latin1', debug=False, clip=False, tracks=None ): self.filename = filename self.type = type self.ticks_per_beat = ticks_per_beat self.charset = charset self.debug = debug self.clip = clip self.tracks = [] if type not in range(3): raise ValueError( 'invalid format {} (must be 0, 1 or 2)'.format(format)) if tracks is not None: self.tracks = tracks elif file is not None: self._load(file) elif self.filename is not None: with io.open(filename, 'rb') as file: self._load(file) def add_track(self, name=None): """Add a new track to the file. This will create a new MidiTrack object and append it to the track list. """ track = MidiTrack() if name is not None: track.name = name self.tracks.append(track) return track def _load(self, infile): if self.debug: infile = DebugFileWrapper(infile) with meta_charset(self.charset): if self.debug: _dbg('Header:') (self.type, num_tracks, self.ticks_per_beat) = read_file_header(infile) if self.debug: _dbg('-> type={}, tracks={}, ticks_per_beat={}'.format( self.type, num_tracks, self.ticks_per_beat)) _dbg() for i in range(num_tracks): if self.debug: _dbg('Track {}:'.format(i)) self.tracks.append(read_track(infile, debug=self.debug, clip=self.clip)) # TODO: used to ignore EOFError. I hope things still work. @property def length(self): """Playback time in seconds. This will be computed by going through every message in every track and adding up delta times. """ if self.type == 2: raise ValueError('impossible to compute length' ' for type 2 (asynchronous) file') return sum(msg.time for msg in self) def __iter__(self): # The tracks of type 2 files are not in sync, so they can # not be played back like this. if self.type == 2: raise TypeError("can't merge tracks in type 2 (asynchronous) file") tempo = DEFAULT_TEMPO for msg in merge_tracks(self.tracks): # Convert message time from absolute time # in ticks to relative time in seconds. if msg.time > 0: delta = tick2second(msg.time, self.ticks_per_beat, tempo) else: delta = 0 yield msg.copy(time=delta) if msg.type == 'set_tempo': tempo = msg.tempo def play(self, meta_messages=False): """Play back all tracks. The generator will sleep between each message by default. Messages are yielded with correct timing. The time attribute is set to the number of seconds slept since the previous message. By default you will only get normal MIDI messages. Pass meta_messages=True if you also want meta messages. You will receive copies of the original messages, so you can safely modify them without ruining the tracks. """ start_time = time.time() input_time = 0.0 for msg in self: input_time += msg.time playback_time = time.time() - start_time duration_to_next_event = input_time - playback_time if duration_to_next_event > 0.0: time.sleep(duration_to_next_event) if isinstance(msg, MetaMessage) and not meta_messages: continue else: yield msg def save(self, filename=None, file=None): """Save to a file. If file is passed the data will be saved to that file. This is typically an in-memory file or and already open file like sys.stdout. If filename is passed the data will be saved to that file. Raises ValueError if both file and filename are None, or if a type 0 file has != one track. """ if self.type == 0 and len(self.tracks) != 1: raise ValueError('type 0 file must have exactly 1 track') if file is not None: self._save(file) elif filename is not None: with io.open(filename, 'wb') as file: self._save(file) else: raise ValueError('requires filename or file') def _save(self, outfile): with meta_charset(self.charset): header = struct.pack('>hhh', self.type, len(self.tracks), self.ticks_per_beat) write_chunk(outfile, b'MThd', header) for track in self.tracks: write_track(outfile, track) def print_tracks(self, meta_only=False): """Prints out all messages in a .midi file. May take argument meta_only to show only meta messages. Use: print_tracks() -> will print all messages print_tracks(meta_only=True) -> will print only MetaMessages """ for i, track in enumerate(self.tracks): print('=== Track {}'.format(i)) for msg in track: if not isinstance(msg, MetaMessage) and meta_only: pass else: print('{!r}'.format(msg)) def __repr__(self): if self.tracks: tracks_str = ',\n'.join(repr(track) for track in self.tracks) tracks_str = ' ' + tracks_str.replace('\n', '\n ') tracks_str = ', tracks=[\n{}\n]'.format(tracks_str) else: tracks_str = '' return '{}(type={}, ticks_per_beat={}{})'.format( self.__class__.__name__, self.type, self.ticks_per_beat, tracks_str, ) # The context manager has no purpose but is kept around since it was # used in examples in the past. def __enter__(self): return self def __exit__(self, type, value, traceback): return False ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1609443176.0 mido-1.2.10/mido/midifiles/tracks.py0000664000175000017500000000646400000000000017514 0ustar00olembolemb00000000000000from .meta import MetaMessage class MidiTrack(list): @property def name(self): """Name of the track. This will return the name from the first track_name meta message in the track, or '' if there is no such message. Setting this property will update the name field of the first track_name message in the track. If no such message is found, one will be added to the beginning of the track with a delta time of 0.""" for message in self: if message.type == 'track_name': return message.name else: return '' @name.setter def name(self, name): # Find the first track_name message and modify it. for message in self: if message.type == 'track_name': message.name = name return else: # No track name found, add one. self.insert(0, MetaMessage('track_name', name=name, time=0)) def copy(self): return self.__class__(self) def __getitem__(self, index_or_slice): # Retrieve item from the MidiTrack lst = list.__getitem__(self, index_or_slice) if isinstance(index_or_slice, int): # If an index was provided, return the list element return lst else: # Otherwise, construct a MidiTrack to return. # TODO: this make a copy of the list. Is there a better way? return self.__class__(lst) def __add__(self, other): return self.__class__(list.__add__(self, other)) def __mul__(self, other): return self.__class__(list.__mul__(self, other)) def __repr__(self): if len(self) == 0: messages = '' elif len(self) == 1: messages = '[{}]'.format(self[0]) else: messages = '[\n {}]'.format(',\n '.join(repr(m) for m in self)) return '{}({})'.format(self.__class__.__name__, messages) def _to_abstime(messages): """Convert messages to absolute time.""" now = 0 for msg in messages: now += msg.time yield msg.copy(time=now) def _to_reltime(messages): """Convert messages to relative time.""" now = 0 for msg in messages: delta = msg.time - now yield msg.copy(time=delta) now = msg.time def fix_end_of_track(messages): """Remove all end_of_track messages and add one at the end. This is used by merge_tracks() and MidiFile.save().""" # Accumulated delta time from removed end of track messages. # This is added to the next message. accum = 0 for msg in messages: if msg.type == 'end_of_track': accum += msg.time else: if accum: delta = accum + msg.time yield msg.copy(time=delta) accum = 0 else: yield msg yield MetaMessage('end_of_track', time=accum) def merge_tracks(tracks): """Returns a MidiTrack object with all messages from all tracks. The messages are returned in playback order with delta times as if they were all in one track. """ messages = [] for track in tracks: messages.extend(_to_abstime(track)) messages.sort(key=lambda msg: msg.time) return MidiTrack(fix_end_of_track(_to_reltime(messages))) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1563454970.0 mido-1.2.10/mido/midifiles/units.py0000644000175000017500000000235000000000000017353 0ustar00olembolemb00000000000000def tick2second(tick, ticks_per_beat, tempo): """Convert absolute time in ticks to seconds. Returns absolute time in seconds for a chosen MIDI file time resolution (ticks per beat, also called PPQN or pulses per quarter note) and tempo (microseconds per beat). """ scale = tempo * 1e-6 / ticks_per_beat return tick * scale def second2tick(second, ticks_per_beat, tempo): """Convert absolute time in seconds to ticks. Returns absolute time in ticks for a chosen MIDI file time resolution (ticks per beat, also called PPQN or pulses per quarter note) and tempo (microseconds per beat). """ scale = tempo * 1e-6 / ticks_per_beat return second / scale def bpm2tempo(bpm): """Convert beats per minute to MIDI file tempo. Returns microseconds per beat as an integer:: 240 => 250000 120 => 500000 60 => 1000000 """ # One minute is 60 million microseconds. return int(round((60 * 1000000) / bpm)) def tempo2bpm(tempo): """Convert MIDI file tempo to BPM. Returns BPM as an integer or float:: 250000 => 240 500000 => 120 1000000 => 60 """ # One minute is 60 million microseconds. return (60 * 1000000) / tempo ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1609342087.0 mido-1.2.10/mido/parser.py0000664000175000017500000000527700000000000015555 0ustar00olembolemb00000000000000""" MIDI Parser There is no need to use this module directly. All you need is available in the top level module. """ from collections import deque from .messages import Message from .tokenizer import Tokenizer class Parser(object): """ MIDI byte stream parser Parses a stream of MIDI bytes and produces messages. Data can be put into the parser in the form of integers, byte arrays or byte strings. """ def __init__(self, data=None): # For historical reasons self.messages is public and must be a # deque(). (It is referenced directly inside ports.) self.messages = deque() self._tok = Tokenizer() if data: self.feed(data) def _decode(self): for midi_bytes in self._tok: self.messages.append(Message.from_bytes(midi_bytes)) def feed(self, data): """Feed MIDI data to the parser. Accepts any object that produces a sequence of integers in range 0..255, such as: [0, 1, 2] (0, 1, 2) [for i in range(256)] (for i in range(256)] bytearray() b'' # Will be converted to integers in Python 2. """ self._tok.feed(data) self._decode() def feed_byte(self, byte): """Feed one MIDI byte into the parser. The byte must be an integer in range 0..255. """ self._tok.feed_byte(byte) self._decode() def get_message(self): """Get the first parsed message. Returns None if there is no message yet. If you don't want to deal with None, you can use pending() to see how many messages you can get before you get None, or just iterate over the parser. """ for msg in self: return msg else: return None def pending(self): """Return the number of pending messages.""" return len(self.messages) __len__ = pending def __iter__(self): """Yield messages that have been parsed so far.""" while len(self.messages) > 0: yield self.messages.popleft() def parse_all(data): """Parse MIDI data and return a list of all messages found. This is typically used to parse a little bit of data with a few messages in it. It's best to use a Parser object for larger amounts of data. Also, tt's often easier to use parse() if you know there is only one message in the data. """ return list(Parser(data)) def parse(data): """ Parse MIDI data and return the first message found. Data after the first message is ignored. Use parse_all() to parse more than one message. """ return Parser(data).get_message() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608496479.0 mido-1.2.10/mido/ports.py0000664000175000017500000002703500000000000015424 0ustar00olembolemb00000000000000""" Useful tools for working with ports """ from __future__ import unicode_literals import threading import time import random from .parser import Parser from .messages import Message # How many seconds to sleep before polling again. _DEFAULT_SLEEP_TIME = 0.001 _sleep_time = _DEFAULT_SLEEP_TIME # TODO: document this more. def sleep(): """Sleep for N seconds. This is used in ports when polling and waiting for messages. N can be set with set_sleep_time().""" time.sleep(_sleep_time) def set_sleep_time(seconds=_DEFAULT_SLEEP_TIME): """Set the number of seconds sleep() will sleep.""" global _sleep_time _sleep_time = seconds def get_sleep_time(): """Get number of seconds sleep() will sleep.""" return _sleep_time def reset_messages(): """Yield "All Notes Off" and "Reset All Controllers" for all channels""" ALL_NOTES_OFF = 123 RESET_ALL_CONTROLLERS = 121 for channel in range(16): for control in [ALL_NOTES_OFF, RESET_ALL_CONTROLLERS]: yield Message('control_change', channel=channel, control=control) def panic_messages(): """Yield "All Sounds Off" for all channels. This will mute all sounding notes regardless of envelopes. Useful when notes are hanging and nothing else helps. """ ALL_SOUNDS_OFF = 120 for channel in range(16): yield Message('control_change', channel=channel, control=ALL_SOUNDS_OFF) class DummyLock(object): def __enter__(self): return self def __exit__(self, *_): return False class BasePort(object): """ Abstract base class for Input and Output ports. """ is_input = False is_output = False _locking = True def __init__(self, name=None, **kwargs): if hasattr(self, 'closed'): # __init__() called twice (from BaseInput and BaseOutput). # This stops _open() from being called twice. return self.name = name if self._locking: self._lock = threading.RLock() else: self._lock = DummyLock() self.closed = True self._open(**kwargs) self.closed = False def _open(self, **kwargs): pass def _close(self): pass def close(self): """Close the port. If the port is already closed, nothing will happen. The port is automatically closed when the object goes out of scope or is garbage collected. """ with self._lock: if not self.closed: if hasattr(self, 'autoreset') and self.autoreset: try: self.reset() except IOError: pass self._close() self.closed = True def __del__(self): self.close() def __enter__(self): return self def __exit__(self, type, value, traceback): self.close() return False def __repr__(self): if self.closed: state = 'closed' else: state = 'open' capabilities = self.is_input, self.is_output port_type = {(True, False): 'input', (False, True): 'output', (True, True): 'I/O port', (False, False): 'mute port', }[capabilities] name = self.name or '' try: device_type = self._device_type except AttributeError: device_type = self.__class__.__name__ return '<{} {} {!r} ({})>'.format( state, port_type, name, device_type) class BaseInput(BasePort): """Base class for input port. Subclass and override _receive() to create a new input port type. (See portmidi.py for an example of how to do this.) """ is_input = True def __init__(self, name='', **kwargs): """Create an input port. name is the port name, as returned by input_names(). If name is not passed, the default input is used instead. """ BasePort.__init__(self, name, **kwargs) self._parser = Parser() self._messages = self._parser.messages # Shortcut. def _check_callback(self): if hasattr(self, 'callback') and self.callback is not None: raise ValueError('a callback is set for this port') def _receive(self, block=True): pass def iter_pending(self): """Iterate through pending messages.""" while True: msg = self.poll() if msg is None: return else: yield msg def receive(self, block=True): """Return the next message. This will block until a message arrives. If you pass block=False it will not block and instead return None if there is no available message. If the port is closed and there are no pending messages IOError will be raised. If the port closes while waiting inside receive(), IOError will be raised. TODO: this seems a bit inconsistent. Should different errors be raised? What's most useful here? """ if not self.is_input: raise ValueError('Not an input port') self._check_callback() # If there is a message pending, return it right away. with self._lock: if self._messages: return self._messages.popleft() if self.closed: if block: raise ValueError('receive() called on closed port') else: return None while True: with self._lock: msg = self._receive(block=block) if msg: return msg if self._messages: return self._messages.popleft() elif not block: return None elif self.closed: raise IOError('port closed during receive()') sleep() def poll(self): """Receive the next pending message or None This is the same as calling `receive(block=False)`.""" return self.receive(block=False) def __iter__(self): """Iterate through messages until the port closes.""" # This could have simply called receive() in a loop, but that # could result in a "port closed during receive()" error which # is hard to catch here. self._check_callback() while True: try: yield self.receive() except IOError: if self.closed: # The port closed before or inside receive(). # (This makes the assumption that this is the reason, # at the risk of masking other errors.) return else: raise class BaseOutput(BasePort): """ Base class for output port. Subclass and override _send() to create a new port type. (See portmidi.py for how to do this.) """ is_output = True def __init__(self, name='', autoreset=False, **kwargs): """Create an output port name is the port name, as returned by output_names(). If name is not passed, the default output is used instead. """ BasePort.__init__(self, name, **kwargs) self.autoreset = autoreset def _send(self, msg): pass def send(self, msg): """Send a message on the port. A copy of the message will be sent, so you can safely modify the original message without any unexpected consequences. """ if not self.is_output: raise ValueError('Not an output port') elif not isinstance(msg, Message): raise TypeError('argument to send() must be a Message') elif self.closed: raise ValueError('send() called on closed port') with self._lock: self._send(msg.copy()) def reset(self): """Send "All Notes Off" and "Reset All Controllers" on all channels""" if self.closed: return for msg in reset_messages(): self.send(msg) def panic(self): """Send "All Sounds Off" on all channels. This will mute all sounding notes regardless of envelopes. Useful when notes are hanging and nothing else helps. """ if self.closed: return for msg in panic_messages(): self.send(msg) class BaseIOPort(BaseInput, BaseOutput): def __init__(self, name='', **kwargs): """Create an IO port. name is the port name, as returned by ioport_names(). """ BaseInput.__init__(self, name, **kwargs) BaseOutput.__init__(self, name, **kwargs) class IOPort(BaseIOPort): """Input / output port. This is a convenient wrapper around an input port and an output port which provides the functionality of both. Every method call is forwarded to the appropriate port. """ _locking = False def __init__(self, input, output): self.input = input self.output = output # We use str() here in case name is None. self.name = '{} + {}'.format(str(input.name), str(output.name)) self._messages = self.input._messages self.closed = False self._lock = DummyLock() def _close(self): self.input.close() self.output.close() def _send(self, message): self.output.send(message) def _receive(self, block=True): return self.input.receive(block=block) class EchoPort(BaseIOPort): def _send(self, message): self._messages.append(message) __iter__ = BaseIOPort.iter_pending class MultiPort(BaseIOPort): def __init__(self, ports, yield_ports=False): BaseIOPort.__init__(self, 'multi') self.ports = list(ports) self.yield_ports = yield_ports def _send(self, message): for port in self.ports: if not port.closed: # TODO: what if a SocketPort connection closes in-between here? port.send(message) def _receive(self, block=True): self._messages.extend(multi_receive(self.ports, yield_ports=self.yield_ports, block=block)) def multi_receive(ports, yield_ports=False, block=True): """Receive messages from multiple ports. Generates messages from ever input port. The ports are polled in random order for fairness, and all messages from each port are yielded before moving on to the next port. If yield_ports=True, (port, message) is yielded instead of just the message. If block=False only pending messages will be yielded. """ ports = list(ports) while True: # Make a shuffled copy of the port list. random.shuffle(ports) for port in ports: if not port.closed: for message in port.iter_pending(): if yield_ports: yield port, message else: yield message if block: sleep() else: break def multi_iter_pending(ports, yield_ports=False): """Iterate through all pending messages in ports. This is the same as calling multi_receive(ports, block=False). The function is kept around for backwards compatability. """ return multi_receive(ports, yield_ports=yield_ports, block=False) def multi_send(ports, msg): """Send message on all ports.""" for port in ports: port.send(msg) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/py2.py0000664000175000017500000000125400000000000014762 0ustar00olembolemb00000000000000import sys PY2 = (sys.version_info.major == 2) def convert_py2_bytes(data): """Convert bytes object to bytearray in Python 2. Many parts of Mido such as ``Parser.feed()`` and ``Message.from_bytes()`` accept an iterable of integers. In Python 3 you can pass a byte string:: >>> list(b'\x01\x02\x03') [1, 2, 3] while in Python 2 this happens:: >>> list(b'\x01\x02\x03') ['\x01', '\x02', '\x03'] This function patches over the difference:: >>> list(convert_py2_bytes(b'\x01\x02\x03')) [1, 2, 3] """ if PY2 and isinstance(data, bytes): return bytearray(data) else: return data ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/sockets.py0000664000175000017500000001062000000000000015720 0ustar00olembolemb00000000000000""" MIDI over TCP/IP. """ import socket import select from .parser import Parser from .ports import MultiPort, BaseIOPort from .py2 import PY2 def _is_readable(socket): """Return True if there is data to be read on the socket.""" timeout = 0 (rlist, wlist, elist) = select.select( [socket.fileno()], [], [], timeout) return bool(rlist) class PortServer(MultiPort): # TODO: queue size. def __init__(self, host, portno, backlog=1): MultiPort.__init__(self, format_address(host, portno)) self.ports = [] self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) self._socket.setblocking(True) self._socket.bind((host, portno)) self._socket.listen(backlog) def _get_device_type(self): return 'server' def _close(self): # Close all connections. for port in self.ports: port.close() self._socket.close() def _update_ports(self): """Remove closed port ports.""" self.ports = [port for port in self.ports if not port.closed] def accept(self, block=True): """ Accept a connection from a client. Will block until there is a new connection, and then return a SocketPort object. If block=False, None will be returned if there is no new connection waiting. """ if not block and not _is_readable(self._socket): return None self._update_ports() conn, (host, port) = self._socket.accept() return SocketPort(host, port, conn=conn) def _send(self, message): self._update_ports() return MultiPort._send(self, message) def _receive(self, block=True): port = self.accept(block=False) if port: self.ports.append(port) self._update_ports() return MultiPort._receive(self) class SocketPort(BaseIOPort): def __init__(self, host, portno, conn=None): BaseIOPort.__init__(self, name=format_address(host, portno)) self.closed = False self._parser = Parser() self._messages = self._parser.messages if conn is None: self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._socket.setblocking(True) self._socket.connect((host, portno)) else: self._socket = conn if PY2: kwargs = {'bufsize': 0} else: kwargs = {'buffering': None} self._rfile = self._socket.makefile('rb', **kwargs) self._wfile = self._socket.makefile('wb', **kwargs) def _get_device_type(self): return 'socket' def _receive(self, block=True): while _is_readable(self._socket): try: byte = self._rfile.read(1) except socket.error as err: raise IOError(err.args[1]) if byte == '': # The other end has disconnected. self.close() break else: self._parser.feed_byte(ord(byte)) def _send(self, message): try: self._wfile.write(message.bin()) self._wfile.flush() except socket.error as err: if err.errno == 32: # Broken pipe. The other end has disconnected. self.close() raise IOError(err.args[1]) def _close(self): self._socket.close() def connect(host, portno): """Connect to a socket port server. The return value is a SocketPort object connected to another SocketPort object at the server end. Messages can be sent either way. """ return SocketPort(host, portno) def parse_address(address): """Parse and address on the format host:port. Returns a tuple (host, port). Raises ValueError if format is invalid or port is not an integer or out of range. """ words = address.split(':') if len(words) != 2: raise ValueError('address must contain exactly one colon') host, port = words try: port = int(port) except ValueError: raise ValueError('port number must be an integer') # Note: port 0 is not allowed. if not 0 < port < (2**16): raise ValueError('port number out of range') return (host, port) def format_address(host, portno): return '{}{:d}'.format(host, portno) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/syx.py0000664000175000017500000000313500000000000015073 0ustar00olembolemb00000000000000""" Read and write SYX file format """ from __future__ import print_function import re from .parser import Parser def read_syx_file(filename): """Read sysex messages from SYX file. Returns a list of sysex messages. This handles both the text (hexadecimal) and binary formats. Messages other than sysex will be ignored. Raises ValueError if file is plain text and byte is not a 2-digit hex number. """ with open(filename, 'rb') as infile: data = infile.read() if len(data) == 0: # Empty file. return [] parser = Parser() # data[0] will give a byte string in Python 2 and an integer in # Python 3. if data[0] in (b'\xf0', 240): # Binary format. parser.feed(data) else: text = data.decode('latin1') data = bytearray.fromhex(re.sub(r'\s', ' ', text)) parser.feed(data) return [msg for msg in parser if msg.type == 'sysex'] def write_syx_file(filename, messages, plaintext=False): """Write sysex messages to a SYX file. Messages other than sysex will be skipped. By default this will write the binary format. Pass ``plaintext=True`` to write the plain text format (hex encoded ASCII text). """ messages = [m for m in messages if m.type == 'sysex'] if plaintext: with open(filename, 'wt') as outfile: for message in messages: outfile.write(message.hex()) outfile.write('\n') else: with open(filename, 'wb') as outfile: for message in messages: outfile.write(message.bin()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/tokenizer.py0000664000175000017500000000543700000000000016271 0ustar00olembolemb00000000000000from collections import deque from numbers import Integral from .messages.specs import SYSEX_START, SYSEX_END, SPEC_BY_STATUS from .py2 import convert_py2_bytes class Tokenizer(object): """ Splits a MIDI byte stream into messages. """ def __init__(self, data=None): """Create a new decoder.""" self._status = 0 self._bytes = [] self._messages = deque() self._datalen = 0 if data is not None: self.feed(data) def _feed_status_byte(self, status): if status == SYSEX_END: if self._status == SYSEX_START: self._bytes.append(SYSEX_END) self._messages.append(self._bytes) self._status = 0 elif 0xf8 <= status <= 0xff: if self._status != SYSEX_START: # Realtime messages are only allowed inside sysex # messages. Reset parser. self._status = 0 if status in SPEC_BY_STATUS: self._messages.append([status]) elif status in SPEC_BY_STATUS: # New message. spec = SPEC_BY_STATUS[status] if spec['length'] == 1: self._messages.append([status]) self._status = 0 else: self._status = status self._bytes = [status] self._len = spec['length'] else: # Undefined message. Reset parser. # (Undefined realtime messages are handled above.) # self._status = 0 pass def _feed_data_byte(self, byte): if self._status: self._bytes.append(byte) if len(self._bytes) == self._len: # Complete message. self._messages.append(self._bytes) self._status = 0 else: # Ignore stray data byte. pass def feed_byte(self, byte): """Feed MIDI byte to the decoder. Takes an int in range [0..255]. """ if not isinstance(byte, Integral): raise TypeError('message byte must be integer') if 0 <= byte <= 255: if byte <= 127: return self._feed_data_byte(byte) else: return self._feed_status_byte(byte) else: raise ValueError('invalid byte value {!r}'.format(byte)) def feed(self, data): """Feed MIDI bytes to the decoder. Takes an iterable of ints in in range [0..255]. """ for byte in convert_py2_bytes(data): self.feed_byte(byte) def __len__(self): return len(self._messages) def __iter__(self): """Yield messages that have been parsed so far.""" while len(self._messages): yield self._messages.popleft() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/mido/version.py0000664000175000017500000000076700000000000015745 0ustar00olembolemb00000000000000from collections import namedtuple from .__about__ import __version__ VersionInfo = namedtuple('VersionInfo', ['major', 'minor', 'micro', 'releaselevel', 'serial']) def _make_version_info(version): if '-' in version: version, releaselevel = version.split('-') else: releaselevel = '' major, minor, micro = map(int, version.split('.')) return VersionInfo(major, minor, micro, releaselevel, 0) version_info = _make_version_info(__version__) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2553298 mido-1.2.10/mido.egg-info/0000775000175000017500000000000000000000000015366 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1620661443.0 mido-1.2.10/mido.egg-info/PKG-INFO0000664000175000017500000001003100000000000016456 0ustar00olembolemb00000000000000Metadata-Version: 2.1 Name: mido Version: 1.2.10 Summary: MIDI Objects for Python Home-page: https://mido.readthedocs.io/ Author: Ole Martin Bjorndalen Author-email: ombdalen@gmail.com License: MIT Description: Mido - MIDI Objects for Python ============================== .. image:: https://travis-ci.org/mido/mido.svg?branch=master :target: https://travis-ci.org/mido/mido .. image:: https://github.com/mido/mido/workflows/Test/badge.svg :target: https://github.com/mido/mido/actions Mido is a library for working with MIDI messages and ports: .. code-block:: python >>> import mido >>> msg = mido.Message('note_on', note=60) >>> msg.type 'note_on' >>> msg.note 60 >>> msg.bytes() [144, 60, 64] >>> msg.copy(channel=2) Message('note_on', channel=2, note=60, velocity=64, time=0) .. code-block:: python port = mido.open_output('Port Name') port.send(msg) .. code-block:: python with mido.open_input() as inport: for msg in inport: print(msg) .. code-block:: python mid = mido.MidiFile('song.mid') for msg in mid.play(): port.send(msg) Full documentation at https://mido.readthedocs.io/ Main Features ------------- * works in Python 2 and 3. * convenient message objects. * supports RtMidi, PortMidi and Pygame. New backends are easy to write. * full support for all 18 messages defined by the MIDI standard. * standard port API allows all kinds of input and output ports to be used interchangeably. New port types can be written by subclassing and overriding a few methods. * includes a reusable MIDI stream parser. * full support for MIDI files (read, write, create and play) with complete access to every message in the file, including all common meta messages. * can read and write SYX files (binary and plain text). * implements (somewhat experimental) MIDI over TCP/IP with socket ports. This allows for example wireless MIDI between two computers. * includes programs for playing MIDI files, listing ports and serving and forwarding ports over a network. Status ------ 1.2 is the third stable release. Requirements ------------ Mido targets Python 3.6 and 2.7. Installing ---------- :: pip install mido If you want to use ports:: pip install python-rtmidi See ``docs/backends/`` for other backends. Source Code ----------- https://github.com/mido/mido/ License ------- Mido is released under the terms of the `MIT license `_. Questions and suggestions ------------------------- For questions and proposals which may not fit into issues or pull requests, we recommend to ask and discuss on `Discussions `_. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.6 Provides-Extra: dev Provides-Extra: ports ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1620661443.0 mido-1.2.10/mido.egg-info/SOURCES.txt0000664000175000017500000000562200000000000017257 0ustar00olembolemb00000000000000.flake8 LICENSE MANIFEST.in README.rst setup.cfg setup.py tox.ini bin/mido-connect bin/mido-play bin/mido-ports bin/mido-serve docs/Makefile docs/about_midi.rst docs/acknowledgements.rst docs/authors.rst docs/bin.rst docs/changes.rst docs/conf.py docs/contributing.rst docs/freezing.rst docs/frozen_messages.rst docs/implementing_backends.rst docs/implementing_ports.rst docs/index.rst docs/installing.rst docs/intro.rst docs/lib.rst docs/license.rst docs/make.bat docs/message_types.rst docs/messages.rst docs/meta_message_types.rst docs/midi_files.rst docs/parsing.rst docs/ports.rst docs/resources.rst docs/roadmap.rst docs/socket_ports.rst docs/string_encoding.rst docs/syx.rst docs/_static/PLACEHOLDER docs/backends/amidi.rst docs/backends/index.rst docs/backends/portmidi.rst docs/backends/pygame.rst docs/backends/rtmidi.rst docs/backends/rtmidi_python.rst docs/images/midi_time.svg examples/using_rtmidi_directly.py examples/backends/printer.py examples/backends/rtm.py examples/backends/use_printer.py examples/midifiles/create_midi_file.py examples/midifiles/midifile_to_json.py examples/midifiles/play_midi_file.py examples/midifiles/print_midi_file.py examples/midifiles/show_midifile.py examples/midifiles/test.sh examples/ports/input_filter.py examples/ports/list_ports.py examples/ports/multi_receive.py examples/ports/nonblocking_receive.py examples/ports/queue_port.py examples/ports/receive.py examples/ports/send.py examples/sockets/forward_ports.py examples/sockets/serve_ports.py examples/sockets/simple_client.py examples/sockets/simple_server.py extras/README.rst extras/hid_joystick.py mido/__about__.py mido/__init__.py mido/frozen.py mido/parser.py mido/ports.py mido/py2.py mido/sockets.py mido/syx.py mido/tokenizer.py mido/version.py mido.egg-info/PKG-INFO mido.egg-info/SOURCES.txt mido.egg-info/dependency_links.txt mido.egg-info/not-zip-safe mido.egg-info/requires.txt mido.egg-info/top_level.txt mido/backends/__init__.py mido/backends/_parser_queue.py mido/backends/amidi.py mido/backends/backend.py mido/backends/portmidi.py mido/backends/portmidi_init.py mido/backends/pygame.py mido/backends/rtmidi.py mido/backends/rtmidi_python.py mido/backends/rtmidi_utils.py mido/messages/__init__.py mido/messages/checks.py mido/messages/decode.py mido/messages/encode.py mido/messages/messages.py mido/messages/specs.py mido/messages/strings.py mido/midifiles/__init__.py mido/midifiles/meta.py mido/midifiles/midifiles.py mido/midifiles/tracks.py mido/midifiles/units.py tests/test_frozen.py tests/test_parser.py tests/test_ports.py tests/test_sockets.py tests/test_syx.py tests/test_tokenizer.py tests/backends/test_backend.py tests/backends/test_rtmidi.py tests/messages/test_checks.py tests/messages/test_decode.py tests/messages/test_encode.py tests/messages/test_messages.py tests/messages/test_strings.py tests/midifiles/test_meta.py tests/midifiles/test_midifiles.py tests/midifiles/test_tracks.py tests/midifiles/test_units.py././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1620661443.0 mido-1.2.10/mido.egg-info/dependency_links.txt0000664000175000017500000000000100000000000021434 0ustar00olembolemb00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1620661439.0 mido-1.2.10/mido.egg-info/not-zip-safe0000664000175000017500000000000100000000000017614 0ustar00olembolemb00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1620661443.0 mido-1.2.10/mido.egg-info/requires.txt0000664000175000017500000000015700000000000017771 0ustar00olembolemb00000000000000 [dev] check-manifest>=0.35 flake8>=3.4.1 pytest>=3.2.2 sphinx>=1.6.3 tox>=2.8.2 [ports] python-rtmidi>=1.1.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1620661443.0 mido-1.2.10/mido.egg-info/top_level.txt0000664000175000017500000000000500000000000020113 0ustar00olembolemb00000000000000mido ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2633297 mido-1.2.10/setup.cfg0000644000175000017500000000020000000000000014553 0ustar00olembolemb00000000000000[wheel] universal = 1 [easy_install] [tool:pytest] norecursedirs = build dist examples [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608567398.0 mido-1.2.10/setup.py0000775000175000017500000000340700000000000014465 0ustar00olembolemb00000000000000#!/usr/bin/env python import os import sys here = os.path.abspath(os.path.dirname(__file__)) def get_about(): about = {} path = os.path.join(here, 'mido', '__about__.py') with open(path, 'rt') as aboutfile: exec(aboutfile.read(), about) return about about = get_about() try: from setuptools import setup except ImportError: from distutils.core import setup if sys.argv[-1] == "publish": os.system("python setup.py sdist upload") sys.exit() elif sys.argv[-1] == "docs": os.system("sphinx-build docs docs/_build") sys.exit() setup( name='mido', version=about['__version__'], description='MIDI Objects for Python', long_description=open('README.rst', 'rt').read(), author=about['__author__'], author_email=about['__author_email__'], url=about['__url__'], license=about['__license__'], package_data={'': ['LICENSE']}, package_dir={'mido': 'mido'}, packages=['mido', 'mido.backends'], scripts=['bin/mido-play', 'bin/mido-ports', 'bin/mido-serve', 'bin/mido-connect'], include_package_data=True, install_requires=[], extras_require={ 'dev': ['check-manifest>=0.35', 'flake8>=3.4.1', 'pytest>=3.2.2', 'sphinx>=1.6.3', 'tox>=2.8.2' ], 'ports': ['python-rtmidi>=1.1.0'] }, zip_safe=False, classifiers=( 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Natural Language :: English', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.6', ), ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2593298 mido-1.2.10/tests/0000775000175000017500000000000000000000000014106 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2593298 mido-1.2.10/tests/backends/0000775000175000017500000000000000000000000015660 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/backends/test_backend.py0000664000175000017500000000041000000000000020653 0ustar00olembolemb00000000000000from mido.backends.backend import Backend def test_split_api(): backend = Backend('test') assert backend.name == 'test' assert backend.api is None backend = Backend('test/ALSA') assert backend.name == 'test' assert backend.api == 'ALSA' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/backends/test_rtmidi.py0000664000175000017500000000137200000000000020564 0ustar00olembolemb00000000000000from mido.backends.rtmidi_utils import expand_alsa_port_name def test_expand_alsa_port_name(): port_names = sorted(['A:port 128:0', 'B:port 129:0', 'B:port 129:0', 'Z:port 130:0']) def expand(name): return expand_alsa_port_name(port_names, name) # Should return first matching port. assert expand('port') == 'A:port 128:0' assert expand('A:port') == 'A:port 128:0' assert expand('B:port') == 'B:port 129:0' # Full name should also work. assert expand('A:port 128:0') == 'A:port 128:0' # If the port is not found the original name should be returned # for the caller to deal with. assert expand('invalid name') == 'invalid name' ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2593298 mido-1.2.10/tests/messages/0000775000175000017500000000000000000000000015715 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/messages/test_checks.py0000664000175000017500000000105700000000000020571 0ustar00olembolemb00000000000000from pytest import raises from mido.messages.checks import check_time from mido.py2 import PY2 def test_check_time(): check_time(1) check_time(1.5) if PY2: # long should be allowed. (It doesn't exist in Python3, # so there's no need to check for it here.) check_time(long('9829389283L')) # noqa: F821 with raises(TypeError): check_time(None) with raises(TypeError): check_time('abc') with raises(TypeError): check_time(None) with raises(TypeError): check_time('abc') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/messages/test_decode.py0000664000175000017500000000176300000000000020560 0ustar00olembolemb00000000000000from pytest import raises from mido.messages.decode import decode_message def sysex(data): """Make sysex data.""" return [0xf0] + list(data) + [0xf7] def test_sysex(): data = b'\xf0\x01\x02\x03\xf7' msg = {'type': 'sysex', 'data': (1, 2, 3), 'time': 0} assert decode_message(data) == msg def test_channel(): assert decode_message(b'\x91\x00\x00')['channel'] == 1 def test_sysex_end(): with raises(ValueError): decode_message(b'\xf0\x00\x12') def test_zero_bytes(): with raises(ValueError): decode_message(b'') def test_too_few_bytes(): with raises(ValueError): decode_message(b'\x90') def test_too_many_bytes(): with raises(ValueError): decode_message(b'\x90\x00\x00\x00') def test_invalid_status(): with raises(ValueError): decode_message(b'\x00') def test_sysex_without_stop_byte(): with raises(ValueError): decode_message([0xf0]) with raises(ValueError): decode_message([0xf0, 0]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/messages/test_encode.py0000664000175000017500000000125500000000000020566 0ustar00olembolemb00000000000000from mido.messages.specs import SPEC_BY_STATUS from mido.messages.encode import encode_message from mido.messages.decode import decode_message def test_encode_decode_all(): """Encode and then decode all messages on all channels. Each data byte is different so that the test will fail if the bytes are swapped during encoding or decoding. """ data_bytes = [1, 2, 3] for status_byte, spec in SPEC_BY_STATUS.items(): if status_byte == 0xf0: msg_bytes = [0xf0] + data_bytes + [0xf7] else: msg_bytes = [status_byte] + data_bytes[:spec['length'] - 1] assert encode_message(decode_message(msg_bytes)) == msg_bytes ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/messages/test_messages.py0000664000175000017500000000655300000000000021146 0ustar00olembolemb00000000000000from pytest import raises from mido.messages.specs import MIN_PITCHWHEEL, MAX_PITCHWHEEL, MIN_SONGPOS, MAX_SONGPOS from mido.messages.messages import Message, SysexData def test_msg_time_equality(): # Since 1.1.18 time is included in comparison. assert Message('clock', time=0) == Message('clock', time=0) assert Message('clock', time=0) != Message('clock', time=1) def test_set_type(): """Can't change the type of a message.""" with raises(AttributeError): Message('note_on').type = 'note_off' def test_encode_pitchwheel(): assert 'E0 00 00' == Message('pitchwheel', pitch=MIN_PITCHWHEEL).hex() assert 'E0 00 40' == Message('pitchwheel', pitch=0).hex() assert 'E0 7F 7F' == Message('pitchwheel', pitch=MAX_PITCHWHEEL).hex() def test_decode_pitchwheel(): assert Message.from_hex('E0 00 00').pitch == MIN_PITCHWHEEL assert Message.from_hex('E0 00 40').pitch == 0 assert Message.from_hex('E0 7F 7F').pitch == MAX_PITCHWHEEL def test_encode_songpos(): assert 'F2 00 00' == Message('songpos', pos=MIN_SONGPOS).hex() assert 'F2 7F 7F' == Message('songpos', pos=MAX_SONGPOS).hex() def test_decode_songpos(): assert Message.from_hex('F2 00 00').pos == MIN_SONGPOS assert Message.from_hex('F2 7F 7F').pos == MAX_SONGPOS def test_sysex_data_is_sysexdata_object(): assert isinstance(Message.from_hex('F0 00 F7').data, SysexData) def test_sysex_data_accepts_different_types(): assert Message('sysex', data=(0, 1, 2)).data == (0, 1, 2) assert Message('sysex', data=[0, 1, 2]).data == (0, 1, 2) assert Message('sysex', data=range(3)).data == (0, 1, 2) assert Message('sysex', data=bytearray([0, 1, 2])).data == (0, 1, 2) assert Message('sysex', data=b'\x00\x01\x02').data == (0, 1, 2) def test_copy(): assert Message('start').copy(time=1) == Message('start', time=1) def test_init_invalid_argument(): with raises(ValueError): Message('note_on', zzzzzzzzzzzz=2) with raises(ValueError): # note_on doesn't take program. Message('note_on', program=2) def test_copy_invalid_argument(): with raises(ValueError): Message('note_on').copy(zzzzzzzzzzzz=2) with raises(ValueError): # note_on doesn't take program. Message('note_on').copy(program=2) def test_copy_cant_change_type(): with raises(ValueError): Message('start').copy(type='stop') def test_copy_can_have_same_type(): Message('start').copy(type='start') def test_copy_handles_data_generator(): msg1 = Message('sysex') msg2 = msg1.copy(data=(i for i in range(3))) assert msg2.data == (0, 1, 2) assert isinstance(msg2.data, SysexData) def test_compare_with_nonmessage(): with raises(TypeError): Message('clock') == 'not a message' def test_from_dict_default_values(): msg = Message('note_on', channel=0, note=0, time=0) data = {'type': 'note_on'} assert Message.from_dict(data) == msg def test_dict_sysex_data(): msg = Message('sysex', data=(1, 2, 3)) data = msg.dict() assert data == {'type': 'sysex', 'data': [1, 2, 3], 'time': 0} assert isinstance(data['data'], list) def test_from_hex_sysex_data_type(): msg = Message.from_hex('F0 01 02 03 F7') assert isinstance(msg.data, SysexData) def test_repr(): msg = Message('note_on', channel=1, note=2, time=3) msg_eval = eval(repr(msg)) assert msg == msg_eval ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/messages/test_strings.py0000664000175000017500000000136200000000000021021 0ustar00olembolemb00000000000000from pytest import raises from mido.messages import Message def test_decode_sysex(): assert Message.from_str('sysex data=(1,2,3)').data == (1, 2, 3) def test_decode_invalid_sysex_with_spaces(): with raises(ValueError): Message.from_str('sysex data=(1, 2, 3)') def test_encode_sysex(): assert str(Message('sysex', data=())) == 'sysex data=() time=0' # This should not have an extra comma. assert str(Message('sysex', data=(1,))) == 'sysex data=(1) time=0' assert str(Message('sysex', data=(1, 2, 3))) == 'sysex data=(1,2,3) time=0' def test_encode_long_int(): # Make sure Python 2 doesn't stick an 'L' at the end of a long # integer. assert 'L' not in str(Message('clock', time=1231421984983298432948)) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1620661443.2633297 mido-1.2.10/tests/midifiles/0000775000175000017500000000000000000000000016053 5ustar00olembolemb00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/midifiles/test_meta.py0000664000175000017500000000326100000000000020414 0ustar00olembolemb00000000000000import pytest from mido.midifiles.meta import MetaMessage, UnknownMetaMessage, MetaSpec_key_signature, KeySignatureError def test_copy_invalid_argument(): with pytest.raises(ValueError): MetaMessage('track_name').copy(a=1) def test_copy_cant_override_type(): with pytest.raises(ValueError): MetaMessage('track_name').copy(type='end_of_track') class TestKeySignature: @pytest.mark.parametrize('bad_key_sig', [[8, 0], [8, 1], [0, 2], [9, 1], [255 - 7, 0]]) def test_bad_key_sig_throws_key_signature_error(self, bad_key_sig): with pytest.raises(KeySignatureError): MetaSpec_key_signature().decode(MetaMessage('key_signature'), bad_key_sig) @pytest.mark.parametrize('input_bytes,expect_sig', [([0, 0], 'C'), ([0, 1], 'Am'), ([255 - 6, 0], 'Cb'), ([255 - 6, 1], 'Abm'), ([7, 1], 'A#m') ]) def test_key_signature(self, input_bytes, expect_sig): msg = MetaMessage('key_signature') MetaSpec_key_signature().decode(msg, input_bytes) assert msg.key == expect_sig def test_meta_message_repr(): msg = MetaMessage('end_of_track', time=10) msg_eval = eval(repr(msg)) assert msg == msg_eval def test_unknown_meta_message_repr(): msg = UnknownMetaMessage(type_byte=99, data=[1, 2], time=10) msg_eval = eval(repr(msg)) assert msg == msg_eval ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/midifiles/test_midifiles.py0000664000175000017500000001235600000000000021440 0ustar00olembolemb00000000000000import io from pytest import raises from mido.messages import Message from mido.midifiles.midifiles import MidiFile, MidiTrack from mido.midifiles.meta import MetaMessage, KeySignatureError HEADER_ONE_TRACK = """ 4d 54 68 64 # MThd 00 00 00 06 # Chunk size 00 01 # Type 1 00 01 # 1 track 00 78 # 120 ticks per beat """ def parse_hexdump(hexdump): data = bytearray() for line in hexdump.splitlines(): data += bytearray.fromhex(line.split('#')[0]) return data def read_file(hexdump, clip=False): return MidiFile(file=io.BytesIO(parse_hexdump(hexdump)), clip=clip) def test_no_tracks(): assert read_file(""" 4d 54 68 64 # MThd 00 00 00 06 # Chunk size 00 01 # Type 1 00 00 # 0 tracks 00 78 # 120 ticks per beat """).tracks == [] def test_single_message(): assert read_file(HEADER_ONE_TRACK + """ 4d 54 72 6b # MTrk 00 00 00 04 20 90 40 40 # note_on """).tracks[0] == [Message('note_on', note=64, velocity=64, time=32)] def test_too_long_message(): with raises(IOError): read_file(HEADER_ONE_TRACK + """ 4d 54 72 6b # MTrk 00 00 00 04 00 ff 03 ff ff 7f # extremely long track name message """) def test_two_tracks(): mid = read_file(""" 4d54 6864 0000 0006 0001 0002 0040 # Header 4d54 726b 0000 0008 00 90 40 10 40 80 40 10 # Track 0 4d54 726b 0000 0008 00 90 47 10 40 80 47 10 # Track 1 """) assert len(mid.tracks) == 2 # TODO: add some more tests here. def test_empty_file(): with raises(EOFError): read_file("") def test_eof_in_track(): with raises(EOFError): read_file(HEADER_ONE_TRACK + """ 4d 54 72 6b # MTrk 00 00 00 01 # Chunk size # Oops, no data here. """) def test_invalid_data_byte_no_clipping(): # TODO: should this raise IOError? with raises(IOError): read_file(HEADER_ONE_TRACK + """ 4d 54 72 6b # MTrk 00 00 00 04 # Chunk size 00 90 ff 40 # note_on note=255 velocity=64 """) def test_invalid_data_byte_with_clipping_high(): midi_file = read_file(HEADER_ONE_TRACK + """ 4d 54 72 6b # MTrk 00 00 00 04 # Chunk size 00 90 ff 40 # note_on note=255 velocity=64 """, clip=True) assert midi_file.tracks[0][0].note == 127 def test_meta_messages(): # TODO: should this raise IOError? mid = read_file(HEADER_ONE_TRACK + """ 4d 54 72 6b # MTrk 00 00 00 0c # Chunk size 00 ff 03 04 54 65 73 74 # track_name name='Test' 00 ff 2f 00 # end_of_track """) track = mid.tracks[0] assert track[0] == MetaMessage('track_name', name='Test') assert track[1] == MetaMessage('end_of_track') def test_meta_message_bad_key_sig_throws_key_signature_error_sharps(): with raises(KeySignatureError): read_file(HEADER_ONE_TRACK + """ 4d 54 72 6b # MTrk 00 00 00 09 # Chunk size 00 ff 59 02 08 # Key Signature with 8 sharps 00 ff 2f 00 # end_of_track """) def test_meta_message_bad_key_sig_throws_key_signature_error_flats(): with raises(KeySignatureError): read_file(HEADER_ONE_TRACK + """ 4d 54 72 6b # MTrk 00 00 00 09 # Chunk size 00 ff 59 02 f8 # Key Signature with 8 flats 00 ff 2f 00 # end_of_track """) def test_meta_messages_with_length_0(): """sequence_number and midi_port with no data bytes should be accepted. In rare cases these messages have length 0 and thus no data bytes. (See issues 42 and 93.) It is unclear why these messages are missing their data. It could be cased by a bug in the software that created the files. So far this has been fixed by adding a test to each of these two meta message classes. If the problem appears with other message types it may be worth finding a more general solution. """ mid = read_file(HEADER_ONE_TRACK + """ 4d 54 72 6b # MTrk 00 00 00 17 00 ff 00 00 # sequence_number with no data bytes (should be 2). 00 ff 21 00 # midi_port with no data bytes (should be 1). 00 ff 00 02 00 01 # sequence_number with correct number of data bytes (2). 00 ff 21 01 01 # midi_port with correct number of data bytes (1). 00 ff 2f 00 """) assert mid.tracks[0] == [ MetaMessage('sequence_number', number=0), MetaMessage('midi_port', port=0), MetaMessage('sequence_number', number=1), MetaMessage('midi_port', port=1), MetaMessage('end_of_track'), ] def test_midifile_repr(): midifile = MidiFile(type=1, ticks_per_beat=123, tracks=[ MidiTrack([ Message('note_on', channel=1, note=2, time=3), Message('note_off', channel=1, note=2, time=3)]), MidiTrack([ MetaMessage('sequence_number', number=5), Message('note_on', channel=2, note=6, time=9), Message('note_off', channel=2, note=6, time=9)]), ]) midifile_eval = eval(repr(midifile)) for track, track_eval in zip(midifile.tracks, midifile_eval.tracks): for m1, m2 in zip(track, track_eval): assert m1 == m2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/midifiles/test_tracks.py0000664000175000017500000000151100000000000020751 0ustar00olembolemb00000000000000import itertools from mido.messages import Message from mido.midifiles.meta import MetaMessage from mido.midifiles.tracks import MidiTrack zip = getattr(itertools, 'izip', zip) def test_track_slice(): track = MidiTrack() # Slice should return MidiTrack object. assert isinstance(track[::], MidiTrack) def test_track_name(): name1 = MetaMessage('track_name', name='name1') name2 = MetaMessage('track_name', name='name2') # The track should use the first name it finds. track = MidiTrack([name1, name2]) assert track.name == name1.name def test_track_repr(): track = MidiTrack([ Message('note_on', channel=1, note=2, time=3), Message('note_off', channel=1, note=2, time=3), ]) track_eval = eval(repr(track)) for m1, m2 in zip(track, track_eval): assert m1 == m2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/midifiles/test_units.py0000664000175000017500000000135000000000000020625 0ustar00olembolemb00000000000000from mido.midifiles.units import tempo2bpm, bpm2tempo, tick2second, second2tick def test_tempo2bpm_bpm2tempo(): for bpm, tempo in [ (20, 3000000), (60, 1000000), (120, 500000), (240, 250000), ]: assert bpm == tempo2bpm(tempo) assert tempo == bpm2tempo(bpm) # TODO: these tests could be improved with better test values such as # edge cases. def test_tick2second(): assert tick2second(1, ticks_per_beat=100, tempo=500000) == 0.005 assert tick2second(2, ticks_per_beat=100, tempo=100000) == 0.002 def test_second2tick(): assert second2tick(0.005, ticks_per_beat=100, tempo=500000) == 1 assert second2tick(0.002, ticks_per_beat=100, tempo=100000) == 2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/test_frozen.py0000664000175000017500000000311000000000000017015 0ustar00olembolemb00000000000000from mido.messages import Message from mido.midifiles.meta import MetaMessage, UnknownMetaMessage from mido.frozen import (is_frozen, freeze_message, thaw_message, FrozenMessage, FrozenMetaMessage, FrozenUnknownMetaMessage) def test_hashability(): """Test that messages are hashable.""" hash(FrozenMessage('note_on')) # List is converted to tuple. hash(FrozenMessage('sysex', data=[1, 2, 3])) hash(FrozenMetaMessage('track_name', name='Some track')) hash(FrozenUnknownMetaMessage(123, [1, 2, 3])) def test_freeze_and_thaw(): """Test that messages are hashable.""" assert not is_frozen(thaw_message(freeze_message(Message('note_on')))) def test_thawed_message_is_copy(): frozen_msg = FrozenMessage('note_on') thawed_msg = Message('note_on') assert thaw_message(frozen_msg) == thawed_msg def test_is_frozen(): assert is_frozen(FrozenMessage('note_on')) assert not is_frozen(Message('note_on')) def test_frozen_repr(): msg = FrozenMessage('note_on', channel=1, note=2, time=3) msg_eval = eval(repr(msg)) assert isinstance(msg_eval, FrozenMessage) assert msg == msg_eval def test_frozen_meta_repr(): msg = FrozenMetaMessage('end_of_track', time=10) msg_eval = eval(repr(msg)) assert isinstance(msg_eval, FrozenMetaMessage) assert msg == msg_eval def test_frozen_unknown_meta_repr(): msg = FrozenUnknownMetaMessage(type_byte=99, data=[1, 2], time=10) msg_eval = eval(repr(msg)) assert isinstance(msg_eval, UnknownMetaMessage) assert msg == msg_eval ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/test_parser.py0000664000175000017500000000642400000000000017021 0ustar00olembolemb00000000000000from __future__ import print_function import random from pytest import raises from mido.messages import Message, specs from mido.parser import Parser, parse, parse_all def test_parse(): """Parse a note_on msg and compare it to one created with Message().""" parsed = parse(b'\x90\x4c\x20') other = Message('note_on', channel=0, note=0x4c, velocity=0x20) assert parsed == other def test_parse_stray_data(): """The parser should ignore stray data bytes.""" assert parse_all(b'\x20\x30') == [] def test_parse_stray_status_bytes(): """The parser should ignore stray status bytes.""" assert parse_all(b'\x90\x90\xf0') == [] def test_encode_and_parse(): """Encode a message and then parse it. Should return the same message. """ note_on = Message('note_on') assert note_on == parse(note_on.bytes()) def test_feed_byte(): """Put various things into feed_byte().""" parser = Parser() parser.feed_byte(0) parser.feed_byte(255) with raises(TypeError): parser.feed_byte([1, 2, 3]) with raises(ValueError): parser.feed_byte(-1) with raises(ValueError): parser.feed_byte(256) def test_feed(): """Put various things into feed().""" parser = Parser() parser.feed([]) parser.feed([1, 2, 3]) # TODO: add more valid types. with raises(TypeError): parser.feed(1) with raises(TypeError): parser.feed(None) with raises(TypeError): parser.feed() def test_parse_random_bytes(): """Parser should not crash when parsing random data.""" randrange = random.Random('a_random_seed').randrange parser = Parser() for _ in range(10000): byte = randrange(256) parser.feed_byte(byte) def test_parse_channel(): """Parser should not discard the channel in channel messages.""" assert parse([0x90, 0x00, 0x00]).channel == 0 assert parse([0x92, 0x00, 0x00]).channel == 2 def test_one_byte_message(): """Messages that are one byte long should not wait for data bytes.""" messages = parse_all([0xf6]) # Tune request. assert len(messages) == 1 assert messages[0].type == 'tune_request' def test_undefined_messages(): """The parser should ignore undefined status bytes and sysex_end.""" messages = parse_all([0xf4, 0xf5, 0xf7, 0xf9, 0xfd]) assert messages == [] def test_realtime_inside_sysex(): """Realtime message inside sysex should be delivered first.""" messages = parse_all([0xf0, 0, 0xfb, 0, 0xf7]) assert len(messages) == 2 assert messages[0].type == 'continue' assert messages[1].type == 'sysex' def test_undefined_realtime_inside_sysex(): """Undefined realtime message inside sysex should ignored.""" messages = parse_all([0xf0, 0, 0xf9, 0xfd, 0, 0xf7]) assert len(messages) == 1 assert messages[0].type == 'sysex' def test_encode_and_parse_all(): """Encode and then parse all message types. This checks mostly for errors in the parser. """ parser = Parser() for type_ in sorted(specs.SPEC_BY_TYPE.keys()): msg = Message(type_) parser.feed(msg.bytes()) parser.get_message() == msg assert parser.get_message() is None def test_parser_ascii_text(): assert parse_all(b'7 bit ASCII should not produce any messages') == [] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/test_ports.py0000664000175000017500000000417000000000000016670 0ustar00olembolemb00000000000000import pytest from mido.messages import Message from mido.ports import BaseIOPort class TestIOPort: class Port(BaseIOPort): def _open(self): self.close_called = False def _send(self, message): self._messages.append(message) def _close(self): self.close_called = True @pytest.fixture def port(self): with self.Port('Name') as p: yield p def test_basic(self, port): assert port.name == 'Name' assert not port.closed assert port._messages is port._parser.messages with pytest.raises(TypeError): port.send('not a message') def test_recv_non_blocking(self, port): message = Message('note_on') port.send(message) port.poll() assert port.poll() is None def test_send_message(self, port): message = Message('note_on') port.send(message) port.send(message) def test_port_close(self, port): port.close() assert port.close_called port.close_called = False port.close() assert port.closed assert not port.close_called def test_mido_port_non_blocking_recv(self, port): assert port.receive(block=False) is None def test_close_inside_iteration(): # This type of port can close when it runs out of messages. # (And example of this is socket ports.) # # Iteration should then stop after all messages in the # internal queue have been received. message = Message('note_on') class Port(BaseIOPort): def __init__(self, messages): BaseIOPort.__init__(self) # Simulate some messages that arrived # earlier. self._messages.extend(messages) self.closed = False def _receive(self, block=True): # Oops, the other end hung up. if self._messages: return self._messages.popleft() else: self.close() return None message = Message('note_on') with Port([message, message]) as port: assert len(list(port)) == 2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/test_sockets.py0000664000175000017500000000244400000000000017176 0ustar00olembolemb00000000000000import pytest from mido.sockets import parse_address class TestParseAddress: @pytest.mark.parametrize('input_str, expected', [(':8080', ('', 8080)), ('localhost:8080', ('localhost', 8080)) ]) def test_parse_address_normal(self, input_str, expected): assert parse_address(input_str) == expected def test_too_many_colons_raises_value_error(self): with pytest.raises(ValueError): parse_address(':to_many_colons:8080') def test_only_hostname_raises_value_error(self): with pytest.raises(ValueError): parse_address('only_hostname') def test_empty_string_raises_value_error(self): with pytest.raises(ValueError): parse_address('') def test_only_colon_raises_value_error(self): with pytest.raises(ValueError): parse_address(':') def test_non_number_port_raises_value_error(self): with pytest.raises(ValueError): parse_address(':shoe') def test_port_zero_raises_value_error(self): with pytest.raises(ValueError): parse_address(':0') def test_out_of_range_port_raises_value_error(self): with pytest.raises(ValueError): parse_address(':65536') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/test_syx.py0000664000175000017500000000242300000000000016343 0ustar00olembolemb00000000000000from pytest import raises from mido.messages import Message from mido.syx import read_syx_file, write_syx_file def test_read(tmpdir): path = tmpdir.join("test.syx").strpath msg = Message('sysex', data=(1, 2, 3)) with open(path, 'wb') as outfile: outfile.write(msg.bin()) assert read_syx_file(path) == [msg] with open(path, 'wt') as outfile: outfile.write(msg.hex()) assert read_syx_file(path) == [msg] with open(path, 'wt') as outfile: outfile.write('NOT HEX') with raises(ValueError): read_syx_file(path) def test_handle_any_whitespace(tmpdir): path = tmpdir.join("test.syx").strpath with open(path, 'wt') as outfile: outfile.write('F0 01 02 \t F7\n F0 03 04 F7\n') assert read_syx_file(path) == [Message('sysex', data=[1, 2]), Message('sysex', data=[3, 4])] def test_write(tmpdir): # p = tmpdir.mkdir("sub").join("hello.txt") path = tmpdir.join("test.syx").strpath msg = Message('sysex', data=(1, 2, 3)) write_syx_file(path, [msg]) with open(path, 'rb') as infile: assert infile.read() == msg.bin() write_syx_file(path, [msg], plaintext=True) with open(path, 'rt') as infile: assert infile.read().strip() == msg.hex() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608494059.0 mido-1.2.10/tests/test_tokenizer.py0000664000175000017500000000275400000000000017541 0ustar00olembolemb00000000000000from mido.tokenizer import Tokenizer def tokenize(midi_bytes): return list(Tokenizer(midi_bytes)) def test_channel_message(): assert tokenize([0x90, 1, 2]) == [[0x90, 1, 2]] def test_sysex(): assert tokenize([0xf0, 1, 2, 3, 0xf7]) == [[0xf0, 1, 2, 3, 0xf7]] def test_empty_sysex(): assert tokenize([0xf0, 0xf7]) == [[0xf0, 0xf7]] def test_realtime(): assert tokenize([0xf8]) == [[0xf8]] def test_double_realtime(): assert tokenize([0xf8, 0xf8]) == [[0xf8], [0xf8]] def test_realtime_inside_message(): """Realtime message inside message should reset the parser.""" assert tokenize([0x90, 1, 0xf8, 2]) == [[0xf8]] def test_realtime_inside_sysex(): """Realtime messages are allowed inside sysex. The sysex messages should be delivered first. This is the only case where a message is allowed inside another message. """ assert tokenize([0xf0, 1, 0xf8, 2, 0xf7]) == [[0xf8], [0xf0, 1, 2, 0xf7]] assert tokenize([0xf0, 0xf8, 0xf7]) == [[0xf8], [0xf0, 0xf7]] def test_message_inside_sysex(): """Non-realtime message inside sysex should reset the parser.""" assert tokenize([0xf0, 0x90, 1, 2, 0xf7]) == [[0x90, 1, 2]] def test_sysex_inside_sysex(): """Sysex inside sysex should reset the parser.""" assert tokenize([0xf0, 1, 0xf0, 2, 0xf7]) == [[0xf0, 2, 0xf7]] def test_stray_data_bytes(): """Data bytes outside messages should be ignored.""" assert tokenize([0, 1, 0x90, 2, 3, 4, 5, 0xf8, 6]) == [[0x90, 2, 3], [0xf8]] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608250925.0 mido-1.2.10/tox.ini0000664000175000017500000000031300000000000014254 0ustar00olembolemb00000000000000[tox] envlist = py27, py36 [testenv] commands = pip install -q -e .[dev] py.test . -rs -q check-manifest -v sphinx-build docs {envtmpdir} -q -E flake8 mido