|
39 | 39 | __version__ = "0.0.0-auto.0"
|
40 | 40 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE_MIDI.git"
|
41 | 41 |
|
| 42 | + |
42 | 43 | class _MidiCharacteristic(ComplexCharacteristic):
|
43 | 44 | """Endpoint for sending commands to a media player. The value read will list all available
|
44 | 45 |
|
45 | 46 | commands."""
|
| 47 | + |
46 | 48 | uuid = VendorUUID("7772E5DB-3868-4112-A1A9-F2669D106BF3")
|
47 | 49 |
|
48 | 50 | def __init__(self):
|
49 |
| - super().__init__(properties=Characteristic.WRITE_NO_RESPONSE | Characteristic.READ | Characteristic.NOTIFY, |
50 |
| - read_perm=Attribute.OPEN, write_perm=Attribute.OPEN, |
51 |
| - max_length=512, |
52 |
| - fixed_length=False) |
| 51 | + super().__init__( |
| 52 | + properties=Characteristic.WRITE_NO_RESPONSE |
| 53 | + | Characteristic.READ |
| 54 | + | Characteristic.NOTIFY, |
| 55 | + read_perm=Attribute.ENCRYPT_NO_MITM, |
| 56 | + write_perm=Attribute.ENCRYPT_NO_MITM, |
| 57 | + max_length=512, |
| 58 | + fixed_length=False, |
| 59 | + ) |
53 | 60 |
|
54 | 61 | def bind(self, service):
|
55 | 62 | """Binds the characteristic to the given Service."""
|
56 | 63 | bound_characteristic = super().bind(service)
|
57 |
| - return _bleio.PacketBuffer(bound_characteristic, |
58 |
| - buffer_size=4) |
| 64 | + return _bleio.PacketBuffer(bound_characteristic, buffer_size=4) |
| 65 | + |
59 | 66 |
|
60 | 67 | class MIDIService(Service):
|
| 68 | + """BLE MIDI service. It acts just like a USB MIDI PortIn and PortOut and can be used as a drop |
| 69 | + in replacement. |
| 70 | +
|
| 71 | + BLE MIDI's protocol includes timestamps for MIDI messages. This class automatically adds them |
| 72 | + to MIDI data written out and strips them from MIDI data read in.""" |
| 73 | + |
61 | 74 | uuid = VendorUUID("03B80E5A-EDE8-4B33-A751-6CE34EC4C700")
|
62 | 75 | _raw = _MidiCharacteristic()
|
| 76 | + # _raw gets shadowed for each MIDIService instance by a PacketBuffer. PyLint doesn't know this |
| 77 | + # so it complains about missing members. |
| 78 | + # pylint: disable=no-member |
63 | 79 |
|
64 | 80 | def __init__(self, **kwargs):
|
65 | 81 | super().__init__(**kwargs)
|
66 |
| - self._in_buffer = bytearray(self._raw.packet_length) |
| 82 | + self._in_buffer = bytearray(self._raw.packet_size) |
67 | 83 | self._out_buffer = None
|
68 | 84 | shared_buffer = memoryview(bytearray(4))
|
69 |
| - self._buffers = [None, shared_buffer[:1], shared_buffer[:2], shared_buffer[:3], shared_buffer[:4]] |
| 85 | + self._buffers = [ |
| 86 | + None, |
| 87 | + shared_buffer[:1], |
| 88 | + shared_buffer[:2], |
| 89 | + shared_buffer[:3], |
| 90 | + shared_buffer[:4], |
| 91 | + ] |
70 | 92 | self._header = bytearray(1)
|
71 | 93 | self._in_sysex = False
|
72 | 94 | self._message_target_length = None
|
73 | 95 | self._message_length = 0
|
74 | 96 | self._pending_realtime = None
|
| 97 | + self._in_length = 0 |
| 98 | + self._in_index = 1 |
| 99 | + self._last_data = True |
| 100 | + |
| 101 | + def readinto(self, buf, length): |
| 102 | + """Reads up to ``length`` bytes into ``buf`` starting at index 0. |
| 103 | +
|
| 104 | + Returns the number of bytes written into ``buf``.""" |
| 105 | + i = 0 |
| 106 | + while i < length: |
| 107 | + if self._in_index < self._in_length: |
| 108 | + byte = self._in_buffer[self._in_index] |
| 109 | + if self._last_data and byte & 0x80 != 0: |
| 110 | + # Maybe manage timing here. Not done now because we're likely slower than we |
| 111 | + # need to be already. |
| 112 | + # low_ms = byte & 0x7f |
| 113 | + # print("low", low_ms) |
| 114 | + self._in_index += 1 |
| 115 | + self._last_data = False |
| 116 | + continue |
| 117 | + self._in_index += 1 |
| 118 | + self._last_data = True |
| 119 | + buf[i] = byte |
| 120 | + i += 1 |
| 121 | + else: |
| 122 | + if len(self._in_buffer) < self._raw.packet_size: |
| 123 | + self._in_buffer = bytearray(self._raw.packet_size) |
| 124 | + self._in_length = self._raw.readinto(self._in_buffer) |
| 125 | + if self._in_length == 0: |
| 126 | + break |
| 127 | + # high_ms = self._in_buffer[0] & 0x3f |
| 128 | + # print("high", high_ms) |
| 129 | + self._in_index = 1 |
| 130 | + self._last_data = True |
| 131 | + |
| 132 | + return i |
75 | 133 |
|
76 | 134 | def read(self, length):
|
77 |
| - self._raw.read(self._in_buffer) |
78 |
| - return None |
| 135 | + """Reads up to ``length`` bytes and returns them.""" |
| 136 | + result = bytearray(length) |
| 137 | + i = self.readinto(result, length) |
| 138 | + return result[:i] |
79 | 139 |
|
80 | 140 | def write(self, buf, length):
|
| 141 | + """Writes ``length`` bytes out.""" |
| 142 | + # pylint: disable=too-many-branches |
81 | 143 | timestamp_ms = time.monotonic_ns() // 1000000
|
82 |
| - self._header[0] = (timestamp_ms >> 7 & 0x3f) | 0x80 |
| 144 | + self._header[0] = (timestamp_ms >> 7 & 0x3F) | 0x80 |
83 | 145 | i = 0
|
84 | 146 | while i < length:
|
85 | 147 | data = buf[i]
|
86 | 148 | command = data & 0x80 != 0
|
87 | 149 | if self._in_sysex:
|
88 |
| - if command: # End of sysex or real time |
| 150 | + if command: # End of sysex or real time |
89 | 151 | b = self._buffers[2]
|
90 |
| - b[0] = 0x80 | (timestamp_ms & 0x7f) |
91 |
| - b[1] = 0xf7 |
92 |
| - self._raw.write(b, self._header) |
93 |
| - self._in_sysex = data == 0xf7 |
| 152 | + b[0] = 0x80 | (timestamp_ms & 0x7F) |
| 153 | + b[1] = 0xF7 |
| 154 | + self._raw.write(b, header=self._header) |
| 155 | + self._in_sysex = data == 0xF7 |
94 | 156 | else:
|
95 | 157 | b = self._buffers[1]
|
96 | 158 | b[0] = data
|
97 |
| - self._raw.write(b, self._header) |
| 159 | + self._raw.write(b, header=self._header) |
98 | 160 | elif command:
|
99 |
| - self._in_sysex = data == 0xf0 |
| 161 | + self._in_sysex = data == 0xF0 |
100 | 162 | b = self._buffers[2]
|
101 |
| - b[0] = 0x80 | (timestamp_ms & 0x7f) |
| 163 | + b[0] = 0x80 | (timestamp_ms & 0x7F) |
102 | 164 | b[1] = data
|
103 |
| - if 0xf6 <= data <= 0xff or self._in_sysex: # Real time, command only or start sysex |
| 165 | + if ( |
| 166 | + 0xF6 <= data <= 0xFF or self._in_sysex |
| 167 | + ): # Real time, command only or start sysex |
104 | 168 | if self._message_target_length:
|
105 | 169 | self._pending_realtime = b
|
106 | 170 | else:
|
107 | 171 | self._raw.write(b, self._header)
|
108 | 172 | else:
|
109 |
| - if 0x80 <= data <= 0xbf or 0xe0 <= data <= 0xef or data == 0xf2: # Two following bytes |
| 173 | + if ( |
| 174 | + 0x80 <= data <= 0xBF or 0xE0 <= data <= 0xEF or data == 0xF2 |
| 175 | + ): # Two following bytes |
110 | 176 | self._message_target_length = 4
|
111 | 177 | else:
|
112 | 178 | self._message_target_length = 3
|
113 | 179 | b = self._buffers[self._message_target_length]
|
114 |
| - # All of the buffers share memory so the timestamp and data have already been set. |
| 180 | + # All of the buffers share memory so the timestamp and data have already been |
| 181 | + # set. |
115 | 182 | self._message_length = 2
|
116 | 183 | self._out_buffer = b
|
117 | 184 | else:
|
118 | 185 | self._out_buffer[self._message_length] = data
|
119 | 186 | self._message_length += 1
|
120 | 187 | if self._message_target_length == self._message_length:
|
121 |
| - self._raw.write(self._out_buffer, self._header) |
122 |
| - if _pending_realtime: |
123 |
| - self._raw.write(self._pending_realtime, self._header) |
| 188 | + self._raw.write(self._out_buffer, header=self._header) |
| 189 | + if self._pending_realtime: |
| 190 | + self._raw.write(self._pending_realtime, header=self._header) |
124 | 191 | self._pending_realtime = None
|
125 | 192 | self._message_target_length = None
|
| 193 | + i += 1 |
0 commit comments