Commit 42a7a945 authored by minus's avatar minus

Initial commit

What's working:
- read and write events from the ALSA sequencer
- subscribe to PulseAudio sink input changes
- map MIDI notes to buttons
- sketch of assigning buttons to actions
parents
build/compile_commands.json
\ No newline at end of file
project('panelcontrol', 'c',
version : '0.1',
default_options : ['warning_level=3', 'c_std=c11'])
add_global_arguments(['-Wno-unused'],
language: 'c')
pa = dependency('libpulse-mainloop-glib')
alsa = dependency('alsa')
glib = dependency('glib-2.0')
executable('panelcontrol',
'panelcontrol.c',
dependencies : [pa, alsa, glib],
install : true)
executable('roll',
'roll.c',
dependencies : [alsa])
#include <alsa/asoundlib.h>
#include <pulse/pulseaudio.h>
#include <pulse/glib-mainloop.h>
#include <glib.h>
static const char *CLIENT_NAME = "panelcontrol";
static const int button_notes[] = {
// top row
89,
90,
40,
41,
42,
43,
44,
45,
// bottom row
87,
88,
91,
92,
86,
93,
94,
95,
// layer buttons
84,
85,
};
enum button {
// top row
TOP1 = 89,
TOP2 = 90,
TOP3 = 40,
TOP4 = 41,
TOP5 = 42,
TOP6 = 43,
TOP7 = 44,
TOP8 = 45,
// bottom row
BOTTOM1 = 87,
BOTTOM2 = 88,
BOTTOM3 = 91,
BOTTOM4 = 92,
BOTTOM5 = 86,
BOTTOM6 = 93,
BOTTOM7 = 94,
BOTTOM8 = 95,
// layer buttons
LAYERA = 84,
LAYERB = 85,
};
static pa_glib_mainloop *xpa_main_loop;
static void xpa_on_sink_input_info(pa_context *ctx,
const pa_sink_input_info *info, int eol, void *userdata) {
if (!info) return;
fprintf(stderr, "pa sink input: name=%s volume=%ui sink=%ui mute=%i\n",
info->name, info->volume.values[0], info->sink, info->mute);
}
static void xpa_on_subscribe(pa_context *ctx,
pa_subscription_event_type_t type, uint32_t idx, void *userdata) {
int etype = type & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
const char *type_str = etype == PA_SUBSCRIPTION_EVENT_NEW ? "new" :
etype == PA_SUBSCRIPTION_EVENT_CHANGE ? "changed" :
etype == PA_SUBSCRIPTION_EVENT_REMOVE ? "removed" : "???";
if (etype != PA_SUBSCRIPTION_EVENT_REMOVE) {
pa_operation *op = pa_context_get_sink_input_info(ctx, idx,
&xpa_on_sink_input_info, NULL);
pa_operation_unref(op);
}
fprintf(stderr, "pa subscribe: %s\n", type_str);
}
void xpa_on_connect(pa_context *ctx, void *userdata) {
if (pa_context_get_state(ctx) == PA_CONTEXT_READY) {
// subscribe to PA sink input changes (volume, output, mute, new source)
pa_context_set_subscribe_callback(ctx, &xpa_on_subscribe, NULL);
pa_context_subscribe(ctx, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL);
}
}
void pa() {
// connect to PA
xpa_main_loop = pa_glib_mainloop_new(g_main_context_default());
pa_mainloop_api *api = pa_glib_mainloop_get_api(xpa_main_loop);
pa_context *ctx = pa_context_new(api, CLIENT_NAME);
pa_context_connect(ctx, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
pa_context_set_state_callback(ctx, &xpa_on_connect, NULL);
// -> update seq with that info (encoder LEDs and buttons)
// subscribe to seq changes
}
gboolean on_alsa_readable(GIOChannel *source, GIOCondition condition, gpointer data) {
fprintf(stdout, "source=%p cond=%i data=%p\n", (void *)source, condition, data);
snd_seq_t *seq = (snd_seq_t *)data;
snd_seq_event_t *ev;
while (1) {
int remaining = snd_seq_event_input(seq, &ev);
if (remaining == -EAGAIN) {
return TRUE;
} else if (remaining < 0) {
perror("Error reading sequencer input");
}
switch (ev->type) {
case SND_SEQ_EVENT_NOTEON:
fprintf(stdout, "button %i: %i\n", ev->data.note.note, ev->data.note.velocity > 0);
snd_seq_ev_set_subs(ev);
snd_seq_ev_set_direct(ev);
snd_seq_event_output_direct(seq, ev);
break;
case SND_SEQ_EVENT_CONTROLLER:
fprintf(stdout, "knob %i: %i\n", ev->data.control.param, ev->data.control.value);
break;
}
snd_seq_free_event(ev);
}
return TRUE;
}
snd_seq_t *alsa(int source) {
// connect to ALSA seq
snd_seq_t *seq;
if (snd_seq_open(&seq, "hw", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK) < 0) {
fprintf(stderr, "Error opening ALSA sequencer.\n");
exit(1);
}
snd_seq_set_client_name(seq, CLIENT_NAME);
int port = snd_seq_create_simple_port(seq, CLIENT_NAME,
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);
if (port < 0) {
fprintf(stderr, "Error creating sequencer port.\n");
exit(1);
}
// read from and write to panel device
snd_seq_connect_from(seq, port, source, 0);
snd_seq_connect_to(seq, port, source, 0);
// event loop
int npfd;
struct pollfd *pfd;
npfd = snd_seq_poll_descriptors_count(seq, POLLIN);
pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd));
snd_seq_poll_descriptors(seq, pfd, npfd, POLLIN);
for (int i = 0; i < npfd; ++i) {
GIOChannel *ch = g_io_channel_unix_new(pfd[i].fd);
guint evsrcid = g_io_add_watch(ch, pfd[i].events, &on_alsa_readable, seq);
}
return seq;
}
void toggle_mute(enum button button, void *userdata) {
const char *name = (const char *)userdata;
//xpa_sink_inputs_apply_by_name(name, &xpa_sink_input_set_volume, volume);
}
typedef void (*action_fn)(enum button, void *);
struct button_map_entry {
enum button button;
action_fn action;
void *userdata;
};
static const struct button_map_entry button_map[] = {
{.button = TOP1, .action = &toggle_mute, .userdata = "mpd pulse"}
};
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: %s CLIENT_ID\n", argv[0]);
exit(1);
}
int source = atoi(argv[1]);
pa();
snd_seq_t *seq = alsa(source);
GMainLoop *main_loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run(main_loop);
g_main_loop_unref(main_loop);
return 0;
}
#include <alsa/asoundlib.h>
#include <pulse/pulseaudio.h>
#include <stdbool.h>
static const char *CLIENT_NAME = "roller";
static const int button_notes[] = {
// top row
89,
90,
40,
41,
42,
43,
44,
45,
// bottom row
87,
88,
91,
92,
86,
93,
94,
95,
// layer buttons
84,
85,
};
static const int button_notes_len = sizeof(button_notes)/sizeof(button_notes[0]);
void alsa(int source) {
// connect to ALSA seq
snd_seq_t *seq;
if (snd_seq_open(&seq, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
fprintf(stderr, "Error opening ALSA sequencer.\n");
exit(1);
}
snd_seq_set_client_name(seq, CLIENT_NAME);
int port = snd_seq_create_simple_port(seq, CLIENT_NAME,
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);
if (port < 0) {
fprintf(stderr, "Error creating sequencer port.\n");
exit(1);
}
snd_seq_connect_from(seq, port, source, 0);
snd_seq_connect_to(seq, port, source, 0);
int n = 8;
for (int t = 0; ; t = (t+1)%(2*n)) {
for (int i = 0; i < n; ++i) {
snd_seq_event_t ev;
snd_seq_ev_clear(&ev);
snd_seq_ev_set_source(&ev, port);
snd_seq_ev_set_subs(&ev);
snd_seq_ev_set_direct(&ev);
bool on = (t-i) > 0 && (t-i) < 9;
snd_seq_ev_set_noteon(&ev, 0, button_notes[i], on ? 127 : 0);
snd_seq_event_output_direct(seq, &ev);
snd_seq_ev_set_noteon(&ev, 0, button_notes[n+i], on ? 127 : 0);
snd_seq_event_output_direct(seq, &ev);
}
fprintf(stderr, ".");
usleep(100000);
}
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: %s CLIENT_ID\n", argv[0]);
exit(1);
}
int source = atoi(argv[1]);
alsa(source);
return 0;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment