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
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 ***/
115 static struct client_map client_map[] = {
116 { 4, "Music Player Daemon", "albireo", 1.5 },
117 { 5, NULL, "albireo", 1.5 },
118 { 6, NULL, "ogion", 1.5 },
119 { 7, NULL, "ursula", 1.5 },
122 #define NUM_CLIENTS ARRAY_SIZE(client_map)
124 struct client_state {
129 static struct client_state client_state[NUM_CLIENTS];
131 static int find_client_by_rotary(int rotary)
134 for (i=0; i < NUM_CLIENTS; i++)
135 if (client_map[i].rotary == rotary)
140 static void calc_clients(void)
142 bzero(client_state, sizeof(client_state));
144 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
146 s->noct_client_idx = -1;
148 if (s->client_idx < 0 || s->sink_idx < 0)
151 struct pulse_client *c = pulse_client_by_idx(s->client_idx);
155 for (uns i=0; i < NUM_CLIENTS; i++)
157 struct client_map *cm = &client_map[i];
158 struct client_state *cs = &client_state[i];
159 if ((!cm->client || !strcmp(cm->client, c->name)) &&
160 (!cm->host || !strcmp(cm->host, c->host)))
162 // DBG("@@ Client #%d, sink input #%d -> rotary %d", s->client_idx, s->idx, cm->rotary);
163 s->noct_client_idx = i;
164 cs->volume = MAX(cs->volume, s->volume);
165 cs->have_muted[!!s->mute] = 1;
172 static void update_clients(void)
176 for (uns i=0; i < NUM_CLIENTS; i++)
178 struct client_map *cm = &client_map[i];
179 struct client_state *cs = &client_state[i];
180 if (!cs->have_muted[0] && !cs->have_muted[1])
182 noct_set_ring(cm->rotary, RING_MODE_LEFT, 0);
183 noct_set_button(cm->rotary, 0);
185 else if (!cs->have_muted[0])
187 noct_set_ring(cm->rotary, RING_MODE_SINGLE_ON, 0x7f);
188 noct_set_button(cm->rotary, 1);
192 double vol = CLAMP(volume_from_pa(cs->volume), 0, cm->range);
193 int val = 0x7f * vol / cm->range;
194 val = CLAMP(val, 12, 0x7f);
195 noct_set_ring(cm->rotary, RING_MODE_LEFT, val);
196 noct_set_button(cm->rotary, 0);
201 static void update_client_from_rotary(int rotary, int delta)
203 int i = find_client_by_rotary(rotary);
206 struct client_map *cm = &client_map[i];
207 struct client_state *cs = &client_state[i];
210 double vol = volume_from_pa(cs->volume) + delta*0.02;
211 pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, cm->range));
213 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
215 if (s->noct_client_idx == i && s->volume != pavol)
217 DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol);
219 pa_cvolume_set(&cvol, s->channels, pavol);
220 pulse_sink_input_set_volume(s->idx, &cvol);
225 static void update_client_from_button(int button, int on)
227 if (button >= 8 || !on)
230 int i = find_client_by_rotary(button);
233 struct client_state *cs = &client_state[i];
236 if (!cs->have_muted[0] && !cs->have_muted[1])
238 uns mute = !cs->have_muted[1];
240 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
242 if (s->noct_client_idx == i)
244 DBG("@@ Client #%d, sink input #%d: setting mute=%u", s->client_idx, s->idx, mute);
245 pulse_sink_input_set_mute(s->idx, mute);
250 static int find_touched_client(void)
254 for (uns i=0; i < NUM_CLIENTS; i++)
255 if (noct_rotary_touched[client_map[i].rotary])
264 /*** Default sink controls ***/
266 static const char *get_client_sink(int i)
268 const char *sink = NULL;
270 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
271 if (s->noct_client_idx == i)
273 struct pulse_sink *sk = (s->sink_idx >= 0) ? pulse_sink_by_idx(s->sink_idx) : NULL;
274 const char *ss = sk ? sk->name : NULL;
277 else if (strcmp(sink, ss))
283 static void update_default_sink(void)
285 int i = find_touched_client();
288 sink = get_client_sink(i);
290 sink = pulse_default_sink_name ? : "?";
292 if (!strcmp(sink, "ursarium"))
294 noct_set_button(8, 1);
295 noct_set_button(9, 0);
296 noct_set_button(10, 0);
298 else if (!strcmp(sink, "catarium"))
300 noct_set_button(8, 0);
301 noct_set_button(9, 1);
302 noct_set_button(10, 0);
304 else if (!strcmp(sink, "compress"))
306 noct_set_button(8, 0);
307 noct_set_button(9, 0);
308 noct_set_button(10, 1);
312 noct_set_button(8, 0);
313 noct_set_button(9, 0);
314 noct_set_button(10, 0);
318 static void update_default_sink_from_button(int button, int on)
323 int i = find_touched_client();
326 sink = get_client_sink(i);
328 sink = pulse_default_sink_name ? : "?";
330 const char *switch_to = NULL;
333 if (!strcmp(sink, "ursarium"))
334 switch_to = "burrow";
336 switch_to = "ursarium";
338 else if (button == 9)
340 if (!strcmp(sink, "catarium"))
341 switch_to = "burrow";
343 switch_to = "catarium";
345 else if (button == 10)
347 if (!strcmp(sink, "compress"))
348 switch_to = "burrow";
350 switch_to = "compress";
358 struct pulse_sink *sk = pulse_sink_by_name(switch_to);
362 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
363 if (s->noct_client_idx == i)
365 DBG("Moving input #%d to sink #%d", s->idx, sk->idx);
366 pulse_sink_input_move(s->idx, sk->idx);
371 DBG("Switching default sink to %s", switch_to);
372 pulse_server_set_default_sink(switch_to);
376 /*** MPD controls ***/
378 static bool mpd_flash_state;
380 static void mpd_flash_timeout(struct main_timer *t)
382 mpd_flash_state ^= 1;
383 noct_set_button(12, mpd_flash_state);
384 timer_add_rel(t, 500);
387 static struct main_timer mpd_flash_timer = {
388 .handler = mpd_flash_timeout,
391 static void update_mpd(void)
393 const char *state = mpd_get_player_state();
394 if (!strcmp(state, "play"))
396 noct_set_button(12, 1);
397 timer_del(&mpd_flash_timer);
399 else if (!strcmp(state, "pause"))
401 if (!timer_is_active(&mpd_flash_timer))
404 mpd_flash_timeout(&mpd_flash_timer);
409 noct_set_button(12, 0);
410 timer_del(&mpd_flash_timer);
414 static void mpd_button_timeout(struct main_timer *t)
421 static void update_mpd_from_button(int button UNUSED, int on)
423 static struct main_timer mpd_button_timer = {
424 .handler = mpd_button_timeout,
427 const char *state = mpd_get_player_state();
431 if (timer_is_active(&mpd_button_timer))
433 timer_del(&mpd_button_timer);
434 if (!strcmp(state, "play"))
439 else if (!strcmp(state, "pause"))
448 if (!strcmp(state, "stop"))
455 DBG("MPD starting button timer");
456 timer_add_rel(&mpd_button_timer, 1000);
460 /*** Main update routines ***/
462 static struct main_timer update_timer;
463 static timestamp_t last_touch_time;
472 static enum update_state update_state;
474 static bool want_sleep_p(void)
476 CLIST_FOR_EACH(struct pulse_sink *, s, pulse_sink_list)
482 static void do_update(struct main_timer *t)
484 DBG("## UPDATE in state %u", update_state);
488 if (!noct_is_ready())
490 DBG("## UPDATE: Nocturn is not ready");
491 update_state = US_OFFLINE;
494 else if (update_state == US_OFFLINE)
496 DBG("## UPDATE: Going online");
497 update_state = US_ONLINE;
501 if (!pulse_is_ready())
503 DBG("## UPDATE: Pulse is not online");
504 if (update_state != US_PULSE_DEAD)
506 update_state = US_PULSE_DEAD;
508 for (int i=0; i<8; i++)
509 noct_set_button(i, 1);
513 else if (update_state == US_PULSE_DEAD)
515 DBG("## UPDATE: Waking up from the dead");
516 update_state = US_ONLINE;
525 bool want_sleep = want_sleep_p();
527 last_touch_time = main_get_now();
528 timestamp_t since_touch = last_touch_time ? main_get_now() - last_touch_time : 0;
529 timestamp_t sleep_in = 5000;
530 if (since_touch >= sleep_in)
532 DBG("UPDATE: Sleeping");
533 if (update_state == US_ONLINE)
535 update_state = US_SLEEPING;
537 noct_set_ring(8, RING_MODE_LEFT, 127);
543 if (update_state == US_SLEEPING)
545 DBG("UPDATE: Waking up");
546 update_state = US_ONLINE;
551 timestamp_t t = sleep_in - since_touch + 10;
552 DBG("UPDATE: Scheduling sleep in %d ms", (int) t);
553 timer_add_rel(&update_timer, t);
558 update_ring_from_sink(0, "ursarium");
559 update_ring_from_sink(1, "catarium");
561 update_default_sink();
565 void schedule_update(void)
567 timer_add_rel(&update_timer, 10);
570 static bool prepare_notify(void)
572 if (!pulse_is_ready())
574 DBG("## NOTIFY: Pulse is not online");
578 last_touch_time = main_get_now();
579 if (update_state == US_SLEEPING)
581 DBG("## NOTIFY: Scheduling wakeup");
588 void notify_rotary(int rotary, int delta)
590 if (!prepare_notify())
596 update_sink_from_rotary(delta, "ursarium");
599 update_sink_from_rotary(delta, "catarium");
602 update_sink_from_rotary(delta, "ursarium");
603 update_sink_from_rotary(delta, "catarium");
606 update_client_from_rotary(rotary, delta);
610 void notify_button(int button, int on)
612 if (!prepare_notify())
618 update_sink_mute_from_button(on, "ursarium");
621 update_sink_mute_from_button(on, "catarium");
626 update_default_sink_from_button(button, on);
629 update_mpd_from_button(button, on);
632 update_client_from_button(button, on);
636 void notify_touch(int rotary, int on UNUSED)
638 if (!prepare_notify())
641 // Rotary touches switch meaning of LEDs, this is handled inside display updates
642 if (rotary >= 4 && rotary < 8)
646 /*** Main entry point ***/
650 static struct opt_section options = {
652 OPT_HELP("Control console for the Ursary"),
654 OPT_HELP("Options:"),
656 OPT_BOOL('d', "debug", debug, 0, "\tEnable debugging mode (no fork etc.)"),
661 static void sigterm_handler(struct main_signal *ms UNUSED)
666 static void daemon_body(struct daemon_params *dp)
669 update_timer.handler = do_update;
675 static struct main_signal term_sig = {
677 .handler = sigterm_handler,
679 signal_add(&term_sig);
681 msg(L_INFO, "Ursary daemon starting");
683 msg(L_INFO, "Ursary daemon shut down");
687 int main(int argc UNUSED, char **argv)
689 opt_parse(&options, argv+1);
693 struct daemon_params dp = {
694 .flags = (debug ? DAEMON_FLAG_SIMULATE : 0),
695 .pid_file = "/run/ursaryd.pid",
696 .run_as_user = "ursary",
703 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
704 ls->levels = ~(1U << L_DEBUG);
705 log_set_default_stream(ls);
708 daemon_run(&dp, daemon_body);