blob: fa689b02d4c23cf967eb7bf6f175cd14941e22af [file] [log] [blame]
Prashant Malanifd1e2002017-08-09 13:22:59 -07001// Copyright 2017 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "midis/seq_handler.h"
6
7#include <map>
Ben Chand496e612017-09-29 00:20:50 -07008#include <memory>
Prashant Malanifd1e2002017-08-09 13:22:59 -07009#include <string>
10#include <utility>
11
12#include <base/bind.h>
Prashant Malanifd1e2002017-08-09 13:22:59 -070013#include <poll.h>
14
Prashant Malani3c540362017-09-28 13:35:14 -070015#include "midis/constants.h"
16
Prashant Malanifd1e2002017-08-09 13:22:59 -070017namespace {
18
19const unsigned int kCreateInputPortCaps =
20 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT;
21const unsigned int kCreateOutputPortCaps =
22 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_NO_EXPORT;
23const unsigned int kCreatePortType =
24 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION;
25const char kSndSeqName[] = "hw";
26
27} // namespace
28
29namespace midis {
30
Prashant Malani3c540362017-09-28 13:35:14 -070031SeqHandler::SeqHandler() : weak_factory_(this) {}
32
Prashant Malanifd1e2002017-08-09 13:22:59 -070033SeqHandler::SeqHandler(AddDeviceCallback add_device_cb,
34 RemoveDeviceCallback remove_device_cb,
35 HandleReceiveDataCallback handle_rx_data_cb,
36 IsDevicePresentCallback is_device_present_cb,
37 IsPortPresentCallback is_port_present_cb)
38 : add_device_cb_(add_device_cb),
39 remove_device_cb_(remove_device_cb),
40 handle_rx_data_cb_(handle_rx_data_cb),
41 is_device_present_cb_(is_device_present_cb),
42 is_port_present_cb_(is_port_present_cb),
43 weak_factory_(this) {}
44
45bool SeqHandler::InitSeq() {
46 // Create client handles.
47 snd_seq_t* tmp_seq = nullptr;
48 int err =
49 snd_seq_open(&tmp_seq, kSndSeqName, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
50 if (err != 0) {
51 LOG(ERROR) << "snd_seq_open fails: " << snd_strerror(err);
52 return false;
53 }
54 ScopedSeqPtr in_client(tmp_seq);
55 tmp_seq = nullptr;
56 in_client_id_ = snd_seq_client_id(in_client.get());
57
58 err = snd_seq_open(&tmp_seq, kSndSeqName, SND_SEQ_OPEN_OUTPUT, 0);
59 if (err != 0) {
60 LOG(ERROR) << "snd_seq_open fails: " << snd_strerror(err);
61 return false;
62 }
63
64 ScopedSeqPtr out_client(tmp_seq);
65 tmp_seq = nullptr;
66 out_client_id_ = snd_seq_client_id(out_client.get());
67
68 // Name the clients.
69 err = snd_seq_set_client_name(in_client.get(), "midis (input)");
70 if (err != 0) {
71 LOG(ERROR) << "snd_seq_set_client_name fails: " << snd_strerror(err);
72 return false;
73 }
74 err = snd_seq_set_client_name(out_client.get(), "midis (output)");
75 if (err != 0) {
76 LOG(ERROR) << "snd_seq_set_client_name fails: " << snd_strerror(err);
77 return false;
78 }
79
80 // Create input port.
81 in_port_id_ = snd_seq_create_simple_port(
82 in_client.get(), NULL, kCreateInputPortCaps, kCreatePortType);
83 if (in_port_id_ < 0) {
84 LOG(ERROR) << "snd_seq_create_simple_port fails: "
85 << snd_strerror(in_port_id_);
86 return false;
87 }
88
89 // Subscribe to the announce port.
90 snd_seq_port_subscribe_t* subs;
91 snd_seq_port_subscribe_alloca(&subs);
92 snd_seq_addr_t announce_sender;
93 snd_seq_addr_t announce_dest;
94 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
95 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
96 announce_dest.client = in_client_id_;
97 announce_dest.port = in_port_id_;
98 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
99 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
100 err = snd_seq_subscribe_port(in_client.get(), subs);
101 if (err != 0) {
102 LOG(ERROR) << "snd_seq_subscribe_port on the announce port fails: "
103 << snd_strerror(err);
104 return false;
105 }
106
Prashant Malani78b59c32017-10-24 22:54:51 -0700107 in_client_ = std::move(in_client);
108 out_client_ = std::move(out_client);
Prashant Malanifd1e2002017-08-09 13:22:59 -0700109
Prashant Malani78b59c32017-10-24 22:54:51 -0700110 // Initialize decoder.
111 decoder_ = CreateMidiEvent(0);
Prashant Malanifd1e2002017-08-09 13:22:59 -0700112
Prashant Malani8ca4ab32017-10-03 15:12:49 -0700113 EnumerateExistingDevices();
114
Prashant Malanifd1e2002017-08-09 13:22:59 -0700115 // Obtain the poll file descriptor to watch.
Ben Chand496e612017-09-29 00:20:50 -0700116 pfd_ = std::make_unique<pollfd>();
Prashant Malanifd1e2002017-08-09 13:22:59 -0700117 snd_seq_poll_descriptors(in_client_.get(), pfd_.get(), 1, POLLIN);
118
119 taskid_ = brillo::MessageLoop::current()->WatchFileDescriptor(
Prashant Malani3c540362017-09-28 13:35:14 -0700120 FROM_HERE, pfd_.get()->fd, brillo::MessageLoop::kWatchRead, true,
Prashant Malanifd1e2002017-08-09 13:22:59 -0700121 base::Bind(&SeqHandler::ProcessAlsaClientFd, weak_factory_.GetWeakPtr()));
122
123 if (taskid_ == brillo::MessageLoop::kTaskIdNull) {
124 in_client_.reset();
125 out_client_.reset();
126 decoder_.reset();
127 pfd_.reset();
128 return false;
129 }
130
131 return true;
132}
133
134void SeqHandler::ProcessAlsaClientFd() {
135 int remaining;
136 do {
137 snd_seq_event_t* event;
Prashant Malania9bbf542017-10-19 18:27:18 -0700138 int err = SndSeqEventInput(in_client_.get(), &event);
139 remaining = SndSeqEventInputPending(in_client_.get(), 0);
Prashant Malanifd1e2002017-08-09 13:22:59 -0700140
141 if (err == -ENOSPC) {
142 // Handle out of space error.
143 LOG(ERROR) << "snd_seq_event_input detected buffer overrun";
144 // We've lost events: check another way to see if we need to shut
145 // down.
146 } else if (err == -EAGAIN) {
147 // We've read all the data.
148 } else if (err < 0) {
149 // Handle other errors.
150 LOG(ERROR) << "snd_seq_event_input fails: " << snd_strerror(err);
151 // TODO(pmalani): Stop the message loop here then.
152 } else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
153 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
154 // Handle announce events.
155 switch (event->type) {
156 case SND_SEQ_EVENT_PORT_START:
157 // Don't use SND_SEQ_EVENT_CLIENT_START because the
158 // client name may not be set by the time we query
159 // it. It should be set by the time ports are made.
160 AddSeqDevice(event->data.addr.client);
161 AddSeqPort(event->data.addr.client, event->data.addr.port);
162 break;
163 case SND_SEQ_EVENT_CLIENT_EXIT:
164 // Check for disconnection of our "out" client. This means "shut
165 // down".
166 if (event->data.addr.client == out_client_id_) {
167 // TODO(pmalani): Stop the message loop here then.
168 remaining = 0;
169 } else {
170 RemoveSeqDevice(event->data.addr.client);
171 }
172 break;
173 case SND_SEQ_EVENT_PORT_EXIT:
174 RemoveSeqPort(event->data.addr.client, event->data.addr.port);
175 break;
176 }
177 } else {
178 // Normal operation.
179 ProcessMidiEvent(event);
180 }
181 } while (remaining > 0);
182}
183
184void SeqHandler::AddSeqDevice(uint32_t device_id) {
Prashant Malani3c540362017-09-28 13:35:14 -0700185 if (is_device_present_cb_.Run(0 /* TODO(pmalani): Remove card number */,
186 device_id)) {
Prashant Malanifd1e2002017-08-09 13:22:59 -0700187 LOG(INFO) << "Device: " << device_id << " already exists.";
188 return;
189 }
190
191 // Check that the device isn't our own in/our client.
192 if (device_id == in_client_id_ || device_id == out_client_id_) {
193 return;
194 }
195
196 snd_seq_client_info_t* client_info;
197 snd_seq_client_info_alloca(&client_info);
198 int err =
199 snd_seq_get_any_client_info(in_client_.get(), device_id, client_info);
200 if (err != 0) {
201 LOG(ERROR) << "Failed to get client info.";
202 return;
203 }
204
205 std::string name(snd_seq_client_info_get_name(client_info));
Prashant Malanifd1e2002017-08-09 13:22:59 -0700206
207 // Store the list of MIDI ports and corresponding capabilities in a map.
208 std::map<uint32_t, unsigned int> port_caps;
209 snd_seq_port_info_t* port_info;
210 snd_seq_port_info_alloca(&port_info);
211 snd_seq_port_info_set_client(port_info, device_id);
212 snd_seq_port_info_set_port(port_info, -1);
213 while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
214 if (!(snd_seq_port_info_get_type(port_info) &
215 SND_SEQ_PORT_TYPE_MIDI_GENERIC)) {
216 LOG(INFO) << "Skipping non-MIDI port.";
217 continue;
218 }
219 port_caps.emplace(snd_seq_port_info_get_port(port_info),
220 snd_seq_port_info_get_capability(port_info));
221 }
222
Prashant Malani06d54652018-03-08 18:58:15 -0800223 // If the number of MIDI ports is 0, there is no use in creating
224 // a device.
225 if (port_caps.size() == 0) {
226 LOG(INFO) << "Connected device: " << name << " has no MIDI ports.";
227 return;
228 }
229
Ben Chan2a3a8e32017-10-05 11:15:11 -0700230 auto dev = std::make_unique<Device>(
Prashant Malani3c540362017-09-28 13:35:14 -0700231 name, std::string(),
232 0 /* card number; TODO(pmalani) remove card number */, device_id,
Prashant Malani06d54652018-03-08 18:58:15 -0800233 port_caps.size(), 0 /* device flags TODO(pmalani): flags not needed. */,
Prashant Malanifd1e2002017-08-09 13:22:59 -0700234 base::Bind(&SeqHandler::SubscribeInPort, base::Unretained(this)),
235 base::Bind(&SeqHandler::SubscribeOutPort, base::Unretained(this)),
236 base::Bind(&SeqHandler::UnsubscribeInPort, weak_factory_.GetWeakPtr()),
237 base::Bind(&SeqHandler::UnsubscribeOutPort, weak_factory_.GetWeakPtr()),
238 base::Bind(&SeqHandler::SendMidiData, weak_factory_.GetWeakPtr()),
239 std::move(port_caps));
240 add_device_cb_.Run(std::move(dev));
241}
242
243void SeqHandler::AddSeqPort(uint32_t device_id, uint32_t port_id) {
244 if (!is_port_present_cb_.Run(0, device_id, port_id)) {
245 LOG(WARNING) << "Received port start event for new port: " << port_id
246 << " on device: " << device_id << "; ignoring";
247 }
248}
249
250void SeqHandler::RemoveSeqDevice(uint32_t device_id) {
251 remove_device_cb_.Run(0 /* FIXME remove card number */, device_id);
252}
253
254void SeqHandler::RemoveSeqPort(uint32_t device_id, uint32_t port_id) {
255 if (!is_port_present_cb_.Run(0, device_id, port_id)) {
256 LOG(WARNING) << "Received port start event for new port: " << port_id
257 << " on device: " << device_id << "; ignoring";
258 }
259}
260
261bool SeqHandler::SubscribeInPort(uint32_t device_id, uint32_t port_id) {
262 snd_seq_port_subscribe_t* subs;
263 snd_seq_port_subscribe_alloca(&subs);
264 snd_seq_addr_t sender;
265 sender.client = device_id;
266 sender.port = port_id;
267 snd_seq_port_subscribe_set_sender(subs, &sender);
268
269 snd_seq_addr_t dest;
270 dest.client = in_client_id_;
271 dest.port = in_port_id_;
272 snd_seq_port_subscribe_set_dest(subs, &dest);
273
274 int err = snd_seq_subscribe_port(in_client_.get(), subs);
275 if (err != 0) {
276 LOG(ERROR) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
277 return false;
278 }
279
280 return true;
281}
282
283int SeqHandler::SubscribeOutPort(uint32_t device_id, uint32_t port_id) {
284 int out_port;
Prashant Malani3c540362017-09-28 13:35:14 -0700285 out_port = snd_seq_create_simple_port(out_client_.get(), NULL,
286 kCreateOutputPortCaps, kCreatePortType);
Prashant Malanifd1e2002017-08-09 13:22:59 -0700287 if (out_port < 0) {
288 LOG(INFO) << "snd_seq_creat_simple_port (output) failed: "
289 << snd_strerror(out_port);
290 return -1;
291 }
292
293 snd_seq_port_subscribe_t* subs;
294 snd_seq_port_subscribe_alloca(&subs);
295 snd_seq_addr_t sender;
296 sender.client = out_client_id_;
297 sender.port = out_port;
298 snd_seq_port_subscribe_set_sender(subs, &sender);
299
300 snd_seq_addr_t dest;
301 dest.client = device_id;
302 dest.port = port_id;
303 snd_seq_port_subscribe_set_dest(subs, &dest);
304
305 int err = snd_seq_subscribe_port(out_client_.get(), subs);
306 if (err != 0) {
307 snd_seq_delete_simple_port(out_client_.get(), out_port);
308 LOG(ERROR) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
309 return -1;
310 }
311
312 return out_port;
313}
314
315void SeqHandler::UnsubscribeInPort(uint32_t device_id, uint32_t port_id) {
316 snd_seq_port_subscribe_t* subs;
317 snd_seq_port_subscribe_alloca(&subs);
318 snd_seq_addr_t sender;
319 sender.client = device_id;
320 sender.port = port_id;
321 snd_seq_port_subscribe_set_sender(subs, &sender);
322 snd_seq_addr_t dest;
323 dest.client = in_client_id_;
324 dest.port = in_port_id_;
325 snd_seq_port_subscribe_set_dest(subs, &dest);
326
327 int err = snd_seq_unsubscribe_port(in_client_.get(), subs);
328 if (err != 0) {
329 LOG(WARNING) << "snd_seq_unsubscribe_port fails: " << snd_strerror(err);
330 return;
331 }
332}
333
334void SeqHandler::UnsubscribeOutPort(int out_port_id) {
335 snd_seq_delete_simple_port(out_client_.get(), out_port_id);
336}
337
Prashant Malani3c540362017-09-28 13:35:14 -0700338bool SeqHandler::EncodeMidiBytes(int out_port_id,
339 snd_seq_t* out_client,
340 const uint8_t* buffer,
341 size_t buf_len,
342 snd_midi_event_t* encoder) {
343 if (buf_len == 0 || buf_len > kMaxBufSize) {
344 return false;
345 }
346
Prashant Malanifd1e2002017-08-09 13:22:59 -0700347 for (int i = 0; i < buf_len; i++) {
348 snd_seq_event_t event;
349 int result = snd_midi_event_encode_byte(encoder, buffer[i], &event);
Prashant Malani3c540362017-09-28 13:35:14 -0700350 if (result < 0) {
351 LOG(ERROR) << "Error snd_midi_event_encode_byte(): " << result;
352 return false;
353 }
Prashant Malanifd1e2002017-08-09 13:22:59 -0700354 if (result == 1) {
355 // Send the message.
356 snd_seq_ev_set_source(&event, out_port_id);
357 snd_seq_ev_set_subs(&event);
358 snd_seq_ev_set_direct(&event);
Prashant Malani3c540362017-09-28 13:35:14 -0700359 int expected_length = snd_seq_event_length(&event);
360 result = SndSeqEventOutputDirect(out_client, &event);
361 if (result != expected_length) {
362 LOG(ERROR) << "Error in snd_seq_event_output_direct(): " << result;
363 return false;
364 }
365 return true;
Prashant Malanifd1e2002017-08-09 13:22:59 -0700366 }
367 }
Prashant Malani3c540362017-09-28 13:35:14 -0700368
369 // If we reached here, something went wrong.
370 return false;
371}
372
373void SeqHandler::SendMidiData(int out_port_id,
374 const uint8_t* buffer,
375 size_t buf_len) {
376 snd_midi_event_t* encoder;
377 int ret = snd_midi_event_new(buf_len, &encoder);
378 if (ret != 0) {
379 LOG(ERROR) << "Error snd_midi_event_new(): " << ret;
380 return;
381 }
382 bool success =
383 EncodeMidiBytes(out_port_id, out_client_.get(), buffer, buf_len, encoder);
384 if (!success) {
385 LOG(ERROR) << "Failed to send MIDI data to output port: " << out_port_id;
386 }
Prashant Malanifd1e2002017-08-09 13:22:59 -0700387 snd_midi_event_free(encoder);
388}
389
390void SeqHandler::ProcessMidiEvent(snd_seq_event_t* event) {
391 uint32_t device_id = event->source.client;
392 uint32_t subdevice_num = event->source.port;
393
394 if (event->type == SND_SEQ_EVENT_SYSEX) {
395 // SysEX, so pass it through without decoding.
Prashant Malani3c540362017-09-28 13:35:14 -0700396 handle_rx_data_cb_.Run(0, device_id, subdevice_num,
Prashant Malanifd1e2002017-08-09 13:22:59 -0700397 static_cast<char*>(event->data.ext.ptr),
398 event->data.ext.len);
399 } else {
400 // Normal message, so decode and send.
401 unsigned char buf[12];
402 int64_t count =
403 snd_midi_event_decode(decoder_.get(), buf, sizeof(buf), event);
404 if (count <= 0) {
405 if (count != -ENOENT) {
406 LOG(ERROR) << "snd_midi_event_decoder failed: " << snd_strerror(count);
407 }
408 } else {
Prashant Malani3c540362017-09-28 13:35:14 -0700409 handle_rx_data_cb_.Run(0, device_id, subdevice_num,
410 reinterpret_cast<char*>(buf), count);
Prashant Malanifd1e2002017-08-09 13:22:59 -0700411 }
412 }
413}
414
Prashant Malani3c540362017-09-28 13:35:14 -0700415int SeqHandler::SndSeqEventOutputDirect(snd_seq_t* out_client,
416 snd_seq_event_t* event) {
417 return snd_seq_event_output_direct(out_client, event);
418}
419
Prashant Malania9bbf542017-10-19 18:27:18 -0700420int SeqHandler::SndSeqEventInput(snd_seq_t* in_client, snd_seq_event_t** ev) {
421 return snd_seq_event_input(in_client, ev);
422}
423
424int SeqHandler::SndSeqEventInputPending(snd_seq_t* in_client,
425 int fetch_sequencer) {
426 return snd_seq_event_input_pending(in_client, fetch_sequencer);
427}
428
Prashant Malani8ca4ab32017-10-03 15:12:49 -0700429void SeqHandler::EnumerateExistingDevices() {
430 snd_seq_client_info_t* client_info;
431 snd_seq_client_info_alloca(&client_info);
432 snd_seq_port_info_t* port_info;
433 snd_seq_port_info_alloca(&port_info);
434
435 snd_seq_client_info_set_client(client_info, -1);
436 while (!snd_seq_query_next_client(in_client_.get(), client_info)) {
437 int device_id = snd_seq_client_info_get_client(client_info);
438 AddSeqDevice(device_id);
439
440 // Call AddSeqPort to make sure we "process" all the ports of a client.
441 // Note that currently we don't support the dynamic addition / deletion
442 // of ports.
443 snd_seq_port_info_set_client(port_info, device_id);
444 snd_seq_port_info_set_port(port_info, -1);
445 while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
446 int port_id = snd_seq_port_info_get_port(port_info);
447 AddSeqPort(device_id, port_id);
448 }
449 }
450}
451
Prashant Malani78b59c32017-10-24 22:54:51 -0700452SeqHandler::ScopedMidiEventPtr SeqHandler::CreateMidiEvent(size_t buf_size) {
453 snd_midi_event_t* tmp = nullptr;
454 snd_midi_event_new(buf_size, &tmp);
455 ScopedMidiEventPtr ev(tmp);
456 tmp = nullptr;
457 snd_midi_event_no_status(ev.get(), 1);
458
459 return ev;
460}
461
Prashant Malanifd1e2002017-08-09 13:22:59 -0700462} // namespace midis