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
29 * 0 sink Ursarium mute select as default (or assign to client selected by touch)
30 * 1 sink Catarium mute dtto
31 * 2 sink Compress - dtto
33 * 4 MPD mute play/pause/stop
36 * 7 Ursula/Havrana 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, NULL, "albireo", },
117 { 6, NULL, "ogion", },
118 { 7, NULL, "ursula", },
119 { 7, NULL, "havrana", },
122 #define CLIENT_MAP_SIZE ARRAY_SIZE(client_map)
124 struct group_config {
136 static struct group_config group_config[NUM_GROUPS] = {
137 [4] = { .enabled = 1, .range = 1.5 },
138 [5] = { .enabled = 1, .range = 1.5 },
139 [6] = { .enabled = 1, .range = 1.5 },
140 [7] = { .enabled = 1, .range = 1.5 },
143 static struct group_state group_state[NUM_GROUPS];
145 static void calc_groups(void)
147 bzero(group_state, sizeof(group_state));
149 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
151 s->noct_group_idx = -1;
153 if (s->client_idx < 0 || s->sink_idx < 0)
156 struct pulse_client *c = pulse_client_by_idx(s->client_idx);
160 for (uns i=0; i < CLIENT_MAP_SIZE; i++)
162 struct client_map *cm = &client_map[i];
163 if ((!cm->client || !strcmp(cm->client, c->name)) &&
164 (!cm->host || !strcmp(cm->host, c->host)))
167 struct group_state *gs = &group_state[g];
168 DBG("@@ Client #%d, sink input #%d -> group %d", s->client_idx, s->idx, g);
169 s->noct_group_idx = g;
170 gs->volume = MAX(gs->volume, s->volume);
171 gs->have_muted[!!s->mute] = 1;
178 static void update_groups(void)
182 for (uns i=0; i < NUM_GROUPS; i++)
184 struct group_config *gc = &group_config[i];
185 struct group_state *gs = &group_state[i];
189 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);
191 if (!gs->have_muted[0] && !gs->have_muted[1])
193 noct_set_ring(i, RING_MODE_LEFT, 0);
194 noct_set_button(i, 0);
196 else if (!gs->have_muted[0])
198 noct_set_ring(i, RING_MODE_SINGLE_ON, 0x7f);
199 noct_set_button(i, 1);
203 double vol = CLAMP(volume_from_pa(gs->volume), 0, gc->range);
204 int val = 0x7f * vol / gc->range;
205 val = CLAMP(val, 12, 0x7f);
206 noct_set_ring(i, RING_MODE_LEFT, val);
207 noct_set_button(i, 0);
212 static void update_group_from_rotary(int i, int delta)
216 struct group_config *gc = &group_config[i];
217 struct group_state *gs = &group_state[i];
222 double vol = volume_from_pa(gs->volume) + delta*0.02;
223 pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, gc->range));
225 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
227 if (s->noct_group_idx == i && s->volume != pavol)
229 DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol);
231 pa_cvolume_set(&cvol, s->channels, pavol);
232 pulse_sink_input_set_volume(s->idx, &cvol);
237 static void update_group_from_button(int i, int on)
243 struct group_config *gc = &group_config[i];
244 struct group_state *gs = &group_state[i];
249 if (!gs->have_muted[0] && !gs->have_muted[1])
251 uns mute = !gs->have_muted[1];
253 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
255 if (s->noct_group_idx == i)
257 DBG("@@ Client #%d, sink input #%d: setting mute=%u", s->client_idx, s->idx, mute);
258 pulse_sink_input_set_mute(s->idx, mute);
263 static int find_touched_client(void)
267 for (uns i=0; i < NUM_GROUPS; i++)
268 if (group_config[i].enabled && noct_rotary_touched[i])
277 /*** Default sink controls ***/
279 static const char *get_client_sink(int i)
281 const char *sink = NULL;
283 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
284 if (s->noct_group_idx == i)
286 struct pulse_sink *sk = (s->sink_idx >= 0) ? pulse_sink_by_idx(s->sink_idx) : NULL;
287 const char *ss = sk ? sk->name : NULL;
290 else if (strcmp(sink, ss))
296 static void update_default_sink(void)
298 int i = find_touched_client();
301 sink = get_client_sink(i);
303 sink = pulse_default_sink_name ? : "?";
305 if (!strcmp(sink, "ursarium"))
307 noct_set_button(8, 1);
308 noct_set_button(9, 0);
309 noct_set_button(10, 0);
311 else if (!strcmp(sink, "catarium"))
313 noct_set_button(8, 0);
314 noct_set_button(9, 1);
315 noct_set_button(10, 0);
317 else if (!strcmp(sink, "compress"))
319 noct_set_button(8, 0);
320 noct_set_button(9, 0);
321 noct_set_button(10, 1);
325 noct_set_button(8, 0);
326 noct_set_button(9, 0);
327 noct_set_button(10, 0);
331 static void update_default_sink_from_button(int button, int on)
336 int i = find_touched_client();
339 sink = get_client_sink(i);
341 sink = pulse_default_sink_name ? : "?";
343 const char *switch_to = NULL;
346 if (!strcmp(sink, "ursarium"))
347 switch_to = "burrow";
349 switch_to = "ursarium";
351 else if (button == 9)
353 if (!strcmp(sink, "catarium"))
354 switch_to = "burrow";
356 switch_to = "catarium";
358 else if (button == 10)
360 if (!strcmp(sink, "compress"))
361 switch_to = "burrow";
363 switch_to = "compress";
371 struct pulse_sink *sk = pulse_sink_by_name(switch_to);
375 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
376 if (s->noct_group_idx == i)
378 DBG("Moving input #%d to sink #%d", s->idx, sk->idx);
379 pulse_sink_input_move(s->idx, sk->idx);
384 DBG("Switching default sink to %s", switch_to);
385 pulse_server_set_default_sink(switch_to);
389 /*** MPD controls ***/
391 static bool mpd_flash_state;
393 static void mpd_flash_timeout(struct main_timer *t)
395 mpd_flash_state ^= 1;
396 noct_set_button(12, mpd_flash_state);
397 timer_add_rel(t, 500);
400 static struct main_timer mpd_flash_timer = {
401 .handler = mpd_flash_timeout,
404 static void update_mpd(void)
406 const char *state = mpd_get_player_state();
407 if (!strcmp(state, "play"))
409 noct_set_button(12, 1);
410 timer_del(&mpd_flash_timer);
412 else if (!strcmp(state, "pause"))
414 if (!timer_is_active(&mpd_flash_timer))
417 mpd_flash_timeout(&mpd_flash_timer);
422 noct_set_button(12, 0);
423 timer_del(&mpd_flash_timer);
427 static void mpd_button_timeout(struct main_timer *t)
434 static void update_mpd_from_button(int button UNUSED, int on)
436 static struct main_timer mpd_button_timer = {
437 .handler = mpd_button_timeout,
440 const char *state = mpd_get_player_state();
444 if (timer_is_active(&mpd_button_timer))
446 timer_del(&mpd_button_timer);
447 if (!strcmp(state, "play"))
452 else if (!strcmp(state, "pause"))
461 if (!strcmp(state, "stop"))
468 DBG("MPD starting button timer");
469 timer_add_rel(&mpd_button_timer, 1000);
473 /*** Main update routines ***/
475 static struct main_timer update_timer;
476 static timestamp_t last_touch_time;
485 static enum update_state update_state;
487 static bool want_sleep_p(void)
489 CLIST_FOR_EACH(struct pulse_sink *, s, pulse_sink_list)
495 static void do_update(struct main_timer *t)
497 DBG("## UPDATE in state %u", update_state);
501 if (!noct_is_ready())
503 DBG("## UPDATE: Nocturn is not ready");
504 update_state = US_OFFLINE;
507 else if (update_state == US_OFFLINE)
509 DBG("## UPDATE: Going online");
510 update_state = US_ONLINE;
514 if (!pulse_is_ready())
516 DBG("## UPDATE: Pulse is not online");
517 if (update_state != US_PULSE_DEAD)
519 update_state = US_PULSE_DEAD;
521 for (int i=0; i<8; i++)
522 noct_set_button(i, 1);
526 else if (update_state == US_PULSE_DEAD)
528 DBG("## UPDATE: Waking up from the dead");
529 update_state = US_ONLINE;
538 bool want_sleep = want_sleep_p();
540 last_touch_time = main_get_now();
541 timestamp_t since_touch = last_touch_time ? main_get_now() - last_touch_time : 0;
542 timestamp_t sleep_in = 5000;
543 if (since_touch >= sleep_in)
545 DBG("UPDATE: Sleeping");
546 if (update_state == US_ONLINE)
548 update_state = US_SLEEPING;
550 noct_set_ring(8, RING_MODE_LEFT, 127);
556 if (update_state == US_SLEEPING)
558 DBG("UPDATE: Waking up");
559 update_state = US_ONLINE;
564 timestamp_t t = sleep_in - since_touch + 10;
565 DBG("UPDATE: Scheduling sleep in %d ms", (int) t);
566 timer_add_rel(&update_timer, t);
571 update_ring_from_sink(0, "ursarium");
572 update_ring_from_sink(1, "catarium");
574 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, "ursarium");
612 update_sink_from_rotary(delta, "catarium");
615 update_sink_from_rotary(delta, "ursarium");
616 update_sink_from_rotary(delta, "catarium");
619 update_group_from_rotary(rotary, delta);
623 void notify_button(int button, int on)
625 if (!prepare_notify())
631 update_sink_mute_from_button(on, "ursarium");
634 update_sink_mute_from_button(on, "catarium");
639 update_default_sink_from_button(button, on);
642 update_mpd_from_button(button, on);
645 update_group_from_button(button, on);
649 void notify_touch(int rotary, int on UNUSED)
651 if (!prepare_notify())
654 // Rotary touches switch meaning of LEDs, this is handled inside display updates
655 if (rotary >= 4 && rotary < 8)
659 /*** Main entry point ***/
663 static struct opt_section options = {
665 OPT_HELP("Control console for the Ursary"),
667 OPT_HELP("Options:"),
669 OPT_BOOL('d', "debug", debug, 0, "\tEnable debugging mode (no fork etc.)"),
674 static void sigterm_handler(struct main_signal *ms UNUSED)
679 static void daemon_body(struct daemon_params *dp)
682 update_timer.handler = do_update;
688 static struct main_signal term_sig = {
690 .handler = sigterm_handler,
692 signal_add(&term_sig);
694 msg(L_INFO, "Ursary daemon starting");
696 msg(L_INFO, "Ursary daemon shut down");
700 int main(int argc UNUSED, char **argv)
702 opt_parse(&options, argv+1);
706 struct daemon_params dp = {
707 .flags = (debug ? DAEMON_FLAG_SIMULATE : 0),
708 .pid_file = "/run/ursaryd.pid",
709 .run_as_user = "ursary",
716 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
717 ls->levels = ~(1U << L_DEBUG);
718 log_set_default_stream(ls);
721 daemon_run(&dp, daemon_body);