diff options
author | Roland Reichwein <mail@reichwein.it> | 2025-01-02 20:10:10 +0100 |
---|---|---|
committer | Roland Reichwein <mail@reichwein.it> | 2025-01-02 20:10:10 +0100 |
commit | ddc31844940b9afedb352643349649fac38269ef (patch) | |
tree | 3bb6e5b63b1721d5d511f06e9dc6c9469b086f53 | |
parent | 083e46d5d23d2105e2df1adc769700fb579c9597 (diff) |
Fix click
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | alsa.cpp | 315 |
2 files changed, 281 insertions, 40 deletions
@@ -13,6 +13,8 @@ Features: * Self generated click, self chosen tempo * Click based on MIDI clock (24 clocks per quarter note during song) * Click based on configurable MIDI note + e.g. ch4 c#1 (37) drum map : stick -ch4 -c#1 drum map : stick +- Display: + - set/detected tempo (from clock/click) + - detected notes @@ -6,40 +6,59 @@ #include <cmath> #include <iostream> #include <limits> +#include <exception> +#include <stdexcept> +#include <stdio.h> -static const char *device = "default"; /* playback device */ -const snd_pcm_sframes_t nframes = 1024; // ~1/44th sec buffer size -int16_t buffer[nframes]; /* some random data */ +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 { -private: - std::vector<uint16_t> m_data; public: - ClickStream() - { - std::string data_s = Reichwein::File::getFile("media/click.s16le"); - m_data.resize(data_s.size() / 2); - memcpy(m_data.data(), data_s.data(), data_s.size()); - } + 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()); + } -double generate(double phase) -{ + void generate() + { int i; - size_t j = phase; + size_t j = m_phase; for (i = 0; i < nframes; i++) { if (j >= m_data.size()) - j = 0; - buffer[i] = m_data[j]; + { + buffer[i] = 0; + } + else + { + buffer[i] = m_data[j]; + } j++; } - return j; -} + m_phase = j; + } + + void click() + { + m_phase = 0; + } +private: + std::vector<uint16_t> m_data; + size_t m_phase; }; class PCM @@ -48,7 +67,7 @@ public: PCM() { // non-blocking - if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + 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); } @@ -58,57 +77,277 @@ public: 1, f_sample, 1, - 20000)) < 0) { /* 0.5sec */ + 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"); + } + + std::cout << "Info: " << std::to_string(npfd) << " poll fds for pcm" << std::endl; } ~PCM() { - /* pass the remaining samples, otherwise they're dropped in close */ + // 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); } // write from buffer to ALSA PCM - bool write() + void write() { - frames = snd_pcm_writei(handle, buffer, nframes); - if (frames < 0) { + 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; - frames = snd_pcm_recover(handle, frames, 0); + written = snd_pcm_recover(handle, written, 0); } - if (frames < 0) { - printf("snd_pcm_writei failed: %s\n", snd_strerror(frames)); - return false; + if (written < 0) { + throw std::runtime_error("snd_pcm_writei failed: "s + snd_strerror(written)); } - if (frames > 0 && frames < nframes) - printf("Short write (expected %li, wrote %li)\n", nframes, frames); - return frames == 0; + 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; - snd_pcm_sframes_t frames; + + 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"); + } + + std::cout << "Info: " << std::to_string(npfd) << " poll fds for MIDI" << std::endl; + } + + ~MIDI() + { + free(pfd); + } + + 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) { - PCM pcm; - - double phase = 0; + try { + MIDI midi; ClickStream stream; - for (unsigned int i = 0; i < 50; i++) { - phase = stream.generate(phase); + PCM pcm; + + stream.generate(); + pcm.write(); + stream.generate(); + + while (true) { + midi.wait_for_event(); + //pcm.wait_for_event(); + + 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; + return 0; } |