File: pygame.py

package info (click to toggle)
python-mido 1.3.3-0.3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 920 kB
  • sloc: python: 4,006; makefile: 127; sh: 4
file content (134 lines) | stat: -rw-r--r-- 3,754 bytes parent folder | download | duplicates (2)
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
# SPDX-FileCopyrightText: 2013 Ole Martin Bjorndalen <ombdalen@gmail.com>
#
# SPDX-License-Identifier: MIT

"""
Mido ports for pygame.midi.

Pygame uses PortMidi, so this is perhaps not very useful.

http://www.pygame.org/docs/ref/midi.html
"""

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 OSError('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 OSError(f'port already opened: {name!r}')

        return device
    else:
        raise OSError(f'unknown port: {name!r}')


def get_devices(**kwargs):
    midi.init()
    return [_get_device(device_id) for device_id in range(midi.get_count())]


class PortCommon:
    """
    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 OSError('{} 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())