#include #include #include #include #include #include #include #include #include #include #include using namespace std::string_literals; //static const int CLICK_NOTE = 37; //static const int CLICK_CHANNEL = 4; static const char *device = "default"; // playback device const snd_pcm_sframes_t nframes = 1024; // ~1/44th sec buffer size int16_t buffer[nframes]; const unsigned int f_sample = 44100; const double pi = std::acos(-1); class ClickStream { public: ClickStream(): m_phase(1000000) { std::string data_s = Reichwein::File::getFile("media/click.s16le"); m_data.resize(data_s.size() / 2); // src is in bytes memcpy(m_data.data(), data_s.data(), data_s.size()); } void generate() { int i; size_t j = m_phase; for (i = 0; i < nframes; i++) { if (j >= m_data.size()) { buffer[i] = 0; } else { buffer[i] = m_data[j]; } j++; } m_phase = j; } void click() { m_phase = 0; } private: std::vector m_data; size_t m_phase; }; class PCM { public: PCM() { // non-blocking if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { printf("Playback open error: %s\n", snd_strerror(err)); exit(EXIT_FAILURE); } if ((err = snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 1, f_sample, 1, 100000)) < 0) { // latency in us printf("Playback open error: %s\n", snd_strerror(err)); exit(EXIT_FAILURE); } npfd = snd_pcm_poll_descriptors_count(handle); if (npfd < 0) { throw std::runtime_error("snd_pcm_poll_descriptors_count() failed"); } pfd = (struct pollfd *)malloc(npfd * sizeof(struct pollfd)); if (pfd == nullptr) { throw std::runtime_error("alloca() error for PCM"); } if (0 > snd_pcm_poll_descriptors(handle, pfd, npfd)) { throw std::runtime_error("snd_pcm_poll_descriptors() failure"); } if (0 > snd_pcm_start(handle)) { throw std::runtime_error("PCM could not be started"); } if (npfd != 1) { std::cout << "Warning: " << std::to_string(npfd) << " poll fds for pcm" << std::endl; } } ~PCM() { // pass the remaining samples, otherwise they're dropped in close err = snd_pcm_drain(handle); if (err < 0) printf("snd_pcm_drain failed: %s\n", snd_strerror(err)); snd_pcm_close(handle); free(pfd); } int fd() { return pfd->fd; } // write from buffer to ALSA PCM void write() { snd_pcm_sframes_t written = snd_pcm_writei(handle, &buffer[0], nframes); if (written < 0) { if (written == -EPIPE) { std::cout << "Warning: PCM underrun" << std::endl; } std::cout << "Recovering." << std::endl; written = snd_pcm_recover(handle, written, 0); } if (written < 0) { throw std::runtime_error("snd_pcm_writei failed: "s + snd_strerror(written)); } if (written != nframes) { std::cout << "Warning: written " << std::to_string(written) << " frames instead of "<< std::to_string(nframes) << std::endl; } } bool wait_for_event() { int result = poll(pfd, npfd, 10000); if (result > 0) { // event return true; } else if (result == 0) { // timeout return false; } else { throw std::runtime_error("Poll unsuccessful"); } } bool write_available() { snd_pcm_sframes_t result = snd_pcm_avail(handle); if (0 > result) { std::cerr << "Error: snd_pcm_avail()" << std::endl; //throw std::runtime_error("snd_pcm_avail()"); } return result >= nframes; } private: int err; snd_pcm_t *handle; int npfd; struct pollfd* pfd; }; class MIDI { public: MIDI() { 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"); } if (0 > ((in_port = snd_seq_create_simple_port(seq_handle, "24:0", 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"); } std::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 = 24; 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)) #endif //if (0 > snd_seq_connect_from(seq_handle, in_port, 24, 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; } } ~MIDI() { free(pfd); } int fd() { return pfd->fd; } bool 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 *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; } // returns if click starts bool process(const snd_seq_event_t *ev) { bool result = false; if((ev->type == SND_SEQ_EVENT_NOTEON) ||(ev->type == SND_SEQ_EVENT_NOTEOFF)) { const char *type = (ev->type == SND_SEQ_EVENT_NOTEON) ? "on " : "off"; printf("[%d] Note %s: %2x vel(%2x), ch %2x\n", ((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_REAL) ? ev->time.time.tv_sec : ev->time.tick, type, ev->data.note.note, ev->data.note.velocity, ev->data.control.channel); if (ev->type == SND_SEQ_EVENT_NOTEON) { // if (ev->data.note.note == CLICK_NOTE && ev->data.control.channel == CLICK_CHANNEL) { result = true; // } } } else if(ev->type == SND_SEQ_EVENT_CONTROLLER) { printf("[%d] Control: %2x val(%2x)\n", ((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_REAL) ? ev->time.time.tv_sec : ev->time.tick, ev->data.control.param, ev->data.control.value); } else if(ev->type == SND_SEQ_EVENT_CLOCK) { printf("[%d] Clock\n", ((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_REAL) ? ev->time.time.tv_sec : ev->time.tick); } else { printf("[%d] Unknown: Unhandled Event Received\n", ev->time.tick); } return result; } void 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"); } } private: snd_seq_t *seq_handle; int in_port; int npfd; struct pollfd* pfd; }; int main(void) { try { MIDI midi; ClickStream stream; PCM pcm; stream.generate(); pcm.write(); stream.generate(); while (true) { //midi.wait_for_event(); //pcm.wait_for_event(); fd_set read_set; FD_ZERO(&read_set); FD_SET(midi.fd(), &read_set); fd_set write_set; FD_ZERO(&write_set); FD_SET(pcm.fd(), &write_set); struct timeval timeout; timeout.tv_sec = 1; timeout.tv_usec = 0; int result = select(FD_SETSIZE, &read_set, &write_set, NULL, &timeout); if (result < 0) { throw std::runtime_error("select() failed"); } else if (result == 0) { throw std::runtime_error("select() timeout"); } if (midi.event_ready()) { //std::cout << "read..." << std::endl; auto event = midi.read(); //std::cout << "process..." << std::endl; if (midi.process(event)) { stream.click(); //std::cout << "DEBUG: CLICK" << std::endl; } } if (pcm.write_available()) { //std::cout << "DEBUG: WRITE" << std::endl; pcm.write(); stream.generate(); } } } catch (const std::exception& ex) { std::cerr << "Error: " << ex.what() << std::endl; } return 0; }