1 | /**************************************************************************/ |
2 | /* midi_driver_alsamidi.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #ifdef ALSAMIDI_ENABLED |
32 | |
33 | #include "midi_driver_alsamidi.h" |
34 | |
35 | #include "core/os/os.h" |
36 | #include "core/string/print_string.h" |
37 | |
38 | #include <errno.h> |
39 | |
40 | MIDIDriverALSAMidi::MessageCategory MIDIDriverALSAMidi::msg_category(uint8_t msg_part) { |
41 | if (msg_part >= 0xf8) { |
42 | return MessageCategory::RealTime; |
43 | } else if (msg_part >= 0xf0) { |
44 | // System Exclusive begin/end are specified as System Common Category messages, |
45 | // but we separate them here and give them their own categories as their |
46 | // behavior is significantly different. |
47 | if (msg_part == 0xf0) { |
48 | return MessageCategory::SysExBegin; |
49 | } else if (msg_part == 0xf7) { |
50 | return MessageCategory::SysExEnd; |
51 | } |
52 | return MessageCategory::SystemCommon; |
53 | } else if (msg_part >= 0x80) { |
54 | return MessageCategory::Voice; |
55 | } |
56 | return MessageCategory::Data; |
57 | } |
58 | |
59 | size_t MIDIDriverALSAMidi::msg_expected_data(uint8_t status_byte) { |
60 | if (msg_category(status_byte) == MessageCategory::Voice) { |
61 | // Voice messages have a channel number in the status byte, mask it out. |
62 | status_byte &= 0xf0; |
63 | } |
64 | |
65 | switch (status_byte) { |
66 | case 0x80: // Note Off |
67 | case 0x90: // Note On |
68 | case 0xA0: // Polyphonic Key Pressure (Aftertouch) |
69 | case 0xB0: // Control Change (CC) |
70 | case 0xE0: // Pitch Bend Change |
71 | case 0xF2: // Song Position Pointer |
72 | return 2; |
73 | |
74 | case 0xC0: // Program Change |
75 | case 0xD0: // Channel Pressure (Aftertouch) |
76 | case 0xF1: // MIDI Time Code Quarter Frame |
77 | case 0xF3: // Song Select |
78 | return 1; |
79 | } |
80 | |
81 | return 0; |
82 | } |
83 | |
84 | void MIDIDriverALSAMidi::InputConnection::parse_byte(uint8_t byte, MIDIDriverALSAMidi &driver, |
85 | uint64_t timestamp) { |
86 | switch (msg_category(byte)) { |
87 | case MessageCategory::RealTime: |
88 | // Real-Time messages are single byte messages that can |
89 | // occur at any point. |
90 | // We pass them straight through. |
91 | driver.receive_input_packet(timestamp, &byte, 1); |
92 | break; |
93 | |
94 | case MessageCategory::Data: |
95 | // We don't currently forward System Exclusive messages so skip their data. |
96 | // Collect any expected data for other message types. |
97 | if (!skipping_sys_ex && expected_data > received_data) { |
98 | buffer[received_data + 1] = byte; |
99 | received_data++; |
100 | |
101 | // Forward a complete message and reset relevant state. |
102 | if (received_data == expected_data) { |
103 | driver.receive_input_packet(timestamp, buffer, received_data + 1); |
104 | received_data = 0; |
105 | |
106 | if (msg_category(buffer[0]) != MessageCategory::Voice) { |
107 | // Voice Category messages can be sent with "running status". |
108 | // This means they don't resend the status byte until it changes. |
109 | // For other categories, we reset expected data, to require a new status byte. |
110 | expected_data = 0; |
111 | } |
112 | } |
113 | } |
114 | break; |
115 | |
116 | case MessageCategory::SysExBegin: |
117 | buffer[0] = byte; |
118 | skipping_sys_ex = true; |
119 | break; |
120 | |
121 | case MessageCategory::SysExEnd: |
122 | expected_data = 0; |
123 | skipping_sys_ex = false; |
124 | break; |
125 | |
126 | case MessageCategory::Voice: |
127 | case MessageCategory::SystemCommon: |
128 | buffer[0] = byte; |
129 | received_data = 0; |
130 | expected_data = msg_expected_data(byte); |
131 | skipping_sys_ex = false; |
132 | if (expected_data == 0) { |
133 | driver.receive_input_packet(timestamp, &byte, 1); |
134 | } |
135 | break; |
136 | } |
137 | } |
138 | |
139 | int MIDIDriverALSAMidi::InputConnection::read_in(MIDIDriverALSAMidi &driver, uint64_t timestamp) { |
140 | int ret; |
141 | do { |
142 | uint8_t byte = 0; |
143 | ret = snd_rawmidi_read(rawmidi_ptr, &byte, 1); |
144 | |
145 | if (ret < 0) { |
146 | if (ret != -EAGAIN) { |
147 | ERR_PRINT("snd_rawmidi_read error: " + String(snd_strerror(ret))); |
148 | } |
149 | } else { |
150 | parse_byte(byte, driver, timestamp); |
151 | } |
152 | } while (ret > 0); |
153 | |
154 | return ret; |
155 | } |
156 | |
157 | void MIDIDriverALSAMidi::thread_func(void *p_udata) { |
158 | MIDIDriverALSAMidi *md = static_cast<MIDIDriverALSAMidi *>(p_udata); |
159 | uint64_t timestamp = 0; |
160 | |
161 | while (!md->exit_thread.is_set()) { |
162 | md->lock(); |
163 | |
164 | InputConnection *connections = md->connected_inputs.ptrw(); |
165 | size_t connection_count = md->connected_inputs.size(); |
166 | |
167 | for (size_t i = 0; i < connection_count; i++) { |
168 | connections[i].read_in(*md, timestamp); |
169 | } |
170 | |
171 | md->unlock(); |
172 | |
173 | OS::get_singleton()->delay_usec(1000); |
174 | } |
175 | } |
176 | |
177 | Error MIDIDriverALSAMidi::open() { |
178 | void **hints; |
179 | |
180 | if (snd_device_name_hint(-1, "rawmidi" , &hints) < 0) { |
181 | return ERR_CANT_OPEN; |
182 | } |
183 | |
184 | int i = 0; |
185 | for (void **n = hints; *n != nullptr; n++) { |
186 | char *name = snd_device_name_get_hint(*n, "NAME" ); |
187 | |
188 | if (name != nullptr) { |
189 | snd_rawmidi_t *midi_in; |
190 | int ret = snd_rawmidi_open(&midi_in, nullptr, name, SND_RAWMIDI_NONBLOCK); |
191 | if (ret >= 0) { |
192 | connected_inputs.insert(i++, InputConnection(midi_in)); |
193 | } |
194 | } |
195 | |
196 | if (name != nullptr) { |
197 | free(name); |
198 | } |
199 | } |
200 | snd_device_name_free_hint(hints); |
201 | |
202 | exit_thread.clear(); |
203 | thread.start(MIDIDriverALSAMidi::thread_func, this); |
204 | |
205 | return OK; |
206 | } |
207 | |
208 | void MIDIDriverALSAMidi::close() { |
209 | exit_thread.set(); |
210 | if (thread.is_started()) { |
211 | thread.wait_to_finish(); |
212 | } |
213 | |
214 | for (int i = 0; i < connected_inputs.size(); i++) { |
215 | snd_rawmidi_t *midi_in = connected_inputs[i].rawmidi_ptr; |
216 | snd_rawmidi_close(midi_in); |
217 | } |
218 | connected_inputs.clear(); |
219 | } |
220 | |
221 | void MIDIDriverALSAMidi::lock() const { |
222 | mutex.lock(); |
223 | } |
224 | |
225 | void MIDIDriverALSAMidi::unlock() const { |
226 | mutex.unlock(); |
227 | } |
228 | |
229 | PackedStringArray MIDIDriverALSAMidi::get_connected_inputs() { |
230 | PackedStringArray list; |
231 | |
232 | lock(); |
233 | for (int i = 0; i < connected_inputs.size(); i++) { |
234 | snd_rawmidi_t *midi_in = connected_inputs[i].rawmidi_ptr; |
235 | snd_rawmidi_info_t *info; |
236 | |
237 | snd_rawmidi_info_malloc(&info); |
238 | snd_rawmidi_info(midi_in, info); |
239 | list.push_back(snd_rawmidi_info_get_name(info)); |
240 | snd_rawmidi_info_free(info); |
241 | } |
242 | unlock(); |
243 | |
244 | return list; |
245 | } |
246 | |
247 | MIDIDriverALSAMidi::MIDIDriverALSAMidi() { |
248 | exit_thread.clear(); |
249 | } |
250 | |
251 | MIDIDriverALSAMidi::~MIDIDriverALSAMidi() { |
252 | close(); |
253 | } |
254 | |
255 | #endif // ALSAMIDI_ENABLED |
256 | |