File: midiutil.py

package info (click to toggle)
python-rtmidi 1.5.8-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,248 kB
  • sloc: cpp: 4,228; python: 2,853; makefile: 287; sh: 109; ansic: 19
file content (285 lines) | stat: -rw-r--r-- 9,261 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# -*- coding: utf-8 -*-
#
# midiutil.py
#
"""Collection of utility functions for handling MIDI I/O and ports.

Currently contains functions to list MIDI input/output ports, to get the RtMidi
API to use from the environment and to open MIDI ports.

"""

from __future__ import print_function, unicode_literals

import logging
import os

try:
    raw_input
except NameError:
    # Python 3
    raw_input = input
    basestring = str

import rtmidi


__all__ = (
    'get_api_from_environment',
    'list_available_ports',
    'list_input_ports',
    'list_output_ports',
    'open_midiinput',
    'open_midioutput',
    'open_midiport',
)

log = logging.getLogger(__name__)


def _prompt_for_virtual(type_):
    """Prompt on the console whether a virtual MIDI port should be opened."""
    return raw_input("Do you want to create a virtual MIDI %s port? (y/N) " %
                     type_).strip().lower() in ['y', 'yes']


def get_api_from_environment(api=rtmidi.API_UNSPECIFIED):
    """Return RtMidi API specified in the environment if any.

    If the optional api argument is ``rtmidi.API_UNSPECIFIED`` (the default),
    look in the environment variable ``RTMIDI_API`` for the name of the RtMidi
    API to use. Valid names are ``LINUX_ALSA``, ``UNIX_JACK``, ``MACOSX_CORE``,
    ``WINDOWS_MM`` and ``RTMIDI_DUMMY``. If no valid value is found,
    ``rtmidi.API_UNSPECIFIED`` will be used.

    Returns a ``rtmidi.API_*`` constant.

    """
    if api == rtmidi.API_UNSPECIFIED and 'RTMIDI_API' in os.environ:
        try:
            api_name = os.environ['RTMIDI_API'].upper()
            api = getattr(rtmidi, 'API_' + api_name)
        except AttributeError:
            log.warning("Ignoring unknown API '%s' in environment variable "
                        "RTMIDI_API." % api_name)

    return api


def list_available_ports(ports=None, midiio=None):
    """List MIDI ports given or available on given MIDI I/O instance."""
    if ports is None:
        ports = midiio.get_ports()
        type_ = " input" if isinstance(midiio, rtmidi.MidiIn) else " ouput"
    else:
        type_ = ''

    if ports:
        print("Available MIDI{} ports:\n".format(type_))

        for portno, name in enumerate(ports):
            print("[{}] {}".format(portno, name))
    else:
        print("No MIDI{} ports found.".format(type_))

    print()


def list_input_ports(api=rtmidi.API_UNSPECIFIED):
    """List available MIDI input ports.

    Optionally the RtMidi API can be passed with the ``api`` argument. If not
    it will be determined via the ``get_api_from_environment`` function.

    Exceptions:

    ``rtmidi.SystemError``
        Raised when RtMidi backend initialization fails.

    """
    midiin = rtmidi.MidiIn(get_api_from_environment(api))
    list_available_ports(midiio=midiin)
    midiin.delete()


def list_output_ports(api=rtmidi.API_UNSPECIFIED):
    """List available MIDI output ports.

    Optionally the RtMidi API can be passed with the ``api`` argument. If not
    it will be determined via the ``get_api_from_environment`` function.

    Exceptions:

    ``rtmidi.SystemError``
        Raised when RtMidi backend initialization fails.

    """
    midiout = rtmidi.MidiOut(get_api_from_environment(api))
    list_available_ports(midiio=midiout)
    midiout.delete()


def open_midiport(port=None, type_="input", api=rtmidi.API_UNSPECIFIED,
                  use_virtual=False, interactive=True, client_name=None,
                  port_name=None):
    """Open MIDI port for in-/output and return MidiIn/-Out instance and port name.

    Arguments:

    ``port``
        A MIDI port number or (substring of) a port name or ``None``.

        Available ports are enumerated starting from zero separately for input
        and output ports. If only a substring of a port name is given, the
        first matching port is used.

    ``type_``
        Must be ``"input"`` or ``"output"``. Determines whether a ``MidiIn``
        or ``MidiOut`` instance will be created and returned.

    ``api``
        Select the low-level MIDI API to use. Defaults to ``API_UNSPECIFIED``,
        The specified api will be passed to the ``get_api_from_environment``
        function and its return value will be used. If it's ``API_UNSPECIFIED``
        the first compiled-in API, which has any input resp. output ports
        available, will be used.

    ``use_virtual``
        If ``port`` is ``None``, should a virtual MIDI port be opened? Defaults
        to ``False``.

    ``interactive``
        If ``port`` is ``None`` or no MIDI port matching the port number or
        name is available, should the user be prompted on the console whether
        to open a virtual MIDI port (if ``use_virtual`` is ``True``) and/or
        with a list of available MIDI ports and the option to choose one?
        Defaults to ``True``.

    ``client_name``
        The name of the MIDI client passed when instantiating a ``MidiIn`` or
        ``MidiOut`` object.

        See the documentation of the constructor for these classes for the
        default values and caveats and OS-dependent ideosyncracies regarding
        the client name.

    ``port_name``
        The name of the MIDI port passed to the ``open_port`` or
        ``open_virtual_port`` method of the new ``MidiIn`` or ``MidiOut``
        instance.

        See the documentation of the ``open_port`` resp. ``open_virtual_port``
        methods for the default values and caveats when wanting to change the
        port name afterwards.

    Returns:

    A two-element tuple of a new ``MidiIn`` or ``MidiOut`` instance and the
    name of the MIDI port which was opened.

    Exceptions:

    ``KeyboardInterrupt, EOFError``
        Raised when the user presses Control-C or Control-D during a console
        prompt.

    ``rtmidi.SystemError``
        Raised when RtMidi backend initialization fails.

    ``rtmidi.NoDevicesError``
        Raised when no MIDI input or output ports (depending on what was
        requested) are available.

    ``rtmidi.InvalidPortError``
        Raised when an invalid port number or name is passed and
        ``interactive`` is ``False``.

    """
    midiclass_ = rtmidi.MidiIn if type_ == "input" else rtmidi.MidiOut
    log.debug("Creating %s object.", midiclass_.__name__)

    api = get_api_from_environment(api)

    midiobj = midiclass_(api, name=client_name)
    type_ = "input" if isinstance(midiobj, rtmidi.MidiIn) else "output"

    ports = midiobj.get_ports()

    if port is None:
        try:
            if (midiobj.get_current_api() != rtmidi.API_WINDOWS_MM and
                    (use_virtual or (interactive and _prompt_for_virtual(type_)))):
                if not port_name:
                    port_name = "Virtual MIDI %s" % type_

                log.info("Opening virtual MIDI %s port.", type_)
                midiobj.open_virtual_port(port_name)
                return midiobj, port_name
        except (KeyboardInterrupt, EOFError):
            del midiobj
            print('')
            raise

    if len(ports) == 0:
        del midiobj
        raise rtmidi.NoDevicesError("No MIDI %s ports found." % type_)

    try:
        port = int(port)
    except (TypeError, ValueError):
        if isinstance(port, basestring):
            portspec = port
            for portno, name in enumerate(ports):
                if portspec in name:
                    port = portno
                    break
            else:
                log.warning("No port matching '%s' found.", portspec)
                port = None

    while interactive and (port is None or (port < 0 or port >= len(ports))):
        list_available_ports(ports)

        try:
            r = raw_input("Select MIDI %s port (Control-C to exit): " % type_)
            port = int(r)
        except (KeyboardInterrupt, EOFError):
            del midiobj
            print('')
            raise
        except (ValueError, TypeError):
            port = None

    if port is not None and (port >= 0 and port < len(ports)):
        if not port_name:
            port_name = ports[port]

        log.info("Opening MIDI %s port #%i (%s)." % (type_, port, port_name))
        midiobj.open_port(port, port_name)
        return midiobj, port_name
    else:
        raise rtmidi.InvalidPortError("Invalid port.")


def open_midiinput(port=None, api=rtmidi.API_UNSPECIFIED, use_virtual=False,
                   interactive=True, client_name=None, port_name=None):
    """Open a MIDI port for input and return a MidiIn instance and port name.

    See the ``open_midiport`` function for information on parameters, return
    types and possible exceptions.

    """
    return open_midiport(port, "input", api, use_virtual, interactive,
                         client_name, port_name)


def open_midioutput(port=None, api=rtmidi.API_UNSPECIFIED, use_virtual=False,
                    interactive=True, client_name=None, port_name=None):
    """Open a MIDI port for output and return a MidiOut instance and port name.

    See the ``open_midiport`` function for information on parameters, return
    types and possible exceptions.

    """
    return open_midiport(port, "output", api, use_virtual, interactive,
                         client_name, port_name)