2 * The Ursary Audio Controls
4 * (c) 2014--2020 Martin Mares <mj@ucw.cz>
10 #include <ucw/clists.h>
11 #include <ucw/daemon.h>
13 #include <ucw/mainloop.h>
15 #include <ucw/stkstring.h>
30 * rotary red button green button
31 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32 * 0 sink PCH mute use headphones
35 * 3 desk brightness - desk lights on
36 * 4 MPD mute MPD play/pause
37 * 5 Albireo MPV mute MPD stop
38 * 6 Albireo other mute MPD prev
39 * 7 other machines mute MPD next
42 * slider light color temperature
45 #define PCH_SINK "alsa_output.pci-0000_00_1f.3.analog-stereo"
47 /*** Sink controls ***/
49 static double volume_from_pa(pa_volume_t vol)
51 return (double) vol / PA_VOLUME_NORM;
54 static pa_volume_t volume_to_pa(double vol)
56 return vol * PA_VOLUME_NORM + 0.0001;
59 static void update_ring_from_sink(int ring, const char *sink_name)
61 struct pulse_sink *s = pulse_sink_by_name(sink_name);
64 noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
65 noct_set_button(ring, 0);
71 noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
72 noct_set_button(ring, 1);
76 double vol = CLAMP(volume_from_pa(s->volume), 0, 1);
77 noct_set_ring(ring, RING_MODE_LEFT, CLAMP((int)(0x7f * vol), 12, 0x7f));
78 noct_set_button(ring, 0);
81 static void update_button_from_port(int button, const char *sink_name, const char *port_name)
83 struct pulse_sink *s = pulse_sink_by_name(sink_name);
86 noct_set_button(button, 0);
90 noct_set_button(button, !strcmp(s->active_port, port_name));
93 static void update_sink_from_rotary(int delta, const char *sink_name)
95 struct pulse_sink *s = pulse_sink_by_name(sink_name);
99 double vol = volume_from_pa(s->volume) + delta * 0.02;
100 pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, 1));
101 if (pavol == s->volume)
104 pa_cvolume_set(&cvol, s->channels, pavol);
106 DBG("## Setting volume of sink %s to %d", s->name, cvol.values[0]);
107 pulse_sink_set_volume(s->idx, &cvol);
110 static void update_sink_mute_from_button(int on, const char *sink_name)
115 struct pulse_sink *s = pulse_sink_by_name(sink_name);
119 DBG("## Setting mute of sink %s to %d", s->name, !s->mute);
120 pulse_sink_set_mute(s->idx, !s->mute);
123 static void update_port_from_button(int on, const char *sink_name, const char *port1, const char *port2)
128 struct pulse_sink *s = pulse_sink_by_name(sink_name);
132 const char *port = port1;
133 if (!strcmp(s->active_port, port1))
136 DBG("## Setting port of sink %s to %s", s->name, port);
137 pulse_sink_set_port(s->idx, port);
140 /*** Client controls ***/
148 static struct client_map client_map[] = {
149 { 4, "Music Player Daemon", "albireo", },
150 { 5, "mpv", "albireo", },
151 { 6, NULL, "albireo", },
155 #define CLIENT_MAP_SIZE ARRAY_SIZE(client_map)
157 struct group_config {
169 static struct group_config group_config[NUM_GROUPS] = {
170 [4] = { .enabled = 1, .range = 1.5 },
171 [5] = { .enabled = 1, .range = 1.5 },
172 [6] = { .enabled = 1, .range = 1.5 },
173 [7] = { .enabled = 1, .range = 1.5 },
176 static struct group_state group_state[NUM_GROUPS];
178 static void calc_groups(void)
180 bzero(group_state, sizeof(group_state));
182 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
184 s->noct_group_idx = -1;
186 if (s->client_idx < 0 || s->sink_idx < 0)
189 struct pulse_client *c = pulse_client_by_idx(s->client_idx);
193 for (uns i=0; i < CLIENT_MAP_SIZE; i++)
195 struct client_map *cm = &client_map[i];
196 if ((!cm->client || !strcmp(cm->client, c->name)) &&
197 (!cm->host || !strcmp(cm->host, c->host)))
200 struct group_state *gs = &group_state[g];
201 DBG("@@ Client #%d, sink input #%d -> group %d", s->client_idx, s->idx, g);
202 s->noct_group_idx = g;
203 gs->volume = MAX(gs->volume, s->volume);
204 gs->have_muted[!!s->mute] = 1;
211 static void update_groups(void)
215 for (uns i=0; i < NUM_GROUPS; i++)
217 struct group_config *gc = &group_config[i];
218 struct group_state *gs = &group_state[i];
222 DBG("@@ Group #%d: mute=%d/%d volume=%.3f/%.3f", i, gs->have_muted[0], gs->have_muted[1], volume_from_pa(gs->volume), gc->range);
224 if (!gs->have_muted[0] && !gs->have_muted[1])
226 noct_set_ring(i, RING_MODE_LEFT, 0);
227 noct_set_button(i, 0);
229 else if (!gs->have_muted[0])
231 noct_set_ring(i, RING_MODE_SINGLE_ON, 0x7f);
232 noct_set_button(i, 1);
236 double vol = CLAMP(volume_from_pa(gs->volume), 0, gc->range);
237 int val = 0x7f * vol / gc->range;
238 val = CLAMP(val, 12, 0x7f);
239 noct_set_ring(i, RING_MODE_LEFT, val);
240 noct_set_button(i, 0);
245 static void update_group_from_rotary(int i, int delta)
249 struct group_config *gc = &group_config[i];
250 struct group_state *gs = &group_state[i];
255 double vol = volume_from_pa(gs->volume) + delta*0.02;
256 pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, gc->range));
258 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
260 if (s->noct_group_idx == i && s->volume != pavol)
262 DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol);
264 pa_cvolume_set(&cvol, s->channels, pavol);
265 pulse_sink_input_set_volume(s->idx, &cvol);
270 static void update_group_from_button(int i, int on)
276 struct group_config *gc = &group_config[i];
277 struct group_state *gs = &group_state[i];
282 if (!gs->have_muted[0] && !gs->have_muted[1])
284 uns mute = !gs->have_muted[1];
286 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
288 if (s->noct_group_idx == i)
290 DBG("@@ Client #%d, sink input #%d: setting mute=%u", s->client_idx, s->idx, mute);
291 pulse_sink_input_set_mute(s->idx, mute);
296 #if 0 // Not used at the moment
298 static int find_touched_client(void)
302 for (uns i=0; i < NUM_GROUPS; i++)
303 if (group_config[i].enabled && noct_rotary_touched[i])
314 /*** Default sink controls ***/
316 #if 0 // Not mapped to any button at the moment
318 static const char *get_client_sink(int i)
320 const char *sink = NULL;
322 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
323 if (s->noct_group_idx == i)
325 struct pulse_sink *sk = (s->sink_idx >= 0) ? pulse_sink_by_idx(s->sink_idx) : NULL;
326 const char *ss = sk ? sk->name : NULL;
329 else if (strcmp(sink, ss))
335 static void update_default_sink(void)
337 int i = find_touched_client();
340 sink = get_client_sink(i);
342 sink = pulse_default_sink_name ? : "?";
344 if (!strcmp(sink, "ursarium"))
346 noct_set_button(8, 1);
347 noct_set_button(9, 0);
348 noct_set_button(10, 0);
350 else if (!strcmp(sink, "catarium"))
352 noct_set_button(8, 0);
353 noct_set_button(9, 1);
354 noct_set_button(10, 0);
356 else if (!strcmp(sink, "compress"))
358 noct_set_button(8, 0);
359 noct_set_button(9, 0);
360 noct_set_button(10, 1);
364 noct_set_button(8, 0);
365 noct_set_button(9, 0);
366 noct_set_button(10, 0);
370 static void update_default_sink_from_button(int button, int on)
375 int i = find_touched_client();
378 sink = get_client_sink(i);
380 sink = pulse_default_sink_name ? : "?";
382 const char *switch_to = NULL;
385 if (!strcmp(sink, "ursarium"))
386 switch_to = "burrow";
388 switch_to = "ursarium";
390 else if (button == 9)
392 if (!strcmp(sink, "catarium"))
393 switch_to = "burrow";
395 switch_to = "catarium";
397 else if (button == 10)
399 if (!strcmp(sink, "compress"))
400 switch_to = "burrow";
402 switch_to = "compress";
410 struct pulse_sink *sk = pulse_sink_by_name(switch_to);
414 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
415 if (s->noct_group_idx == i)
417 DBG("Moving input #%d to sink #%d", s->idx, sk->idx);
418 pulse_sink_input_move(s->idx, sk->idx);
423 DBG("Switching default sink to %s", switch_to);
424 pulse_server_set_default_sink(switch_to);
430 /*** MPD controls ***/
432 static bool mpd_flash_state;
434 static void mpd_flash_timeout(struct main_timer *t)
436 mpd_flash_state ^= 1;
437 noct_set_button(12, mpd_flash_state);
438 timer_add_rel(t, 500);
441 static struct main_timer mpd_flash_timer = {
442 .handler = mpd_flash_timeout,
445 static void update_mpd(void)
447 const char *state = mpd_get_player_state();
448 if (!strcmp(state, "play"))
450 noct_set_button(12, 1);
451 timer_del(&mpd_flash_timer);
453 else if (!strcmp(state, "pause"))
455 if (!timer_is_active(&mpd_flash_timer))
458 mpd_flash_timeout(&mpd_flash_timer);
463 noct_set_button(12, 0);
464 timer_del(&mpd_flash_timer);
468 static void mpd_button_timeout(struct main_timer *t)
475 static void update_mpd_from_button(int button UNUSED, int on)
477 static struct main_timer mpd_button_timer = {
478 .handler = mpd_button_timeout,
481 const char *state = mpd_get_player_state();
485 if (timer_is_active(&mpd_button_timer))
487 timer_del(&mpd_button_timer);
488 if (!strcmp(state, "play"))
493 else if (!strcmp(state, "pause"))
502 if (!strcmp(state, "stop"))
509 DBG("MPD starting button timer");
510 timer_add_rel(&mpd_button_timer, 1000);
516 static bool lights_on[2];
517 static double lights_brightness[2];
518 static double lights_temperature[2];
520 static void update_lights(void)
524 noct_set_ring(3, RING_MODE_SINGLE_ON, 0x7f);
525 noct_set_button(11, 0);
529 for (uint i=0; i<1; i++)
534 noct_set_ring(3, RING_MODE_LEFT, lights_brightness[i] * 127);
535 noct_set_button(11, 1);
537 double x = (exp(r*lights_brightness[i]) - 1) / (exp(r) - 1);
538 double t = lights_temperature[i];
539 double w = 2*x*(1-t);
541 warm = CLAMP((int)(255*w), 0, 255);
542 cold = CLAMP((int)(255*c), 0, 255);
546 noct_set_ring(3, RING_MODE_LEFT, 0);
547 noct_set_button(11, 0);
550 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);
551 dmx_set_pwm(2*i, warm);
552 dmx_set_pwm(2*i+1, cold);
556 static void update_lights_from_rotary(int ch, int delta)
559 lights_brightness[ch] = CLAMP(lights_brightness[ch] + 0.015*delta*abs(delta), 0., 1.);
563 static void update_lights_from_slider(int value)
565 lights_temperature[0] = value / 127.;
569 static void update_lights_from_button(int ch, int on)
573 lights_on[ch] = !lights_on[ch];
578 /*** Main update routines ***/
580 static struct main_timer update_timer;
581 static timestamp_t last_touch_time;
590 static enum update_state update_state;
592 static bool want_sleep_p(void)
594 CLIST_FOR_EACH(struct pulse_sink *, s, pulse_sink_list)
600 static void do_update(struct main_timer *t)
602 DBG("## UPDATE in state %u", update_state);
606 if (!noct_is_ready())
608 DBG("## UPDATE: Nocturn is not ready");
609 update_state = US_OFFLINE;
612 else if (update_state == US_OFFLINE)
614 DBG("## UPDATE: Going online");
615 update_state = US_ONLINE;
619 if (!pulse_is_ready())
621 DBG("## UPDATE: Pulse is not online");
622 if (update_state != US_PULSE_DEAD)
624 update_state = US_PULSE_DEAD;
626 for (int i=0; i<8; i++)
627 noct_set_button(i, 1);
631 else if (update_state == US_PULSE_DEAD)
633 DBG("## UPDATE: Waking up from the dead");
634 update_state = US_ONLINE;
643 bool want_sleep = want_sleep_p();
645 last_touch_time = main_get_now();
646 timestamp_t since_touch = last_touch_time ? main_get_now() - last_touch_time : 0;
647 timestamp_t sleep_in = 5000;
648 if (since_touch >= sleep_in)
650 DBG("UPDATE: Sleeping");
651 if (update_state == US_ONLINE)
653 update_state = US_SLEEPING;
655 noct_set_ring(8, RING_MODE_LEFT, 127);
661 if (update_state == US_SLEEPING)
663 DBG("UPDATE: Waking up");
664 update_state = US_ONLINE;
669 timestamp_t t = sleep_in - since_touch + 10;
670 DBG("UPDATE: Scheduling sleep in %d ms", (int) t);
671 timer_add_rel(&update_timer, t);
676 update_ring_from_sink(0, PCH_SINK);
677 update_button_from_port(8, PCH_SINK, "analog-output-headphones");
680 update_default_sink();
686 void schedule_update(void)
688 timer_add_rel(&update_timer, 10);
691 static bool prepare_notify(void)
693 if (!pulse_is_ready())
695 DBG("## NOTIFY: Pulse is not online");
699 last_touch_time = main_get_now();
700 if (update_state == US_SLEEPING)
702 DBG("## NOTIFY: Scheduling wakeup");
709 void notify_rotary(int rotary, int delta)
711 if (!prepare_notify())
717 update_sink_from_rotary(delta, PCH_SINK);
720 update_lights_from_rotary(0, delta);
723 update_lights_from_slider(delta);
726 update_group_from_rotary(rotary, delta);
730 void notify_button(int button, int on)
732 if (!prepare_notify())
738 update_sink_mute_from_button(on, PCH_SINK);
741 update_port_from_button(on, PCH_SINK, "analog-output-lineout", "analog-output-headphones");
746 update_default_sink_from_button(button, on);
750 update_lights_from_button(0, on);
753 update_mpd_from_button(button, on);
768 update_group_from_button(button, on);
772 void notify_touch(int rotary UNUSED, int on UNUSED)
774 if (!prepare_notify())
778 // Rotary touches switch meaning of LEDs, this is handled inside display updates
779 if (rotary >= 4 && rotary < 8)
784 /*** Main entry point ***/
789 static struct opt_section options = {
791 OPT_HELP("Control console for the Ursary"),
793 OPT_HELP("Options:"),
795 OPT_BOOL('d', "debug", debug, 0, "\tEnable debugging mode (no fork etc.)"),
796 OPT_BOOL(0, "no-fork", no_fork, 0, "\tDo not fork\n"),
801 static void sigterm_handler(struct main_signal *ms UNUSED)
806 static void daemon_body(struct daemon_params *dp)
809 update_timer.handler = do_update;
817 static struct main_signal term_sig = {
819 .handler = sigterm_handler,
821 signal_add(&term_sig);
823 msg(L_INFO, "Ursary daemon starting");
825 msg(L_INFO, "Ursary daemon shut down");
829 int main(int argc UNUSED, char **argv)
831 opt_parse(&options, argv+1);
835 struct daemon_params dp = {
836 .flags = ((debug || no_fork) ? DAEMON_FLAG_SIMULATE : 0),
837 .pid_file = "/run/ursaryd.pid",
838 .run_as_user = "ursary",
845 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
846 ls->levels = ~(1U << L_DEBUG);
847 log_set_default_stream(ls);
850 daemon_run(&dp, daemon_body);