* see https://github.com/dewert/nocturn-linux-midi for inspiration.
*/
-#define LOCAL_DEBUG
+#undef LOCAL_DEBUG
#include <ucw/lib.h>
#include <ucw/bitops.h>
int r = cmd - 0x40;
int delta = (arg < 0x40 ? arg : arg - 0x80);
DBG("Noct: Rotary %d = %d", r, delta);
+ notify_rotary(r, delta);
continue;
}
break;
{
int delta = (arg < 0x40 ? arg : arg - 0x80);
DBG("Noct: Center = %d", delta);
+ notify_rotary(8, delta);
continue;
}
break;
int b = cmd - 0x70;
int state = !!arg;
DBG("Noct: Button %d = %d", b, state);
+ notify_button(b, state);
continue;
}
break;
}
static byte noct_button_state[16];
-static byte noct_ring_mode[8]; // 0=from-min, 1=from-max, 2=from-mid-right, 3=from-mid-both, 4=single-on, 5=single-off
+static byte noct_ring_mode[8]; // RING_MODE_xxx
static byte noct_ring_val[9];
static uns noct_dirty_button;
if (xfer->status != LIBUSB_TRANSFER_COMPLETED)
{
msg(L_ERROR, "USB write failed with status %d", xfer->status);
+ // FIXME: Handle the error in a meaningful way
return;
}
+ if (len < xfer->length)
+ msg(L_ERROR, "USB partial write: %d out of %d", len, xfer->length);
noct_write_pending = 0;
noct_sched_write();
}
}
+void noct_set_ring(int ring, int mode, int val)
+{
+ ASSERT(ring >= 0 && ring <= 8);
+ ASSERT(val >= 0 && val <= 0x7f);
+ ASSERT(mode >= 0 && mode <= 5);
+ ASSERT(ring < 8 || !mode);
+ if (noct_ring_mode[ring] != mode)
+ {
+ noct_ring_mode[ring] = mode;
+ noct_dirty_ring_mode |= 1U << ring;
+ noct_dirty_ring_val |= 1U << ring; // HW needs to re-send the value
+ }
+ if (noct_ring_val[ring] != val)
+ {
+ noct_ring_val[ring] = val;
+ noct_dirty_ring_val |= 1U << ring;
+ }
+ noct_sched_write();
+}
+
+void noct_set_button(int button, int val)
+{
+ ASSERT(button >= 0 && button < 16);
+ ASSERT(val == 0 || val == 1);
+ if (noct_button_state[button] != val)
+ {
+ noct_button_state[button] = val;
+ noct_dirty_button |= 1U << button;
+ noct_sched_write();
+ }
+}
+
static void noct_write_init(void)
{
DBG("Noct: Write init");
noct_sched_write();
}
-void noct_init(void)
+static struct main_timer noct_connect_timer;
+
+static void noct_connect(struct main_timer *t)
{
+ timer_del(t);
+ msg(L_DEBUG, "Looking for Nocturn");
int err;
- if ((err = libusb_init(&usb_ctx)) < 0)
- die("libusb_init failed: error %d", err);
- libusb_set_debug(usb_ctx, 3);
-
libusb_device **dev_list;
libusb_device *found_dev = NULL;
ssize_t len = libusb_get_device_list(usb_ctx, &dev_list);
{
msg(L_DEBUG, "Found device: bus %d, addr %d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
if (found_dev)
- die("Multiple Nocturn devices found. Please fix me to handle it.");
+ {
+ msg(L_ERROR, "Multiple Nocturn devices found. Using the first one.");
+ break;
+ }
found_dev = libusb_ref_device(dev);
}
}
libusb_free_device_list(dev_list, 1);
if (!found_dev)
- die("No Nocturn device found");
+ {
+ msg(L_INFO, "No Nocturn device found");
+ timer_add_rel(t, 5000);
+ return;
+ }
- msg(L_DEBUG, "Initializing device");
+ msg(L_DEBUG, "Initializing Nocturn");
if ((err = libusb_open(found_dev, &usb_dev)) < 0)
die("libusb_open failed: error %d", err);
libusb_interrupt_transfer(usb_dev, 0x02, xxx, 2, &done, 5000);
#endif
- DBG("USB: Connecting libusb to mainloop");
+ noct_read_init();
+ noct_write_init();
+ schedule_update();
+}
+
+bool noct_is_ready(void)
+{
+ return !!usb_dev; // FIXME
+}
+
+void noct_init(void)
+{
+ int err;
+
+ // Initialize libusb
+ if ((err = libusb_init(&usb_ctx)) < 0)
+ die("libusb_init failed: error %d", err);
+ libusb_set_debug(usb_ctx, 3);
+
+ // Connect libusb to UCW mainloop
if (!libusb_pollfds_handle_timeouts(usb_ctx))
die("Unsupported version of libusb, please fix me");
usb_added_fd(fds[i]->fd, fds[i]->events, NULL);
free(fds);
- noct_read_init();
- noct_write_init();
+ // Schedule search for the Nocturn
+ noct_connect_timer.handler = noct_connect;
+ timer_add_rel(&noct_connect_timer, 0);
}
#include "ursaryd.h"
-/*
- * Interface to PulseAudio
- */
+/*** Interface to PulseAudio ***/
static pa_context *pulse_ctx;
static void pulse_dump(void);
-static void pulse_schedule_update(void);
enum pulse_state {
PS_OFFLINE,
#define PULSE_ASYNC_RUN(name, ...) do { struct pulse_op *_op = pulse_op_new(); _op->o = name(__VA_ARGS__, _op); } while (0)
#define PULSE_ASYNC_INIT_RUN(name, ...) do { struct pulse_op *_op = pulse_op_new(); _op->is_init = 1; _op->o = name(__VA_ARGS__, _op); } while (0)
+static void pulse_success_cb(pa_context *ctx UNUSED, int success, void *userdata)
+{
+ if (!success)
+ msg(L_ERROR, "Pulse: Failure reported");
+ pulse_op_done(userdata);
+}
+
static void pulse_dump_proplist(pa_proplist *pl UNUSED)
{
-#ifdef LOCAL_DEBUG
+#if 0
void *iterator = NULL;
const char *key;
if (op->is_init)
{
PULSE_STATE(PS_ONLINE);
- pulse_schedule_update();
+ schedule_update();
}
pulse_op_done(op);
return;
s->sink_idx = i->sink;
s->volume = pa_cvolume_avg(&i->volume);
s->mute = i->mute;
- pulse_schedule_update();
+ schedule_update();
}
static void pulse_sink_input_gone(int idx)
DBG("Pulse: REMOVE SINK INPUT #%d", idx);
struct pulse_sink_input *s = pulse_sink_input_lookup(idx);
pulse_sink_input_remove(s);
- pulse_schedule_update();
+ schedule_update();
}
struct pulse_sink {
s->volume = pa_cvolume_avg(&i->volume);
s->base_volume = i->base_volume;
s->mute = i->mute;
- pulse_schedule_update();
+ schedule_update();
}
static void pulse_sink_gone(int idx)
DBG("Pulse: REMOVE SINK #%d", idx);
struct pulse_sink *s = pulse_sink_lookup(idx);
pulse_sink_remove(s);
- pulse_schedule_update();
+ schedule_update();
+}
+
+static struct pulse_sink *pulse_sink_by_name(const char *name)
+{
+ HASH_FOR_ALL(pulse_sink, s)
+ {
+ if (!strcmp(s->name, name))
+ return s;
+ }
+ HASH_END_FOR;
+ return NULL;
}
struct pulse_client {
struct pulse_client *c = pulse_client_lookup(i->index);
SET_STRING(c->name, i->name);
SET_STRING(c->host, host);
- pulse_schedule_update();
+ schedule_update();
}
static void pulse_client_gone(int idx)
DBG("Pulse: REMOVE CLIENT #%d", idx);
struct pulse_client *c = pulse_client_lookup(idx);
pulse_client_remove(c);
- pulse_schedule_update();
+ schedule_update();
}
static void pulse_subscribe_done_cb(pa_context *ctx, int success, void *userdata)
{
PULSE_STATE(PS_OFFLINE);
pulse_op_cancel_all();
+ // FIXME: Reset all data structures
}
}
}
HASH_END_FOR;
}
-static struct main_timer pulse_update_timer;
+static void pulse_init(void)
+{
+ pmain_init();
+ clist_init(&pulse_op_list);
+ pulse_client_init();
+ pulse_sink_init();
+ pulse_sink_input_init();
-static void pulse_update(struct main_timer *t)
+ pulse_ctx = pa_context_new(&pmain_api, "ursaryd");
+ pa_context_set_state_callback(pulse_ctx, pulse_state_cb, NULL);
+ pa_context_connect(pulse_ctx, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
+}
+
+/*** High-level logic ***/
+
+static struct main_timer update_timer;
+
+static void update_ring_from_sink(int ring, const char *sink_name)
+{
+ struct pulse_sink *s = pulse_sink_by_name(sink_name);
+ if (!s)
+ {
+ noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
+ noct_set_button(ring, 0);
+ return;
+ }
+
+ if (s->mute)
+ {
+ noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
+ noct_set_button(ring, 1);
+ return;
+ }
+
+ double vol = pa_sw_volume_to_linear(s->volume);
+ vol = CLAMP(vol, 0, 1);
+ int val = 0x7f * vol;
+ val = CLAMP(val, 0, 0x7f);
+ noct_set_ring(ring, RING_MODE_LEFT, val);
+ noct_set_button(ring, 0);
+}
+
+static void do_update(struct main_timer *t)
{
timer_del(t);
+ if (pulse_state != PS_ONLINE)
+ {
+ DBG("## UPDATE: Pulse is not online");
+ return;
+ }
+ if (!noct_is_ready())
+ {
+ DBG("## UPDATE: Nocturn is not ready");
+ return;
+ }
+
DBG("## UPDATE");
pulse_dump();
+
+ update_ring_from_sink(0, "ursarium");
+ update_ring_from_sink(1, "catarium");
}
-static void pulse_schedule_update(void)
+void schedule_update(void)
{
- timer_add_rel(&pulse_update_timer, 500);
+ timer_add_rel(&update_timer, 10); // FIXME
}
-static void pulse_init(void)
+static void update_sink_from_rotary(int delta, const char *sink_name)
{
- pmain_init();
- clist_init(&pulse_op_list);
- pulse_client_init();
- pulse_sink_init();
- pulse_sink_input_init();
- pulse_update_timer.handler = pulse_update;
+ struct pulse_sink *s = pulse_sink_by_name(sink_name);
+ if (!s)
+ return;
+
+ double vol = pa_sw_volume_to_linear(s->volume);
+ vol += delta * 0.02;
+ vol = CLAMP(vol, 0, 1);
+ pa_cvolume cvol;
+ pa_cvolume_set(&cvol, 2, pa_sw_volume_from_linear(vol));
+
+ DBG("## Setting volume of sink %s to %d", s->name, cvol.values[0]);
+ PULSE_ASYNC_RUN(pa_context_set_sink_volume_by_index, pulse_ctx, s->idx, &cvol, pulse_success_cb);
+}
- pulse_ctx = pa_context_new(&pmain_api, "ursaryd");
- pa_context_set_state_callback(pulse_ctx, pulse_state_cb, NULL);
- pa_context_connect(pulse_ctx, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
+void notify_rotary(int rotary, int delta)
+{
+ if (pulse_state != PS_ONLINE)
+ {
+ DBG("## NOTIFY: Pulse is not inline");
+ return;
+ }
+
+ switch (rotary)
+ {
+ case 0:
+ update_sink_from_rotary(delta, "ursarium");
+ break;
+ case 1:
+ update_sink_from_rotary(delta, "catarium");
+ break;
+ case 8:
+ update_sink_from_rotary(delta, "ursarium");
+ update_sink_from_rotary(delta, "catarium");
+ break;
+ }
+}
+
+static void update_sink_mute_from_button(int on, const char *sink_name)
+{
+ if (!on)
+ return;
+
+ struct pulse_sink *s = pulse_sink_by_name(sink_name);
+ if (!s)
+ return;
+
+ DBG("## Setting mute of sink %s to %d", s->name, !s->mute);
+ PULSE_ASYNC_RUN(pa_context_set_sink_mute_by_index, pulse_ctx, s->idx, !s->mute, pulse_success_cb);
+}
+
+void notify_button(int button, int on)
+{
+ if (pulse_state != PS_ONLINE)
+ {
+ DBG("## NOTIFY: Pulse is not inline");
+ return;
+ }
+
+ switch (button)
+ {
+ case 0:
+ update_sink_mute_from_button(on, "ursarium");
+ break;
+ case 1:
+ update_sink_mute_from_button(on, "catarium");
+ break;
+ }
}
int main(int argc UNUSED, char **argv)
{
log_init(argv[0]);
main_init();
+ update_timer.handler = do_update;
- // msg(L_INFO, "Initializing Nocturn");
- // noct_init();
+ noct_init();
msg(L_INFO, "Initializing PulseAudio");
pulse_init();