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 switch to PCH
33 * 1 sink BT mute switch to BT
34 * 2 ceil brightness mic non-mute ceiling lights on
35 * 3 desk brightness - desk lights on
36 * 4 Albireo MPD mute MPD play/pause
37 * 5 Albireo MPV mute MPD stop
38 * 6 Albireo Zoom mute MPD prev
39 * 7 eveyrhing else mute MPD next
42 * slider light color temperature
45 #define PCH_SINK "alsa_output.pci-0000_00_1f.3.analog-stereo"
46 #define BT_SINK "bluez_sink.CC_98_8B_D0_8C_06.a2dp_sink"
47 #define LOGI_SOURCE "alsa_input.usb-046d_Logitech_Webcam_C925e_EF163C5F-02.analog-stereo"
49 /*** Sink controls ***/
51 static double volume_from_pa(pa_volume_t vol)
53 return (double) vol / PA_VOLUME_NORM;
56 static pa_volume_t volume_to_pa(double vol)
58 return vol * PA_VOLUME_NORM + 0.0001;
61 static void update_ring_from_sink(int ring, const char *sink_name)
63 struct pulse_sink *s = pulse_sink_by_name(sink_name);
66 noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
67 noct_set_button(ring, 0);
73 noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
74 noct_set_button(ring, 1);
78 double vol = CLAMP(volume_from_pa(s->volume), 0, 1);
79 noct_set_ring(ring, RING_MODE_LEFT, CLAMP((int)(0x7f * vol), 12, 0x7f));
80 noct_set_button(ring, 0);
83 static void update_sink_from_rotary(int delta, const char *sink_name)
85 struct pulse_sink *s = pulse_sink_by_name(sink_name);
89 double vol = volume_from_pa(s->volume) + delta * 0.02;
90 pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, 1));
91 if (pavol == s->volume)
94 pa_cvolume_set(&cvol, s->channels, pavol);
96 DBG("## Setting volume of sink %s to %d", s->name, cvol.values[0]);
97 pulse_sink_set_volume(s->idx, &cvol);
100 static void update_sink_mute_from_button(int on, const char *sink_name)
105 struct pulse_sink *s = pulse_sink_by_name(sink_name);
109 DBG("## Setting mute of sink %s to %d", s->name, !s->mute);
110 pulse_sink_set_mute(s->idx, !s->mute);
113 /*** Client controls ***/
121 static struct client_map client_map[] = {
122 { 4, "Music Player Daemon", "albireo", },
123 { 5, "mpv", "albireo", },
124 { 6, "ZOOM VoiceEngine", "albireo", },
128 #define CLIENT_MAP_SIZE ARRAY_SIZE(client_map)
130 struct group_config {
142 static struct group_config group_config[NUM_GROUPS] = {
143 [4] = { .enabled = 1, .range = 1.5 },
144 [5] = { .enabled = 1, .range = 1.5 },
145 [6] = { .enabled = 1, .range = 1.5 },
146 [7] = { .enabled = 1, .range = 1.5 },
149 static struct group_state group_state[NUM_GROUPS];
151 static void calc_groups(void)
153 bzero(group_state, sizeof(group_state));
155 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
157 s->noct_group_idx = -1;
159 if (s->client_idx < 0 || s->sink_idx < 0)
162 struct pulse_client *c = pulse_client_by_idx(s->client_idx);
166 for (uns i=0; i < CLIENT_MAP_SIZE; i++)
168 struct client_map *cm = &client_map[i];
169 if ((!cm->client || !strcmp(cm->client, c->name)) &&
170 (!cm->host || !strcmp(cm->host, c->host)))
173 struct group_state *gs = &group_state[g];
174 DBG("@@ Client #%d, sink input #%d -> group %d", s->client_idx, s->idx, g);
175 s->noct_group_idx = g;
176 gs->volume = MAX(gs->volume, s->volume);
177 gs->have_muted[!!s->mute] = 1;
184 static void update_groups(void)
188 for (uns i=0; i < NUM_GROUPS; i++)
190 struct group_config *gc = &group_config[i];
191 struct group_state *gs = &group_state[i];
195 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);
197 if (!gs->have_muted[0] && !gs->have_muted[1])
199 noct_set_ring(i, RING_MODE_LEFT, 0);
200 noct_set_button(i, 0);
202 else if (!gs->have_muted[0])
204 noct_set_ring(i, RING_MODE_SINGLE_ON, 0x7f);
205 noct_set_button(i, 1);
209 double vol = CLAMP(volume_from_pa(gs->volume), 0, gc->range);
210 int val = 0x7f * vol / gc->range;
211 val = CLAMP(val, 12, 0x7f);
212 noct_set_ring(i, RING_MODE_LEFT, val);
213 noct_set_button(i, 0);
218 static void update_group_from_rotary(int i, int delta)
222 struct group_config *gc = &group_config[i];
223 struct group_state *gs = &group_state[i];
228 double vol = volume_from_pa(gs->volume) + delta*0.02;
229 pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, gc->range));
231 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
233 if (s->noct_group_idx == i && s->volume != pavol)
235 DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol);
237 pa_cvolume_set(&cvol, s->channels, pavol);
238 pulse_sink_input_set_volume(s->idx, &cvol);
243 static void update_group_from_button(int i, int on)
249 struct group_config *gc = &group_config[i];
250 struct group_state *gs = &group_state[i];
255 if (!gs->have_muted[0] && !gs->have_muted[1])
257 uns mute = !gs->have_muted[1];
259 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
261 if (s->noct_group_idx == i)
263 DBG("@@ Client #%d, sink input #%d: setting mute=%u", s->client_idx, s->idx, mute);
264 pulse_sink_input_set_mute(s->idx, mute);
269 static int find_touched_client(void)
273 for (uns i=0; i < NUM_GROUPS; i++)
274 if (group_config[i].enabled && noct_rotary_touched[i])
283 /*** Default sink controls ***/
285 static const char *get_client_sink(int i)
287 const char *sink = NULL;
289 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
290 if (s->noct_group_idx == i)
292 struct pulse_sink *sk = (s->sink_idx >= 0) ? pulse_sink_by_idx(s->sink_idx) : NULL;
293 const char *ss = sk ? sk->name : NULL;
296 else if (strcmp(sink, ss))
302 static void update_default_sink(void)
304 int i = find_touched_client();
307 sink = get_client_sink(i);
309 sink = pulse_default_sink_name ? : "?";
311 if (!strcmp(sink, PCH_SINK))
313 noct_set_button(8, 1);
314 noct_set_button(9, 0);
316 else if (!strcmp(sink, BT_SINK))
318 noct_set_button(8, 0);
319 noct_set_button(9, 1);
323 noct_set_button(8, 0);
324 noct_set_button(9, 0);
328 static void update_default_sink_from_button(int button, int on)
333 int i = find_touched_client();
337 sink = get_client_sink(i);
339 sink = pulse_default_sink_name ? : "?";
342 const char *switch_to = NULL;
344 switch_to = PCH_SINK;
345 else if (button == 9)
353 struct pulse_sink *sk = pulse_sink_by_name(switch_to);
357 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
358 if (s->noct_group_idx == i)
360 DBG("Moving input #%d to sink #%d", s->idx, sk->idx);
361 pulse_sink_input_move(s->idx, sk->idx);
366 DBG("Switching default sink to %s", switch_to);
367 pulse_server_set_default_sink(switch_to);
371 /*** Source mute controls ***/
373 static void update_source_buttons(void)
375 struct pulse_source *source = pulse_source_by_name(LOGI_SOURCE);
379 #if 0 // Disabled for now
380 // if (source->suspended || source->mute)
382 noct_set_button(2, 0);
384 noct_set_button(2, 1);
388 static void update_source_mute_from_button(int on, const char *source_name)
393 struct pulse_source *s = pulse_source_by_name(source_name);
398 DBG("## Setting mute of source %s to %d", s->name, !s->mute);
399 pulse_source_set_mute(s->idx, !s->mute);
403 /*** MPD controls ***/
405 static bool mpd_flash_state;
407 static void mpd_flash_timeout(struct main_timer *t)
409 mpd_flash_state ^= 1;
410 noct_set_button(12, mpd_flash_state);
411 timer_add_rel(t, 500);
414 static struct main_timer mpd_flash_timer = {
415 .handler = mpd_flash_timeout,
418 static void update_mpd(void)
420 const char *state = mpd_get_player_state();
421 if (!strcmp(state, "play"))
423 noct_set_button(12, 1);
424 timer_del(&mpd_flash_timer);
426 else if (!strcmp(state, "pause"))
428 if (!timer_is_active(&mpd_flash_timer))
431 mpd_flash_timeout(&mpd_flash_timer);
436 noct_set_button(12, 0);
437 timer_del(&mpd_flash_timer);
441 static void mpd_button_timeout(struct main_timer *t)
448 static void update_mpd_from_button(int button UNUSED, int on)
450 static struct main_timer mpd_button_timer = {
451 .handler = mpd_button_timeout,
454 const char *state = mpd_get_player_state();
458 if (timer_is_active(&mpd_button_timer))
460 timer_del(&mpd_button_timer);
461 if (!strcmp(state, "play"))
466 else if (!strcmp(state, "pause"))
475 if (!strcmp(state, "stop"))
482 DBG("MPD starting button timer");
483 timer_add_rel(&mpd_button_timer, 1000);
489 static bool lights_on[2];
490 static double lights_brightness[2];
491 static double lights_temperature[2];
493 static void update_lights(void)
497 noct_set_ring(3, RING_MODE_SINGLE_ON, 0x7f);
498 noct_set_button(10, 0);
499 noct_set_button(11, 0);
503 for (uint i=0; i<2; i++)
508 noct_set_ring(3-i, RING_MODE_LEFT, lights_brightness[i] * 127);
509 noct_set_button(11-i, 1);
511 double x = (exp(r*lights_brightness[i]) - 1) / (exp(r) - 1);
512 double t = lights_temperature[i];
513 double w = 2*x*(1-t);
515 warm = CLAMP((int)(255*w), 0, 255);
516 cold = CLAMP((int)(255*c), 0, 255);
520 noct_set_ring(3-i, RING_MODE_LEFT, 0);
521 noct_set_button(11-i, 0);
524 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);
525 dmx_set_pwm(2*i, warm);
526 dmx_set_pwm(2*i+1, cold);
530 static void update_lights_from_rotary(int ch, int delta)
533 lights_brightness[ch] = CLAMP(lights_brightness[ch] + 0.015*delta*abs(delta), 0., 1.);
537 static void update_lights_from_slider(int value)
539 lights_temperature[0] = value / 127.;
540 lights_temperature[1] = value / 127.;
544 static void lights_button_timeout(struct main_timer *t)
546 int ch = (uintptr_t) t->data;
547 DBG("Lights[%d]: Full throttle!", ch);
550 lights_brightness[ch] = 1;
554 static void update_lights_from_button(int ch, int on)
556 static struct main_timer lights_button_timer[2] = {{
557 .handler = lights_button_timeout,
558 .data = (void *)(uintptr_t) 0,
560 .handler = lights_button_timeout,
561 .data = (void *)(uintptr_t) 1,
565 timer_add_rel(&lights_button_timer[ch], 500);
566 else if (timer_is_active(&lights_button_timer[ch]))
568 timer_del(&lights_button_timer[ch]);
569 lights_on[ch] = !lights_on[ch];
574 /*** Main update routines ***/
576 static struct main_timer update_timer;
577 static timestamp_t last_touch_time;
586 static enum update_state update_state;
588 static bool want_sleep_p(void)
590 CLIST_FOR_EACH(struct pulse_sink *, s, pulse_sink_list)
596 static void do_update(struct main_timer *t)
598 DBG("## UPDATE in state %u", update_state);
602 if (!noct_is_ready())
604 DBG("## UPDATE: Nocturn is not ready");
605 update_state = US_OFFLINE;
608 else if (update_state == US_OFFLINE)
610 DBG("## UPDATE: Going online");
611 update_state = US_ONLINE;
615 if (!pulse_is_ready())
617 DBG("## UPDATE: Pulse is not online");
618 if (update_state != US_PULSE_DEAD)
620 update_state = US_PULSE_DEAD;
622 for (int i=0; i<8; i++)
623 noct_set_button(i, 1);
627 else if (update_state == US_PULSE_DEAD)
629 DBG("## UPDATE: Waking up from the dead");
630 update_state = US_ONLINE;
639 bool want_sleep = want_sleep_p();
641 last_touch_time = main_get_now();
642 timestamp_t since_touch = last_touch_time ? main_get_now() - last_touch_time : 0;
643 timestamp_t sleep_in = 30000;
644 if (since_touch >= sleep_in)
646 DBG("UPDATE: Sleeping");
647 if (update_state == US_ONLINE)
649 update_state = US_SLEEPING;
651 noct_set_ring(8, RING_MODE_LEFT, 127);
657 if (update_state == US_SLEEPING)
659 DBG("UPDATE: Waking up");
660 update_state = US_ONLINE;
665 timestamp_t t = sleep_in - since_touch + 10;
666 DBG("UPDATE: Scheduling sleep in %d ms", (int) t);
667 timer_add_rel(&update_timer, t);
672 update_ring_from_sink(0, PCH_SINK);
673 update_ring_from_sink(1, BT_SINK);
675 update_default_sink();
676 update_source_buttons();
681 void schedule_update(void)
683 timer_add_rel(&update_timer, 10);
686 static bool prepare_notify(void)
688 if (!pulse_is_ready())
690 DBG("## NOTIFY: Pulse is not online");
694 last_touch_time = main_get_now();
695 if (update_state == US_SLEEPING)
697 DBG("## NOTIFY: Scheduling wakeup");
704 void notify_rotary(int rotary, int delta)
706 if (!prepare_notify())
712 update_sink_from_rotary(delta, PCH_SINK);
715 update_sink_from_rotary(delta, BT_SINK);
718 update_lights_from_rotary(1, delta);
721 update_lights_from_rotary(0, delta);
724 update_lights_from_slider(delta);
727 update_group_from_rotary(rotary, delta);
731 void notify_button(int button, int on)
733 if (!prepare_notify())
739 update_sink_mute_from_button(on, PCH_SINK);
742 update_sink_mute_from_button(on, BT_SINK);
745 update_source_mute_from_button(on, LOGI_SOURCE);
749 update_default_sink_from_button(button, on);
752 update_lights_from_button(1, on);
755 update_lights_from_button(0, on);
758 update_mpd_from_button(button, on);
773 update_group_from_button(button, on);
777 void notify_touch(int rotary UNUSED, int on UNUSED)
779 if (!prepare_notify())
782 // Rotary touches switch meaning of LEDs, this is handled inside display updates
783 if (rotary >= 4 && rotary < 8)
787 /*** Main entry point ***/
792 static struct opt_section options = {
794 OPT_HELP("Control console for the Ursary"),
796 OPT_HELP("Options:"),
798 OPT_BOOL('d', "debug", debug, 0, "\tEnable debugging mode (no fork etc.)"),
799 OPT_BOOL(0, "no-fork", no_fork, 0, "\tDo not fork\n"),
804 static void sigterm_handler(struct main_signal *ms UNUSED)
809 static void daemon_body(struct daemon_params *dp)
812 update_timer.handler = do_update;
820 static struct main_signal term_sig = {
822 .handler = sigterm_handler,
824 signal_add(&term_sig);
826 msg(L_INFO, "Ursary daemon starting");
828 msg(L_INFO, "Ursary daemon shut down");
832 int main(int argc UNUSED, char **argv)
834 opt_parse(&options, argv+1);
838 struct daemon_params dp = {
839 .flags = ((debug || no_fork) ? DAEMON_FLAG_SIMULATE : 0),
840 .pid_file = "/run/ursaryd.pid",
841 .run_as_user = "ursary",
848 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
849 ls->levels = ~(1U << L_DEBUG);
850 log_set_default_stream(ls);
853 daemon_run(&dp, daemon_body);