2 * The Ursary Audio Controls
4 * (c) 2014 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>
28 * rotary red button green button
33 * 4 MPD mute play/pause/stop
34 * 5 Albireo MPV mute -
35 * 6 Albireo other mute -
36 * 7 other machines mute -
42 /*** Sink controls ***/
44 static double volume_from_pa(pa_volume_t vol)
46 return (double) vol / PA_VOLUME_NORM;
49 static pa_volume_t volume_to_pa(double vol)
51 return vol * PA_VOLUME_NORM + 0.0001;
54 static void update_ring_from_sink(int ring, const char *sink_name)
56 struct pulse_sink *s = pulse_sink_by_name(sink_name);
59 noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
60 noct_set_button(ring, 0);
66 noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
67 noct_set_button(ring, 1);
71 double vol = CLAMP(volume_from_pa(s->volume), 0, 1);
72 noct_set_ring(ring, RING_MODE_LEFT, CLAMP((int)(0x7f * vol), 12, 0x7f));
73 noct_set_button(ring, 0);
76 static void update_sink_from_rotary(int delta, const char *sink_name)
78 struct pulse_sink *s = pulse_sink_by_name(sink_name);
82 double vol = volume_from_pa(s->volume) + delta * 0.02;
83 pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, 1));
84 if (pavol == s->volume)
87 pa_cvolume_set(&cvol, s->channels, pavol);
89 DBG("## Setting volume of sink %s to %d", s->name, cvol.values[0]);
90 pulse_sink_set_volume(s->idx, &cvol);
93 static void update_sink_mute_from_button(int on, const char *sink_name)
98 struct pulse_sink *s = pulse_sink_by_name(sink_name);
102 DBG("## Setting mute of sink %s to %d", s->name, !s->mute);
103 pulse_sink_set_mute(s->idx, !s->mute);
106 /*** Client controls ***/
114 static struct client_map client_map[] = {
115 { 4, "Music Player Daemon", "albireo", },
116 { 5, "mpv", "albireo", },
117 { 6, NULL, "albireo", },
121 #define CLIENT_MAP_SIZE ARRAY_SIZE(client_map)
123 struct group_config {
135 static struct group_config group_config[NUM_GROUPS] = {
136 [4] = { .enabled = 1, .range = 1.5 },
137 [5] = { .enabled = 1, .range = 1.5 },
138 [6] = { .enabled = 1, .range = 1.5 },
139 [7] = { .enabled = 1, .range = 1.5 },
142 static struct group_state group_state[NUM_GROUPS];
144 static void calc_groups(void)
146 bzero(group_state, sizeof(group_state));
148 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
150 s->noct_group_idx = -1;
152 if (s->client_idx < 0 || s->sink_idx < 0)
155 struct pulse_client *c = pulse_client_by_idx(s->client_idx);
159 for (uns i=0; i < CLIENT_MAP_SIZE; i++)
161 struct client_map *cm = &client_map[i];
162 if ((!cm->client || !strcmp(cm->client, c->name)) &&
163 (!cm->host || !strcmp(cm->host, c->host)))
166 struct group_state *gs = &group_state[g];
167 DBG("@@ Client #%d, sink input #%d -> group %d", s->client_idx, s->idx, g);
168 s->noct_group_idx = g;
169 gs->volume = MAX(gs->volume, s->volume);
170 gs->have_muted[!!s->mute] = 1;
177 static void update_groups(void)
181 for (uns i=0; i < NUM_GROUPS; i++)
183 struct group_config *gc = &group_config[i];
184 struct group_state *gs = &group_state[i];
188 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);
190 if (!gs->have_muted[0] && !gs->have_muted[1])
192 noct_set_ring(i, RING_MODE_LEFT, 0);
193 noct_set_button(i, 0);
195 else if (!gs->have_muted[0])
197 noct_set_ring(i, RING_MODE_SINGLE_ON, 0x7f);
198 noct_set_button(i, 1);
202 double vol = CLAMP(volume_from_pa(gs->volume), 0, gc->range);
203 int val = 0x7f * vol / gc->range;
204 val = CLAMP(val, 12, 0x7f);
205 noct_set_ring(i, RING_MODE_LEFT, val);
206 noct_set_button(i, 0);
211 static void update_group_from_rotary(int i, int delta)
215 struct group_config *gc = &group_config[i];
216 struct group_state *gs = &group_state[i];
221 double vol = volume_from_pa(gs->volume) + delta*0.02;
222 pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, gc->range));
224 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
226 if (s->noct_group_idx == i && s->volume != pavol)
228 DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol);
230 pa_cvolume_set(&cvol, s->channels, pavol);
231 pulse_sink_input_set_volume(s->idx, &cvol);
236 static void update_group_from_button(int i, int on)
242 struct group_config *gc = &group_config[i];
243 struct group_state *gs = &group_state[i];
248 if (!gs->have_muted[0] && !gs->have_muted[1])
250 uns mute = !gs->have_muted[1];
252 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
254 if (s->noct_group_idx == i)
256 DBG("@@ Client #%d, sink input #%d: setting mute=%u", s->client_idx, s->idx, mute);
257 pulse_sink_input_set_mute(s->idx, mute);
262 static int find_touched_client(void)
266 for (uns i=0; i < NUM_GROUPS; i++)
267 if (group_config[i].enabled && noct_rotary_touched[i])
276 /*** Default sink controls ***/
278 static const char *get_client_sink(int i)
280 const char *sink = NULL;
282 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
283 if (s->noct_group_idx == i)
285 struct pulse_sink *sk = (s->sink_idx >= 0) ? pulse_sink_by_idx(s->sink_idx) : NULL;
286 const char *ss = sk ? sk->name : NULL;
289 else if (strcmp(sink, ss))
295 static void update_default_sink(void)
297 int i = find_touched_client();
300 sink = get_client_sink(i);
302 sink = pulse_default_sink_name ? : "?";
304 if (!strcmp(sink, "ursarium"))
306 noct_set_button(8, 1);
307 noct_set_button(9, 0);
308 noct_set_button(10, 0);
310 else if (!strcmp(sink, "catarium"))
312 noct_set_button(8, 0);
313 noct_set_button(9, 1);
314 noct_set_button(10, 0);
316 else if (!strcmp(sink, "compress"))
318 noct_set_button(8, 0);
319 noct_set_button(9, 0);
320 noct_set_button(10, 1);
324 noct_set_button(8, 0);
325 noct_set_button(9, 0);
326 noct_set_button(10, 0);
330 static void update_default_sink_from_button(int button, int on)
335 int i = find_touched_client();
338 sink = get_client_sink(i);
340 sink = pulse_default_sink_name ? : "?";
342 const char *switch_to = NULL;
345 if (!strcmp(sink, "ursarium"))
346 switch_to = "burrow";
348 switch_to = "ursarium";
350 else if (button == 9)
352 if (!strcmp(sink, "catarium"))
353 switch_to = "burrow";
355 switch_to = "catarium";
357 else if (button == 10)
359 if (!strcmp(sink, "compress"))
360 switch_to = "burrow";
362 switch_to = "compress";
370 struct pulse_sink *sk = pulse_sink_by_name(switch_to);
374 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
375 if (s->noct_group_idx == i)
377 DBG("Moving input #%d to sink #%d", s->idx, sk->idx);
378 pulse_sink_input_move(s->idx, sk->idx);
383 DBG("Switching default sink to %s", switch_to);
384 pulse_server_set_default_sink(switch_to);
388 /*** MPD controls ***/
390 static bool mpd_flash_state;
392 static void mpd_flash_timeout(struct main_timer *t)
394 mpd_flash_state ^= 1;
395 noct_set_button(12, mpd_flash_state);
396 timer_add_rel(t, 500);
399 static struct main_timer mpd_flash_timer = {
400 .handler = mpd_flash_timeout,
403 static void update_mpd(void)
405 const char *state = mpd_get_player_state();
406 if (!strcmp(state, "play"))
408 noct_set_button(12, 1);
409 timer_del(&mpd_flash_timer);
411 else if (!strcmp(state, "pause"))
413 if (!timer_is_active(&mpd_flash_timer))
416 mpd_flash_timeout(&mpd_flash_timer);
421 noct_set_button(12, 0);
422 timer_del(&mpd_flash_timer);
426 static void mpd_button_timeout(struct main_timer *t)
433 static void update_mpd_from_button(int button UNUSED, int on)
435 static struct main_timer mpd_button_timer = {
436 .handler = mpd_button_timeout,
439 const char *state = mpd_get_player_state();
443 if (timer_is_active(&mpd_button_timer))
445 timer_del(&mpd_button_timer);
446 if (!strcmp(state, "play"))
451 else if (!strcmp(state, "pause"))
460 if (!strcmp(state, "stop"))
467 DBG("MPD starting button timer");
468 timer_add_rel(&mpd_button_timer, 1000);
472 /*** Main update routines ***/
474 static struct main_timer update_timer;
475 static timestamp_t last_touch_time;
484 static enum update_state update_state;
486 static bool want_sleep_p(void)
488 CLIST_FOR_EACH(struct pulse_sink *, s, pulse_sink_list)
494 static void do_update(struct main_timer *t)
496 DBG("## UPDATE in state %u", update_state);
500 if (!noct_is_ready())
502 DBG("## UPDATE: Nocturn is not ready");
503 update_state = US_OFFLINE;
506 else if (update_state == US_OFFLINE)
508 DBG("## UPDATE: Going online");
509 update_state = US_ONLINE;
513 if (!pulse_is_ready())
515 DBG("## UPDATE: Pulse is not online");
516 if (update_state != US_PULSE_DEAD)
518 update_state = US_PULSE_DEAD;
520 for (int i=0; i<8; i++)
521 noct_set_button(i, 1);
525 else if (update_state == US_PULSE_DEAD)
527 DBG("## UPDATE: Waking up from the dead");
528 update_state = US_ONLINE;
537 bool want_sleep = want_sleep_p();
539 last_touch_time = main_get_now();
540 timestamp_t since_touch = last_touch_time ? main_get_now() - last_touch_time : 0;
541 timestamp_t sleep_in = 5000;
542 if (since_touch >= sleep_in)
544 DBG("UPDATE: Sleeping");
545 if (update_state == US_ONLINE)
547 update_state = US_SLEEPING;
549 noct_set_ring(8, RING_MODE_LEFT, 127);
555 if (update_state == US_SLEEPING)
557 DBG("UPDATE: Waking up");
558 update_state = US_ONLINE;
563 timestamp_t t = sleep_in - since_touch + 10;
564 DBG("UPDATE: Scheduling sleep in %d ms", (int) t);
565 timer_add_rel(&update_timer, t);
570 update_ring_from_sink(0, "alsa_output.brum.analog-stereo");
573 update_default_sink();
578 void schedule_update(void)
580 timer_add_rel(&update_timer, 10);
583 static bool prepare_notify(void)
585 if (!pulse_is_ready())
587 DBG("## NOTIFY: Pulse is not online");
591 last_touch_time = main_get_now();
592 if (update_state == US_SLEEPING)
594 DBG("## NOTIFY: Scheduling wakeup");
601 void notify_rotary(int rotary, int delta)
603 if (!prepare_notify())
609 update_sink_from_rotary(delta, "alsa_output.brum.analog-stereo");
612 update_group_from_rotary(rotary, delta);
616 void notify_button(int button, int on)
618 if (!prepare_notify())
624 update_sink_mute_from_button(on, "alsa_output.brum.analog-stereo");
630 update_default_sink_from_button(button, on);
634 update_mpd_from_button(button, on);
637 update_group_from_button(button, on);
641 void notify_touch(int rotary, int on UNUSED)
643 if (!prepare_notify())
646 // Rotary touches switch meaning of LEDs, this is handled inside display updates
647 if (rotary >= 4 && rotary < 8)
651 /*** Main entry point ***/
656 static struct opt_section options = {
658 OPT_HELP("Control console for the Ursary"),
660 OPT_HELP("Options:"),
662 OPT_BOOL('d', "debug", debug, 0, "\tEnable debugging mode (no fork etc.)"),
663 OPT_BOOL(0, "no-fork", no_fork, 0, "\tDo not fork\n"),
668 static void sigterm_handler(struct main_signal *ms UNUSED)
673 static void daemon_body(struct daemon_params *dp)
676 update_timer.handler = do_update;
682 static struct main_signal term_sig = {
684 .handler = sigterm_handler,
686 signal_add(&term_sig);
688 msg(L_INFO, "Ursary daemon starting");
690 msg(L_INFO, "Ursary daemon shut down");
694 int main(int argc UNUSED, char **argv)
696 opt_parse(&options, argv+1);
700 struct daemon_params dp = {
701 .flags = ((debug || no_fork) ? DAEMON_FLAG_SIMULATE : 0),
702 .pid_file = "/run/ursaryd.pid",
703 .run_as_user = "ursary",
710 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
711 ls->levels = ~(1U << L_DEBUG);
712 log_set_default_stream(ls);
715 daemon_run(&dp, daemon_body);