]> git.sesse.net Git - nageru/blob - shared/midi_device.cpp
ab84fda6db51099e3ecf03013bc08a7b0a60947a
[nageru] / shared / midi_device.cpp
1 #include "midi_device.h"
2
3 #include <alsa/asoundlib.h>
4 #include <sys/eventfd.h>
5 #include <unistd.h>
6 #include <thread>
7 #include <utility>
8
9 using namespace std;
10
11 MIDIDevice::MIDIDevice(MIDIReceiver *receiver)
12         : receiver(receiver)
13 {
14         should_quit_fd = eventfd(/*initval=*/0, /*flags=*/0);
15         assert(should_quit_fd != -1);
16 }
17
18 MIDIDevice::~MIDIDevice()
19 {
20         should_quit = true;
21         const uint64_t one = 1;
22         if (write(should_quit_fd, &one, sizeof(one)) != sizeof(one)) {
23                 perror("write(should_quit_fd)");
24                 abort();
25         }
26         midi_thread.join();
27         close(should_quit_fd);
28 }
29
30 void MIDIDevice::start_thread()
31 {
32         midi_thread = thread(&MIDIDevice::thread_func, this);
33 }
34
35 #define RETURN_ON_ERROR(msg, expr) do {                            \
36         int err = (expr);                                          \
37         if (err < 0) {                                             \
38                 fprintf(stderr, msg ": %s\n", snd_strerror(err));  \
39                 return;                                            \
40         }                                                          \
41 } while (false)
42
43 #define WARN_ON_ERROR(msg, expr) do {                              \
44         int err = (expr);                                          \
45         if (err < 0) {                                             \
46                 fprintf(stderr, msg ": %s\n", snd_strerror(err));  \
47         }                                                          \
48 } while (false)
49
50
51 void MIDIDevice::thread_func()
52 {
53         pthread_setname_np(pthread_self(), "MIDIDevice");
54
55         snd_seq_t *seq;
56         int err;
57
58         RETURN_ON_ERROR("snd_seq_open", snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0));
59         RETURN_ON_ERROR("snd_seq_nonblock", snd_seq_nonblock(seq, 1));
60         RETURN_ON_ERROR("snd_seq_client_name", snd_seq_set_client_name(seq, "nageru"));
61         RETURN_ON_ERROR("snd_seq_create_simple_port",
62                 snd_seq_create_simple_port(seq, "nageru",
63                         SND_SEQ_PORT_CAP_READ |
64                                 SND_SEQ_PORT_CAP_SUBS_READ |
65                                 SND_SEQ_PORT_CAP_WRITE |
66                                 SND_SEQ_PORT_CAP_SUBS_WRITE,
67                         SND_SEQ_PORT_TYPE_MIDI_GENERIC |
68                                 SND_SEQ_PORT_TYPE_APPLICATION));
69
70         int queue_id = snd_seq_alloc_queue(seq);
71         RETURN_ON_ERROR("snd_seq_create_queue", queue_id);
72         RETURN_ON_ERROR("snd_seq_start_queue", snd_seq_start_queue(seq, queue_id, nullptr));
73
74         // The sequencer object is now ready to be used from other threads.
75         {
76                 lock_guard<recursive_mutex> lock(mu);
77                 alsa_seq = seq;
78                 alsa_queue_id = queue_id;
79         }
80
81         // Listen to the announce port (0:1), which will tell us about new ports.
82         RETURN_ON_ERROR("snd_seq_connect_from", snd_seq_connect_from(seq, 0, /*client=*/0, /*port=*/1));
83
84         // Now go through all ports and subscribe to them.
85         snd_seq_client_info_t *cinfo;
86         snd_seq_client_info_alloca(&cinfo);
87
88         snd_seq_client_info_set_client(cinfo, -1);
89         while (snd_seq_query_next_client(seq, cinfo) >= 0) {
90                 int client = snd_seq_client_info_get_client(cinfo);
91
92                 snd_seq_port_info_t *pinfo;
93                 snd_seq_port_info_alloca(&pinfo);
94
95                 snd_seq_port_info_set_client(pinfo, client);
96                 snd_seq_port_info_set_port(pinfo, -1);
97                 while (snd_seq_query_next_port(seq, pinfo) >= 0) {
98                         constexpr int mask = SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
99                         if ((snd_seq_port_info_get_capability(pinfo) & mask) == mask) {
100                                 lock_guard<recursive_mutex> lock(mu);
101                                 subscribe_to_port_lock_held(seq, *snd_seq_port_info_get_addr(pinfo));
102                         }
103                 }
104         }
105
106         int num_alsa_fds = snd_seq_poll_descriptors_count(seq, POLLIN);
107         unique_ptr<pollfd[]> fds(new pollfd[num_alsa_fds + 1]);
108
109         while (!should_quit) {
110                 snd_seq_poll_descriptors(seq, fds.get(), num_alsa_fds, POLLIN);
111                 fds[num_alsa_fds].fd = should_quit_fd;
112                 fds[num_alsa_fds].events = POLLIN;
113                 fds[num_alsa_fds].revents = 0;
114
115                 err = poll(fds.get(), num_alsa_fds + 1, -1);
116                 if (err == 0 || (err == -1 && errno == EINTR)) {
117                         continue;
118                 }
119                 if (err == -1) {
120                         perror("poll");
121                         break;
122                 }
123                 if (fds[num_alsa_fds].revents) {
124                         // Activity on should_quit_fd.
125                         break;
126                 }
127
128                 // Seemingly we can get multiple events in a single poll,
129                 // and if we don't handle them all, poll will _not_ alert us!
130                 while (!should_quit) {
131                         snd_seq_event_t *event;
132                         err = snd_seq_event_input(seq, &event);
133                         if (err < 0) {
134                                 if (err == -EINTR) continue;
135                                 if (err == -EAGAIN) break;
136                                 if (err == -ENOSPC) {
137                                         fprintf(stderr, "snd_seq_event_input: Some events were lost.\n");
138                                         continue;
139                                 }
140                                 fprintf(stderr, "snd_seq_event_input: %s\n", snd_strerror(err));
141                                 return;
142                         }
143                         if (event) {
144                                 handle_event(seq, event);
145                         }
146                 }
147         }
148 }
149
150 void MIDIDevice::handle_event(snd_seq_t *seq, snd_seq_event_t *event)
151 {
152         if (event->source.client == snd_seq_client_id(seq)) {
153                 // Ignore events we sent out ourselves.
154                 return;
155         }
156
157         lock_guard<recursive_mutex> lock(mu);
158         switch (event->type) {
159         case SND_SEQ_EVENT_CONTROLLER: {
160                 receiver->controller_received(event->data.control.param, event->data.control.value);
161                 break;
162         }
163         case SND_SEQ_EVENT_PITCHBEND: {
164                 // Note, -8192 to 8191 instead of 0 to 127.
165                 receiver->controller_received(MIDIReceiver::PITCH_BEND_CONTROLLER, event->data.control.value);
166                 break;
167         }
168         case SND_SEQ_EVENT_NOTEON: {
169                 receiver->note_on_received(event->data.note.note);
170                 break;
171         }
172         case SND_SEQ_EVENT_PORT_START:
173                 subscribe_to_port_lock_held(seq, event->data.addr);
174                 break;
175         case SND_SEQ_EVENT_PORT_EXIT:
176                 printf("MIDI port %d:%d went away.\n", event->data.addr.client, event->data.addr.port);
177                 break;
178         case SND_SEQ_EVENT_PORT_SUBSCRIBED:
179                 if (event->data.connect.sender.client != 0 &&  // Ignore system senders.
180                     event->data.connect.sender.client != snd_seq_client_id(seq) &&
181                     event->data.connect.dest.client == snd_seq_client_id(seq)) {
182                         receiver->update_num_subscribers(++num_subscribed_ports);
183                 }
184                 break;
185         case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
186                 if (event->data.connect.sender.client != 0 &&  // Ignore system senders.
187                     event->data.connect.sender.client != snd_seq_client_id(seq) &&
188                     event->data.connect.dest.client == snd_seq_client_id(seq)) {
189                         receiver->update_num_subscribers(--num_subscribed_ports);
190                 }
191                 break;
192         case SND_SEQ_EVENT_NOTEOFF:
193         case SND_SEQ_EVENT_CLIENT_START:
194         case SND_SEQ_EVENT_CLIENT_EXIT:
195         case SND_SEQ_EVENT_CLIENT_CHANGE:
196         case SND_SEQ_EVENT_PORT_CHANGE:
197                 break;
198         default:
199                 printf("Ignoring MIDI event of unknown type %d.\n", event->type);
200         }
201 }
202
203 void MIDIDevice::subscribe_to_port_lock_held(snd_seq_t *seq, const snd_seq_addr_t &addr)
204 {
205         // Client 0 (SNDRV_SEQ_CLIENT_SYSTEM) is basically the system; ignore it.
206         // MIDI through (SNDRV_SEQ_CLIENT_DUMMY) echoes back what we give it, so ignore that, too.
207         if (addr.client == 0 || addr.client == 14) {
208                 return;
209         }
210
211         // Don't listen to ourselves.
212         if (addr.client == snd_seq_client_id(seq)) {
213                 return;
214         }
215
216         int err = snd_seq_connect_from(seq, 0, addr.client, addr.port);
217         if (err < 0) {
218                 // Just print out a warning (i.e., don't die); it could
219                 // very well just be e.g. another application.
220                 printf("Couldn't subscribe to MIDI port %d:%d (%s).\n",
221                         addr.client, addr.port, snd_strerror(err));
222         } else {
223                 printf("Subscribed to MIDI port %d:%d.\n", addr.client, addr.port);
224         }
225
226         // For sending data back.
227         err = snd_seq_connect_to(seq, 0, addr.client, addr.port);
228         if (err < 0) {
229                 printf("Couldn't subscribe MIDI port %d:%d (%s) to us.\n",
230                         addr.client, addr.port, snd_strerror(err));
231         } else {
232                 printf("Subscribed MIDI port %d:%d to us.\n", addr.client, addr.port);
233         }
234
235         // The current status of the device is unknown, so refresh it.
236         map<LightKey, uint8_t> active_lights = move(current_light_status);
237         current_light_status.clear();
238         update_lights_lock_held(active_lights);
239 }
240
241 void MIDIDevice::update_lights_lock_held(const map<LightKey, uint8_t> &active_lights)
242 {
243         if (alsa_seq == nullptr) {
244                 return;
245         }
246
247         unsigned num_events = 0;
248         for (auto type : { LightKey::NOTE, LightKey::CONTROLLER }) {
249                 for (unsigned num = 1; num <= 127; ++num) {  // Note: Pitch bend is ignored.
250                         LightKey key{type, num};
251                         const auto it = active_lights.find(key);
252                         uint8_t value;  // Velocity for notes, controller value for controllers.
253
254                         // Notes have a natural “off”, while controllers don't really.
255                         // For some reason, not all devices respond to note off.
256                         // Use note-on with value of 0 (which is equivalent) instead.
257                         if (it == active_lights.end()) {
258                                 // Notes have a natural “off”, while controllers don't really,
259                                 // so just skip them if we have no set value.
260                                 if (type == LightKey::CONTROLLER) continue;
261
262                                 // For some reason, not all devices respond to note off.
263                                 // Use note-on with value of 0 (which is equivalent) instead.
264                                 value = 0;
265                         } else {
266                                 value = it->second;
267                         }
268                         if (current_light_status.count(key) &&
269                             current_light_status[key] == value) {
270                                 // Already known to be in the desired state.
271                                 continue;
272                         }
273
274                         snd_seq_event_t ev;
275                         snd_seq_ev_clear(&ev);
276
277                         // Some devices drop events if we throw them onto them
278                         // too quickly. Add a 1 ms delay for each.
279                         snd_seq_real_time_t tm{0, num_events++ * 1000000};
280                         snd_seq_ev_schedule_real(&ev, alsa_queue_id, true, &tm);
281                         snd_seq_ev_set_source(&ev, 0);
282                         snd_seq_ev_set_subs(&ev);
283
284                         if (type == LightKey::NOTE) {
285                                 snd_seq_ev_set_noteon(&ev, /*channel=*/0, num, value);
286                                 current_light_status[key] = value;
287                         } else {
288                                 snd_seq_ev_set_controller(&ev, /*channel=*/0, num, value);
289                                 current_light_status[key] = value;
290                         }
291                         WARN_ON_ERROR("snd_seq_event_output", snd_seq_event_output(alsa_seq, &ev));
292                 }
293         }
294         WARN_ON_ERROR("snd_seq_drain_output", snd_seq_drain_output(alsa_seq));
295 }