Skip to content

Instantly share code, notes, and snippets.

@liquidev
Created August 22, 2020 20:25
Show Gist options
  • Save liquidev/6007ff8169768bc9dd02cfe29c3a9916 to your computer and use it in GitHub Desktop.
Save liquidev/6007ff8169768bc9dd02cfe29c3a9916 to your computer and use it in GitHub Desktop.
Novation Launchpad S to keyboard/mouse controls script, for torturing yourself
#include <stdio.h>
#include <string.h>
#include <alsa/asoundlib.h>
#include <math.h>
#include <unistd.h>
#include <xdo.h>
#define LOG(...) do { \
fprintf(stderr, __VA_ARGS__); \
fflush(stderr); \
} while (0)
#define LEN(x) (sizeof((x)) / sizeof((x)[0]))
enum action_type
{
action_key,
action_mouse_move,
action_mouse_click
};
enum mouse_button
{
mouse_button_left = 1,
mouse_button_middle = 2,
mouse_button_right = 3,
mouse_wheel_up = 4,
mouse_wheel_down = 5,
};
enum direction
{
direction_right,
direction_down,
direction_left,
direction_up,
direction_len,
};
struct color
{
unsigned char r, g;
};
struct action
{
int note;
enum action_type type;
union {
const char *key;
enum direction dir;
enum mouse_button click;
} data;
struct color normal, pressed;
};
const struct action action_list[] = {
// W, S, A, D, Ctrl, Space
{ 50, action_key, { .key = "w" }, {1, 0}, {3, 0} },
{ 66, action_key, { .key = "s" }, {1, 0}, {3, 0} },
{ 65, action_key, { .key = "a" }, {1, 0}, {3, 0} },
{ 67, action_key, { .key = "d" }, {1, 0}, {3, 0} },
{ 51, action_key, { .key = "e" }, {1, 0}, {3, 0} },
{ 80, action_key, { .key = "Control_L" }, {1, 0}, {3, 0} },
{ 116, action_key, { .key = "space" }, {1, 0}, {3, 0} },
{ 32, action_key, { .key = "Escape" }, {1, 0}, {3, 0} },
// mouse up, down, left, right
{ 54, action_mouse_move, { .dir = direction_up }, {0, 1}, {0, 3} },
{ 70, action_mouse_move, { .dir = direction_down }, {0, 1}, {0, 3} },
{ 69, action_mouse_move, { .dir = direction_left }, {0, 1}, {0, 3} },
{ 71, action_mouse_move, { .dir = direction_right }, {0, 1}, {0, 3} },
// mouse click left, right, middle
{ 53, action_mouse_click, { .click = mouse_button_left }, {2, 1}, {3, 2} },
{ 55, action_mouse_click, { .click = mouse_button_right }, {2, 1}, {3, 2} },
{ 38, action_mouse_click, { .click = mouse_button_middle }, {2, 1}, {3, 2} },
{ 56, action_mouse_click, { .click = mouse_wheel_up }, {1, 2}, {1, 3} },
{ 72, action_mouse_click, { .click = mouse_wheel_down }, {1, 2}, {1, 3} },
};
struct action_table {
const struct action *key[128];
const struct action *mouse_move[128];
const struct action *mouse_click[128];
};
unsigned char color_to_velocity(struct color c)
{
return (c.r & 0x3) | (c.g & 0x3) << 4;
}
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);
}
void load_actions(
const struct action action_list[], int n_actions,
struct action_table *out_actions
)
{
memset(out_actions, 0, sizeof(*out_actions));
for (int i = 0; i < n_actions; ++i) {
const struct action *action = &action_list[i];
switch (action->type) {
case action_key:
out_actions->key[action->note] = action;
break;
case action_mouse_move:
out_actions->mouse_move[action->note] = action;
break;
case action_mouse_click:
out_actions->mouse_click[action->note] = action;
break;
}
}
}
void initial_lights(
const struct action action_list[], int n_actions,
snd_seq_t *seq_handle, int output_port
)
{
snd_seq_event_t event;
snd_seq_ev_set_source(&event, output_port);
snd_seq_ev_set_subs(&event);
snd_seq_ev_set_direct(&event);
// reset
snd_seq_ev_set_controller(&event, 0, 0, 0);
snd_seq_event_output(seq_handle, &event);
for (int i = 0; i < n_actions; ++i) {
const struct action *action = &action_list[i];
snd_seq_ev_set_noteon(
&event,
/* channel */ 0,
action->note,
color_to_velocity(action->normal)
);
snd_seq_event_output(seq_handle, &event);
}
snd_seq_drain_output(seq_handle);
}
void process_event(
struct action_table *actions, snd_seq_event_t *in_event,
snd_seq_t *seq_handle, int output_port,
xdo_t *xdo, char mouse_moves[]
)
{
if (in_event->type & SND_SEQ_EVENT_NOTEON) {
snd_seq_event_t out_event;
snd_seq_ev_note_t *note_event = &in_event->data.note;
int note = note_event->note;
int velocity = note_event->velocity;
char down = velocity > 0;
const struct action *action;
if (note_event->channel != 0) return;
LOG("-- N%i V%i\n", note, velocity);
if ((action = actions->key[note]) != 0) {
const char *key = action->data.key;
LOG("key action\n");
if (down) {
xdo_send_keysequence_window_down(xdo, CURRENTWINDOW, key, 0);
} else {
xdo_send_keysequence_window_up(xdo, CURRENTWINDOW, key, 0);
}
} else if ((action = actions->mouse_move[note]) != 0) {
LOG("mouse move action\n");
mouse_moves[action->data.dir] = down;
} else if ((action = actions->mouse_click[note]) != 0) {
int mouse_button = action->data.click;
LOG("mouse click action\n");
if (mouse_button == mouse_wheel_up || mouse_button == mouse_wheel_down) {
xdo_click_window(xdo, CURRENTWINDOW, mouse_button);
} else {
if (down) {
xdo_mouse_down(xdo, CURRENTWINDOW, mouse_button);
} else {
xdo_mouse_up(xdo, CURRENTWINDOW, mouse_button);
}
}
} else {
LOG("no action\n");
return;
}
snd_seq_ev_set_source(&out_event, output_port);
snd_seq_ev_set_subs(&out_event);
snd_seq_ev_set_direct(&out_event);
unsigned char color =
color_to_velocity(down ? action->pressed : action->normal);
snd_seq_ev_set_noteon(&out_event, 0, note, color);
snd_seq_event_output(seq_handle, &out_event);
snd_seq_drain_output(seq_handle);
LOG("- A* %p\n", action);
}
}
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;
struct action_table actions;
xdo_t *xdo = xdo_new(0);
char mouse_moves[direction_len];
float mouse_move_speed = 0.0;
load_actions(action_list, LEN(action_list), &actions);
memset(mouse_moves, 0, sizeof(mouse_moves));
LOG("opening sequencer\n");
error = snd_seq_open(
&seq_handle, "default",
SND_SEQ_OPEN_DUPLEX,
SND_SEQ_NONBLOCK
);
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 IO ports\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
);
initial_lights(action_list, LEN(action_list), seq_handle, output_port);
LOG("listening for events\n");
for (;;) {
snd_seq_event_t *in_event = 0;
if (snd_seq_event_input(seq_handle, &in_event) >= 0) {
process_event(
&actions,
in_event,
seq_handle, output_port,
xdo, mouse_moves
);
}
char mouse_moving = 0;
for (int i = 0; i < direction_len; ++i) {
if (mouse_moves[i]) {
mouse_moving = 1;
break;
}
}
mouse_move_speed = mouse_moving ? fmax(3.0, mouse_move_speed + 0.1) : 0.0;
if (mouse_moves[direction_right]) {
xdo_move_mouse_relative(xdo, mouse_move_speed, 0);
}
if (mouse_moves[direction_down]) {
xdo_move_mouse_relative(xdo, 0, mouse_move_speed);
}
if (mouse_moves[direction_left]) {
xdo_move_mouse_relative(xdo, -mouse_move_speed, 0);
}
if (mouse_moves[direction_up]) {
xdo_move_mouse_relative(xdo, 0, -mouse_move_speed);
}
usleep(16666);
}
xdo_free(xdo);
}
@liquidev
Copy link
Author

compile with:

cc -lasound -lxdo -lm main.c -o torture_device

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment