Skip to content

Instantly share code, notes, and snippets.

@liquidev
Created August 22, 2020 14:59
Show Gist options
  • Save liquidev/76d5953503f7c5367d8ceff9e8e36e8a to your computer and use it in GitHub Desktop.
Save liquidev/76d5953503f7c5367d8ceff9e8e36e8a to your computer and use it in GitHub Desktop.
Novation Launchpad I/O with ALSA
#include <stdio.h>
#include <string.h>
#include <alsa/asoundlib.h>
#define LOG(...) do { \
fprintf(stderr, __VA_ARGS__); \
fflush(stderr); \
} while (0)
int get_launchpad_client_id(snd_seq_t *seq_handle)
{
snd_seq_client_info_t *client_info;
int error;
int own_id = snd_seq_client_id(seq_handle);
int launchpad_id = -1;
snd_seq_client_info_malloc(&client_info);
snd_seq_client_info_set_client(client_info, -1);
while ((error = snd_seq_query_next_client(seq_handle, client_info)) >= 0) {
int id = snd_seq_client_info_get_client(client_info);
const char *name = snd_seq_client_info_get_name(client_info);
LOG("client %i: %s", id, name);
if (id == own_id) {
LOG(" -- that's me!");
}
if (strcmp(name, "Launchpad S") == 0) {
LOG(" -- that's the launchpad\n");
return id;
}
LOG("\n");
}
snd_seq_client_info_free(client_info);
return -1;
}
int get_launchpad_port_id(snd_seq_t *seq_handle, int launchpad_id)
{
snd_seq_port_info_t *port_info;
int error;
snd_seq_port_info_malloc(&port_info);
snd_seq_port_info_set_client(port_info, launchpad_id);
snd_seq_port_info_set_port(port_info, -1);
while ((error = snd_seq_query_next_port(seq_handle, port_info)) >= 0) {
int id = snd_seq_port_info_get_port(port_info);
const char *name = snd_seq_port_info_get_name(port_info);
LOG("port %i: %s", id, name);
if (strcmp(name, "Launchpad S MIDI 1") == 0) {
LOG(" -- that's the port\n");
return id;
}
LOG("\n");
}
snd_seq_port_info_free(port_info);
return -1;
}
void setup_launchpad_io(
snd_seq_t *seq_handle,
snd_seq_addr_t *lp_address,
int input_port, int output_port,
int input_event_queue, int output_event_queue
)
{
snd_seq_addr_t seq_input = {
.client = snd_seq_client_id(seq_handle),
.port = input_port,
};
snd_seq_addr_t seq_output = {
.client = snd_seq_client_id(seq_handle),
.port = output_port,
};
snd_seq_port_subscribe_t *sub_i, *sub_o;
// input
LOG(
"routing input from launchpad - %i:%i -> %i:%i\n",
lp_address->client, lp_address->port,
seq_input.client, seq_input.port
);
snd_seq_port_subscribe_malloc(&sub_i);
snd_seq_port_subscribe_set_sender(sub_i, lp_address);
snd_seq_port_subscribe_set_dest(sub_i, &seq_input);
snd_seq_port_subscribe_set_queue(sub_i, input_event_queue);
snd_seq_port_subscribe_set_time_update(sub_i, 1);
snd_seq_port_subscribe_set_time_real(sub_i, 1);
snd_seq_subscribe_port(seq_handle, sub_i);
snd_seq_port_subscribe_free(sub_i);
// output
LOG(
"routing output to launchpad - %i:%i -> %i:%i\n",
seq_output.client, seq_output.port,
lp_address->client, lp_address->port
);
snd_seq_port_subscribe_malloc(&sub_o);
snd_seq_port_subscribe_set_sender(sub_o, &seq_output);
snd_seq_port_subscribe_set_dest(sub_o, lp_address);
snd_seq_port_subscribe_set_queue(sub_o, output_event_queue);
snd_seq_subscribe_port(seq_handle, sub_o);
snd_seq_port_subscribe_free(sub_o);
}
int main(void)
{
snd_seq_t *seq_handle;
int error;
int input_port, output_port;
int input_event_queue, output_event_queue;
snd_seq_addr_t lp_address;
LOG("opening\n");
error = snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_DUPLEX, 0);
if (error < 0) {
return 1;
}
LOG("setting client name\n");
if (snd_seq_set_client_name(seq_handle, "ALSA MIDI testing")) {
return 2;
}
lp_address.client = get_launchpad_client_id(seq_handle);
lp_address.port = get_launchpad_port_id(seq_handle, lp_address.client);
LOG("launchpad address - %i:%i\n", lp_address.client, lp_address.port);
LOG("creating input port\n");
input_port = snd_seq_create_simple_port(
seq_handle, "launchpad_input",
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION
);
if (input_port < 0) {
return 3;
}
output_port = snd_seq_create_simple_port(
seq_handle, "launchpad_output",
SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ,
SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION
);
if (output_port < 0) {
return 3;
}
LOG("creating event queues\n");
input_event_queue = snd_seq_alloc_named_queue(seq_handle, "input_events");
if (input_event_queue < 0) {
return 4;
}
output_event_queue = snd_seq_alloc_named_queue(seq_handle, "output_events");
if (output_event_queue < 0) {
return 4;
}
setup_launchpad_io(
seq_handle, &lp_address,
input_port, output_port,
input_event_queue, output_event_queue
);
LOG("listening for events\n");
for (;;) {
snd_seq_event_t *event = 0;
snd_seq_event_input(seq_handle, &event);
if (event->type & SND_SEQ_EVENT_NOTEON) {
snd_seq_ev_note_t note = event->data.note;
LOG(
"note on: CH%i N%i V%i OV%i D%i\n",
note.channel, note.note,
note.velocity, note.off_velocity,
note.duration
);
snd_seq_ev_set_source(event, output_port);
snd_seq_ev_set_subs(event);
snd_seq_ev_set_direct(event);
snd_seq_event_output(seq_handle, event);
snd_seq_drain_output(seq_handle);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment