summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2025-01-02 20:10:10 +0100
committerRoland Reichwein <mail@reichwein.it>2025-01-02 20:10:10 +0100
commitddc31844940b9afedb352643349649fac38269ef (patch)
tree3bb6e5b63b1721d5d511f06e9dc6c9469b086f53
parent083e46d5d23d2105e2df1adc769700fb579c9597 (diff)
Fix click
-rw-r--r--README.md6
-rw-r--r--alsa.cpp315
2 files changed, 281 insertions, 40 deletions
diff --git a/README.md b/README.md
index fbf72d6..190d9a2 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/alsa.cpp b/alsa.cpp
index 7df0805..9aca9be 100644
--- a/alsa.cpp
+++ b/alsa.cpp
@@ -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;
}