/*
* The Ursary Audio Controls
*
- * (c) 2014 Martin Mares <mj@ucw.cz>
+ * (c) 2014--2020 Martin Mares <mj@ucw.cz>
*/
-#undef LOCAL_DEBUG
+#define LOCAL_DEBUG
#include <ucw/lib.h>
#include <ucw/clists.h>
#include <ucw/opt.h>
#include <ucw/stkstring.h>
+#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include "ursaryd.h"
+#include "usb.h"
/*
* Map of all controls
*
* rotary red button green button
- * 0 sink Brum mute -
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * 0 sink PCH mute use headphones
* 1 - - -
* 2 - - -
- * 3 - - -
- * 4 MPD mute play/pause/stop
- * 5 Albireo MPV mute -
- * 6 Albireo other mute -
- * 7 other machines mute -
+ * 3 desk brightness - desk lights on
+ * 4 MPD mute MPD play/pause
+ * 5 Albireo MPV mute MPD stop
+ * 6 Albireo other mute MPD prev
+ * 7 other machines mute MPD next
*
* center -
- * slider -
+ * slider light color temperature
*/
+#define PCH_SINK "alsa_output.pci-0000_00_1f.3.analog-stereo"
+
/*** Sink controls ***/
static double volume_from_pa(pa_volume_t vol)
noct_set_button(ring, 0);
}
+static void update_button_from_port(int button, const char *sink_name, const char *port_name)
+{
+ struct pulse_sink *s = pulse_sink_by_name(sink_name);
+ if (!s)
+ {
+ noct_set_button(button, 0);
+ return;
+ }
+
+ noct_set_button(button, !strcmp(s->active_port, port_name));
+}
+
static void update_sink_from_rotary(int delta, const char *sink_name)
{
struct pulse_sink *s = pulse_sink_by_name(sink_name);
pulse_sink_set_mute(s->idx, !s->mute);
}
+static void update_port_from_button(int on, const char *sink_name, const char *port1, const char *port2)
+{
+ if (!on)
+ return;
+
+ struct pulse_sink *s = pulse_sink_by_name(sink_name);
+ if (!s)
+ return;
+
+ const char *port = port1;
+ if (!strcmp(s->active_port, port1))
+ port = port2;
+
+ DBG("## Setting port of sink %s to %s", s->name, port);
+ pulse_sink_set_port(s->idx, port);
+}
+
/*** Client controls ***/
struct client_map {
}
}
+#if 0 // Not used at the moment
+
static int find_touched_client(void)
{
int touched = -1;
return touched;
}
+#endif
+
/*** Default sink controls ***/
+#if 0 // Not mapped to any button at the moment
+
static const char *get_client_sink(int i)
{
const char *sink = NULL;
}
}
+#endif
+
/*** MPD controls ***/
static bool mpd_flash_state;
}
}
+/*** Lights ***/
+
+static bool lights_on[2];
+static double lights_brightness[2];
+static double lights_temperature[2];
+
+static void update_lights(void)
+{
+ if (!dmx_is_ready())
+ {
+ noct_set_ring(3, RING_MODE_SINGLE_ON, 0x7f);
+ noct_set_button(11, 0);
+ return;
+ }
+
+ for (uint i=0; i<1; i++)
+ {
+ uint warm, cold;
+ if (lights_on[i])
+ {
+ noct_set_ring(3, RING_MODE_LEFT, lights_brightness[i] * 127);
+ noct_set_button(11, 1);
+ double r = 2;
+ double x = (exp(r*lights_brightness[i]) - 1) / (exp(r) - 1);
+ double t = lights_temperature[i];
+ double w = 2*x*(1-t);
+ double c = 2*x*t;
+ warm = CLAMP((int)(255*w), 0, 255);
+ cold = CLAMP((int)(255*c), 0, 255);
+ }
+ else
+ {
+ noct_set_ring(3, RING_MODE_LEFT, 0);
+ noct_set_button(11, 0);
+ warm = cold = 0;
+ }
+ DBG("Lights[%d]: on=%d bri=%.3f temp=%.3f -> warm=%d cold=%d", i, lights_on[i], lights_brightness[i], lights_temperature[i], warm, cold);
+ dmx_set_pwm(2*i, warm);
+ dmx_set_pwm(2*i+1, cold);
+ }
+}
+
+static void update_lights_from_rotary(int ch, int delta)
+{
+ if (lights_on[ch])
+ lights_brightness[ch] = CLAMP(lights_brightness[ch] + 0.015*delta*abs(delta), 0., 1.);
+ update_lights();
+}
+
+static void update_lights_from_slider(int value)
+{
+ lights_temperature[0] = value / 127.;
+ update_lights();
+}
+
+static void lights_button_timeout(struct main_timer *t)
+{
+ int ch = (uintptr_t) t->data;
+ DBG("Lights[%d]: Full throttle!", ch);
+ timer_del(t);
+ lights_on[ch] = 1;
+ lights_brightness[ch] = 1;
+ update_lights();
+}
+
+static void update_lights_from_button(int ch, int on)
+{
+ static struct main_timer lights_button_timer[2] = {{
+ .handler = lights_button_timeout,
+ .data = (void *)(uintptr_t) 0,
+ },{
+ .handler = lights_button_timeout,
+ .data = (void *)(uintptr_t) 1,
+ }};
+
+ if (on)
+ {
+ lights_on[ch] = !lights_on[ch];
+ update_lights();
+ timer_add_rel(&lights_button_timer[ch], 1000);
+ }
+ else
+ timer_del(&lights_button_timer[ch]);
+}
+
/*** Main update routines ***/
static struct main_timer update_timer;
}
// Everything normal
- update_ring_from_sink(0, "alsa_output.brum.analog-stereo");
+ update_ring_from_sink(0, PCH_SINK);
+ update_button_from_port(8, PCH_SINK, "analog-output-headphones");
update_groups();
#if 0
update_default_sink();
#endif
update_mpd();
+ update_lights();
}
void schedule_update(void)
switch (rotary)
{
case 0:
- update_sink_from_rotary(delta, "alsa_output.brum.analog-stereo");
+ update_sink_from_rotary(delta, PCH_SINK);
+ break;
+ case 3:
+ update_lights_from_rotary(0, delta);
+ break;
+ case 9:
+ update_lights_from_slider(delta);
break;
default:
update_group_from_rotary(rotary, delta);
switch (button)
{
case 0:
- update_sink_mute_from_button(on, "alsa_output.brum.analog-stereo");
+ update_sink_mute_from_button(on, PCH_SINK);
break;
-#if 0
case 8:
+ update_port_from_button(on, PCH_SINK, "analog-output-lineout", "analog-output-headphones");
+ break;
+#if 0
case 9:
case 10:
update_default_sink_from_button(button, on);
break;
#endif
+ case 11:
+ update_lights_from_button(0, on);
+ break;
case 12:
update_mpd_from_button(button, on);
break;
+ case 13:
+ if (on)
+ mpd_stop();
+ break;
+ case 14:
+ if (on)
+ mpd_prev();
+ break;
+ case 15:
+ if (on)
+ mpd_next();
+ break;
default:
update_group_from_button(button, on);
}
}
-void notify_touch(int rotary, int on UNUSED)
+void notify_touch(int rotary UNUSED, int on UNUSED)
{
if (!prepare_notify())
return;
+#if 0
// Rotary touches switch meaning of LEDs, this is handled inside display updates
if (rotary >= 4 && rotary < 8)
schedule_update();
+#endif
}
/*** Main entry point ***/
static int debug;
+static int no_fork;
static struct opt_section options = {
OPT_ITEMS {
OPT_HELP("Options:"),
OPT_HELP_OPTION,
OPT_BOOL('d', "debug", debug, 0, "\tEnable debugging mode (no fork etc.)"),
+ OPT_BOOL(0, "no-fork", no_fork, 0, "\tDo not fork\n"),
OPT_END
}
};
main_init();
update_timer.handler = do_update;
+ usb_init();
noct_init();
+ dmx_init();
pulse_init();
mpd_init();
unsetenv("HOME");
struct daemon_params dp = {
- .flags = (debug ? DAEMON_FLAG_SIMULATE : 0),
+ .flags = ((debug || no_fork) ? DAEMON_FLAG_SIMULATE : 0),
.pid_file = "/run/ursaryd.pid",
.run_as_user = "ursary",
};