#include "MIDI.h" #include #include namespace { std::unordered_set supported_devices{"AudioBox 22 VSL", "CH345", "M2"}; } MIDI::MIDI(): m_client{}, seq_handle{}, in_port{}, npfd{}, pfd{}, midi_event_parser{} { if (0 > snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK)) { throw std::runtime_error("MIDI sequencer couldn't be opened"); } if (0 > snd_seq_set_client_name(seq_handle, "Midi Listener")) { throw std::runtime_error("MIDI client name couldn't be set"); } detect_port(); if (0 > ((in_port = snd_seq_create_simple_port(seq_handle, fmt::format("{}:0", m_client).c_str(), SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_APPLICATION)))) { throw std::runtime_error("MIDI port couldn't be opened"); } debug_cout << "in_port: " << std::to_string(in_port) << std::endl; if (1) { snd_seq_addr_t sender, dest; snd_seq_port_subscribe_t *subs; sender.client = m_client; sender.port = 0; dest.client = snd_seq_client_id(seq_handle); dest.port = in_port; snd_seq_port_subscribe_alloca(&subs); snd_seq_port_subscribe_set_sender(subs, &sender); snd_seq_port_subscribe_set_dest(subs, &dest); snd_seq_port_subscribe_set_queue(subs, 1); snd_seq_port_subscribe_set_time_update(subs, 1); snd_seq_port_subscribe_set_time_real(subs, 1); // TODO: fix timestamp (currently always 0) if (0 > snd_seq_subscribe_port(seq_handle, subs)) { throw std::runtime_error("MIDI port couldn't be connected"); } } else { if (0 > snd_seq_connect_from(seq_handle, in_port, m_client, 0)) { throw std::runtime_error("MIDI port couldn't be connected"); } } npfd = snd_seq_poll_descriptors_count(seq_handle, POLLIN); if (npfd < 0) { throw std::runtime_error("snd_seq_poll_descriptors_count() failed"); } pfd = (struct pollfd *)malloc(npfd * sizeof(struct pollfd)); if (pfd == nullptr) { throw std::runtime_error("alloca() error for MIDI"); } if (0 > snd_seq_poll_descriptors(seq_handle, pfd, npfd, POLLIN)) { throw std::runtime_error("snd_seq_poll_descriptors() failure"); } if (npfd != 1) { std::cout << "Warning: " << std::to_string(npfd) << " poll fds for MIDI" << std::endl; } else if (fd() <= 2) { std::cout << "Warning: Bad MIDI fd: " << std::to_string(fd()) << std::endl; } if (0 > snd_midi_event_new(1024, &midi_event_parser)) { throw std::runtime_error("snd_midi_event_new()"); } } void MIDI::detect_port(void) { bool found{}; snd_seq_client_info_t *cinfo; snd_seq_port_info_t *pinfo; snd_seq_client_info_alloca(&cinfo); snd_seq_port_info_alloca(&pinfo); snd_seq_client_info_set_client(cinfo, -1); while (snd_seq_query_next_client(seq_handle, cinfo) >= 0) { int client = snd_seq_client_info_get_client(cinfo); snd_seq_port_info_set_client(pinfo, client); snd_seq_port_info_set_port(pinfo, -1); while (snd_seq_query_next_port(seq_handle, pinfo) >= 0) { /* port must understand MIDI messages */ if (!(snd_seq_port_info_get_type(pinfo) & SND_SEQ_PORT_TYPE_MIDI_GENERIC)) continue; /* we need both WRITE and SUBS_WRITE */ if ((snd_seq_port_info_get_capability(pinfo) & (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE)) != (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE)) continue; if (supported_devices.contains(std::string{snd_seq_client_info_get_name(cinfo)})) { m_client = snd_seq_port_info_get_client(pinfo); found = true; } //std::cout << fmt::format("DEBUG: {}", get_midi_port()) << std::endl; //printf("%3d:%-3d %-32.32s %s\n", // snd_seq_port_info_get_client(pinfo), // snd_seq_port_info_get_port(pinfo), // snd_seq_client_info_get_name(cinfo), // snd_seq_port_info_get_name(pinfo)); } } if (!found) { throw std::runtime_error("No MIDI device found"); } } MIDI::~MIDI() { snd_midi_event_free(midi_event_parser); free(pfd); } int MIDI::fd() { return pfd->fd; } bool MIDI::event_ready() { int result = snd_seq_event_input_pending(seq_handle, 1); if (result < 0) { throw std::runtime_error("snd_seq_event_input_pending() failed"); } return result > 0; } snd_seq_event_t* MIDI::read(void) { snd_seq_event_t *ev = NULL; if (0 > snd_seq_event_input(seq_handle, &ev)) { std::cerr << "snd_seq_event_input(): -EAGAIN" << std::endl; } return ev; } namespace { uint64_t timestamp_from_event(const snd_seq_event_t *ev) { return ((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_REAL) ? ev->time.time.tv_sec : ev->time.tick; } } // returns if click starts void MIDI::process(snd_seq_event_t *ev) { uint64_t timestamp = timestamp_from_event(ev); // original timestamp // TODO: fix timestamp to be set automatically struct timespec t; if (TIME_UTC != timespec_get(&t, TIME_UTC)) { log_cout << "Error: timespec_get()" << std::endl; } else { ev->time.time.tv_sec = t.tv_sec; ev->time.time.tv_nsec = t.tv_nsec; } if ((ev->type == SND_SEQ_EVENT_NOTEON) ||(ev->type == SND_SEQ_EVENT_NOTEOFF)) { const char *type = (ev->type == SND_SEQ_EVENT_NOTEON) ? "on " : "off"; debug_cout << fmt::format("[{}] Note {}: {:2x} vel({:2x}), ch {:2x}\n", timestamp, type, ev->data.note.note, ev->data.note.velocity, ev->data.control.channel); if (ev->type == SND_SEQ_EVENT_NOTEON) { signal_note(ev->data.control.channel, ev->data.note.note, timestamp); } } else if (ev->type == SND_SEQ_EVENT_CONTROLLER) { debug_cout << fmt::format("[{}] Control: {:2x} val({:2x})\n", timestamp_from_event(ev), ev->data.control.param, ev->data.control.value); } else if (ev->type == SND_SEQ_EVENT_SENSING) { signal_active_sensing(); debug_cout << fmt::format("[{}] Active Sensing", timestamp_from_event(ev)) << std::endl; } else if (ev->type == SND_SEQ_EVENT_CLOCK) { signal_clock(); debug_cout << fmt::format("[{}] Clock\n", timestamp_from_event(ev)) << std::endl; } else { log_cout << fmt::format("[{}] Unknown MIDI event: {}", timestamp_from_event(ev), ev->type) << std::endl; } unsigned char buf[1024]; // dummy long midi_bytes {snd_midi_event_decode(midi_event_parser, buf, sizeof(buf), ev)}; signal_count_events(midi_bytes); } void MIDI::flush() { while (event_ready()) { (void) read(); } } void MIDI::wait_for_event() { int result = poll(pfd, npfd, 10000); if (result > 0) { // event } else if (result == 0) { // timeout } else { throw std::runtime_error("Poll unsuccessful"); } }